githits 0.4.3 → 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-15argn2z.js";
54
+ } from "./shared/chunk-tfqxat16.js";
53
55
  import {
54
56
  __require,
55
57
  version
56
- } from "./shared/chunk-q6d3ttgn.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
  }
@@ -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}`;
@@ -3272,7 +3289,7 @@ function buildPackageVulnerabilitiesParams(input) {
3272
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').`);
3273
3290
  }
3274
3291
  const advisoryScope = resolveAdvisoryScope(input.advisoryScope);
3275
- const filterWithScope = buildFilterEcho3(severityLabel2, input.includeWithdrawn, advisoryScope);
3292
+ const filterWithScope = buildFilterEcho2(severityLabel2, input.includeWithdrawn, advisoryScope);
3276
3293
  return {
3277
3294
  params: {
3278
3295
  registry,
@@ -3297,7 +3314,7 @@ function resolveMinSeverityLabel(raw) {
3297
3314
  }
3298
3315
  return lower;
3299
3316
  }
3300
- function buildFilterEcho3(minSeverity, includeWithdrawn, advisoryScope) {
3317
+ function buildFilterEcho2(minSeverity, includeWithdrawn, advisoryScope) {
3301
3318
  const filter = {};
3302
3319
  if (minSeverity !== undefined)
3303
3320
  filter.minSeverity = minSeverity;
@@ -4094,16 +4111,144 @@ function requirePositiveInteger(raw, label) {
4094
4111
  }
4095
4112
  return parsed;
4096
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
+
4097
4242
  // src/shared/read-file-text.ts
4098
4243
  var SEP4 = " | ";
4099
4244
  function renderReadFileText(envelope) {
4100
4245
  const lines = [];
4101
- lines.push(buildHeader4(envelope));
4246
+ lines.push(buildHeader5(envelope));
4102
4247
  lines.push("");
4103
4248
  if (envelope.isBinary) {
4104
4249
  lines.push("Binary file - cannot display as text.");
4105
4250
  } else if (envelope.content) {
4106
- appendNumberedContent(lines, envelope.content, envelope.startLine ?? 1);
4251
+ appendNumberedContent(lines, envelope.content, envelope.startLine ?? 1, envelope.endLine);
4107
4252
  } else {
4108
4253
  lines.push("(no content returned)");
4109
4254
  }
@@ -4114,7 +4259,7 @@ function renderReadFileText(envelope) {
4114
4259
  return lines.join(`
4115
4260
  `);
4116
4261
  }
4117
- function buildHeader4(envelope) {
4262
+ function buildHeader5(envelope) {
4118
4263
  const parts = [`code_read${SEP4}${envelope.path}`];
4119
4264
  if (envelope.language)
4120
4265
  parts.push(envelope.language);
@@ -4131,14 +4276,10 @@ function buildRange(envelope) {
4131
4276
  return `${envelope.totalLines} lines`;
4132
4277
  return;
4133
4278
  }
4134
- function appendNumberedContent(lines, content, startLine) {
4135
- const bodyLines = content.split(`
4136
- `);
4137
- if (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === "") {
4138
- bodyLines.pop();
4139
- }
4140
- const endLine = startLine + bodyLines.length - 1;
4141
- 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;
4142
4283
  for (let i = 0;i < bodyLines.length; i += 1) {
4143
4284
  lines.push(`${String(startLine + i).padStart(width, " ")} ${bodyLines[i]}`);
4144
4285
  }
@@ -4234,7 +4375,7 @@ function formatReadPackageDocTerminal(envelope, options) {
4234
4375
  return envelope.content ?? "";
4235
4376
  }
4236
4377
  const lines = [];
4237
- lines.push(buildHeader5(envelope, options.useColors));
4378
+ lines.push(buildHeader6(envelope, options.useColors));
4238
4379
  lines.push(`pageId: ${envelope.pageId}`);
4239
4380
  if (envelope.sourceUrl)
4240
4381
  lines.push(`source: ${envelope.sourceUrl}`);
@@ -4254,7 +4395,7 @@ function formatReadPackageDocTerminal(envelope, options) {
4254
4395
  `)}
4255
4396
  `;
4256
4397
  }
4257
- function buildHeader5(envelope, useColors) {
4398
+ function buildHeader6(envelope, useColors) {
4258
4399
  const badge = envelope.sourceKind === "repo" ? "[repo]" : "[crawled]";
4259
4400
  const title = envelope.title ?? envelope.pageId;
4260
4401
  const prefix = envelope.registry && envelope.name ? `${envelope.registry}:${envelope.name}${envelope.version ? `@${envelope.version}` : ""}` : "documentation";
@@ -4264,7 +4405,7 @@ function buildHeader5(envelope, useColors) {
4264
4405
  var SEP5 = " | ";
4265
4406
  function renderReadPackageDocText(envelope) {
4266
4407
  const lines = [];
4267
- lines.push(buildHeader6(envelope));
4408
+ lines.push(buildHeader7(envelope));
4268
4409
  if (envelope.sourceUrl)
4269
4410
  lines.push(`source: ${envelope.sourceUrl}`);
4270
4411
  if (envelope.filePath) {
@@ -4281,7 +4422,7 @@ function renderReadPackageDocText(envelope) {
4281
4422
  return lines.join(`
4282
4423
  `);
4283
4424
  }
4284
- function buildHeader6(envelope) {
4425
+ function buildHeader7(envelope) {
4285
4426
  const parts = [`docs_read${SEP5}${envelope.pageId}`];
4286
4427
  if (envelope.title)
4287
4428
  parts.push(envelope.title);
@@ -4601,6 +4742,7 @@ var AUTO_LOGIN_ELIGIBLE_COMMANDS = new Set([
4601
4742
  "pkg deps",
4602
4743
  "pkg changelog"
4603
4744
  ]);
4745
+ var AUTH_METADATA_TRUST_WINDOW_MS = 10 * 60 * 1000;
4604
4746
  function getCommandPath(command) {
4605
4747
  const names = [];
4606
4748
  let current = command;
@@ -4633,10 +4775,15 @@ async function maybeAutoLoginBeforeCommand(command, deps) {
4633
4775
  })) {
4634
4776
  return { status: "skipped" };
4635
4777
  }
4778
+ const metadata = await deps.loadAuthSessionMetadata?.();
4779
+ if (metadata && isUnexpiredAuthSessionMetadata(metadata, new Date)) {
4780
+ return { status: "already-authenticated" };
4781
+ }
4636
4782
  const container = await deps.createContainer();
4637
4783
  if (container.hasValidToken) {
4638
4784
  return { status: "already-authenticated" };
4639
4785
  }
4786
+ await deps.clearAuthSessionMetadata?.();
4640
4787
  const result = await deps.loginFlow({}, container);
4641
4788
  switch (result.status) {
4642
4789
  case "success":
@@ -4647,6 +4794,20 @@ async function maybeAutoLoginBeforeCommand(command, deps) {
4647
4794
  return { status: "failed", message: result.message };
4648
4795
  }
4649
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
+ }
4650
4811
 
4651
4812
  // src/shared/root-cli-pre-action.ts
4652
4813
  function createRootCliPreAction(deps) {
@@ -5071,7 +5232,7 @@ function compactProgress(progress) {
5071
5232
  payload.requestedTargets = progress.requestedTargets;
5072
5233
  }
5073
5234
  if (progress.filters)
5074
- payload.filters = buildFilterEcho4(progress.filters);
5235
+ payload.filters = buildFilterEcho3(progress.filters);
5075
5236
  if (typeof progress.limit === "number")
5076
5237
  payload.limit = progress.limit;
5077
5238
  if (typeof progress.offset === "number")
@@ -5118,7 +5279,7 @@ function compactProgressTarget(target) {
5118
5279
  payload.requestedRefKind = target.requestedRefKind;
5119
5280
  return Object.keys(payload).length > 0 ? payload : undefined;
5120
5281
  }
5121
- function buildFilterEcho4(filters) {
5282
+ function buildFilterEcho3(filters) {
5122
5283
  const echo = {};
5123
5284
  if (filters.kind)
5124
5285
  echo.kind = filters.kind.toLowerCase();
@@ -5293,7 +5454,7 @@ var SUMMARY_WRAP_WIDTH = 76;
5293
5454
  var SEP6 = " | ";
5294
5455
  function renderUnifiedSearchSuccess(payload) {
5295
5456
  const lines = [];
5296
- lines.push(buildHeader7(payload));
5457
+ lines.push(buildHeader8(payload));
5297
5458
  lines.push("");
5298
5459
  if (payload.results.length === 0) {
5299
5460
  lines.push(payload.completed ? "No hits." : "No hits yet - indexing.");
@@ -5324,7 +5485,7 @@ function renderUnifiedSearchError(payload) {
5324
5485
  return lines.join(`
5325
5486
  `);
5326
5487
  }
5327
- function buildHeader7(payload) {
5488
+ function buildHeader8(payload) {
5328
5489
  const count = payload.results.length;
5329
5490
  const status = payload.completed ? `${count} hit${count === 1 ? "" : "s"}` : `${count} partial`;
5330
5491
  const parts = [`search${SEP6}${status}`];
@@ -5342,7 +5503,7 @@ function appendUnifiedSearchHits(lines, hits) {
5342
5503
  });
5343
5504
  }
5344
5505
  function appendHit(lines, index, hit) {
5345
- const headerParts = [hit.target, shortType(hit.type)];
5506
+ const headerParts = [formatHitPrimary(hit), shortType(hit.type)];
5346
5507
  lines.push(`[${index}] ${headerParts.join(" ")}`);
5347
5508
  const locator = buildLocatorLine(hit);
5348
5509
  if (locator)
@@ -5356,6 +5517,26 @@ function appendHit(lines, index, hit) {
5356
5517
  }
5357
5518
  }
5358
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
+ }
5359
5540
  function shortType(type) {
5360
5541
  switch (type) {
5361
5542
  case "repository_code":
@@ -5524,7 +5705,7 @@ function wrapText2(text, width) {
5524
5705
  var SEP7 = " | ";
5525
5706
  function renderUnifiedSearchStatusText(payload) {
5526
5707
  const lines = [];
5527
- lines.push(buildHeader8(payload));
5708
+ lines.push(buildHeader9(payload));
5528
5709
  if (!payload.completed && payload.progress) {
5529
5710
  lines.push(formatProgress(payload.progress));
5530
5711
  if (payload.progress.targets?.length) {
@@ -5548,7 +5729,7 @@ function renderUnifiedSearchStatusText(payload) {
5548
5729
  return lines.join(`
5549
5730
  `);
5550
5731
  }
5551
- function buildHeader8(payload) {
5732
+ function buildHeader9(payload) {
5552
5733
  const state = payload.completed ? "complete" : "indexing";
5553
5734
  const parts = [`search_status${SEP7}${state}`];
5554
5735
  if (payload.searchRef)
@@ -6121,8 +6302,8 @@ function looksLikeMissingNavpackMessage(message) {
6121
6302
  const lower = message.toLowerCase();
6122
6303
  return lower.includes("has no navpack for this ref") || lower.includes("navpack was pruned") || lower.includes("indexedrepository row still claims current state");
6123
6304
  }
6124
- function handleCodeNavCommandError(error2, json, terminalRenderer, exitCode = 1) {
6125
- const mapped = mapCodeNavigationError(error2);
6305
+ function handleCodeNavCommandError(error2, json, terminalRenderer, exitCode = 1, mapMappedError = (mapped) => mapped) {
6306
+ const mapped = mapMappedError(mapCodeNavigationError(error2));
6126
6307
  if (json) {
6127
6308
  console.error(JSON.stringify({
6128
6309
  error: mapped.message,
@@ -6393,7 +6574,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
6393
6574
  match in --verbose output; full payload in --json).`;
6394
6575
  function registerCodeGrepCommand(pkgCommand) {
6395
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) => {
6396
- const { createContainer: createContainer2 } = await import("./shared/chunk-kmka6wpx.js");
6577
+ const { createContainer: createContainer2 } = await import("./shared/chunk-k35egwwf.js");
6397
6578
  const deps = await createContainer2();
6398
6579
  await pkgGrepAction(arg1, arg2, arg3, options, {
6399
6580
  codeNavigationService: deps.codeNavigationService,
@@ -6404,6 +6585,36 @@ function registerCodeGrepCommand(pkgCommand) {
6404
6585
  });
6405
6586
  }
6406
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
+
6407
6618
  // src/shared/read-file-request.ts
6408
6619
  var WAIT_MIN3 = 0;
6409
6620
  var WAIT_MAX2 = 60000;
@@ -6412,6 +6623,9 @@ function buildReadFileParams(input) {
6412
6623
  if (!filePath) {
6413
6624
  throw new InvalidPackageSpecError("`file_path` is required — pass the path to the file within the package or repo.");
6414
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
+ }
6415
6629
  const startLine = normaliseLine(input.startLine, "start_line");
6416
6630
  const endLine = normaliseLine(input.endLine, "end_line");
6417
6631
  if (startLine !== undefined && endLine !== undefined && startLine > endLine) {
@@ -6445,164 +6659,57 @@ function normaliseWaitTimeoutMs2(raw) {
6445
6659
  return raw;
6446
6660
  }
6447
6661
 
6448
- // src/shared/read-file-response.ts
6449
- function buildReadFileSuccessPayload(result, options) {
6450
- const envelope = {
6451
- path: result.filePath ?? options.requestedFilePath
6452
- };
6453
- if (options.registry)
6454
- envelope.registry = options.registry;
6455
- if (options.name)
6456
- envelope.name = options.name;
6457
- if (options.repoUrl)
6458
- envelope.repoUrl = options.repoUrl;
6459
- if (options.gitRef)
6460
- envelope.gitRef = options.gitRef;
6461
- if (result.language != null)
6462
- envelope.language = result.language;
6463
- if (result.totalLines != null)
6464
- envelope.totalLines = result.totalLines;
6465
- if (result.startLine != null)
6466
- envelope.startLine = result.startLine;
6467
- if (result.endLine != null)
6468
- envelope.endLine = result.endLine;
6469
- if (result.isBinary) {
6470
- envelope.isBinary = true;
6471
- } else if (result.content != null) {
6472
- envelope.content = result.content;
6662
+ // src/commands/code/read.ts
6663
+ async function pkgReadAction(firstArg, secondArg, options, deps) {
6664
+ requireAuth(deps);
6665
+ let requestedFilePath = "";
6666
+ try {
6667
+ if (!deps.codeNavigationUrl || !deps.codeNavigationService) {
6668
+ throw new InvalidPackageSpecError("Code navigation is not configured for this environment.");
6669
+ }
6670
+ const hasRepoUrl = Boolean(options.repoUrl);
6671
+ const { spec, path } = resolvePositionals3(firstArg, secondArg, hasRepoUrl);
6672
+ if (!path || path.trim().length === 0) {
6673
+ throw new InvalidPackageSpecError("A <path> argument is required — pass the path to the file within the package or repo.");
6674
+ }
6675
+ const target = resolveCliCodeNavTarget(spec, options);
6676
+ const pathWithRange = parsePathWithOptionalRange(path.trim());
6677
+ requestedFilePath = pathWithRange.filePath;
6678
+ const range = resolveLineRange(options, pathWithRange);
6679
+ const wait = parseIntCliOption(options.wait, "--wait", 0, MAX_WAIT_TIMEOUT_MS);
6680
+ const build = buildReadFileParams({
6681
+ target,
6682
+ filePath: pathWithRange.filePath,
6683
+ startLine: range.startLine,
6684
+ endLine: range.endLine,
6685
+ waitTimeoutMs: wait
6686
+ });
6687
+ const result = await deps.codeNavigationService.readFile(build.params);
6688
+ const payload = buildReadFileSuccessPayload(result, {
6689
+ registry: target.registry ? toPkgseerRegistryLowercase(target.registry) : undefined,
6690
+ name: target.packageName,
6691
+ repoUrl: target.repoUrl,
6692
+ gitRef: target.gitRef,
6693
+ requestedFilePath: build.params.filePath
6694
+ });
6695
+ if (options.json) {
6696
+ console.log(JSON.stringify(payload));
6697
+ return;
6698
+ }
6699
+ process.stdout.write(formatReadFileTerminal(payload, {
6700
+ useColors: shouldUseColors(),
6701
+ verbose: options.verbose ?? false
6702
+ }));
6703
+ } catch (error2) {
6704
+ handleCodeNavCommandError(error2, options.json ?? false, formatFileErrorWithFilesHint, 1, (mapped) => withReadFileRecovery(mapped, requestedFilePath));
6473
6705
  }
6474
- return envelope;
6475
6706
  }
6476
- function formatReadFileTerminal(envelope, options) {
6477
- const verbose = options.verbose ?? false;
6478
- if (envelope.isBinary) {
6479
- return formatBinary(envelope, options, verbose);
6480
- }
6481
- if (envelope.content == null) {
6482
- return formatNoContent(envelope, options, verbose);
6483
- }
6484
- if (!verbose) {
6485
- return envelope.content;
6486
- }
6487
- return formatVerboseBody(envelope, options);
6488
- }
6489
- function formatBinary(envelope, options, verbose) {
6490
- const sentinel = dim("Binary file — cannot display as text.", options.useColors);
6491
- if (verbose) {
6492
- return `${buildHeader9(envelope, options)}
6493
-
6494
- ${sentinel}
6495
- `;
6496
- }
6497
- return `${sentinel}
6498
- `;
6499
- }
6500
- function formatNoContent(envelope, options, verbose) {
6501
- const sentinel = dim("(no content returned)", options.useColors);
6502
- if (verbose) {
6503
- return `${buildHeader9(envelope, options)}
6504
-
6505
- ${sentinel}
6506
- `;
6507
- }
6508
- return `${sentinel}
6509
- `;
6510
- }
6511
- function formatVerboseBody(envelope, options) {
6512
- const lines = [];
6513
- lines.push(buildHeader9(envelope, options));
6514
- lines.push("");
6515
- const content = envelope.content ?? "";
6516
- const bodyLines = content.split(`
6517
- `);
6518
- if (bodyLines.length > 0 && bodyLines[bodyLines.length - 1] === "") {
6519
- bodyLines.pop();
6520
- }
6521
- const startLine = envelope.startLine ?? 1;
6522
- const endLine = startLine + bodyLines.length - 1;
6523
- const gutterWidth = String(endLine).length;
6524
- for (let i = 0;i < bodyLines.length; i++) {
6525
- const lineNumber = startLine + i;
6526
- const gutter = dim(String(lineNumber).padStart(gutterWidth, " "), options.useColors);
6527
- lines.push(`${gutter} ${bodyLines[i]}`);
6528
- }
6529
- if (envelope.hint) {
6530
- lines.push("");
6531
- lines.push(dim(envelope.hint, options.useColors));
6532
- }
6533
- lines.push("");
6534
- return lines.join(`
6535
- `);
6536
- }
6537
- function buildHeader9(envelope, options) {
6538
- const parts = [envelope.path];
6539
- if (envelope.language)
6540
- parts.push(envelope.language);
6541
- const rangeLabel = buildRangeLabel(envelope);
6542
- if (rangeLabel)
6543
- parts.push(rangeLabel);
6544
- return colorize(parts.join(" · "), "bold", options.useColors);
6545
- }
6546
- function buildRangeLabel(envelope) {
6547
- const { startLine, endLine, totalLines } = envelope;
6548
- if (startLine != null && endLine != null) {
6549
- return totalLines != null ? `lines ${startLine}-${endLine} of ${totalLines}` : `lines ${startLine}-${endLine}`;
6550
- }
6551
- if (totalLines != null) {
6552
- return `${totalLines} lines`;
6553
- }
6554
- return;
6555
- }
6556
-
6557
- // src/commands/code/read.ts
6558
- async function pkgReadAction(firstArg, secondArg, options, deps) {
6559
- requireAuth(deps);
6560
- try {
6561
- if (!deps.codeNavigationUrl || !deps.codeNavigationService) {
6562
- throw new InvalidPackageSpecError("Code navigation is not configured for this environment.");
6563
- }
6564
- const hasRepoUrl = Boolean(options.repoUrl);
6565
- const { spec, path } = resolvePositionals3(firstArg, secondArg, hasRepoUrl);
6566
- if (!path || path.trim().length === 0) {
6567
- throw new InvalidPackageSpecError("A <path> argument is required — pass the path to the file within the package or repo.");
6568
- }
6569
- const target = resolveCliCodeNavTarget(spec, options);
6570
- const pathWithRange = parsePathWithOptionalRange(path.trim());
6571
- const range = resolveLineRange(options, pathWithRange);
6572
- const wait = parseIntCliOption(options.wait, "--wait", 0, MAX_WAIT_TIMEOUT_MS);
6573
- const build = buildReadFileParams({
6574
- target,
6575
- filePath: pathWithRange.filePath,
6576
- startLine: range.startLine,
6577
- endLine: range.endLine,
6578
- waitTimeoutMs: wait
6579
- });
6580
- const result = await deps.codeNavigationService.readFile(build.params);
6581
- const payload = buildReadFileSuccessPayload(result, {
6582
- registry: target.registry ? toPkgseerRegistryLowercase(target.registry) : undefined,
6583
- name: target.packageName,
6584
- repoUrl: target.repoUrl,
6585
- gitRef: target.gitRef,
6586
- requestedFilePath: build.params.filePath
6587
- });
6588
- if (options.json) {
6589
- console.log(JSON.stringify(payload));
6590
- return;
6591
- }
6592
- process.stdout.write(formatReadFileTerminal(payload, {
6593
- useColors: shouldUseColors(),
6594
- verbose: options.verbose ?? false
6595
- }));
6596
- } catch (error2) {
6597
- handleCodeNavCommandError(error2, options.json ?? false, formatFileErrorWithFilesHint);
6598
- }
6599
- }
6600
- function resolvePositionals3(firstArg, secondArg, hasRepoUrl) {
6601
- if (hasRepoUrl) {
6602
- if (secondArg !== undefined) {
6603
- throw new InvalidPackageSpecError("In --repo-url mode, pass only the <path> positional — the package spec is replaced by --repo-url.");
6604
- }
6605
- return { spec: undefined, path: firstArg };
6707
+ function resolvePositionals3(firstArg, secondArg, hasRepoUrl) {
6708
+ if (hasRepoUrl) {
6709
+ if (secondArg !== undefined) {
6710
+ throw new InvalidPackageSpecError("In --repo-url mode, pass only the <path> positional — the package spec is replaced by --repo-url.");
6711
+ }
6712
+ return { spec: undefined, path: firstArg };
6606
6713
  }
6607
6714
  return { spec: firstArg, path: secondArg };
6608
6715
  }
@@ -6927,7 +7034,7 @@ function registerExampleCommand(program) {
6927
7034
  });
6928
7035
  }
6929
7036
  async function loadContainer() {
6930
- const { createContainer: createContainer2 } = await import("./shared/chunk-kmka6wpx.js");
7037
+ const { createContainer: createContainer2 } = await import("./shared/chunk-k35egwwf.js");
6931
7038
  return createContainer2();
6932
7039
  }
6933
7040
  // src/commands/feedback.ts
@@ -7183,6 +7290,39 @@ function mergeServerConfig(existingContent, serversKey, serverName, serverConfig
7183
7290
  `
7184
7291
  };
7185
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
+ }
7186
7326
  function formatSetupPreview(config) {
7187
7327
  if (config.method === "cli") {
7188
7328
  return config.commands.map((cmd) => `Will run: ${cmd.command} ${cmd.args.join(" ")}`).join(`
@@ -7193,6 +7333,13 @@ function formatSetupPreview(config) {
7193
7333
 
7194
7334
  ${snippet}`;
7195
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
+ }
7196
7343
  async function isAlreadyConfigured(config, fs) {
7197
7344
  try {
7198
7345
  const content = await fs.readFile(config.configPath);
@@ -7215,16 +7362,48 @@ async function isAlreadyConfigured(config, fs) {
7215
7362
  return false;
7216
7363
  }
7217
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
+ }
7218
7397
  async function getCliCheckStatus(check, execService) {
7219
7398
  try {
7220
7399
  const result = await execService.exec(check.command, check.args);
7221
- if (check.requireExitCodeZero && result.exitCode !== 0) {
7222
- return "probe_failed";
7223
- }
7224
7400
  const combined = `${result.stdout} ${result.stderr}`;
7225
7401
  if (check.notConfiguredPattern?.test(combined)) {
7226
7402
  return "not_configured";
7227
7403
  }
7404
+ if (check.requireExitCodeZero && result.exitCode !== 0) {
7405
+ return "probe_failed";
7406
+ }
7228
7407
  if (check.configuredPattern) {
7229
7408
  return check.configuredPattern.test(combined) ? "configured" : "not_configured";
7230
7409
  }
@@ -7242,9 +7421,18 @@ var ALREADY_EXISTS_PATTERNS = [
7242
7421
  /already added/i,
7243
7422
  /extension\s+"githits"\s+is\s+already\s+installed/i
7244
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
+ ];
7245
7430
  function isAlreadyConfiguredOutput(output) {
7246
7431
  return ALREADY_EXISTS_PATTERNS.some((pattern) => pattern.test(output));
7247
7432
  }
7433
+ function isAlreadyAbsentOutput(output) {
7434
+ return ALREADY_ABSENT_PATTERNS.some((pattern) => pattern.test(output));
7435
+ }
7248
7436
  async function executeCliCommand(cmd, execService) {
7249
7437
  try {
7250
7438
  const result = await execService.exec(cmd.command, cmd.args);
@@ -7276,6 +7464,37 @@ async function executeCliCommand(cmd, execService) {
7276
7464
  };
7277
7465
  }
7278
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
+ }
7279
7498
  async function executeCliSetup(setup, execService) {
7280
7499
  let anyAlreadyConfigured = false;
7281
7500
  for (const cmd of setup.commands) {
@@ -7295,6 +7514,52 @@ async function executeCliSetup(setup, execService) {
7295
7514
  }
7296
7515
  return { status: "success", message: "Configured successfully" };
7297
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
+ }
7298
7563
  async function executeConfigFileSetup(setup, fs) {
7299
7564
  try {
7300
7565
  const parentDir = fs.getDirname(setup.configPath);
@@ -7338,6 +7603,51 @@ async function executeConfigFileSetup(setup, fs) {
7338
7603
  };
7339
7604
  }
7340
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
+ }
7341
7651
 
7342
7652
  // src/commands/init/agent-definitions.ts
7343
7653
  var GITHITS_SERVER_NAME = "GitHits";
@@ -7347,6 +7657,10 @@ var GITHITS_MCP_INVOCATION = [
7347
7657
  GITHITS_MCP_COMMAND,
7348
7658
  ...GITHITS_MCP_ARGS
7349
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";
7350
7664
  function getAppDataPath(fs, appName) {
7351
7665
  const home = fs.getHomeDir();
7352
7666
  switch (process.platform) {
@@ -7404,18 +7718,36 @@ var claudeCode = {
7404
7718
  commands: [
7405
7719
  {
7406
7720
  command: "claude",
7407
- args: ["plugin", "marketplace", "add", "githits-com/githits-cli"]
7721
+ args: [
7722
+ "plugin",
7723
+ "marketplace",
7724
+ "add",
7725
+ CLAUDE_GITHITS_MARKETPLACE_SOURCE
7726
+ ]
7408
7727
  },
7409
7728
  {
7410
7729
  command: "claude",
7411
- args: ["plugin", "install", "githits@githits-plugins"]
7730
+ args: ["plugin", "install", CLAUDE_GITHITS_PLUGIN_REF]
7412
7731
  }
7413
7732
  ],
7414
7733
  checkCommand: {
7415
7734
  command: "claude",
7416
7735
  args: ["plugin", "list"],
7417
- configuredPattern: /githits/i
7736
+ configuredPattern: /(^|\s)githits@githits-plugins\b/i
7418
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
+ ]
7419
7751
  })
7420
7752
  };
7421
7753
  var cursor = {
@@ -7501,8 +7833,17 @@ var codexCli = {
7501
7833
  checkCommand: {
7502
7834
  command: "codex",
7503
7835
  args: ["mcp", "list"],
7504
- configuredPattern: /githits/i
7836
+ configuredPattern: /^\s*githits\b/im
7505
7837
  }
7838
+ }),
7839
+ getUninstallConfig: () => ({
7840
+ method: "cli",
7841
+ commands: [
7842
+ {
7843
+ command: "codex",
7844
+ args: ["mcp", "remove", "githits"]
7845
+ }
7846
+ ]
7506
7847
  })
7507
7848
  };
7508
7849
  var vscode = {
@@ -7570,6 +7911,15 @@ var geminiCli = {
7570
7911
  notConfiguredPattern: /not installed/i,
7571
7912
  requireExitCodeZero: true
7572
7913
  }
7914
+ }),
7915
+ getUninstallConfig: () => ({
7916
+ method: "cli",
7917
+ commands: [
7918
+ {
7919
+ command: "gemini",
7920
+ args: ["extensions", "uninstall", "githits"]
7921
+ }
7922
+ ]
7573
7923
  })
7574
7924
  };
7575
7925
  async function isGeminiExtensionInstalledFromFilesystem(fs) {
@@ -7721,6 +8071,90 @@ async function verifyAgentConfigured(agent, fileSystemService, execService) {
7721
8071
  message: `${agent.name} verification failed: agent not detected after setup.`
7722
8072
  };
7723
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
+ }
7724
8158
  async function initAction(options, deps) {
7725
8159
  const useColors = shouldUseColors();
7726
8160
  const { fileSystemService, promptService, execService, createLoginDeps } = deps;
@@ -7886,6 +8320,147 @@ async function initAction(options, deps) {
7886
8320
  }
7887
8321
  console.log();
7888
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
+ }
7889
8464
  function printAuthRecoveryHint() {
7890
8465
  console.log(" You can still configure MCP, but GitHits tools will require auth.");
7891
8466
  console.log(" Recovery steps:");
@@ -7905,8 +8480,13 @@ and sets up unconfigured ones with your confirmation.
7905
8480
  Supports CLI-based setup (Claude Code, Codex, Gemini CLI) and config
7906
8481
  file editing (Cursor, Windsurf, VS Code, Cline, Claude Desktop,
7907
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.`;
7908
8488
  function registerInitCommand(program) {
7909
- 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) => {
7910
8490
  const fileSystemService = new FileSystemServiceImpl;
7911
8491
  const promptService = new PromptServiceImpl;
7912
8492
  const execService = new ExecServiceImpl;
@@ -7917,6 +8497,16 @@ function registerInitCommand(program) {
7917
8497
  createLoginDeps: () => createAuthCommandDependencies()
7918
8498
  });
7919
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
+ });
7920
8510
  }
7921
8511
  // src/commands/languages.ts
7922
8512
  async function languagesAction(query, options, deps) {
@@ -8178,7 +8768,7 @@ function invalidTargetResult(message) {
8178
8768
  // src/tools/grep-repo.ts
8179
8769
  var schema3 = {
8180
8770
  target: codeTargetSchema,
8181
- pattern: z4.string().describe(GREP_REPO_PATTERN_NOTE),
8771
+ pattern: z4.string().optional().describe(GREP_REPO_PATTERN_NOTE),
8182
8772
  path: z4.string().optional().describe("Exact file path to grep. Shares the same path vocabulary as `code_read`."),
8183
8773
  path_prefix: z4.string().optional().describe("Literal directory prefix to scope grep, matching `code_files` / `search` naming."),
8184
8774
  globs: z4.array(z4.string()).optional().describe("Repeatable glob scopes with real glob semantics (e.g. `src/**/*.ts`)."),
@@ -8197,7 +8787,7 @@ var schema3 = {
8197
8787
  wait_timeout_ms: z4.number().optional(),
8198
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.')
8199
8789
  };
8200
- 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 `path` chains into `code_read.path`; pick a window around `match.line` for `code_read.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`.";
8201
8791
  function createGrepRepoTool(service) {
8202
8792
  return {
8203
8793
  name: "code_grep",
@@ -8292,7 +8882,7 @@ var schema4 = {
8292
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`."),
8293
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.')
8294
8884
  };
8295
- var DESCRIPTION4 = "List files in an indexed dependency. 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`.";
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`.";
8296
8886
  function createListFilesTool(service) {
8297
8887
  return {
8298
8888
  name: "code_files",
@@ -8371,7 +8961,7 @@ var schema5 = {
8371
8961
  after: z6.string().optional().describe("Pagination cursor from a prior response."),
8372
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.')
8373
8963
  };
8374
- 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.";
8375
8965
  function createListPackageDocsTool(service) {
8376
8966
  return {
8377
8967
  name: "docs_list",
@@ -8781,7 +9371,7 @@ var schema7 = {
8781
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`."),
8782
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.')
8783
9373
  };
8784
- 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 " + "runtime plus matching groups, or `all` for runtime plus every " + "available group. 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.";
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.";
8785
9375
  function createPackageDependenciesTool(service) {
8786
9376
  return {
8787
9377
  name: "pkg_deps",
@@ -8974,13 +9564,13 @@ import { z as z11 } from "zod";
8974
9564
  var MCP_READ_MAX_SPAN = 150;
8975
9565
  var schema10 = {
8976
9566
  target: codeTargetSchema,
8977
- 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."),
8978
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.`),
8979
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.`),
8980
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`."),
8981
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.')
8982
9572
  };
8983
- 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.";
8984
9574
  function deriveBoundedRange(startLine, endLine) {
8985
9575
  const start = startLine ?? 1;
8986
9576
  if (endLine === undefined) {
@@ -9035,7 +9625,7 @@ function createReadFileTool(service) {
9035
9625
  }
9036
9626
  return textResult(JSON.stringify(payload));
9037
9627
  } catch (error2) {
9038
- const mapped = mapCodeNavigationError(error2);
9628
+ const mapped = withReadFileRecovery(mapCodeNavigationError(error2), args.path);
9039
9629
  return errorResult(JSON.stringify({
9040
9630
  error: mapped.message,
9041
9631
  code: mapped.code,
@@ -9378,23 +9968,23 @@ Package spec: \`registry:name[@version]\`. Default outputs are compact \`text-v1
9378
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.';
9379
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`.';
9380
9970
  var SEARCH_STATUS_BULLET = "- `search_status` — follow up a prior `search` by `searchRef` to check progress, fetch partial hits, or fetch final results.";
9381
- 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`.";
9382
- var CODE_READ_BULLET = "- `code_read` — read a dependency file by `path`. **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` or `code_grep` and pick a focused `start_line` / `end_line` window. Binary files set `isBinary: true` and omit `content`. On `FILE_NOT_FOUND` / `NOT_FOUND`, call `code_files` for the actual path.";
9383
- 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.';
9384
- 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.";
9385
- var DOCS_READ_BULLET = "- `docs_read` — read a documentation page by pageId. Works for both hosted docs and repo-backed docs.";
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.";
9386
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.';
9387
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.';
9388
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.';
9389
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.';
9390
- var STRATEGY_TIP = 'Strategy — reference-first. 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 for behavioral claims; 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.';
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.';
9391
9981
  function buildMcpInstructions(_deps) {
9392
9982
  const bullets = [
9393
9983
  SEARCH_BULLET,
9394
9984
  SEARCH_STATUS_BULLET,
9985
+ CODE_FILES_BULLET,
9395
9986
  CODE_GREP_BULLET,
9396
9987
  CODE_READ_BULLET,
9397
- CODE_FILES_BULLET,
9398
9988
  DOCS_LIST_BULLET,
9399
9989
  DOCS_READ_BULLET,
9400
9990
  PKG_INFO_BULLET,
@@ -9506,14 +10096,14 @@ Authenticated tool calls require a valid GitHits token.`).action(async () => {
9506
10096
  showMcpSetupInstructions();
9507
10097
  return;
9508
10098
  }
9509
- const deps = await createContainer();
10099
+ const deps = await createContainer({ resolveStoredToken: false });
9510
10100
  await startMcpServer(deps);
9511
10101
  });
9512
10102
  mcpCommand.command("start").summary("Start MCP server (stdio mode)").description(`Start the MCP server using STDIO transport.
9513
10103
 
9514
10104
  This command explicitly starts the server and is intended for use
9515
10105
  in MCP configuration files. Use 'githits mcp' for interactive setup.`).action(async () => {
9516
- const deps = await createContainer();
10106
+ const deps = await createContainer({ resolveStoredToken: false });
9517
10107
  await startMcpServer(deps);
9518
10108
  });
9519
10109
  }
@@ -9744,7 +10334,7 @@ function formatDepsTerminalError(mapped) {
9744
10334
  var PKG_DEPS_DESCRIPTION = `Analyze package dependencies. By default shows the flat list of
9745
10335
  direct runtime dependencies. Use --lifecycle all for the structured view
9746
10336
  (dev / peer / build / optional, plus registry-specific feature / TFM
9747
- groups). Concrete --lifecycle values include runtime plus matching groups.
10337
+ groups). Runtime group rows include resolved versions when available.
9748
10338
  --transitive opts into aggregate edge / unique-package counts,
9749
10339
  conflict detection, and circular-dependency flags.
9750
10340
 
@@ -10067,7 +10657,7 @@ function requireSearchService(deps) {
10067
10657
  return deps.codeNavigationService;
10068
10658
  }
10069
10659
  async function loadContainer2() {
10070
- const { createContainer: createContainer2 } = await import("./shared/chunk-kmka6wpx.js");
10660
+ const { createContainer: createContainer2 } = await import("./shared/chunk-k35egwwf.js");
10071
10661
  return createContainer2();
10072
10662
  }
10073
10663
  function parseTargetSpecs(specs) {
@@ -10177,13 +10767,13 @@ function formatUnifiedSearchTerminal(payload) {
10177
10767
  `).trimEnd();
10178
10768
  }
10179
10769
  const { display, duplicatesFolded } = dedupeSearchResultsForDisplay(payload.results);
10180
- const baseCount = `${display.length} result(s)`;
10770
+ const baseCount = `${display.length} result${display.length === 1 ? "" : "s"}`;
10181
10771
  const countSuffix = [
10182
10772
  payload.hasMore ? " (more available)" : "",
10183
10773
  duplicatesFolded > 0 ? ` (+${duplicatesFolded} near-duplicate folded)` : ""
10184
10774
  ].join("");
10185
- lines.push(`${highlight(baseCount, useColors)}${dim(countSuffix, useColors)}`);
10186
- lines.push(dim(formatUnifiedSearchTypeSummary(display), useColors));
10775
+ const typeSummary = formatUnifiedSearchTypeSummary(display);
10776
+ lines.push(`${highlight(baseCount, useColors)}${dim(countSuffix, useColors)}${typeSummary ? dim(` | ${typeSummary}`, useColors) : ""}`);
10187
10777
  lines.push("");
10188
10778
  for (const entry of display) {
10189
10779
  const location = formatUnifiedSearchLocation(entry.locator);
@@ -10333,7 +10923,7 @@ function formatUnifiedSearchTypeSummary(results) {
10333
10923
  for (const result of results) {
10334
10924
  counts.set(result.type, (counts.get(result.type) ?? 0) + 1);
10335
10925
  }
10336
- 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(", ");
10337
10927
  }
10338
10928
  function formatUnifiedSearchResultLabel(type) {
10339
10929
  switch (type) {
@@ -10386,11 +10976,33 @@ function formatUnifiedSearchLocation(locator) {
10386
10976
  return `${locator.filePath}:${locator.startLine}${locator.endLine && locator.endLine !== locator.startLine ? `-${locator.endLine}` : ""}`;
10387
10977
  }
10388
10978
  function formatUnifiedSearchHeader(entry, useColors, location, rawQuery) {
10979
+ if (entry.type === "documentation_page") {
10980
+ return formatDocumentationPageHeader(entry, useColors);
10981
+ }
10389
10982
  const primary = formatUnifiedSearchPrimary(entry.type, entry.target, location, rawQuery, useColors);
10390
10983
  const badge = `[${formatUnifiedSearchResultLabel(entry.type)}]`;
10391
10984
  const title = entry.title ? highlightRanges(entry.title, entry.highlights?.title, useColors) : undefined;
10392
10985
  return `${primary} ${dim(badge, useColors)}${title ? ` - ${title}` : ""}`;
10393
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
+ }
10394
11006
  function formatUnifiedSearchPrimary(type, target, location, rawQuery, useColors) {
10395
11007
  const formattedTarget = highlight(target, useColors);
10396
11008
  if (type === "documentation_page" || !location) {
@@ -10498,14 +11110,8 @@ function formatUnifiedSearchMetadata(entry, useColors) {
10498
11110
  return [];
10499
11111
  }
10500
11112
  const lines = [];
10501
- if (entry.locator.pageId) {
10502
- if (entry.type === "documentation_page") {
10503
- lines.push(` ${dim("pageId:", useColors)} ${entry.locator.pageId}`);
10504
- }
10505
- }
10506
- const sourceBadge = entry.locator.sourceKind?.toLowerCase() === "repository" ? "[repo]" : entry.locator.sourceKind?.toLowerCase() === "crawled" ? "[crawled]" : undefined;
10507
- if (entry.locator.sourceUrl && entry.type === "documentation_page") {
10508
- lines.push(` ${dim("source:", useColors)} ${sourceBadge ? `${sourceBadge} ` : ""}${entry.locator.sourceUrl}`);
11113
+ if (entry.type === "documentation_page") {
11114
+ return lines;
10509
11115
  }
10510
11116
  return lines;
10511
11117
  }
@@ -10544,6 +11150,8 @@ if (isTelemetryEnabled()) {
10544
11150
  }
10545
11151
  var rootCliPreAction = createRootCliPreAction({
10546
11152
  createContainer,
11153
+ loadAuthSessionMetadata: loadAutoLoginAuthSessionMetadata,
11154
+ clearAuthSessionMetadata: clearAutoLoginAuthSessionMetadata,
10547
11155
  loginFlow: (options, deps) => loginFlow(options, deps, stderrLoginOutput)
10548
11156
  });
10549
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) => {