githits 0.4.5 → 0.4.7

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
@@ -51,11 +51,11 @@ import {
51
51
  shouldRunUpdateCheck,
52
52
  startTelemetrySpan,
53
53
  withTelemetrySpan
54
- } from "./shared/chunk-ygqss6gp.js";
54
+ } from "./shared/chunk-tm11qwgr.js";
55
55
  import {
56
56
  __require,
57
57
  version
58
- } from "./shared/chunk-vfyz65v1.js";
58
+ } from "./shared/chunk-7b4f7fd5.js";
59
59
 
60
60
  // src/cli.ts
61
61
  import { Command } from "commander";
@@ -331,6 +331,7 @@ var colors = {
331
331
  reset: "\x1B[0m",
332
332
  bold: "\x1B[1m",
333
333
  dim: "\x1B[2m",
334
+ italic: "\x1B[3m",
334
335
  green: "\x1B[32m",
335
336
  yellow: "\x1B[33m",
336
337
  blue: "\x1B[34m",
@@ -338,6 +339,20 @@ var colors = {
338
339
  cyan: "\x1B[36m",
339
340
  red: "\x1B[31m"
340
341
  };
342
+ var brandColors = {
343
+ primary: {
344
+ hex: "#FF72BE",
345
+ rgb: [255, 114, 190],
346
+ ansi256: 205,
347
+ ansi16: "magenta"
348
+ },
349
+ secondary: {
350
+ hex: "#FF872F",
351
+ rgb: [255, 135, 47],
352
+ ansi256: 208,
353
+ ansi16: "yellow"
354
+ }
355
+ };
341
356
  function shouldUseColors(noColor) {
342
357
  if (noColor)
343
358
  return false;
@@ -350,6 +365,32 @@ function colorize(text, color, useColors) {
350
365
  return text;
351
366
  return `${colors[color]}${text}${colors.reset}`;
352
367
  }
368
+ function getColorDepth() {
369
+ const stream = process.stdout;
370
+ return stream.getColorDepth?.() ?? 1;
371
+ }
372
+ function foregroundColorCode(color, colorDepth) {
373
+ if (colorDepth >= 24) {
374
+ const [red, green, blue] = color.rgb;
375
+ return `\x1B[38;2;${red};${green};${blue}m`;
376
+ }
377
+ if (colorDepth >= 8) {
378
+ return `\x1B[38;5;${color.ansi256}m`;
379
+ }
380
+ return colors[color.ansi16];
381
+ }
382
+ function colorizeTerminal(text, color, useColors, options = {}) {
383
+ if (!useColors)
384
+ return text;
385
+ const colorDepth = options.colorDepth ?? getColorDepth();
386
+ if (colorDepth <= 1)
387
+ return text;
388
+ const prefix = `${options.bold ? colors.bold : ""}${options.dim ? colors.dim : ""}${foregroundColorCode(color, colorDepth)}`;
389
+ return `${prefix}${text}${colors.reset}`;
390
+ }
391
+ function colorizeBrand(text, colorName, useColors, options) {
392
+ return colorizeTerminal(text, brandColors[colorName], useColors, options);
393
+ }
353
394
  function success(text, useColors) {
354
395
  const checkmark = useColors ? `${colors.green}✓${colors.reset}` : "✓";
355
396
  return `${checkmark} ${text}`;
@@ -583,6 +624,12 @@ function classify(error2) {
583
624
  if (error2.availableVersions && error2.availableVersions.length > 0) {
584
625
  details.availableVersions = error2.availableVersions;
585
626
  }
627
+ if (error2.availableRefs && error2.availableRefs.length > 0) {
628
+ details.availableRefs = error2.availableRefs;
629
+ }
630
+ if (error2.targetResolution) {
631
+ details.targetResolution = error2.targetResolution;
632
+ }
586
633
  return {
587
634
  code: "INDEXING",
588
635
  message: error2.message,
@@ -788,7 +835,7 @@ function parseCodeNavigationTargetSpec(spec) {
788
835
  function parseRepoTarget(spec) {
789
836
  const hashIndex = spec.lastIndexOf("#");
790
837
  if (hashIndex === -1) {
791
- throw new InvalidArgumentError("Repository target must include #gitRef for code_files/code_read/code_grep.");
838
+ return { repoUrl: spec };
792
839
  }
793
840
  const repoUrl = spec.slice(0, hashIndex);
794
841
  const gitRef = spec.slice(hashIndex + 1);
@@ -827,6 +874,7 @@ function buildSearchHitFollowUpCommand(hit) {
827
874
  version: loc.version,
828
875
  repoUrl: loc.repoUrl,
829
876
  gitRef: loc.gitRef,
877
+ requestedRef: loc.requestedRef,
830
878
  filePath: loc.filePath,
831
879
  startLine: loc.startLine,
832
880
  endLine: loc.endLine,
@@ -863,9 +911,8 @@ function buildTargetSpec(input) {
863
911
  return `${input.registry}:${input.packageName}${input.version ? `@${input.version}` : ""}`;
864
912
  }
865
913
  if (input.repoUrl) {
866
- if (!input.gitRef)
867
- return;
868
- return `${input.repoUrl}#${input.gitRef}`;
914
+ const ref = input.gitRef ?? input.requestedRef;
915
+ return ref ? `${input.repoUrl}#${ref}` : input.repoUrl;
869
916
  }
870
917
  if (input.registry && input.packageName) {
871
918
  return `${input.registry}:${input.packageName}${input.version ? `@${input.version}` : ""}`;
@@ -1061,6 +1108,161 @@ function shellQuote(value) {
1061
1108
  return `'${value.replaceAll("'", `'"'"'`)}'`;
1062
1109
  }
1063
1110
 
1111
+ // src/shared/target-resolution.ts
1112
+ function projectTargetResolution(resolution) {
1113
+ if (!resolution)
1114
+ return;
1115
+ return {
1116
+ ...resolution.requested ? { requested: projectIdentity(resolution.requested) } : {},
1117
+ ...resolution.resolvedRequested ? { resolvedRequested: projectIdentity(resolution.resolvedRequested) } : {},
1118
+ ...resolution.served ? { served: projectIdentity(resolution.served) } : {},
1119
+ ...resolution.freshness ? { freshness: resolution.freshness } : {},
1120
+ ...resolution.freshnessReason ? { freshnessReason: resolution.freshnessReason } : {},
1121
+ ...resolution.indexingRef ? { indexingRef: resolution.indexingRef } : {},
1122
+ availableVersions: resolution.availableVersions.map(projectArtifact),
1123
+ availableRefs: resolution.availableRefs.map(projectArtifact)
1124
+ };
1125
+ }
1126
+ function buildTargetResolutionNotes(resolution) {
1127
+ if (!resolution)
1128
+ return [];
1129
+ const lines = [];
1130
+ const requested = formatIdentity(resolution.requested);
1131
+ const fresh = formatIdentity(resolution.resolvedRequested);
1132
+ const served = formatIdentity(resolution.served);
1133
+ const reason = resolution.freshnessReason ? ` (${resolution.freshnessReason})` : "";
1134
+ switch (resolution.freshness) {
1135
+ case "fallback_recent": {
1136
+ const parts = ["using recent index"];
1137
+ if (served)
1138
+ parts.push(`served=${served}`);
1139
+ if (fresh && identitiesMateriallyDiffer(fresh, served)) {
1140
+ parts.push(`fresh=${fresh}`);
1141
+ }
1142
+ lines.push(`${parts.join(" | ")}${reason}`);
1143
+ break;
1144
+ }
1145
+ case "indexing": {
1146
+ const parts = ["indexing fresh target"];
1147
+ if (requested)
1148
+ parts.push(`requested=${requested}`);
1149
+ if (fresh)
1150
+ parts.push(`fresh=${fresh}`);
1151
+ if (resolution.indexingRef)
1152
+ parts.push(`indexingRef=${resolution.indexingRef}`);
1153
+ lines.push(`${parts.join(" | ")}${reason}`);
1154
+ break;
1155
+ }
1156
+ case "unavailable": {
1157
+ const parts = ["target unavailable"];
1158
+ if (requested)
1159
+ parts.push(`requested=${requested}`);
1160
+ lines.push(`${parts.join(" | ")}${reason}`);
1161
+ break;
1162
+ }
1163
+ case "current": {
1164
+ break;
1165
+ }
1166
+ default: {
1167
+ if (resolution.freshness || identitiesDiffer(requested, fresh, served)) {
1168
+ const parts = [
1169
+ `target resolution: ${resolution.freshness ?? "unknown"}`
1170
+ ];
1171
+ if (served)
1172
+ parts.push(`served=${served}`);
1173
+ if (requested)
1174
+ parts.push(`requested=${requested}`);
1175
+ if (fresh && fresh !== served)
1176
+ parts.push(`fresh=${fresh}`);
1177
+ lines.push(`${parts.join(" | ")}${reason}`);
1178
+ }
1179
+ break;
1180
+ }
1181
+ }
1182
+ const candidates = buildRetryCandidateLine(resolution);
1183
+ if (candidates)
1184
+ lines.push(candidates);
1185
+ return lines;
1186
+ }
1187
+ function buildRetryCandidateLine(resolution) {
1188
+ if (!resolution)
1189
+ return;
1190
+ const parts = [];
1191
+ if (resolution.availableVersions.length > 0) {
1192
+ parts.push(`versions=${resolution.availableVersions.map(formatArtifact).join(",")}`);
1193
+ }
1194
+ if (resolution.availableRefs.length > 0) {
1195
+ parts.push(`refs=${resolution.availableRefs.map(formatArtifact).join(",")}`);
1196
+ }
1197
+ return parts.length > 0 ? `queryable now: ${parts.join(" | ")}` : undefined;
1198
+ }
1199
+ function buildResolutionFromRetryCandidates(target) {
1200
+ if (!target.availableVersions?.length && !target.availableRefs?.length) {
1201
+ return;
1202
+ }
1203
+ return {
1204
+ freshness: target.freshness,
1205
+ indexingRef: target.indexingRef,
1206
+ availableVersions: target.availableVersions ?? [],
1207
+ availableRefs: target.availableRefs ?? []
1208
+ };
1209
+ }
1210
+ function projectIdentity(identity) {
1211
+ const out = {};
1212
+ if (identity.kind)
1213
+ out.kind = identity.kind;
1214
+ if (identity.registry)
1215
+ out.registry = identity.registry;
1216
+ if (identity.packageName)
1217
+ out.packageName = identity.packageName;
1218
+ if (identity.version)
1219
+ out.version = identity.version;
1220
+ if (identity.repoUrl)
1221
+ out.repoUrl = identity.repoUrl;
1222
+ if (identity.gitRef)
1223
+ out.gitRef = identity.gitRef;
1224
+ if (identity.commitSha)
1225
+ out.commitSha = identity.commitSha;
1226
+ return out;
1227
+ }
1228
+ function projectArtifact(artifact) {
1229
+ return artifact.version ? { version: artifact.version, ref: artifact.ref } : { ref: artifact.ref };
1230
+ }
1231
+ function formatIdentity(identity) {
1232
+ if (!identity)
1233
+ return;
1234
+ if (identity.registry && identity.packageName) {
1235
+ const version2 = identity.version ? `@${identity.version}` : "";
1236
+ const commit = identity.commitSha ? `#${shortSha(identity.commitSha)}` : "";
1237
+ return `${identity.registry.toLowerCase()}:${identity.packageName}${version2}${commit}`;
1238
+ }
1239
+ if (identity.repoUrl) {
1240
+ const ref = identity.gitRef ? `#${identity.gitRef}` : "";
1241
+ const commit = identity.commitSha ? `@${shortSha(identity.commitSha)}` : "";
1242
+ return `${identity.repoUrl}${ref}${commit}`;
1243
+ }
1244
+ return identity.gitRef ?? identity.version ?? identity.commitSha ?? identity.kind;
1245
+ }
1246
+ function formatArtifact(artifact) {
1247
+ return artifact.version ? `${artifact.version}@${artifact.ref}` : artifact.ref;
1248
+ }
1249
+ function identitiesDiffer(requested, fresh, served) {
1250
+ if (!served)
1251
+ return Boolean(requested || fresh);
1252
+ return Boolean(requested && requested !== served || fresh && fresh !== served);
1253
+ }
1254
+ function identitiesMateriallyDiffer(left, right) {
1255
+ if (!left || !right)
1256
+ return Boolean(left || right);
1257
+ return stripShortCommit(left) !== stripShortCommit(right);
1258
+ }
1259
+ function stripShortCommit(value) {
1260
+ return value.replace(/[@#][0-9a-f]{7}$/i, "");
1261
+ }
1262
+ function shortSha(value) {
1263
+ return /^[0-9a-f]{12,}$/i.test(value) ? value.slice(0, 7) : value;
1264
+ }
1265
+
1064
1266
  // src/shared/grep-repo-response.ts
1065
1267
  var UTF8_ENCODER = new TextEncoder;
1066
1268
  function buildGrepRepoSuccessPayload(result, options) {
@@ -1102,6 +1304,9 @@ function buildGrepRepoSuccessPayload(result, options) {
1102
1304
  if (result.resolution) {
1103
1305
  envelope.resolution = projectResolution(result.resolution);
1104
1306
  }
1307
+ const targetResolution = projectTargetResolution(result.targetResolution);
1308
+ if (targetResolution)
1309
+ envelope.targetResolution = targetResolution;
1105
1310
  const filter = buildFilterBlock(options);
1106
1311
  if (filter)
1107
1312
  envelope.filter = filter;
@@ -1189,8 +1394,11 @@ function buildFilterBlock(options) {
1189
1394
  return Object.keys(filter).length > 0 ? filter : undefined;
1190
1395
  }
1191
1396
  function formatGrepRepoTerminal(envelope, options) {
1192
- if (envelope.matches.length === 0) {
1193
- return { stdout: "" };
1397
+ if (envelope.matches.length === 0 && !options.verbose) {
1398
+ return {
1399
+ stdout: "",
1400
+ stderr: formatTerminalNotes(envelope, options.useColors)
1401
+ };
1194
1402
  }
1195
1403
  const blocks = buildRenderBlocks(envelope.matches);
1196
1404
  return options.verbose ? formatVerbose(envelope, blocks, options) : formatPlain(envelope, blocks, options);
@@ -1247,6 +1455,10 @@ function formatVerbose(envelope, blocks, options) {
1247
1455
  }
1248
1456
  lines.push("");
1249
1457
  const blocksByFile = groupBlocksByFile(blocks);
1458
+ if (blocksByFile.size === 0) {
1459
+ lines.push("No matches.");
1460
+ lines.push("");
1461
+ }
1250
1462
  for (const [filePath, fileBlocks] of blocksByFile) {
1251
1463
  lines.push(colorize(filePath, "bold", options.useColors));
1252
1464
  const gutterWidth = widestLineNumberInBlocks(fileBlocks);
@@ -1418,6 +1630,9 @@ function formatTerminalNotes(envelope, useColors) {
1418
1630
  if (envelope.hasMore && envelope.nextCursor) {
1419
1631
  lines.push(dim(`More grep results available — rerun with --cursor ${shellQuote(envelope.nextCursor)}`, useColors));
1420
1632
  }
1633
+ for (const note of buildTargetResolutionNotes(envelope.targetResolution)) {
1634
+ lines.push(dim(note, useColors));
1635
+ }
1421
1636
  if (lines.length === 0)
1422
1637
  return;
1423
1638
  return `${lines.join(`
@@ -1548,6 +1763,9 @@ function buildTrailer(envelope) {
1548
1763
  if (skipNotes.length > 0) {
1549
1764
  lines.push(`Note: ${skipNotes.join(", ")}.`);
1550
1765
  }
1766
+ for (const note of buildTargetResolutionNotes(envelope.targetResolution)) {
1767
+ lines.push(note);
1768
+ }
1551
1769
  return lines;
1552
1770
  }
1553
1771
  function buildRenderBlocks2(matches) {
@@ -1668,6 +1886,7 @@ function renderListFilesText(envelope) {
1668
1886
  lines.push("");
1669
1887
  if (envelope.files.length === 0) {
1670
1888
  lines.push(envelope.hint ?? "No files match the requested filter.");
1889
+ appendTargetResolutionNotes(lines, envelope);
1671
1890
  return lines.join(`
1672
1891
  `);
1673
1892
  }
@@ -1682,9 +1901,18 @@ function renderListFilesText(envelope) {
1682
1901
  lines.push("");
1683
1902
  lines.push(envelope.hint);
1684
1903
  }
1904
+ appendTargetResolutionNotes(lines, envelope);
1685
1905
  return lines.join(`
1686
1906
  `);
1687
1907
  }
1908
+ function appendTargetResolutionNotes(lines, envelope) {
1909
+ const notes = buildTargetResolutionNotes(envelope.targetResolution);
1910
+ if (notes.length === 0)
1911
+ return;
1912
+ lines.push("");
1913
+ for (const note of notes)
1914
+ lines.push(note);
1915
+ }
1688
1916
  function buildHeader2(envelope) {
1689
1917
  const identity = buildIdentity(envelope);
1690
1918
  const countValue = envelope.hasMore ? `${envelope.files.length}+` : String(envelope.total);
@@ -2203,7 +2431,7 @@ function buildDirectVersionLookup(graph) {
2203
2431
  if (!fromRoot)
2204
2432
  continue;
2205
2433
  const node = graph.nodes[edge.toIndex];
2206
- if (!node || !node.version)
2434
+ if (!node?.version)
2207
2435
  continue;
2208
2436
  if (!out.has(node.name)) {
2209
2437
  out.set(node.name, node.version);
@@ -5176,6 +5404,9 @@ function buildReadFileSuccessPayload(result, options) {
5176
5404
  } else if (result.content != null) {
5177
5405
  envelope.content = result.content;
5178
5406
  }
5407
+ const targetResolution = projectTargetResolution(result.targetResolution);
5408
+ if (targetResolution)
5409
+ envelope.targetResolution = targetResolution;
5179
5410
  return envelope;
5180
5411
  }
5181
5412
  function formatReadFileTerminal(envelope, options) {
@@ -5230,10 +5461,19 @@ function formatVerboseBody(envelope, options) {
5230
5461
  lines.push("");
5231
5462
  lines.push(dim(envelope.hint, options.useColors));
5232
5463
  }
5464
+ appendTargetResolutionNotes2(lines, envelope, options);
5233
5465
  lines.push("");
5234
5466
  return lines.join(`
5235
5467
  `);
5236
5468
  }
5469
+ function appendTargetResolutionNotes2(lines, envelope, options) {
5470
+ const notes = buildTargetResolutionNotes(envelope.targetResolution);
5471
+ if (notes.length === 0)
5472
+ return;
5473
+ lines.push("");
5474
+ for (const note of notes)
5475
+ lines.push(dim(note, options.useColors));
5476
+ }
5237
5477
  function splitReadFileContentLines(envelope) {
5238
5478
  if (!envelope.content)
5239
5479
  return [];
@@ -5295,6 +5535,12 @@ function renderReadFileText(envelope) {
5295
5535
  lines.push("");
5296
5536
  lines.push(`hint: ${envelope.hint}`);
5297
5537
  }
5538
+ const resolutionNotes = buildTargetResolutionNotes(envelope.targetResolution);
5539
+ if (resolutionNotes.length > 0) {
5540
+ lines.push("");
5541
+ for (const note of resolutionNotes)
5542
+ lines.push(note);
5543
+ }
5298
5544
  return lines.join(`
5299
5545
  `);
5300
5546
  }
@@ -5758,7 +6004,7 @@ auth.storage = "file". File storage is plaintext on disk.
5758
6004
  Use --no-browser in environments without a display (CI, SSH sessions)
5759
6005
  to get a URL you can open on another device.`;
5760
6006
  function registerLoginCommand(program) {
5761
- program.command("login").summary("Authenticate with your GitHits account").description(LOGIN_DESCRIPTION).option("--no-browser", "Print URL instead of opening browser").option("--port <port>", "Port for local callback server", parseInt).option("--force", "Re-authenticate even if already logged in").action(async (options) => {
6007
+ program.command("login").summary("Sign in to your GitHits account").description(LOGIN_DESCRIPTION).option("--no-browser", "Print URL instead of opening browser").option("--port <port>", "Port for local callback server", parseInt).option("--force", "Re-authenticate even if already logged in").action(async (options) => {
5762
6008
  const deps = await createAuthCommandDependencies();
5763
6009
  await loginAction(options, deps);
5764
6010
  });
@@ -5900,6 +6146,61 @@ function getPostLoginContinuationMessage(command) {
5900
6146
  return;
5901
6147
  }
5902
6148
  }
6149
+ // src/shared/spinner.ts
6150
+ var SPINNER_FRAMES = ["|", "/", "-", "\\"];
6151
+ var FRAME_INTERVAL_MS = 80;
6152
+ var MESSAGE_INTERVAL_MS = 2000;
6153
+ function startSpinner(message, enabled = true, runtime = {}) {
6154
+ const stdoutIsTTY = runtime.stdoutIsTTY ?? process.stdout.isTTY;
6155
+ const stderrIsTTY = runtime.stderrIsTTY ?? process.stderr.isTTY;
6156
+ const writeStderr = runtime.writeStderr ?? ((chunk) => process.stderr.write(chunk));
6157
+ if (!enabled || !stdoutIsTTY || !stderrIsTTY) {
6158
+ return { stop: () => {} };
6159
+ }
6160
+ const messages = typeof message === "string" ? [message] : message;
6161
+ const framesPerMessage = Math.round(MESSAGE_INTERVAL_MS / FRAME_INTERVAL_MS);
6162
+ const useColors = runtime.useColors ?? process.env.NO_COLOR === undefined;
6163
+ let frame = 0;
6164
+ const render = () => {
6165
+ const glyph = SPINNER_FRAMES[frame % SPINNER_FRAMES.length] ?? "|";
6166
+ const label = messages[Math.floor(frame / framesPerMessage) % messages.length] ?? "";
6167
+ frame += 1;
6168
+ writeStderr(`\r\x1B[2K${colorizeBrand(glyph, "primary", useColors)} ${label}`);
6169
+ };
6170
+ render();
6171
+ const interval = setInterval(render, FRAME_INTERVAL_MS);
6172
+ return {
6173
+ stop: () => {
6174
+ clearInterval(interval);
6175
+ writeStderr("\r\x1B[2K");
6176
+ }
6177
+ };
6178
+ }
6179
+ // src/shared/spinner-messages.ts
6180
+ var SPINNER_MESSAGES = {
6181
+ example: [
6182
+ "Searching real implementations...",
6183
+ "Exploring open-source code...",
6184
+ "Finding production patterns...",
6185
+ "Grounding results..."
6186
+ ],
6187
+ search: [
6188
+ "Exploring repositories...",
6189
+ "Tracing symbols...",
6190
+ "Inspecting dependencies...",
6191
+ "Scanning source code..."
6192
+ ],
6193
+ code: [
6194
+ "Inspecting source code...",
6195
+ "Resolving symbols...",
6196
+ "Reading dependency internals..."
6197
+ ],
6198
+ docs: [
6199
+ "Reading documentation...",
6200
+ "Resolving references...",
6201
+ "Collecting package docs..."
6202
+ ]
6203
+ };
5903
6204
  // src/shared/unified-search-request.ts
5904
6205
  var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
5905
6206
  function buildUnifiedSearchParams(input) {
@@ -6316,6 +6617,15 @@ function compactProgressTarget(target) {
6316
6617
  payload.indexingRef = target.indexingRef;
6317
6618
  if (target.requestedRefKind)
6318
6619
  payload.requestedRefKind = target.requestedRefKind;
6620
+ const targetResolution = projectTargetResolution(target.targetResolution);
6621
+ if (targetResolution)
6622
+ payload.targetResolution = targetResolution;
6623
+ if (target.availableVersions?.length) {
6624
+ payload.availableVersions = target.availableVersions;
6625
+ }
6626
+ if (target.availableRefs?.length) {
6627
+ payload.availableRefs = target.availableRefs;
6628
+ }
6319
6629
  return Object.keys(payload).length > 0 ? payload : undefined;
6320
6630
  }
6321
6631
  function buildFilterEcho3(filters) {
@@ -6358,7 +6668,11 @@ function buildProgressFreshnessWarnings(progress) {
6358
6668
  requestedTarget: target.requested,
6359
6669
  freshTarget: target.resolvedRequested,
6360
6670
  servedTarget: target.served
6361
- })).filter((entry) => Boolean(entry));
6671
+ })).concat((progress?.targets ?? []).map((target) => progressTargetResolutionWarning(target)).filter((entry) => Boolean(entry))).filter((entry) => Boolean(entry));
6672
+ }
6673
+ function progressTargetResolutionWarning(target) {
6674
+ const notes = buildTargetResolutionNotes(target.targetResolution ?? buildResolutionFromRetryCandidates(target));
6675
+ return notes.length > 0 ? notes.join(" ") : undefined;
6362
6676
  }
6363
6677
  function freshnessWarning(input) {
6364
6678
  if (!isTrustRelevantFreshness(input.freshness))
@@ -6377,7 +6691,30 @@ function labelsDiverge(input) {
6377
6691
  const served = input.servedTarget;
6378
6692
  if (!served)
6379
6693
  return false;
6380
- return Boolean(input.freshTarget && input.freshTarget !== served || input.requestedTarget && input.requestedTarget !== served);
6694
+ return Boolean(input.freshTarget && canonicalTargetLabel(input.freshTarget) !== canonicalTargetLabel(served));
6695
+ }
6696
+ function canonicalTargetLabel(label) {
6697
+ const parsed = parsePackageVersionLabel(label);
6698
+ if (!parsed)
6699
+ return label;
6700
+ const version2 = parsed.version.replace(/^v(?=\d)/i, "");
6701
+ return `${parsed.registry.toLowerCase()}:${parsed.packageName}@${version2}`;
6702
+ }
6703
+ function parsePackageVersionLabel(label) {
6704
+ const registryEnd = label.indexOf(":");
6705
+ if (registryEnd <= 0)
6706
+ return;
6707
+ const versionStart = label.lastIndexOf("@");
6708
+ if (versionStart <= registryEnd + 1)
6709
+ return;
6710
+ const version2 = label.slice(versionStart + 1);
6711
+ if (!version2)
6712
+ return;
6713
+ return {
6714
+ registry: label.slice(0, registryEnd),
6715
+ packageName: label.slice(registryEnd + 1, versionStart),
6716
+ version: version2
6717
+ };
6381
6718
  }
6382
6719
  function warningForEntry(entry) {
6383
6720
  const reasons = [];
@@ -6389,6 +6726,14 @@ function warningForEntry(entry) {
6389
6726
  });
6390
6727
  if (freshness)
6391
6728
  return freshness;
6729
+ const terminalLifecycleReason = terminalLifecycleWarningReason(entry);
6730
+ if (terminalLifecycleReason) {
6731
+ reasons.push(terminalLifecycleReason);
6732
+ } else {
6733
+ const targetResolutionWarning = targetResolutionWarningForEntry(entry);
6734
+ if (targetResolutionWarning)
6735
+ reasons.push(targetResolutionWarning);
6736
+ }
6392
6737
  if (entry.incompatibleQueryFeatures?.length) {
6393
6738
  reasons.push(`incompatible query features [${entry.incompatibleQueryFeatures.join(", ")}]`);
6394
6739
  }
@@ -6401,10 +6746,10 @@ function warningForEntry(entry) {
6401
6746
  if (entry.ignoredFilters?.length) {
6402
6747
  reasons.push(`ignored filters [${entry.ignoredFilters.join(", ")}]`);
6403
6748
  }
6404
- if (entry.indexingStatus) {
6749
+ if (!terminalLifecycleReason && reasons.length === 0 && entry.indexingStatus) {
6405
6750
  reasons.push(`indexing status ${entry.indexingStatus}`);
6406
6751
  }
6407
- if (entry.codeIndexState) {
6752
+ if (!terminalLifecycleReason && reasons.length === 0 && entry.codeIndexState) {
6408
6753
  if (entry.codeIndexState !== "STALE") {
6409
6754
  reasons.push(`code index state ${entry.codeIndexState}`);
6410
6755
  }
@@ -6418,6 +6763,18 @@ function warningForEntry(entry) {
6418
6763
  }
6419
6764
  return;
6420
6765
  }
6766
+ function terminalLifecycleWarningReason(entry) {
6767
+ const states = Array.from(new Set([entry.indexingStatus, entry.codeIndexState].filter(Boolean)));
6768
+ const terminalStates = states.filter((state) => state !== "INDEXING" && state !== "STALE");
6769
+ if (terminalStates.length === 0)
6770
+ return;
6771
+ const status = terminalStates.join("/");
6772
+ return entry.note ? `${entry.note} (${status})` : `status ${status}`;
6773
+ }
6774
+ function targetResolutionWarningForEntry(entry) {
6775
+ const notes = buildTargetResolutionNotes(entry.targetResolution);
6776
+ return notes.length > 0 ? notes.join(" ") : undefined;
6777
+ }
6421
6778
  function compactSourceStatus(sourceStatus) {
6422
6779
  if (!sourceStatus || sourceStatus.length === 0)
6423
6780
  return;
@@ -6447,6 +6804,13 @@ function compactSourceStatusEntry(entry) {
6447
6804
  payload.codeIndexState = entry.codeIndexState;
6448
6805
  interesting = true;
6449
6806
  }
6807
+ const targetResolution = projectTargetResolution(entry.targetResolution);
6808
+ if (targetResolution) {
6809
+ payload.targetResolution = targetResolution;
6810
+ if (buildTargetResolutionNotes(targetResolution).length > 0) {
6811
+ interesting = true;
6812
+ }
6813
+ }
6450
6814
  if (entry.indexingStatus && entry.indexingStatus !== "INDEXED") {
6451
6815
  payload.indexingStatus = entry.indexingStatus;
6452
6816
  interesting = true;
@@ -6484,7 +6848,7 @@ function assertSearchFollowUpInvariant(hit) {
6484
6848
  if ((hit.resultType === "DOCUMENTATION_PAGE" || hit.resultType === "REPOSITORY_DOC") && !hit.locator.pageId) {
6485
6849
  throw new MalformedCodeNavigationResponseError(`${hit.resultType} search hit missing required pageId.`);
6486
6850
  }
6487
- if (hit.resultType === "REPOSITORY_DOC" && (!hit.locator.repoUrl || !hit.locator.gitRef || !hit.locator.filePath)) {
6851
+ if (hit.resultType === "REPOSITORY_DOC" && (!hit.locator.repoUrl || !hit.locator.filePath)) {
6488
6852
  throw new MalformedCodeNavigationResponseError("REPOSITORY_DOC search hit missing repo locator fields.");
6489
6853
  }
6490
6854
  }
@@ -6496,7 +6860,7 @@ function renderUnifiedSearchSuccess(payload) {
6496
6860
  lines.push(buildHeader8(payload));
6497
6861
  lines.push("");
6498
6862
  if (payload.results.length === 0) {
6499
- lines.push(payload.completed ? "No hits." : "No hits yet - indexing.");
6863
+ lines.push(payload.completed ? "No hits." : noHitsYetMessage(payload));
6500
6864
  } else {
6501
6865
  appendUnifiedSearchHits(lines, payload.results);
6502
6866
  }
@@ -6509,6 +6873,18 @@ function renderUnifiedSearchSuccess(payload) {
6509
6873
  return lines.join(`
6510
6874
  `);
6511
6875
  }
6876
+ function noHitsYetMessage(payload) {
6877
+ if (payload.completed)
6878
+ return "No hits.";
6879
+ const status = payload.progress?.status;
6880
+ if (status === "TIMEOUT")
6881
+ return "No hits yet - timed out waiting.";
6882
+ if (status === "FAILED")
6883
+ return "No hits - search failed.";
6884
+ if (status === "SEARCHING")
6885
+ return "No hits yet - searching.";
6886
+ return "No hits yet - indexing.";
6887
+ }
6512
6888
  function renderUnifiedSearchError(payload) {
6513
6889
  const lines = [];
6514
6890
  const header = `search${SEP6}ERROR${SEP6}code=${payload.code}${payload.retryable ? `${SEP6}retryable` : ""}`;
@@ -6638,7 +7014,9 @@ function buildTrailer2(payload) {
6638
7014
  lines.push(`More hits available.${nextOffsetHint}`);
6639
7015
  }
6640
7016
  if (!payload.completed && payload.searchRef) {
6641
- lines.push(`Indexing in progress. Call search_status with searchRef=${payload.searchRef} to follow up.`);
7017
+ const status = payload.progress?.status;
7018
+ const action = status === "TIMEOUT" ? "Search timed out waiting for fresh data." : status === "FAILED" ? "Search failed before completion." : status === "SEARCHING" ? "Search in progress." : "Indexing in progress.";
7019
+ lines.push(`${action} Call search_status with searchRef=${payload.searchRef} to follow up.`);
6642
7020
  }
6643
7021
  if (payload.sourceStatus && payload.sourceStatus.length > 0) {
6644
7022
  lines.push("source notes:");
@@ -6669,6 +7047,9 @@ function formatProgressTarget(target) {
6669
7047
  parts.push(`intent=${target.requestedRefKind}`);
6670
7048
  if (target.indexingRef)
6671
7049
  parts.push(`indexingRef=${target.indexingRef}`);
7050
+ for (const note of buildTargetResolutionNotes(target.targetResolution ?? buildResolutionFromRetryCandidates(target))) {
7051
+ parts.push(note);
7052
+ }
6672
7053
  return parts.length > 0 ? parts.join(SEP6) : "target progress unavailable";
6673
7054
  }
6674
7055
  function describeFreshness(value) {
@@ -6686,6 +7067,10 @@ function describeFreshness(value) {
6686
7067
  }
6687
7068
  }
6688
7069
  function formatSourceStatus(entry) {
7070
+ const terminalReason = terminalLifecycleReason(entry);
7071
+ if (terminalReason) {
7072
+ return `${entry.source} (${entry.targetLabel})${SEP6}${terminalReason}`;
7073
+ }
6689
7074
  const parts = [`${entry.source} (${entry.targetLabel})`];
6690
7075
  if (entry.indexingStatus)
6691
7076
  parts.push(`indexing=${entry.indexingStatus}`);
@@ -6705,8 +7090,19 @@ function formatSourceStatus(entry) {
6705
7090
  }
6706
7091
  if (entry.note)
6707
7092
  parts.push(entry.note);
7093
+ for (const note of buildTargetResolutionNotes(entry.targetResolution)) {
7094
+ parts.push(note);
7095
+ }
6708
7096
  return parts.join(SEP6);
6709
7097
  }
7098
+ function terminalLifecycleReason(entry) {
7099
+ const states = Array.from(new Set([entry.indexingStatus, entry.codeIndexState].filter(Boolean)));
7100
+ const terminalStates = states.filter((state) => state !== "INDEXING" && state !== "STALE");
7101
+ if (terminalStates.length === 0)
7102
+ return;
7103
+ const status = terminalStates.join("/");
7104
+ return entry.note ? `${entry.note} (${status})` : `status ${status}`;
7105
+ }
6710
7106
  function quote4(value) {
6711
7107
  return value.includes('"') ? `'${value}'` : `"${value}"`;
6712
7108
  }
@@ -6769,7 +7165,7 @@ function renderUnifiedSearchStatusText(payload) {
6769
7165
  `);
6770
7166
  }
6771
7167
  function buildHeader9(payload) {
6772
- const state = payload.completed ? "complete" : "indexing";
7168
+ const state = payload.completed ? "complete" : payload.progress?.status.toLowerCase() ?? "incomplete";
6773
7169
  const parts = [`search_status${SEP7}${state}`];
6774
7170
  if (payload.searchRef)
6775
7171
  parts.push(`searchRef=${payload.searchRef}`);
@@ -6797,32 +7193,10 @@ function appendResult(lines, result) {
6797
7193
  lines.push("");
6798
7194
  lines.push("source notes:");
6799
7195
  for (const entry of result.sourceStatus) {
6800
- lines.push(` - ${formatSourceStatus2(entry)}`);
7196
+ lines.push(` - ${formatSourceStatus(entry)}`);
6801
7197
  }
6802
7198
  }
6803
7199
  }
6804
- function formatSourceStatus2(entry) {
6805
- const parts = [`${entry.source} (${entry.targetLabel})`];
6806
- if (entry.indexingStatus)
6807
- parts.push(`indexing=${entry.indexingStatus}`);
6808
- if (entry.codeIndexState)
6809
- parts.push(`codeIndex=${entry.codeIndexState}`);
6810
- if (entry.ignoredFilters?.length) {
6811
- parts.push(`ignored=${entry.ignoredFilters.join(",")}`);
6812
- }
6813
- if (entry.incompatibleFilters?.length) {
6814
- parts.push(`incompatible=${entry.incompatibleFilters.join(",")}`);
6815
- }
6816
- if (entry.ignoredQueryFeatures?.length) {
6817
- parts.push(`ignoredQuery=${entry.ignoredQueryFeatures.join(",")}`);
6818
- }
6819
- if (entry.incompatibleQueryFeatures?.length) {
6820
- parts.push(`incompatibleQuery=${entry.incompatibleQueryFeatures.join(",")}`);
6821
- }
6822
- if (entry.note)
6823
- parts.push(entry.note);
6824
- return parts.join(SEP7);
6825
- }
6826
7200
  function formatProgress(progress) {
6827
7201
  const next = progress.next ? `; next: ${progress.next}` : "";
6828
7202
  return `progress: ${progress.status}, ${progress.targetsReady}/${progress.targetsTotal} targets ready, ${progress.elapsedMs}ms elapsed${next}`;
@@ -7042,6 +7416,9 @@ function buildListFilesSuccessPayload(result, options) {
7042
7416
  envelope.indexedVersion = result.indexedVersion;
7043
7417
  if (result.resolution)
7044
7418
  envelope.resolution = projectResolution2(result.resolution);
7419
+ const targetResolution = projectTargetResolution(result.targetResolution);
7420
+ if (targetResolution)
7421
+ envelope.targetResolution = targetResolution;
7045
7422
  if (result.hint)
7046
7423
  envelope.hint = result.hint;
7047
7424
  const filter = buildFilterBlock2(options);
@@ -7152,6 +7529,7 @@ function formatVerbose2(envelope, options) {
7152
7529
  if (envelope.resolution || envelope.indexedVersion) {
7153
7530
  lines.push(buildResolutionLine(envelope, options));
7154
7531
  }
7532
+ appendTargetResolutionNotes3(lines, envelope, options);
7155
7533
  lines.push("");
7156
7534
  const pathWidth = longestPathLength(envelope.files);
7157
7535
  for (const file of envelope.files) {
@@ -7180,6 +7558,7 @@ function formatEmpty(envelope, options, verbose) {
7180
7558
  if (envelope.resolution || envelope.indexedVersion) {
7181
7559
  lines.push(buildResolutionLine(envelope, options));
7182
7560
  }
7561
+ appendTargetResolutionNotes3(lines, envelope, options);
7183
7562
  lines.push("");
7184
7563
  lines.push(dim(hint, options.useColors));
7185
7564
  lines.push("");
@@ -7202,6 +7581,11 @@ function buildResolutionLine(envelope, options) {
7202
7581
  parts.push(`commit ${commit.slice(0, 7)}`);
7203
7582
  return dim(parts.join(" · "), options.useColors);
7204
7583
  }
7584
+ function appendTargetResolutionNotes3(lines, envelope, options) {
7585
+ const notes = buildTargetResolutionNotes(envelope.targetResolution);
7586
+ for (const note of notes)
7587
+ lines.push(dim(note, options.useColors));
7588
+ }
7205
7589
  function buildIdentityLabel(envelope) {
7206
7590
  if (envelope.registry && envelope.name) {
7207
7591
  return `${envelope.name} · ${envelope.registry}`;
@@ -7251,13 +7635,10 @@ function resolveCliCodeNavTarget(spec, options) {
7251
7635
  const hasRepoUrl = Boolean(options.repoUrl);
7252
7636
  const hasGitRef = Boolean(options.gitRef);
7253
7637
  if (hasSpec && (hasRepoUrl || hasGitRef)) {
7254
- throw new InvalidPackageSpecError("Provide either a package spec (e.g. `npm:express`) or `--repo-url` + `--git-ref`, not both.");
7638
+ throw new InvalidPackageSpecError("Provide either a package spec (e.g. `npm:express`) or `--repo-url` with optional `--git-ref`, not both.");
7255
7639
  }
7256
7640
  if (!hasSpec && !hasRepoUrl) {
7257
- throw new InvalidPackageSpecError("A package spec (e.g. `npm:express`) or `--repo-url` + `--git-ref` is required.");
7258
- }
7259
- if (hasRepoUrl && !hasGitRef) {
7260
- throw new InvalidPackageSpecError("`--repo-url` requires `--git-ref` for code files/read/grep (a tag, branch, commit, or `HEAD`).");
7641
+ throw new InvalidPackageSpecError("A package spec (e.g. `npm:express`) or `--repo-url` is required.");
7261
7642
  }
7262
7643
  if (hasSpec) {
7263
7644
  const parsed = parsePackageSpec(spec);
@@ -7301,6 +7682,13 @@ function formatIndexingError(mapped) {
7301
7682
  const suffix = more > 0 ? ` (+${more} more)` : "";
7302
7683
  lines.push(` already-indexed versions: ${shown}${suffix}`);
7303
7684
  }
7685
+ const refs = detail.availableRefs;
7686
+ if (refs && refs.length > 0) {
7687
+ const shown = refs.slice(0, 5).map((entry) => entry.ref).join(", ");
7688
+ const more = refs.length - 5;
7689
+ const suffix = more > 0 ? ` (+${more} more)` : "";
7690
+ lines.push(` already-indexed refs: ${shown}${suffix}`);
7691
+ }
7304
7692
  return lines.join(`
7305
7693
  `);
7306
7694
  }
@@ -7381,7 +7769,8 @@ async function pkgFilesAction(firstArg, secondArg, options, deps) {
7381
7769
  limit,
7382
7770
  waitTimeoutMs: wait
7383
7771
  });
7384
- const result = await deps.codeNavigationService.listFiles(build.params);
7772
+ const spinner = startSpinner(SPINNER_MESSAGES.code, !options.json);
7773
+ const result = await deps.codeNavigationService.listFiles(build.params).finally(() => spinner.stop());
7385
7774
  const payload = buildListFilesSuccessPayload(result, {
7386
7775
  registry: target.registry ? toPkgseerRegistryLowercase(target.registry) : undefined,
7387
7776
  name: target.packageName,
@@ -7439,7 +7828,7 @@ function resolvePositionals(firstArg, secondArg, hasRepoUrl) {
7439
7828
  throw new InvalidPackageSpecError("In --repo-url mode, pass only [path-prefix] — the package spec is replaced by --repo-url.");
7440
7829
  }
7441
7830
  if (firstArg && REGISTRY_SPEC_HINT.test(firstArg)) {
7442
- throw new InvalidPackageSpecError(`'${firstArg}' looks like a package spec. Provide either a package spec or \`--repo-url\` + \`--git-ref\`, not both.`);
7831
+ throw new InvalidPackageSpecError(`'${firstArg}' looks like a package spec. Provide either a package spec or \`--repo-url\` with optional \`--git-ref\`, not both.`);
7443
7832
  }
7444
7833
  return { spec: undefined, pathPrefix: firstArg };
7445
7834
  }
@@ -7467,7 +7856,7 @@ On an INDEXING response, the dependency is being indexed on-demand
7467
7856
  — retry with a longer --wait (up to 60000 ms) or pick one of the
7468
7857
  already-indexed versions surfaced in the error detail.`;
7469
7858
  function registerCodeFilesCommand(pkgCommand) {
7470
- return pkgCommand.command("files").summary("List files in an indexed dependency").description(PKG_FILES_DESCRIPTION).argument("[spec-or-prefix]", "Spec mode: package spec (e.g. npm:express). Repo mode (with --repo-url): the path-prefix.").argument("[path-prefix]", "Spec mode only: literal directory prefix (not a glob). 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 selector").option("--glob <glob>", "Glob selector (repeatable)", collectRepeatable).option("--ext <ext>", "Extension filter without leading dot (repeatable)", collectRepeatable).option("--file-type <type>", "File type filter such as source or doc (repeatable)", collectRepeatable).option("--language <language>", "Language filter matching aigrep language names (repeatable)", collectRepeatable).option("--file-intent <intent>", "Inclusive file-intent filter. Repeat to include multiple intents: production, test, benchmark, example, generated, fixture, build, vendor", collectRepeatable).option("--exclude-intent <intent>", "Exclude these file intents after inclusive filtering (repeatable)", collectRepeatable).option("--exclude-docs", "Skip files classified as documentation").option("--exclude-tests", "Skip files classified as tests").option("--hidden", "Include dotfiles and dot-prefixed paths").option("--limit <n>", "Max entries (1-1000, default 200)").option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Annotate each path with language / file-type / byte size").option("--json", "Emit the JSON envelope").action(async (arg1, arg2, options) => {
7859
+ return pkgCommand.command("files").summary("List files in an indexed dependency").description(PKG_FILES_DESCRIPTION).argument("[spec-or-prefix]", "Spec mode: package spec (e.g. npm:express). Repo mode (with --repo-url): the path-prefix.").argument("[path-prefix]", "Spec mode only: literal directory prefix (not a glob). Ignored with --repo-url.").option("--repo-url <url>", "Repository URL addressing (defaults to the repo default branch)").option("--git-ref <ref>", "Optional tag, commit, branch, or HEAD for --repo-url.").option("--path <path>", "Exact file selector").option("--glob <glob>", "Glob selector (repeatable)", collectRepeatable).option("--ext <ext>", "Extension filter without leading dot (repeatable)", collectRepeatable).option("--file-type <type>", "File type filter such as source or doc (repeatable)", collectRepeatable).option("--language <language>", "Language filter matching aigrep language names (repeatable)", collectRepeatable).option("--file-intent <intent>", "Inclusive file-intent filter. Repeat to include multiple intents: production, test, benchmark, example, generated, fixture, build, vendor", collectRepeatable).option("--exclude-intent <intent>", "Exclude these file intents after inclusive filtering (repeatable)", collectRepeatable).option("--exclude-docs", "Skip files classified as documentation").option("--exclude-tests", "Skip files classified as tests").option("--hidden", "Include dotfiles and dot-prefixed paths").option("--limit <n>", "Max entries (1-1000, default 200)").option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Annotate each path with language / file-type / byte size").option("--json", "Emit the JSON envelope").action(async (arg1, arg2, options) => {
7471
7860
  const deps = await createContainer();
7472
7861
  await pkgFilesAction(arg1, arg2, options, {
7473
7862
  codeNavigationService: deps.codeNavigationService,
@@ -7517,7 +7906,8 @@ async function pkgGrepAction(first, second, third, options, deps) {
7517
7906
  symbolFields: options.symbolField,
7518
7907
  waitTimeoutMs: wait
7519
7908
  });
7520
- const result = await deps.codeNavigationService.grepRepo(build.params);
7909
+ const spinner = startSpinner(SPINNER_MESSAGES.code, !options.json);
7910
+ const result = await deps.codeNavigationService.grepRepo(build.params).finally(() => spinner.stop());
7521
7911
  const payload = buildGrepRepoSuccessPayload(result, {
7522
7912
  registry: target.registry ? toPkgseerRegistryLowercase(target.registry) : undefined,
7523
7913
  name: target.packageName,
@@ -7544,7 +7934,7 @@ async function pkgGrepAction(first, second, third, options, deps) {
7544
7934
  if (options.json) {
7545
7935
  console.log(JSON.stringify(payload));
7546
7936
  if (payload.totalMatches === 0)
7547
- process.exit(1);
7937
+ process.exitCode = 1;
7548
7938
  return;
7549
7939
  }
7550
7940
  const rendered = formatGrepRepoTerminal(payload, {
@@ -7557,7 +7947,7 @@ async function pkgGrepAction(first, second, third, options, deps) {
7557
7947
  if (rendered.stderr)
7558
7948
  process.stderr.write(rendered.stderr);
7559
7949
  if (payload.totalMatches === 0)
7560
- process.exit(1);
7950
+ process.exitCode = 1;
7561
7951
  } catch (error2) {
7562
7952
  handleCodeNavCommandError(error2, options.json ?? false, formatFileErrorWithFilesHint, 2);
7563
7953
  }
@@ -7570,7 +7960,7 @@ function resolvePositionals2(first, second, third, hasRepoUrl) {
7570
7960
  return { spec: undefined, pattern: first, pathPrefix: second };
7571
7961
  }
7572
7962
  if (first !== undefined && second === undefined) {
7573
- throw new InvalidPackageSpecError("In spec mode, pass at least <spec> <pattern>. If you meant to target a repository instead, pass --repo-url <url> --git-ref <ref>.");
7963
+ throw new InvalidPackageSpecError("In spec mode, pass at least <spec> <pattern>. If you meant to target a repository instead, pass --repo-url <url> with optional --git-ref <ref>.");
7574
7964
  }
7575
7965
  return { spec: first, pattern: second, pathPrefix: third };
7576
7966
  }
@@ -7595,7 +7985,7 @@ var PKG_GREP_DESCRIPTION = `Deterministic text grep over indexed dependency and
7595
7985
  ${CLI_GREP_PATTERN_NOTE}
7596
7986
  Use \`githits search\` for discovery; use \`githits code grep\` when you know the text or regex to match.
7597
7987
 
7598
- Addressing: <spec> (registry:name[@version]) OR --repo-url <url> --git-ref <ref>.
7988
+ Addressing: <spec> (registry:name[@version]) OR --repo-url <url> [--git-ref <ref>].
7599
7989
  Omitted version means latest release.
7600
7990
  In spec mode pass <spec> <pattern> [path-prefix]; in repo-URL mode pass only <pattern> [path-prefix].
7601
7991
 
@@ -7610,8 +8000,8 @@ for context, --verbose for grouped output, and --cursor to continue a paginated
7610
8000
  grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
7611
8001
  match in --verbose output; full payload in --json).`;
7612
8002
  function registerCodeGrepCommand(pkgCommand) {
7613
- 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) => {
7614
- const { createContainer: createContainer2 } = await import("./shared/chunk-zzmbjttb.js");
8003
+ 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 (defaults to the repo default branch)").option("--git-ref <ref>", "Optional tag, commit, branch, or HEAD for --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) => {
8004
+ const { createContainer: createContainer2 } = await import("./shared/chunk-70bn2pmx.js");
7615
8005
  const deps = await createContainer2();
7616
8006
  await pkgGrepAction(arg1, arg2, arg3, options, {
7617
8007
  codeNavigationService: deps.codeNavigationService,
@@ -7721,7 +8111,8 @@ async function pkgReadAction(firstArg, secondArg, options, deps) {
7721
8111
  endLine: range.endLine,
7722
8112
  waitTimeoutMs: wait
7723
8113
  });
7724
- const result = await deps.codeNavigationService.readFile(build.params);
8114
+ const spinner = startSpinner(SPINNER_MESSAGES.code, !options.json);
8115
+ const result = await deps.codeNavigationService.readFile(build.params).finally(() => spinner.stop());
7725
8116
  const payload = buildReadFileSuccessPayload(result, {
7726
8117
  registry: target.registry ? toPkgseerRegistryLowercase(target.registry) : undefined,
7727
8118
  name: target.packageName,
@@ -7845,7 +8236,7 @@ Binary files show a one-line sentinel instead of content. When a
7845
8236
  path is missing, the response is a FILE_NOT_FOUND error — use
7846
8237
  \`code files\` to discover available paths.`;
7847
8238
  function registerCodeReadCommand(pkgCommand) {
7848
- return pkgCommand.command("read").summary("Read a file from an indexed dependency").description(PKG_READ_DESCRIPTION).argument("[spec-or-path]", "In spec mode: package spec (e.g. npm:express). In --repo-url mode: the file path. See examples in `--help`.").argument("[path]", "File path (spec mode only — in --repo-url mode use the first positional).").option("--repo-url <url>", "Repository URL addressing (requires --git-ref)").option("--git-ref <ref>", "Tag, commit, branch, or HEAD. Required with --repo-url.").option("--lines <start-end>", "Line range (e.g. `10-40`, `10-` for open end, `-40` for open start)").option("--start <n>", "Starting line (1-indexed). Alternative to --lines.").option("--end <n>", "Ending line (inclusive). Alternative to --lines.").option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Render a header and a line-number gutter alongside the content").option("--json", "Emit the JSON envelope").action(async (spec, path, options) => {
8239
+ return pkgCommand.command("read").summary("Read a file from an indexed dependency").description(PKG_READ_DESCRIPTION).argument("[spec-or-path]", "In spec mode: package spec (e.g. npm:express). In --repo-url mode: the file path. See examples in `--help`.").argument("[path]", "File path (spec mode only — in --repo-url mode use the first positional).").option("--repo-url <url>", "Repository URL addressing (defaults to the repo default branch)").option("--git-ref <ref>", "Optional tag, commit, branch, or HEAD for --repo-url.").option("--lines <start-end>", "Line range (e.g. `10-40`, `10-` for open end, `-40` for open start)").option("--start <n>", "Starting line (1-indexed). Alternative to --lines.").option("--end <n>", "Ending line (inclusive). Alternative to --lines.").option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Render a header and a line-number gutter alongside the content").option("--json", "Emit the JSON envelope").action(async (spec, path, options) => {
7849
8240
  const deps = await createContainer();
7850
8241
  await pkgReadAction(spec, path, options, {
7851
8242
  codeNavigationService: deps.codeNavigationService,
@@ -7862,7 +8253,7 @@ async function registerCodeCommandGroup(program, options = {}) {
7862
8253
  if (!registration.shouldRegister) {
7863
8254
  return;
7864
8255
  }
7865
- const codeCommand = program.command("code").summary("Source-level operations on indexed dependencies").description("List files, read files, and grep substrings inside indexed dependency source. Every command accepts either `<spec>` (registry:name[@version]) or `--repo-url <url> --git-ref <ref>`. Omitted versions use the latest release. For repo default-branch discovery without refs use `githits search`; for package-level metadata use `githits pkg`.");
8256
+ const codeCommand = program.command("code").summary("Inspect dependency source code and symbols").description("List files, read files, and grep substrings inside indexed dependency source. Every command accepts either `<spec>` (registry:name[@version]) or `--repo-url <url> [--git-ref <ref>]`. Omitted package versions use the latest release; omitted repo refs use the default-branch intent. For package-level metadata use `githits pkg`.");
7866
8257
  registerCodeFilesCommand(codeCommand);
7867
8258
  registerCodeReadCommand(codeCommand);
7868
8259
  registerCodeGrepCommand(codeCommand);
@@ -7883,7 +8274,8 @@ async function docsListAction(spec, options, deps) {
7883
8274
  limit,
7884
8275
  after: options.after
7885
8276
  });
7886
- const result = await deps.packageIntelligenceService.listPackageDocs(build.params);
8277
+ const spinner = startSpinner(SPINNER_MESSAGES.docs, !options.json);
8278
+ const result = await deps.packageIntelligenceService.listPackageDocs(build.params).finally(() => spinner.stop());
7887
8279
  const payload = buildListPackageDocsSuccessPayload(result, {
7888
8280
  limitExplicit: build.limitExplicit,
7889
8281
  afterExplicit: build.afterExplicit,
@@ -7953,7 +8345,8 @@ async function docsReadAction(pageId, options, deps) {
7953
8345
  }
7954
8346
  const range = options.lines ? parseLinesOption(options.lines) : undefined;
7955
8347
  const build = buildReadPackageDocParams({ pageId });
7956
- const result = await deps.packageIntelligenceService.readPackageDoc(build.params);
8348
+ const spinner = startSpinner(SPINNER_MESSAGES.docs, !options.json);
8349
+ const result = await deps.packageIntelligenceService.readPackageDoc(build.params).finally(() => spinner.stop());
7957
8350
  const payload = buildReadPackageDocSuccessPayload(result, build.params.pageId, range);
7958
8351
  if (options.json) {
7959
8352
  console.log(JSON.stringify(payload));
@@ -8006,7 +8399,7 @@ async function registerDocsCommandGroup(program, options = {}) {
8006
8399
  if (!registration.shouldRegister) {
8007
8400
  return;
8008
8401
  }
8009
- const docsCommand = program.command("docs").summary("Browse and read mixed package documentation").description("Browse and read package documentation across hosted docs and repository-backed docs. Docs are mixed by default; entries are source-badged and repo-backed pages also expose exact file follow-up metadata.");
8402
+ const docsCommand = program.command("docs").summary("Browse and read package documentation").description("Browse and read package documentation across hosted docs and repository-backed docs. Docs are mixed by default; entries are source-badged and repo-backed pages also expose exact file follow-up metadata.");
8010
8403
  registerDocsListCommand(docsCommand);
8011
8404
  registerDocsReadCommand(docsCommand);
8012
8405
  }
@@ -8019,12 +8412,13 @@ async function exampleAction(query, options, deps) {
8019
8412
  }
8020
8413
  requireAuth(deps);
8021
8414
  try {
8415
+ const spinner = startSpinner(SPINNER_MESSAGES.example, !options.json);
8022
8416
  const result = await deps.githitsService.search({
8023
8417
  query,
8024
8418
  language: options.lang,
8025
8419
  licenseMode: options.license,
8026
8420
  includeExplanation: options.explain
8027
- });
8421
+ }).finally(() => spinner.stop());
8028
8422
  if (options.json) {
8029
8423
  const solutionId = extractSolutionId(result);
8030
8424
  const payload = solutionId ? { result, solution_id: solutionId } : { result };
@@ -8059,7 +8453,7 @@ Examples:
8059
8453
  githits example "react hooks patterns" -l typescript --explain
8060
8454
  githits example "react hooks patterns" -l typescript --json`;
8061
8455
  function registerExampleCommand(program) {
8062
- program.command("example").summary("Get code examples from global open source").description(EXAMPLE_DESCRIPTION).argument("<query>", "Natural language example-search query").option("-l, --lang <language>", "Optional programming language; omitted values are inferred by GitHits").addOption(new Option("--license <mode>", "License filter mode").choices(["strict", "yolo", "custom"]).default(undefined)).option("--explain", "Include AI-generated explanation").option("--json", "Output as JSON for piping").action(async (query, options) => {
8456
+ program.command("example").summary("Find real-world implementations from open-source code").description(EXAMPLE_DESCRIPTION).argument("<query>", "Natural language example-search query").option("-l, --lang <language>", "Optional programming language; omitted values are inferred by GitHits").addOption(new Option("--license <mode>", "License filter mode").choices(["strict", "yolo", "custom"]).default(undefined)).option("--explain", "Include AI-generated explanation").option("--json", "Output as JSON for piping").action(async (query, options) => {
8063
8457
  try {
8064
8458
  const deps = await loadContainer();
8065
8459
  await exampleAction(query, options, deps);
@@ -8071,7 +8465,7 @@ function registerExampleCommand(program) {
8071
8465
  });
8072
8466
  }
8073
8467
  async function loadContainer() {
8074
- const { createContainer: createContainer2 } = await import("./shared/chunk-zzmbjttb.js");
8468
+ const { createContainer: createContainer2 } = await import("./shared/chunk-70bn2pmx.js");
8075
8469
  return createContainer2();
8076
8470
  }
8077
8471
  // src/commands/feedback.ts
@@ -8119,7 +8513,7 @@ Examples:
8119
8513
  githits feedback --accept --tool code_grep -m "regex is fast on npm:lodash"
8120
8514
  githits feedback --reject --tool search -m "missing kotlin support"`;
8121
8515
  function registerFeedbackCommand(program) {
8122
- program.command("feedback").summary("Submit feedback on a tool result or the GitHits experience").description(FEEDBACK_DESCRIPTION).argument("[solution_id]", "Solution ID from a prior 'githits example' result (omit for generic feedback)").addOption(new Option2("--accept", "Mark as helpful").conflicts("reject")).addOption(new Option2("--reject", "Mark as unhelpful").conflicts("accept")).option("-m, --message <text>", "Feedback explanation").option("--tool <name>", "Command or MCP tool name being rated").option("--json", "Output as JSON for piping").action(async (solutionId, options) => {
8516
+ program.command("feedback").summary("Submit feedback about GitHits results or experience").description(FEEDBACK_DESCRIPTION).argument("[solution_id]", "Solution ID from a prior 'githits example' result (omit for generic feedback)").addOption(new Option2("--accept", "Mark as helpful").conflicts("reject")).addOption(new Option2("--reject", "Mark as unhelpful").conflicts("accept")).option("-m, --message <text>", "Feedback explanation").option("--tool <name>", "Command or MCP tool name being rated").option("--json", "Output as JSON for piping").action(async (solutionId, options) => {
8123
8517
  try {
8124
8518
  const deps = await createContainer();
8125
8519
  await feedbackAction(solutionId, options, deps);
@@ -8362,17 +8756,11 @@ function removeServerConfig(existingContent, serversKey, serverName) {
8362
8756
  `
8363
8757
  };
8364
8758
  }
8365
- function formatSetupPreview(config) {
8366
- if (config.method === "cli") {
8367
- return config.commands.map((cmd) => `Will run: ${cmd.command} ${cmd.args.join(" ")}`).join(`
8759
+ function formatUninstallPreview(config) {
8760
+ if (config.method === "composite") {
8761
+ return config.steps.map(({ step }) => formatUninstallPreview(step)).join(`
8368
8762
  `);
8369
8763
  }
8370
- const snippet = JSON.stringify({ [config.serverName]: config.serverConfig }, null, 2);
8371
- return `Will add to ${config.configPath}:
8372
-
8373
- ${snippet}`;
8374
- }
8375
- function formatUninstallPreview(config) {
8376
8764
  if (config.method === "cli") {
8377
8765
  return config.commands.map((cmd) => `Will run: ${cmd.command} ${cmd.args.join(" ")}`).join(`
8378
8766
  `);
@@ -8433,6 +8821,26 @@ async function getConfigUninstallCheckStatus(config, fs) {
8433
8821
  };
8434
8822
  }
8435
8823
  }
8824
+ async function isSetupAlreadyConfigured(config, fs, execService) {
8825
+ if (config.method === "config-file") {
8826
+ return isAlreadyConfigured(config, fs);
8827
+ }
8828
+ if (config.method === "cli") {
8829
+ if (!config.checkCommand) {
8830
+ return false;
8831
+ }
8832
+ return isCliAlreadyConfigured(config.checkCommand, execService);
8833
+ }
8834
+ for (const step of config.steps) {
8835
+ if (!await isSetupAlreadyConfigured(step, fs, execService)) {
8836
+ return false;
8837
+ }
8838
+ }
8839
+ return true;
8840
+ }
8841
+ async function isCliAlreadyConfigured(check, execService) {
8842
+ return await getCliCheckStatus(check, execService) === "configured";
8843
+ }
8436
8844
  async function getCliCheckStatus(check, execService) {
8437
8845
  try {
8438
8846
  const result = await execService.exec(check.command, check.args);
@@ -8463,6 +8871,7 @@ var ALREADY_EXISTS_PATTERNS = [
8463
8871
  var ALREADY_ABSENT_PATTERNS = [
8464
8872
  /(?:plugin|extension|server|mcp server)\s+["']?githits["']?\s+(?:was\s+)?not\s+found/i,
8465
8873
  /["']?githits["']?\s+(?:plugin|extension|server)?\s*(?:does\s+not\s+exist|is\s+not\s+installed|not\s+installed)/i,
8874
+ /(?:package\s+)?["']?pi-mcp-adapter["']?\s+(?:is\s+)?not\s+installed/i,
8466
8875
  /unknown\s+(?:plugin|extension|server)\s+["']?githits["']?/i,
8467
8876
  /marketplace\s+["']?githits-plugins["']?\s+(?:was\s+)?not\s+found/i
8468
8877
  ];
@@ -8563,8 +8972,7 @@ async function executeCliUninstall(uninstall, execService) {
8563
8972
  let anyRemoved = false;
8564
8973
  let anyNotConfigured = false;
8565
8974
  const warnings = [];
8566
- for (let index = 0;index < uninstall.commands.length; index += 1) {
8567
- const cmd = uninstall.commands[index];
8975
+ for (const cmd of uninstall.commands) {
8568
8976
  const result = await executeCliUninstallCommand(cmd, execService);
8569
8977
  if (result.status === "failed") {
8570
8978
  if (anyRemoved) {
@@ -8599,6 +9007,49 @@ async function executeCliUninstall(uninstall, execService) {
8599
9007
  }
8600
9008
  return { status: "removed", message: "Removed successfully" };
8601
9009
  }
9010
+ async function executeCompositeUninstall(uninstall, fs, execService) {
9011
+ let anyRemoved = false;
9012
+ let anyNotConfigured = false;
9013
+ const warnings = [];
9014
+ for (const { step, failureMode } of uninstall.steps) {
9015
+ const result = await executeUninstallStep(step, fs, execService);
9016
+ if (result.status === "removed") {
9017
+ anyRemoved = true;
9018
+ warnings.push(...result.warnings ?? []);
9019
+ continue;
9020
+ }
9021
+ if (result.status === "not_configured") {
9022
+ if (anyRemoved) {
9023
+ warnings.push(result.message);
9024
+ } else {
9025
+ anyNotConfigured = true;
9026
+ }
9027
+ continue;
9028
+ }
9029
+ if (failureMode === "best-effort" && anyRemoved) {
9030
+ warnings.push(result.message);
9031
+ continue;
9032
+ }
9033
+ return result;
9034
+ }
9035
+ if (anyRemoved) {
9036
+ return {
9037
+ status: "removed",
9038
+ message: "Removed successfully",
9039
+ warnings: warnings.length > 0 ? warnings : undefined
9040
+ };
9041
+ }
9042
+ if (anyNotConfigured) {
9043
+ return {
9044
+ status: "not_configured",
9045
+ message: "GitHits not configured"
9046
+ };
9047
+ }
9048
+ return { status: "not_configured", message: "GitHits not configured" };
9049
+ }
9050
+ async function executeUninstallStep(step, fs, execService) {
9051
+ return step.method === "cli" ? executeCliUninstall(step, execService) : executeConfigFileUninstall(step, fs);
9052
+ }
8602
9053
  async function executeConfigFileSetup(setup, fs) {
8603
9054
  try {
8604
9055
  const parentDir = fs.getDirname(setup.configPath);
@@ -8642,6 +9093,26 @@ async function executeConfigFileSetup(setup, fs) {
8642
9093
  };
8643
9094
  }
8644
9095
  }
9096
+ async function executeCompositeSetup(setup, fs, execService) {
9097
+ let executedAny = false;
9098
+ for (const step of setup.steps) {
9099
+ if (await isSetupAlreadyConfigured(step, fs, execService)) {
9100
+ continue;
9101
+ }
9102
+ executedAny = true;
9103
+ const result = step.method === "cli" ? await executeCliSetup(step, execService) : await executeConfigFileSetup(step, fs);
9104
+ if (result.status === "failed") {
9105
+ return result;
9106
+ }
9107
+ }
9108
+ if (!executedAny) {
9109
+ return {
9110
+ status: "already_configured",
9111
+ message: "GitHits already configured"
9112
+ };
9113
+ }
9114
+ return { status: "success", message: "Configured successfully" };
9115
+ }
8645
9116
  async function executeConfigFileUninstall(setup, fs) {
8646
9117
  try {
8647
9118
  let existingContent = "";
@@ -8728,6 +9199,25 @@ function getOpenCodeConfigDir(fs) {
8728
9199
  }
8729
9200
  return fs.joinPath(fs.getHomeDir(), ".config", "opencode");
8730
9201
  }
9202
+ function expandHomePath(fs, path) {
9203
+ if (path === "~") {
9204
+ return fs.getHomeDir();
9205
+ }
9206
+ if (path.startsWith("~/")) {
9207
+ return fs.joinPath(fs.getHomeDir(), path.slice(2));
9208
+ }
9209
+ return path;
9210
+ }
9211
+ function getPiAgentDir(fs) {
9212
+ const configuredDir = process.env.PI_CODING_AGENT_DIR?.trim();
9213
+ if (configuredDir) {
9214
+ return expandHomePath(fs, configuredDir);
9215
+ }
9216
+ return fs.joinPath(fs.getHomeDir(), ".pi", "agent");
9217
+ }
9218
+ function getPiMcpConfigPath(fs) {
9219
+ return fs.joinPath(getPiAgentDir(fs), "mcp.json");
9220
+ }
8731
9221
  function getOpenCodeDesktopDetectPaths(fs) {
8732
9222
  const userDataRoot = getUserDataRoot(fs);
8733
9223
  return [
@@ -8746,6 +9236,57 @@ async function isExecutableAvailable(exec, executable) {
8746
9236
  return false;
8747
9237
  }
8748
9238
  }
9239
+ async function resolveExecutableFromPath(exec, executable) {
9240
+ return isExecutableAvailable(exec, executable);
9241
+ }
9242
+ var PI_GLOBAL_BIN_PROBES = [
9243
+ { command: "npm", args: ["prefix", "-g"], output: "prefix" },
9244
+ { command: "pnpm", args: ["bin", "-g"], output: "binDir" },
9245
+ { command: "bun", args: ["pm", "bin", "-g"], output: "binDir" }
9246
+ ];
9247
+ var PI_ADAPTER_CONFIGURED_PATTERN = /(?:^|\s|:)(?:npm:)?pi-mcp-adapter(?:[\s@:]|$)/i;
9248
+ function getPiExecutableNames() {
9249
+ return process.platform === "win32" ? ["pi.cmd", "pi.exe", "pi"] : ["pi"];
9250
+ }
9251
+ async function runGlobalBinProbe(exec, probe) {
9252
+ try {
9253
+ const result = await exec.exec(probe.command, [...probe.args]);
9254
+ if (result.exitCode !== 0) {
9255
+ return null;
9256
+ }
9257
+ const probePath = result.stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
9258
+ if (!probePath) {
9259
+ return null;
9260
+ }
9261
+ if (probe.output === "prefix" && process.platform !== "win32") {
9262
+ return fsJoinPathLike(probePath, "bin");
9263
+ }
9264
+ return probePath;
9265
+ } catch {
9266
+ return null;
9267
+ }
9268
+ }
9269
+ function fsJoinPathLike(base, child) {
9270
+ return base.endsWith("/") ? `${base}${child}` : `${base}/${child}`;
9271
+ }
9272
+ async function detectPiExecutable(exec, fs) {
9273
+ if (await resolveExecutableFromPath(exec, "pi")) {
9274
+ return { command: "pi" };
9275
+ }
9276
+ for (const probe of PI_GLOBAL_BIN_PROBES) {
9277
+ const binDir = await runGlobalBinProbe(exec, probe);
9278
+ if (!binDir) {
9279
+ continue;
9280
+ }
9281
+ for (const executableName of getPiExecutableNames()) {
9282
+ const candidate = fs.joinPath(binDir, executableName);
9283
+ if (await fs.exists(candidate)) {
9284
+ return { command: candidate };
9285
+ }
9286
+ }
9287
+ }
9288
+ return null;
9289
+ }
8749
9290
  var claudeCode = {
8750
9291
  name: "Claude Code",
8751
9292
  id: "claude-code",
@@ -8885,6 +9426,76 @@ var codexCli = {
8885
9426
  ]
8886
9427
  })
8887
9428
  };
9429
+ var pi = {
9430
+ name: "Pi",
9431
+ id: "pi",
9432
+ detectionMethod: "binary",
9433
+ setupMethod: "composite",
9434
+ detectCommand: detectPiExecutable,
9435
+ getSetupConfig: (fs, context) => {
9436
+ const piCommand = context?.command ?? "pi";
9437
+ return {
9438
+ method: "composite",
9439
+ steps: [
9440
+ {
9441
+ method: "cli",
9442
+ commands: [
9443
+ {
9444
+ command: piCommand,
9445
+ args: ["install", "npm:pi-mcp-adapter"]
9446
+ }
9447
+ ],
9448
+ checkCommand: {
9449
+ command: piCommand,
9450
+ args: ["list"],
9451
+ configuredPattern: PI_ADAPTER_CONFIGURED_PATTERN
9452
+ }
9453
+ },
9454
+ {
9455
+ method: "config-file",
9456
+ configPath: getPiMcpConfigPath(fs),
9457
+ serversKey: "mcpServers",
9458
+ serverName: GITHITS_SERVER_NAME,
9459
+ serverConfig: {
9460
+ command: GITHITS_MCP_COMMAND,
9461
+ args: [...GITHITS_MCP_ARGS],
9462
+ lifecycle: "eager"
9463
+ }
9464
+ }
9465
+ ]
9466
+ };
9467
+ },
9468
+ getUninstallConfig: (fs, context) => {
9469
+ const piCommand = context?.command ?? "pi";
9470
+ return {
9471
+ method: "composite",
9472
+ steps: [
9473
+ {
9474
+ failureMode: "required",
9475
+ step: {
9476
+ method: "config-file",
9477
+ configPath: getPiMcpConfigPath(fs),
9478
+ serversKey: "mcpServers",
9479
+ serverName: GITHITS_SERVER_NAME,
9480
+ serverConfig: {}
9481
+ }
9482
+ },
9483
+ {
9484
+ failureMode: "required",
9485
+ step: {
9486
+ method: "cli",
9487
+ commands: [
9488
+ {
9489
+ command: piCommand,
9490
+ args: ["remove", "npm:pi-mcp-adapter"]
9491
+ }
9492
+ ]
9493
+ }
9494
+ }
9495
+ ]
9496
+ };
9497
+ }
9498
+ };
8888
9499
  var vscode = {
8889
9500
  name: "VS Code / Copilot",
8890
9501
  id: "vscode",
@@ -9009,82 +9620,110 @@ var agentDefinitions = [
9009
9620
  cline,
9010
9621
  claudeDesktop,
9011
9622
  codexCli,
9623
+ pi,
9012
9624
  geminiCli,
9013
9625
  googleAntigravity,
9014
9626
  openCode
9015
9627
  ];
9016
- async function scanAgents(definitions, fs, execService) {
9017
- const result = {
9018
- needsSetup: [],
9019
- alreadyConfigured: [],
9020
- notDetected: []
9021
- };
9022
- for (const agent of definitions) {
9023
- let detected = false;
9024
- if (agent.detectionMethod === "binary" && agent.detectBinary) {
9025
- try {
9026
- detected = await agent.detectBinary(execService);
9027
- } catch {
9028
- detected = false;
9628
+ async function scanSingleAgent(agent, fs, execService) {
9629
+ let detected = false;
9630
+ let setupContext;
9631
+ if (agent.detectCommand) {
9632
+ try {
9633
+ const resolvedCommand = await agent.detectCommand(execService, fs);
9634
+ if (resolvedCommand) {
9635
+ detected = true;
9636
+ setupContext = { command: resolvedCommand.command };
9029
9637
  }
9030
- } else if (agent.detectionMethod === "path" && agent.detectPaths) {
9031
- const paths = agent.detectPaths(fs);
9032
- for (const path of paths) {
9033
- if (await fs.isDirectory(path)) {
9034
- detected = true;
9035
- break;
9036
- }
9638
+ } catch {
9639
+ detected = false;
9640
+ }
9641
+ } else if (agent.detectionMethod === "binary" && agent.detectBinary) {
9642
+ try {
9643
+ detected = await agent.detectBinary(execService);
9644
+ } catch {
9645
+ detected = false;
9646
+ }
9647
+ } else if (agent.detectionMethod === "path" && agent.detectPaths) {
9648
+ const paths = agent.detectPaths(fs);
9649
+ for (const path of paths) {
9650
+ if (await fs.isDirectory(path)) {
9651
+ detected = true;
9652
+ break;
9037
9653
  }
9038
- } else if (agent.detectionMethod === "hybrid") {
9039
- let binaryDetected = false;
9040
- let pathDetected = false;
9041
- if (agent.detectBinary) {
9042
- try {
9043
- binaryDetected = await agent.detectBinary(execService);
9044
- } catch {
9045
- binaryDetected = false;
9046
- }
9654
+ }
9655
+ } else if (agent.detectionMethod === "hybrid") {
9656
+ let binaryDetected = false;
9657
+ let pathDetected = false;
9658
+ if (agent.detectBinary) {
9659
+ try {
9660
+ binaryDetected = await agent.detectBinary(execService);
9661
+ } catch {
9662
+ binaryDetected = false;
9047
9663
  }
9048
- if (!binaryDetected && agent.detectPaths) {
9049
- const paths = agent.detectPaths(fs);
9050
- for (const path of paths) {
9051
- if (await fs.isDirectory(path)) {
9052
- pathDetected = true;
9053
- break;
9054
- }
9664
+ }
9665
+ if (!binaryDetected && agent.detectPaths) {
9666
+ const paths = agent.detectPaths(fs);
9667
+ for (const path of paths) {
9668
+ if (await fs.isDirectory(path)) {
9669
+ pathDetected = true;
9670
+ break;
9055
9671
  }
9056
9672
  }
9057
- detected = binaryDetected || pathDetected;
9058
9673
  }
9059
- if (!detected) {
9060
- result.notDetected.push(agent);
9061
- continue;
9674
+ detected = binaryDetected || pathDetected;
9675
+ }
9676
+ if (!detected) {
9677
+ return { status: "not_detected", agent };
9678
+ }
9679
+ const config = agent.getSetupConfig(fs, setupContext);
9680
+ const scannedAgent = {
9681
+ ...agent,
9682
+ resolvedSetupConfig: config,
9683
+ resolvedSetupContext: setupContext
9684
+ };
9685
+ if (agent.id === "gemini-cli" && config.method === "cli") {
9686
+ if (!config.checkCommand) {
9687
+ return { status: "needs_setup", agent: scannedAgent };
9062
9688
  }
9063
- if (agent.setupMethod === "config-file") {
9064
- const config = agent.getSetupConfig(fs);
9065
- if (config.method === "config-file" && await isAlreadyConfigured(config, fs)) {
9066
- result.alreadyConfigured.push(agent);
9067
- } else {
9068
- result.needsSetup.push(agent);
9069
- }
9689
+ const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
9690
+ let configured = checkStatus === "configured";
9691
+ if (!configured && checkStatus === "probe_failed") {
9692
+ configured = await isGeminiExtensionInstalledFromFilesystem(fs);
9693
+ }
9694
+ return {
9695
+ status: configured ? "already_configured" : "needs_setup",
9696
+ agent: scannedAgent
9697
+ };
9698
+ }
9699
+ if (await isSetupAlreadyConfigured(config, fs, execService)) {
9700
+ return { status: "already_configured", agent: scannedAgent };
9701
+ }
9702
+ return { status: "needs_setup", agent: scannedAgent };
9703
+ }
9704
+ async function scanAgents(definitions, fs, execService, options = {}) {
9705
+ const result = {
9706
+ needsSetup: [],
9707
+ alreadyConfigured: [],
9708
+ notDetected: []
9709
+ };
9710
+ let completed = 0;
9711
+ const outcomes = await Promise.all(definitions.map((agent) => scanSingleAgent(agent, fs, execService).then((outcome) => {
9712
+ completed += 1;
9713
+ options.onProgress?.({
9714
+ completed,
9715
+ total: definitions.length,
9716
+ agent: outcome.agent
9717
+ });
9718
+ return outcome;
9719
+ })));
9720
+ for (const outcome of outcomes) {
9721
+ if (outcome.status === "already_configured") {
9722
+ result.alreadyConfigured.push(outcome.agent);
9723
+ } else if (outcome.status === "needs_setup") {
9724
+ result.needsSetup.push(outcome.agent);
9070
9725
  } else {
9071
- const config = agent.getSetupConfig(fs);
9072
- if (config.method === "cli" && config.checkCommand) {
9073
- const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
9074
- let configured = checkStatus === "configured";
9075
- if (!configured && agent.id === "gemini-cli") {
9076
- if (checkStatus === "probe_failed") {
9077
- configured = await isGeminiExtensionInstalledFromFilesystem(fs);
9078
- }
9079
- }
9080
- if (configured) {
9081
- result.alreadyConfigured.push(agent);
9082
- } else {
9083
- result.needsSetup.push(agent);
9084
- }
9085
- } else {
9086
- result.needsSetup.push(agent);
9087
- }
9726
+ result.notDetected.push(outcome.agent);
9088
9727
  }
9089
9728
  }
9090
9729
  return result;
@@ -9094,14 +9733,440 @@ class ExitPromptError extends Error {
9094
9733
  name = "ExitPromptError";
9095
9734
  }
9096
9735
  // src/commands/init/init.ts
9736
+ function createInitLoginOutput() {
9737
+ return {
9738
+ write: (message) => {
9739
+ const lines = message.replace(/\n$/, "").split(`
9740
+ `);
9741
+ for (const line of lines) {
9742
+ console.log(line.length > 0 ? ` ${line}` : "");
9743
+ }
9744
+ }
9745
+ };
9746
+ }
9747
+ function getResolvedSetupConfig(agent, fileSystemService) {
9748
+ return agent.resolvedSetupConfig ?? agent.getSetupConfig(fileSystemService, agent.resolvedSetupContext);
9749
+ }
9750
+ function getPiConfigFileUninstall(agent, fileSystemService) {
9751
+ if (agent.id !== "pi") {
9752
+ return null;
9753
+ }
9754
+ const uninstallConfig = agent.getUninstallConfig?.(fileSystemService);
9755
+ if (uninstallConfig?.method !== "composite") {
9756
+ return null;
9757
+ }
9758
+ const configStep = uninstallConfig.steps.map(({ step }) => step).find((step) => step.method === "config-file");
9759
+ return configStep ?? null;
9760
+ }
9761
+ function formatCommand(command, useColors) {
9762
+ return colorizeBrand(command, "secondary", useColors, { bold: true });
9763
+ }
9097
9764
  function printReadyNextSteps() {
9098
- console.log(" Setup complete. You're ready to use GitHits.");
9765
+ console.log(" GitHits is now connected to your coding agents.");
9766
+ console.log();
9767
+ console.log(" Here are some examples of the new abilities that your agent just got:");
9768
+ console.log();
9769
+ console.log(" • Find usage examples");
9770
+ console.log(" -> “Find a real example of using Azure Speech SDK TranscribeDefinition”");
9771
+ console.log();
9772
+ console.log(" • Search, grep, list files, and read exact lines in any repo or package to gather information");
9773
+ console.log(" -> “How does Next.js implement route prefetching internally?”");
9774
+ console.log();
9775
+ console.log(" • Inspect dependency versions, changelogs, and upgrade changes");
9776
+ console.log(" -> “What changed between pydantic-ai 1.95 and 1.99?”");
9777
+ console.log();
9778
+ console.log(" Open a new coding agent session and try out one of the above.");
9779
+ console.log();
9780
+ console.log(' In your normal workflow, your agent will call GitHits automatically depending on the task, but you can prompt it to use GitHits explicitly by adding "use GitHits".');
9781
+ console.log();
9782
+ console.log(" See docs for more use cases and trigger guides: https://docs.githits.com");
9783
+ }
9784
+ function printAuthRequiredNextSteps(useColors) {
9785
+ console.log(" GitHits MCP is configured, but sign-in is still needed.");
9786
+ console.log();
9787
+ console.log(" Sign in when you're ready:");
9788
+ console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
9789
+ }
9790
+ function printAuthNotCheckedNextSteps(useColors) {
9791
+ console.log(" GitHits MCP is configured. Sign-in was not checked.");
9792
+ console.log();
9793
+ console.log(" If your agent asks you to sign in, run:");
9794
+ console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
9795
+ }
9796
+ var GITHITS_ASCII_LOGO = String.raw`
9797
+ ____ _ _ _ _ _ _
9798
+ / ___(_) |_| | | (_) |_ ___
9799
+ | | _| | __| |_| | | __/ __|
9800
+ | |_| | | |_| _ | | |_\__ \
9801
+ \____|_|\__|_| |_|_|\__|___/
9802
+ `;
9803
+ var LOGO_GRADIENT = [
9804
+ {
9805
+ until: 13,
9806
+ color: {
9807
+ hex: "#FF4FAE",
9808
+ rgb: [255, 79, 174],
9809
+ ansi256: 205,
9810
+ ansi16: "magenta"
9811
+ }
9812
+ },
9813
+ {
9814
+ until: 19,
9815
+ color: {
9816
+ hex: "#FF5D8E",
9817
+ rgb: [255, 93, 142],
9818
+ ansi256: 204,
9819
+ ansi16: "magenta"
9820
+ }
9821
+ },
9822
+ {
9823
+ until: 21,
9824
+ color: {
9825
+ hex: "#FF6B6F",
9826
+ rgb: [255, 107, 111],
9827
+ ansi256: 203,
9828
+ ansi16: "red"
9829
+ }
9830
+ },
9831
+ {
9832
+ until: 25,
9833
+ color: { hex: "#FF794F", rgb: [255, 121, 79], ansi256: 209, ansi16: "red" }
9834
+ },
9835
+ {
9836
+ until: 30,
9837
+ color: {
9838
+ hex: "#FF872F",
9839
+ rgb: [255, 135, 47],
9840
+ ansi256: 208,
9841
+ ansi16: "yellow"
9842
+ }
9843
+ }
9844
+ ];
9845
+ function colorizeLogo(logo, useColors) {
9846
+ if (!useColors) {
9847
+ return logo;
9848
+ }
9849
+ return logo.split(`
9850
+ `).map((line) => {
9851
+ if (line.length === 0) {
9852
+ return line;
9853
+ }
9854
+ let cursor2 = 0;
9855
+ let colored = "";
9856
+ for (const { until, color } of LOGO_GRADIENT) {
9857
+ if (cursor2 >= line.length) {
9858
+ break;
9859
+ }
9860
+ colored += colorizeTerminal(line.slice(cursor2, until), color, useColors);
9861
+ cursor2 = until;
9862
+ }
9863
+ return cursor2 < line.length ? colored + line.slice(cursor2) : colored;
9864
+ }).join(`
9865
+ `);
9866
+ }
9867
+ var INIT_INTENT_CHOICES = [
9868
+ {
9869
+ name: "Connect GitHits to my agent (Recommended)",
9870
+ value: "mcp",
9871
+ description: "Install the local GitHits MCP server for your coding agents. Allows agents to seamlessly use GitHits."
9872
+ },
9873
+ {
9874
+ name: "Use Agent Skills instead",
9875
+ value: "skills",
9876
+ description: "Use Skills instead of the local MCP server."
9877
+ },
9878
+ {
9879
+ name: "Exit",
9880
+ value: "later",
9881
+ description: "Leave setup without making changes."
9882
+ }
9883
+ ];
9884
+ var AUTH_RECOVERY_CHOICES = [
9885
+ { name: "Retry sign in", value: "retry" },
9886
+ {
9887
+ name: "Configure MCP without signing in",
9888
+ value: "continue_without_auth",
9889
+ description: "Your agent will ask you to sign in before using GitHits."
9890
+ },
9891
+ { name: "Cancel setup", value: "cancel" }
9892
+ ];
9893
+ var AUTH_START_CHOICES = [
9894
+ {
9895
+ name: "Sign in now",
9896
+ value: "sign_in",
9897
+ description: "Open your browser and connect this CLI to GitHits."
9898
+ },
9899
+ {
9900
+ name: "Skip for now",
9901
+ value: "skip",
9902
+ description: "Finish MCP setup and sign in later with `githits login`."
9903
+ },
9904
+ { name: "Cancel setup", value: "cancel" }
9905
+ ];
9906
+ function printSection(index, title, useColors) {
9907
+ console.log();
9908
+ console.log(` ${colorizeBrand(`${index}. ${title}`, "primary", useColors, { bold: true })}`);
9909
+ console.log(` ${colorize("-".repeat(title.length + 3), "dim", useColors)}`);
9910
+ }
9911
+ function printTask(status, label, detail, useColors) {
9912
+ const suffix = detail ? ` ${colorize(detail, "dim", useColors)}` : "";
9913
+ if (status === "success") {
9914
+ console.log(` ${success(label, useColors)}${suffix}`);
9915
+ } else if (status === "failed") {
9916
+ console.log(` ${error(label, useColors)}${suffix}`);
9917
+ } else if (status === "warning") {
9918
+ console.log(` ${warning(label, useColors)}${suffix}`);
9919
+ } else {
9920
+ console.log(` ${colorize("○", "dim", useColors)} ${label}${suffix}`);
9921
+ }
9922
+ }
9923
+ function printInitIntro(useColors) {
9924
+ console.log(colorizeLogo(GITHITS_ASCII_LOGO, useColors));
9925
+ console.log(" Your agent can read your local codebase.");
9926
+ console.log();
9927
+ console.log(" GitHits lets it navigate the open-source code your app depends on.");
9928
+ console.log();
9929
+ console.log(` ${colorizeBrand("With GitHits, your agent can:", "primary", useColors)}`);
9930
+ console.log(" • Find implementation examples from open-source code, issues, discussions, and pull requests");
9931
+ console.log(" • Search, grep, list files, and read exact lines in any repo or package");
9932
+ console.log(" • Inspect dependency internals, versions, changelogs, and upgrade changes");
9933
+ console.log(" • Access package documentation");
9934
+ console.log();
9935
+ console.log(" No cloning or local indexing required. GitHits handles everything automatically.");
9936
+ console.log();
9937
+ console.log(" Works with Cursor, Claude Code, Codex, OpenCode, Pi, VS Code, Windsurf, and more.");
9938
+ console.log();
9939
+ console.log(" More info: https://docs.githits.com");
9940
+ console.log();
9941
+ }
9942
+ function printSkillsInstructions(useColors) {
9943
+ console.log(`
9944
+ Install GitHits Agent Skills:`);
9945
+ console.log();
9946
+ console.log(` ${formatCommand("npx skills add githits-com/githits-cli", useColors)}`);
9947
+ console.log();
9948
+ console.log(" During setup, choose where you want to enable GitHits.");
9949
+ console.log();
9950
+ console.log(" IMPORTANT: Use either Agent Skills or the local MCP server in the same");
9951
+ console.log(" coding tool, not both.");
9952
+ console.log();
9953
+ console.log(" Then sign in so your agent can use GitHits:");
9954
+ console.log();
9955
+ console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
9956
+ console.log();
9957
+ }
9958
+ function startSafeInitScan(fileSystemService, execService, onProgress) {
9959
+ return scanAgents(agentDefinitions, fileSystemService, execService, {
9960
+ onProgress
9961
+ }).then((scan) => ({ ok: true, scan })).catch((error2) => ({
9962
+ ok: false,
9963
+ error: error2 instanceof Error ? error2 : new Error(String(error2))
9964
+ }));
9965
+ }
9966
+ function createScanProgressReporter(useColors) {
9967
+ if (!process.stdout.isTTY) {
9968
+ return { onProgress: () => {}, finish: () => {} };
9969
+ }
9970
+ let wrote = false;
9971
+ return {
9972
+ onProgress: (progress) => {
9973
+ const width = 20;
9974
+ const filled = Math.round(progress.completed / progress.total * width);
9975
+ const bar = `${colorizeBrand("#".repeat(filled), "primary", useColors)}${"-".repeat(width - filled)}`;
9976
+ const line = ` Scanning tools [${bar}] ${progress.completed}/${progress.total} ${progress.agent.name}`;
9977
+ process.stdout.write(`\r\x1B[2K${line}`);
9978
+ wrote = true;
9979
+ },
9980
+ finish: () => {
9981
+ if (wrote) {
9982
+ process.stdout.write("\r\x1B[2K");
9983
+ }
9984
+ }
9985
+ };
9986
+ }
9987
+ function createInstallTaskReporter(useColors) {
9988
+ if (!process.stdout.isTTY) {
9989
+ return {
9990
+ start: (label) => {
9991
+ printTask("skipped", label, "installing...", useColors);
9992
+ return () => {};
9993
+ }
9994
+ };
9995
+ }
9996
+ const frames = ["-", "\\", "|", "/"];
9997
+ return {
9998
+ start: (label) => {
9999
+ let frame = 0;
10000
+ const render = () => {
10001
+ const spinner = colorizeBrand(frames[frame % frames.length] ?? "-", "primary", useColors);
10002
+ frame += 1;
10003
+ process.stdout.write(`\r\x1B[2K ${spinner} ${label} installing...`);
10004
+ };
10005
+ render();
10006
+ const interval = setInterval(render, 80);
10007
+ return () => {
10008
+ clearInterval(interval);
10009
+ process.stdout.write("\r\x1B[2K");
10010
+ };
10011
+ }
10012
+ };
10013
+ }
10014
+ async function unwrapSafeScan(scanPromise) {
10015
+ const result = await scanPromise;
10016
+ if (!result.ok) {
10017
+ throw result.error;
10018
+ }
10019
+ return result.scan;
10020
+ }
10021
+ function formatAgentNames(agents) {
10022
+ if (agents.length === 0)
10023
+ return "none";
10024
+ if (agents.length === 1)
10025
+ return agents[0]?.name ?? "unknown";
10026
+ if (agents.length === 2) {
10027
+ return `${agents[0]?.name ?? "unknown"} and ${agents[1]?.name ?? "unknown"}`;
10028
+ }
10029
+ const names = agents.map((agent) => agent.name);
10030
+ return `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`;
10031
+ }
10032
+ function buildInitAgentChoices(scan) {
10033
+ return [
10034
+ ...scan.needsSetup.map((agent) => ({
10035
+ name: `${agent.name} (detected)`,
10036
+ value: agent,
10037
+ checked: true
10038
+ })),
10039
+ ...scan.alreadyConfigured.map((agent) => ({
10040
+ name: `${agent.name} (already configured)`,
10041
+ value: agent,
10042
+ disabled: "already configured"
10043
+ }))
10044
+ ];
10045
+ }
10046
+ function printScanSummary(scan, useColors) {
10047
+ const detected = scan.needsSetup.length + scan.alreadyConfigured.length;
10048
+ for (const agent of scan.alreadyConfigured) {
10049
+ printTask("success", agent.name, "already configured", useColors);
10050
+ }
10051
+ for (const agent of scan.needsSetup) {
10052
+ printTask("warning", agent.name, "needs setup", useColors);
10053
+ }
10054
+ if (scan.notDetected.length > 0) {
10055
+ printTask("skipped", `${scan.notDetected.length} supported tool${scan.notDetected.length !== 1 ? "s" : ""} not found`, formatAgentNames(scan.notDetected), useColors);
10056
+ }
10057
+ if (detected > 0) {
10058
+ console.log();
10059
+ console.log(` Found ${detected} supported tool${detected !== 1 ? "s" : ""}.`);
10060
+ }
10061
+ }
10062
+ function printAuthExplanation() {
10063
+ console.log(" GitHits authentication is required before your agent can use GitHits tools.");
10064
+ console.log();
10065
+ console.log(" We'll open your browser to connect your account.");
10066
+ console.log(" Credentials are stored securely in your OS keychain.");
9099
10067
  console.log();
9100
- console.log(" Try a quick code example search:");
9101
- console.log(' npx githits@latest example "How do I use useEffect cleanup?"');
10068
+ console.log(" No API keys or secrets are written into your MCP config.");
9102
10069
  console.log();
9103
- console.log(" Or ask your agent to explore a real codebase:");
9104
- console.log(" Use GitHits to inspect postgres/postgres and explain how the query planner selects join strategies.");
10070
+ }
10071
+ async function runInitAuthentication(options, promptService, createLoginDeps, useColors) {
10072
+ if (options.skipLogin) {
10073
+ console.log(` Skipping authentication (--skip-login).
10074
+ `);
10075
+ return "skipped";
10076
+ }
10077
+ if (!createLoginDeps) {
10078
+ printTask("warning", "Sign-in unavailable", "sign in later with `githits login`", useColors);
10079
+ return "unavailable";
10080
+ }
10081
+ while (true) {
10082
+ let loginResult;
10083
+ try {
10084
+ const loginDeps = await createLoginDeps();
10085
+ if (loginDeps.hasValidToken) {
10086
+ printTask("success", "Already signed in", undefined, useColors);
10087
+ return "authenticated";
10088
+ }
10089
+ printAuthExplanation();
10090
+ if (!options.yes) {
10091
+ let authChoice;
10092
+ try {
10093
+ authChoice = await promptService.select(" Continue with browser sign-in?", AUTH_START_CHOICES, "sign_in");
10094
+ } catch (err) {
10095
+ if (err instanceof ExitPromptError) {
10096
+ console.log(`
10097
+ Setup cancelled.
10098
+ `);
10099
+ return "cancelled";
10100
+ }
10101
+ throw err;
10102
+ }
10103
+ if (authChoice === "skip") {
10104
+ printTask("warning", "Sign-in skipped", "your agent will ask you to sign in later", useColors);
10105
+ return "skipped";
10106
+ }
10107
+ if (authChoice === "cancel") {
10108
+ console.log("\n Setup cancelled. Run `githits login` to authenticate.\n");
10109
+ return "cancelled";
10110
+ }
10111
+ }
10112
+ loginResult = await loginFlow({}, loginDeps, createInitLoginOutput());
10113
+ } catch (error2) {
10114
+ const msg = error2 instanceof Error ? error2.message : String(error2);
10115
+ loginResult = { status: "failed", message: msg };
10116
+ }
10117
+ if (loginResult.status === "already_authenticated") {
10118
+ printTask("success", "Already signed in", undefined, useColors);
10119
+ return "authenticated";
10120
+ }
10121
+ if (loginResult.status === "success") {
10122
+ printTask("success", "Signed in successfully", undefined, useColors);
10123
+ return "authenticated";
10124
+ }
10125
+ console.log(` ${warning(`Login failed: ${loginResult.message}`, useColors)}
10126
+ `);
10127
+ printAuthRecoveryHint(useColors);
10128
+ if (options.yes) {
10129
+ console.log(` Continuing without authentication...
10130
+ `);
10131
+ return "failed_continue";
10132
+ }
10133
+ let choice;
10134
+ try {
10135
+ choice = await promptService.select(" Authentication failed. What would you like to do?", AUTH_RECOVERY_CHOICES, "retry");
10136
+ } catch (err) {
10137
+ if (err instanceof ExitPromptError) {
10138
+ console.log(`
10139
+ Setup cancelled.
10140
+ `);
10141
+ return "cancelled";
10142
+ }
10143
+ throw err;
10144
+ }
10145
+ if (choice === "retry") {
10146
+ console.log();
10147
+ continue;
10148
+ }
10149
+ if (choice === "cancel") {
10150
+ console.log("\n Setup cancelled. Run `githits login` to authenticate.\n");
10151
+ return "cancelled";
10152
+ }
10153
+ console.log(` Continuing without authentication...
10154
+ `);
10155
+ return "failed_continue";
10156
+ }
10157
+ }
10158
+ function shouldPrintReady(authStatus) {
10159
+ return authStatus === "authenticated";
10160
+ }
10161
+ function printPostSetupNextSteps(authStatus, useColors) {
10162
+ printSection(5, shouldPrintReady(authStatus) ? "Ready" : "Next Steps", useColors);
10163
+ if (shouldPrintReady(authStatus)) {
10164
+ printReadyNextSteps();
10165
+ } else if (authStatus === "failed_continue") {
10166
+ printAuthRequiredNextSteps(useColors);
10167
+ } else {
10168
+ printAuthNotCheckedNextSteps(useColors);
10169
+ }
9105
10170
  }
9106
10171
  async function verifyAgentConfigured(agent, fileSystemService, execService) {
9107
10172
  const postCheck = await scanAgents([agent], fileSystemService, execService);
@@ -9135,28 +10200,54 @@ async function verifyAgentUnconfigured(agent, fileSystemService, execService) {
9135
10200
  message: `${agent.name} verification failed: still configured after uninstall.`
9136
10201
  };
9137
10202
  }
9138
- async function scanCliAgentForUninstall(agent, fileSystemService, execService) {
9139
- const config = agent.getSetupConfig(fileSystemService);
9140
- if (config.method !== "cli" || !config.checkCommand) {
10203
+ async function inspectSetupForUninstall(agent, config, fileSystemService, execService) {
10204
+ if (config.method === "config-file") {
10205
+ const check = await getConfigUninstallCheckStatus(config, fileSystemService);
10206
+ if (check.status === "configured")
10207
+ return "configured";
10208
+ if (check.status === "not_configured")
10209
+ return "not_configured";
10210
+ return { status: "failed", message: check.message };
10211
+ }
10212
+ if (config.method === "cli") {
10213
+ if (!config.checkCommand) {
10214
+ return {
10215
+ status: "failed",
10216
+ message: `${agent.name} does not have a verified uninstall check command.`
10217
+ };
10218
+ }
10219
+ const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
10220
+ if (checkStatus === "configured")
10221
+ return "configured";
10222
+ if (checkStatus === "not_configured")
10223
+ return "not_configured";
10224
+ if (agent.id === "gemini-cli" && await isGeminiExtensionInstalledFromFilesystem(fileSystemService)) {
10225
+ return "configured";
10226
+ }
9141
10227
  return {
9142
10228
  status: "failed",
9143
- message: `${agent.name} does not have a verified uninstall check command.`
10229
+ message: `Cannot inspect ${agent.name}: ${config.checkCommand.command} ${config.checkCommand.args.join(" ")} failed.`
9144
10230
  };
9145
10231
  }
9146
- const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
9147
- if (checkStatus === "configured") {
9148
- return "configured";
9149
- }
9150
- if (checkStatus === "not_configured") {
9151
- return "not_configured";
10232
+ const accumulated = {
10233
+ configured: false,
10234
+ notConfigured: false
10235
+ };
10236
+ for (const step of config.steps) {
10237
+ const check = await inspectSetupForUninstall(agent, step, fileSystemService, execService);
10238
+ if (check === "configured") {
10239
+ accumulated.configured = true;
10240
+ } else if (check === "not_configured") {
10241
+ accumulated.notConfigured = true;
10242
+ } else {
10243
+ accumulated.failure = check;
10244
+ }
9152
10245
  }
9153
- if (agent.id === "gemini-cli" && await isGeminiExtensionInstalledFromFilesystem(fileSystemService)) {
10246
+ if (accumulated.failure)
10247
+ return accumulated.failure;
10248
+ if (accumulated.configured)
9154
10249
  return "configured";
9155
- }
9156
- return {
9157
- status: "failed",
9158
- message: `Cannot inspect ${agent.name}: ${config.checkCommand.command} ${config.checkCommand.args.join(" ")} failed.`
9159
- };
10250
+ return "not_configured";
9160
10251
  }
9161
10252
  async function scanAgentsForUninstall(fileSystemService, execService) {
9162
10253
  const setupScan = await scanAgents(agentDefinitions, fileSystemService, execService);
@@ -9170,35 +10261,41 @@ async function scanAgentsForUninstall(fileSystemService, execService) {
9170
10261
  ...setupScan.alreadyConfigured,
9171
10262
  ...setupScan.needsSetup
9172
10263
  ]) {
9173
- const config = agent.getSetupConfig(fileSystemService);
9174
- if (config.method === "config-file") {
9175
- const check = await getConfigUninstallCheckStatus(config, fileSystemService);
9176
- if (check.status === "configured") {
9177
- result.configured.push(agent);
9178
- } else if (check.status === "failed") {
9179
- result.failed.push({
9180
- id: agent.id,
9181
- name: agent.name,
9182
- status: "failed",
9183
- message: check.message
9184
- });
9185
- } else {
9186
- result.notConfigured.push(agent);
9187
- }
10264
+ const config = getResolvedSetupConfig(agent, fileSystemService);
10265
+ const check = await inspectSetupForUninstall(agent, config, fileSystemService, execService);
10266
+ if (check === "configured") {
10267
+ result.configured.push(agent);
10268
+ } else if (check === "not_configured") {
10269
+ result.notConfigured.push(agent);
9188
10270
  } else {
9189
- const check = await scanCliAgentForUninstall(agent, fileSystemService, execService);
9190
- if (check === "configured") {
9191
- result.configured.push(agent);
9192
- } else if (check === "not_configured") {
9193
- result.notConfigured.push(agent);
9194
- } else {
9195
- result.failed.push({
9196
- id: agent.id,
9197
- name: agent.name,
9198
- status: "failed",
9199
- message: check.message
9200
- });
9201
- }
10271
+ result.failed.push({
10272
+ id: agent.id,
10273
+ name: agent.name,
10274
+ status: "failed",
10275
+ message: check.message
10276
+ });
10277
+ }
10278
+ }
10279
+ for (const agent of setupScan.notDetected) {
10280
+ const piConfigUninstall = getPiConfigFileUninstall(agent, fileSystemService);
10281
+ if (!piConfigUninstall) {
10282
+ continue;
10283
+ }
10284
+ const check = await getConfigUninstallCheckStatus(piConfigUninstall, fileSystemService);
10285
+ if (check.status === "configured") {
10286
+ result.configured.push({
10287
+ ...agent,
10288
+ resolvedUninstallConfig: piConfigUninstall,
10289
+ skipUninstallVerification: true
10290
+ });
10291
+ result.notDetected = result.notDetected.filter((a) => a.id !== agent.id);
10292
+ } else if (check.status === "failed") {
10293
+ result.failed.push({
10294
+ id: agent.id,
10295
+ name: agent.name,
10296
+ status: "failed",
10297
+ message: check.message
10298
+ });
9202
10299
  }
9203
10300
  }
9204
10301
  return result;
@@ -9206,126 +10303,100 @@ async function scanAgentsForUninstall(fileSystemService, execService) {
9206
10303
  async function initAction(options, deps) {
9207
10304
  const useColors = shouldUseColors();
9208
10305
  const { fileSystemService, promptService, execService, createLoginDeps } = deps;
9209
- let continuedWithoutAuth = false;
9210
- console.log(`
9211
- ${colorize("GitHits", "bold", useColors)} — Set up MCP server for your coding agents
9212
- `);
9213
- if (!options.skipLogin && createLoginDeps) {
9214
- console.log(` Checking authentication...
9215
- `);
9216
- let loginResult;
10306
+ printInitIntro(useColors);
10307
+ if (!options.yes) {
10308
+ let intent;
9217
10309
  try {
9218
- const loginDeps = await createLoginDeps();
9219
- loginResult = await loginFlow({}, loginDeps);
9220
- } catch (error2) {
9221
- const msg = error2 instanceof Error ? error2.message : String(error2);
9222
- loginResult = { status: "failed", message: msg };
9223
- }
9224
- if (loginResult.status === "already_authenticated") {
9225
- console.log(` ${success("Already authenticated", useColors)}
9226
- `);
9227
- } else if (loginResult.status === "success") {
9228
- console.log(` ${success("Logged in successfully", useColors)}
9229
- `);
9230
- } else {
9231
- console.log(` ${warning(`Login failed: ${loginResult.message}`, useColors)}
9232
- `);
9233
- printAuthRecoveryHint();
9234
- if (!options.yes) {
9235
- try {
9236
- const choice = await promptService.confirm3("Continue without authentication?");
9237
- if (choice === "no") {
9238
- console.log("\n Setup cancelled. Run `githits login` to authenticate.\n");
9239
- return;
9240
- }
9241
- } catch (err) {
9242
- if (err instanceof ExitPromptError) {
9243
- console.log(`
9244
- Setup cancelled.
10310
+ intent = await promptService.select(" What do you want to do?", INIT_INTENT_CHOICES, "mcp");
10311
+ } catch (err) {
10312
+ if (err instanceof ExitPromptError) {
10313
+ console.log(`
10314
+ Setup cancelled. No changes made.
9245
10315
  `);
9246
- return;
9247
- }
9248
- throw err;
9249
- }
10316
+ return;
9250
10317
  }
9251
- continuedWithoutAuth = true;
9252
- console.log(` Continuing without authentication...
9253
- `);
10318
+ throw err;
10319
+ }
10320
+ if (intent === "skills") {
10321
+ printSkillsInstructions(useColors);
10322
+ return;
10323
+ }
10324
+ if (intent === "later") {
10325
+ console.log("\n No changes made. Run `githits init` whenever you're ready.\n");
10326
+ return;
9254
10327
  }
9255
10328
  }
9256
- console.log(` Scanning for available agents...
9257
- `);
9258
- const scan = await scanAgents(agentDefinitions, fileSystemService, execService);
9259
- for (const agent of scan.alreadyConfigured) {
9260
- console.log(` ${success(`${agent.name} — already configured`, useColors)}`);
9261
- }
9262
- for (const agent of scan.needsSetup) {
9263
- console.log(` ${colorize(`● ${agent.name} needs setup`, "cyan", useColors)}`);
9264
- }
9265
- for (const agent of scan.notDetected) {
9266
- console.log(` ${colorize(`${agent.name} — not detected`, "dim", useColors)}`);
10329
+ printSection(1, "Detect tools", useColors);
10330
+ console.log(" Scanning for compatible AI coding tools...");
10331
+ const progress = createScanProgressReporter(useColors);
10332
+ const scanPromise = startSafeInitScan(fileSystemService, execService, (scanProgress) => progress.onProgress(scanProgress));
10333
+ let scan;
10334
+ try {
10335
+ scan = await unwrapSafeScan(scanPromise);
10336
+ } finally {
10337
+ progress.finish();
9267
10338
  }
9268
- console.log();
10339
+ printScanSummary(scan, useColors);
9269
10340
  if (scan.needsSetup.length === 0 && scan.alreadyConfigured.length === 0) {
9270
- console.log(` No coding agents detected. Install an agent and try again.
9271
- `);
10341
+ printTask("warning", "No supported AI coding tools detected", "install a supported tool and run `githits init` again", useColors);
10342
+ console.log();
9272
10343
  return;
9273
10344
  }
9274
- if (scan.needsSetup.length === 0) {
9275
- if (continuedWithoutAuth) {
9276
- console.log(" MCP is already configured, but authentication is still required.");
9277
- console.log(" Run `githits login` before using GitHits tools.\n");
9278
- return;
10345
+ let toSetup = scan.needsSetup;
10346
+ printSection(2, "Choose tools", useColors);
10347
+ if (!options.yes && scan.needsSetup.length > 0) {
10348
+ try {
10349
+ toSetup = await promptService.checkbox(" Select which tools should use GitHits:", buildInitAgentChoices(scan));
10350
+ } catch (err) {
10351
+ if (err instanceof ExitPromptError) {
10352
+ console.log(`
10353
+ Setup cancelled. No changes made.
10354
+ `);
10355
+ return;
10356
+ }
10357
+ throw err;
9279
10358
  }
9280
- console.log(" All detected agents are already configured.");
9281
- printReadyNextSteps();
10359
+ } else if (scan.needsSetup.length === 0) {
10360
+ printTask("success", "No tool changes needed", "all detected tools already have GitHits MCP", useColors);
10361
+ } else {
10362
+ printTask("success", "Selected all detected tools", "--yes", useColors);
10363
+ }
10364
+ const outcomes = [];
10365
+ if (toSetup.length === 0 && scan.needsSetup.length > 0) {
10366
+ printTask("skipped", "Setup skipped", "no tools selected", useColors);
9282
10367
  console.log();
9283
10368
  return;
9284
10369
  }
9285
- const toSetup = scan.needsSetup;
9286
- const outcomes = [];
9287
- let alwaysMode = options.yes ?? false;
9288
- for (const agent of toSetup) {
9289
- console.log(` Setting up ${colorize(agent.name, "bold", useColors)}...
9290
- `);
9291
- const config = agent.getSetupConfig(fileSystemService);
9292
- const preview2 = formatSetupPreview(config);
9293
- for (const line of preview2.split(`
9294
- `)) {
9295
- console.log(` ${line}`);
9296
- }
10370
+ printSection(3, "Sign in", useColors);
10371
+ const authStatus = await runInitAuthentication(options, promptService, createLoginDeps, useColors);
10372
+ if (authStatus === "cancelled") {
10373
+ return;
10374
+ }
10375
+ printSection(4, "Install and verify", useColors);
10376
+ if (toSetup.length === 0) {
10377
+ printTask("success", "Nothing to install", "all detected tools are already configured", useColors);
10378
+ printPostSetupNextSteps(authStatus, useColors);
9297
10379
  console.log();
9298
- if (!alwaysMode) {
9299
- let choice;
9300
- try {
9301
- choice = await promptService.confirm3("Proceed?");
9302
- } catch (err) {
9303
- if (err instanceof ExitPromptError) {
9304
- console.log(`
9305
- Setup cancelled.
9306
- `);
9307
- return;
10380
+ return;
10381
+ }
10382
+ const installTasks = createInstallTaskReporter(useColors);
10383
+ for (const agent of toSetup) {
10384
+ const config = getResolvedSetupConfig(agent, fileSystemService);
10385
+ const finishTask = installTasks.start(agent.name);
10386
+ let result;
10387
+ try {
10388
+ result = config.method === "cli" ? await executeCliSetup(config, execService) : config.method === "config-file" ? await executeConfigFileSetup(config, fileSystemService) : await executeCompositeSetup(config, fileSystemService, execService);
10389
+ if (result.status === "success" || result.status === "already_configured") {
10390
+ const verification = await verifyAgentConfigured(agent, fileSystemService, execService);
10391
+ if (!verification.ok) {
10392
+ result = {
10393
+ status: "failed",
10394
+ message: agent.id === "gemini-cli" ? "Gemini installation did not complete. Retry, or run: gemini extensions install --consent https://github.com/githits-com/githits-cli" : verification.message ?? `${agent.name} verification failed after setup.`
10395
+ };
9308
10396
  }
9309
- throw err;
9310
- }
9311
- if (choice === "no") {
9312
- outcomes.push({ id: agent.id, name: agent.name, status: "skipped" });
9313
- console.log();
9314
- continue;
9315
- }
9316
- if (choice === "always") {
9317
- alwaysMode = true;
9318
- }
9319
- }
9320
- let result = config.method === "cli" ? await executeCliSetup(config, execService) : await executeConfigFileSetup(config, fileSystemService);
9321
- if (result.status === "success" || result.status === "already_configured") {
9322
- const verification = await verifyAgentConfigured(agent, fileSystemService, execService);
9323
- if (!verification.ok) {
9324
- result = {
9325
- status: "failed",
9326
- message: agent.id === "gemini-cli" ? "Gemini installation did not complete. Retry, or run: gemini extensions install --consent https://github.com/githits-com/githits-cli" : verification.message ?? `${agent.name} verification failed after setup.`
9327
- };
9328
10397
  }
10398
+ } finally {
10399
+ finishTask();
9329
10400
  }
9330
10401
  outcomes.push({
9331
10402
  id: agent.id,
@@ -9334,38 +10405,30 @@ async function initAction(options, deps) {
9334
10405
  message: result.status === "failed" ? result.message : undefined
9335
10406
  });
9336
10407
  if (result.status === "success") {
9337
- console.log(` ${success(`${agent.name} configured`, useColors)}
9338
- `);
10408
+ printTask("success", agent.name, "configured and verified", useColors);
9339
10409
  } else if (result.status === "already_configured") {
9340
- console.log(` ${warning(`${agent.name} already configured`, useColors)}
9341
- `);
10410
+ printTask("warning", agent.name, "already configured", useColors);
9342
10411
  } else {
9343
- console.log(` ${error(result.message, useColors)}
9344
- `);
10412
+ printTask("failed", agent.name, result.message, useColors);
9345
10413
  }
9346
10414
  }
10415
+ console.log();
9347
10416
  const configured = outcomes.filter((o) => o.status === "success").length;
9348
10417
  const alreadyDone = outcomes.filter((o) => o.status === "already_configured").length + scan.alreadyConfigured.length;
9349
10418
  const failed = outcomes.filter((o) => o.status === "failed").length;
9350
- const skipped = outcomes.filter((o) => o.status === "skipped").length;
9351
10419
  if (failed > 0) {
9352
10420
  console.log(" Setup completed with errors.");
9353
- } else if (continuedWithoutAuth && (configured > 0 || alreadyDone > 0)) {
9354
- console.log(" MCP is configured, but authentication is still required.");
9355
- console.log(" Run `githits login` before using GitHits tools.");
9356
10421
  } else if (configured > 0 || alreadyDone > 0) {
9357
- printReadyNextSteps();
9358
- } else if (skipped > 0) {
9359
- console.log(" Setup skipped.");
10422
+ printPostSetupNextSteps(authStatus, useColors);
9360
10423
  }
9361
10424
  if (failed > 0) {
9362
- console.log(` ${failed} agent${failed !== 1 ? "s" : ""} failed to configure.`);
10425
+ console.log(` ${failed} tool${failed !== 1 ? "s" : ""} failed to configure.`);
9363
10426
  for (const outcome of outcomes.filter((o) => o.status === "failed")) {
9364
10427
  console.log(` - ${outcome.name}: ${outcome.message ?? "Unknown error"}`);
9365
10428
  }
9366
10429
  }
9367
- if (skipped > 0) {
9368
- console.log(` ${skipped} agent${skipped !== 1 ? "s" : ""} skipped.`);
10430
+ if (scan.alreadyConfigured.length > 0) {
10431
+ console.log(` ${scan.alreadyConfigured.length} tool${scan.alreadyConfigured.length !== 1 ? "s" : ""} already configured.`);
9369
10432
  }
9370
10433
  console.log();
9371
10434
  }
@@ -9373,7 +10436,8 @@ async function initUninstallAction(options, deps) {
9373
10436
  const useColors = shouldUseColors();
9374
10437
  const { fileSystemService, promptService, execService } = deps;
9375
10438
  console.log(`
9376
- ${colorize("GitHits", "bold", useColors)} — Remove MCP server from your coding agents
10439
+ ${colorize("Disconnect GitHits from your coding agents.", "bold", useColors)}`);
10440
+ console.log(` ${colorize("Removes the local GitHits MCP configuration.", "dim", useColors)}
9377
10441
  `);
9378
10442
  console.log(` Scanning for configured agents...
9379
10443
  `);
@@ -9401,8 +10465,8 @@ async function initUninstallAction(options, deps) {
9401
10465
  for (const agent of scan.configured) {
9402
10466
  console.log(` Uninstalling from ${colorize(agent.name, "bold", useColors)}...
9403
10467
  `);
9404
- const setupConfig = agent.getSetupConfig(fileSystemService);
9405
- const uninstallConfig = setupConfig.method === "config-file" ? setupConfig : agent.getUninstallConfig?.(fileSystemService);
10468
+ const setupConfig = getResolvedSetupConfig(agent, fileSystemService);
10469
+ const uninstallConfig = agent.resolvedUninstallConfig ?? (setupConfig.method === "config-file" ? setupConfig : agent.getUninstallConfig?.(fileSystemService, agent.resolvedSetupContext));
9406
10470
  if (!uninstallConfig) {
9407
10471
  outcomes.push({
9408
10472
  id: agent.id,
@@ -9442,8 +10506,8 @@ async function initUninstallAction(options, deps) {
9442
10506
  alwaysMode = true;
9443
10507
  }
9444
10508
  }
9445
- let result = uninstallConfig.method === "cli" ? await executeCliUninstall(uninstallConfig, execService) : await executeConfigFileUninstall(uninstallConfig, fileSystemService);
9446
- if (result.status === "removed") {
10509
+ let result = uninstallConfig.method === "cli" ? await executeCliUninstall(uninstallConfig, execService) : uninstallConfig.method === "config-file" ? await executeConfigFileUninstall(uninstallConfig, fileSystemService) : await executeCompositeUninstall(uninstallConfig, fileSystemService, execService);
10510
+ if (result.status === "removed" && !agent.skipUninstallVerification) {
9447
10511
  const verification = await verifyAgentUnconfigured(agent, fileSystemService, execService);
9448
10512
  if (!verification.ok) {
9449
10513
  result = {
@@ -9510,32 +10574,27 @@ async function initUninstallAction(options, deps) {
9510
10574
  }
9511
10575
  console.log();
9512
10576
  }
9513
- function printAuthRecoveryHint() {
10577
+ function printAuthRecoveryHint(useColors) {
9514
10578
  console.log(" You can still configure MCP, but GitHits tools will require auth.");
9515
10579
  console.log(" Recovery steps:");
9516
- console.log(" githits auth status");
9517
- console.log(" githits login --force");
10580
+ console.log(` ${formatCommand("githits auth status", useColors)}`);
10581
+ console.log(` ${formatCommand("githits login --force", useColors)}`);
9518
10582
  console.log(" For CI or locked-down machines, set GITHITS_API_TOKEN.");
9519
10583
  console.log(` If your system keychain is unavailable, set GITHITS_AUTH_STORAGE=file after accepting plaintext storage.
9520
10584
  `);
9521
10585
  }
9522
- var INIT_DESCRIPTION = `Set up GitHits MCP server for your coding agents.
9523
-
9524
- Authenticates with your GitHits account, then scans for available agents
9525
- (Claude Code, Cursor, Windsurf, VS Code, Cline, Claude Desktop, Codex CLI,
9526
- Gemini CLI, Google Antigravity), checks which are already configured,
9527
- and sets up unconfigured ones with your confirmation.
10586
+ var INIT_DESCRIPTION = `Connect GitHits to your coding agents.
9528
10587
 
9529
- Supports CLI-based setup (Claude Code, Codex, Gemini CLI) and config
9530
- file editing (Cursor, Windsurf, VS Code, Cline, Claude Desktop,
9531
- Google Antigravity) with atomic writes.`;
10588
+ Installs the local GitHits MCP server the recommended way to connect — or
10589
+ sets up Agent Skills instead. Detects supported coding tools on this machine,
10590
+ signs you in, and configures the tools you select.`;
9532
10591
  var INIT_UNINSTALL_DESCRIPTION = `Remove GitHits MCP server configuration from your coding agents.
9533
10592
 
9534
10593
  Scans for available agents that currently have GitHits configured, then removes
9535
10594
  only the GitHits MCP/plugin configuration with your confirmation. Authentication
9536
10595
  tokens are not removed; use \`githits logout\` to remove stored credentials.`;
9537
10596
  function registerInitCommand(program) {
9538
- 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) => {
10597
+ const initCommand = program.command("init").summary("Connect GitHits to your coding agents").description(INIT_DESCRIPTION).option("-y, --yes", "Skip prompts, configure all detected tools").option("--skip-login", "Skip authentication step").action(async (options) => {
9539
10598
  const fileSystemService = new FileSystemServiceImpl;
9540
10599
  const promptService = new PromptServiceImpl;
9541
10600
  const execService = new ExecServiceImpl;
@@ -9543,7 +10602,7 @@ function registerInitCommand(program) {
9543
10602
  fileSystemService,
9544
10603
  promptService,
9545
10604
  execService,
9546
- createLoginDeps: () => createAuthCommandDependencies()
10605
+ createLoginDeps: () => createContainer()
9547
10606
  });
9548
10607
  });
9549
10608
  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) => {
@@ -9779,11 +10838,11 @@ var structuredCodeTargetSchema = z3.object({
9779
10838
  package_name: z3.string().max(255).optional().describe("Package name. Required for package scope."),
9780
10839
  version: z3.string().max(100).optional().describe("Package version, e.g. '4.18.2' (defaults to latest). For package scope only."),
9781
10840
  repo_url: z3.string().optional().describe("Repository URL (GitHub). Required for repo scope. Example: https://github.com/expressjs/express"),
9782
- git_ref: z3.string().optional().describe("Git ref - tag, branch, or commit. Required with repo_url for code_files/code_read/code_grep. Use HEAD for latest.")
9783
- }).describe("Target: provide registry + package_name (package scope) or repo_url + git_ref (repo scope).");
10841
+ git_ref: z3.string().optional().describe("Git ref - tag, branch, commit, or HEAD. Omit with repo_url to request the backend-resolved default branch.")
10842
+ }).describe("Target: provide registry + package_name (package scope) or repo_url with optional git_ref (repo scope; omitted ref means default branch intent).");
9784
10843
  var codeTargetSchema = z3.union([
9785
10844
  structuredCodeTargetSchema,
9786
- z3.string().min(1).describe("Compact target string. Package: `npm:react@18.2.0` or `npm:react` for latest release. Repository: `https://github.com/facebook/react#HEAD` (git ref required for code_files/code_read/code_grep).")
10845
+ z3.string().min(1).describe("Compact target string. Package: `npm:react@18.2.0` or `npm:react` for latest release. Repository: `https://github.com/facebook/react#HEAD` or `https://github.com/facebook/react` for default branch intent.")
9787
10846
  ]);
9788
10847
  function resolveCodeTarget(target) {
9789
10848
  if (typeof target === "string") {
@@ -9796,10 +10855,10 @@ function resolveCodeTarget(target) {
9796
10855
  const hasPackageTarget = Boolean(target.registry || target.package_name);
9797
10856
  const hasRepoTarget = Boolean(target.repo_url || target.git_ref);
9798
10857
  if (hasPackageTarget && hasRepoTarget) {
9799
- return invalidTargetResult("Invalid target: provide either registry + package_name or repo_url + git_ref, not both.");
10858
+ return invalidTargetResult("Invalid target: provide either registry + package_name or repo_url with optional git_ref, not both.");
9800
10859
  }
9801
10860
  if (!hasPackageTarget && !hasRepoTarget) {
9802
- return invalidTargetResult("Missing target: provide registry + package_name or repo_url + git_ref.");
10861
+ return invalidTargetResult("Missing target: provide registry + package_name or repo_url.");
9803
10862
  }
9804
10863
  if (hasPackageTarget) {
9805
10864
  if (!target.registry || !target.package_name) {
@@ -9811,11 +10870,8 @@ function resolveCodeTarget(target) {
9811
10870
  version: target.version
9812
10871
  };
9813
10872
  }
9814
- if (!target.repo_url || !target.git_ref) {
9815
- if (!target.repo_url) {
9816
- return invalidTargetResult("Incomplete repository target: repo_url is required.");
9817
- }
9818
- return invalidTargetResult("Incomplete repository target: git_ref is required for code_files/code_read/code_grep.");
10873
+ if (!target.repo_url) {
10874
+ return invalidTargetResult("Incomplete repository target: repo_url is required.");
9819
10875
  }
9820
10876
  return {
9821
10877
  repoUrl: target.repo_url,
@@ -9861,7 +10917,7 @@ var schema3 = {
9861
10917
  wait_timeout_ms: z4.number().optional(),
9862
10918
  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.')
9863
10919
  };
9864
- 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`." + `
10920
+ 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`. " + "When fresh data is not ready within the wait window, responses may include `targetResolution` provenance and immediately-queryable alternatives in error details." + `
9865
10921
 
9866
10922
  ${CODE_GREP_GUARDRAIL}`;
9867
10923
  function createGrepRepoTool(service) {
@@ -9955,10 +11011,10 @@ var schema4 = {
9955
11011
  exclude_test_files: z5.boolean().optional(),
9956
11012
  include_hidden: z5.boolean().optional(),
9957
11013
  limit: z5.number().optional().describe("Max entries to return (1–1000, default 200). Out-of-range values return an `INVALID_ARGUMENT` envelope."),
9958
- 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`."),
11014
+ 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/ref from `details.availableVersions` / `details.availableRefs`."),
9959
11015
  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.')
9960
11016
  };
9961
- 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`.";
11017
+ 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` + optional `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}`. " + "When fresh data is not ready within the wait window, responses may " + "include `targetResolution` provenance and immediately-queryable " + "alternatives. On an `INDEXING` error envelope, retry with a longer " + "`wait_timeout_ms` or use a version/ref from `details.availableVersions` " + "/ `details.availableRefs`.";
9962
11018
  function createListFilesTool(service) {
9963
11019
  return {
9964
11020
  name: "code_files",
@@ -10721,10 +11777,10 @@ var schema11 = {
10721
11777
  path: z12.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."),
10722
11778
  start_line: z12.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.`),
10723
11779
  end_line: z12.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.`),
10724
- wait_timeout_ms: z12.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`."),
11780
+ wait_timeout_ms: z12.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/ref from `details.availableVersions` / `details.availableRefs`."),
10725
11781
  format: z12.enum(["json", "text", "text-v1"]).optional().describe('Response format. Default `text-v1` — line-numbered source content. Pass `format: "json"` for the structured envelope.')
10726
11782
  };
10727
- var DESCRIPTION11 = "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." + `
11783
+ var DESCRIPTION11 = "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` + optional `target.git_ref` (repo scope), " + "mutually exclusive. When fresh data is not ready within the wait " + "window, responses may include `targetResolution` provenance and " + "immediately-queryable alternatives. On `INDEXING` retry with a " + "longer `wait_timeout_ms` or use a version/ref from error details. " + "On `NOT_FOUND` / `FILE_NOT_FOUND` call " + "`code_files` to discover the actual path." + `
10728
11784
 
10729
11785
  ${CODE_READ_GUARDRAIL}`;
10730
11786
  function deriveBoundedRange(startLine, endLine) {
@@ -11124,7 +12180,7 @@ var CORE_BLOCK = `GitHits provides verified, canonical code examples from global
11124
12180
  \`get_example\` workflow: pass \`language\` only when you know the exact name; otherwise call \`search_language\` first. Default output is markdown with a trailing \`solution_id\`. Reuse prior results before searching again. For dependency-specific grounding, prefer package-scoped \`search\` before global \`get_example\`.`;
11125
12181
  var PACKAGE_TOOLS_PREAMBLE = `Indexed package/source tools inspect third-party dependency source, docs, and registry metadata. Use them when a stack trace points into a dependency, you need to verify how a library works, or you're evaluating whether to add or upgrade a package.
11126
12182
 
11127
- Targets: \`registry:name[@version]\` (\`registry:name\` = latest release). For \`search\`, repo URL without \`#\` uses the backend default-branch snapshot; exact \`code_*\` tools need \`#ref\` such as \`#HEAD\`. Default outputs are compact \`text-v1\`; pass \`format: "json"\` only for structured parsing.`;
12183
+ Targets: \`registry:name[@version]\` (\`registry:name\` = latest release). Repo targets can request the backend default-branch intent by omitting the ref (\`https://github.com/org/repo\` for \`search\`, or \`repo_url\` without \`git_ref\` for \`code_*\`); pass \`#HEAD\` / \`git_ref:"HEAD"\` when you specifically need latest HEAD. Default outputs are compact \`text-v1\`; pass \`format: "json"\` only for structured parsing.`;
11128
12184
  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.';
11129
12185
  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`.';
11130
12186
  var SEARCH_STATUS_BULLET = "- `search_status` — follow up a prior `search` by `searchRef` to check progress, fetch partial hits, or fetch final results.";
@@ -11251,7 +12307,7 @@ function showMcpSetupInstructions() {
11251
12307
  console.log("Learn more at https://githits.com");
11252
12308
  }
11253
12309
  function registerMcpCommand(program) {
11254
- const mcpCommand = program.command("mcp").summary("Show setup instructions or start MCP server").description(`Start the Model Context Protocol (MCP) server using STDIO transport.
12310
+ const mcpCommand = program.command("mcp").summary("Show MCP setup instructions or start the local MCP server").description(`Start the Model Context Protocol (MCP) server using STDIO transport.
11255
12311
 
11256
12312
  When run interactively (TTY), shows setup instructions.
11257
12313
  When run via stdio (non-TTY), starts the MCP server.
@@ -11823,7 +12879,7 @@ async function registerPkgCommandGroup(program, options = {}) {
11823
12879
  if (!registration.shouldRegister) {
11824
12880
  return;
11825
12881
  }
11826
- const pkgCommand = program.command("pkg").summary("Package metadata: info, vulnerabilities, dependencies, changelog, upgrade reviews").description("Inspect package metadata from npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, RubyGems, Go, vcpkg, and Zig: overviews, advisories, dependency graphs, and changelogs. Advisory data is unavailable for vcpkg and Zig. For source-level operations inside a dependency, use `githits code`.");
12882
+ const pkgCommand = program.command("pkg").summary("Package metadata, dependencies, vulnerabilities and changelogs").description("Inspect package metadata from npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, RubyGems, Go, vcpkg, and Zig: overviews, advisories, dependency graphs, and changelogs. Advisory data is unavailable for vcpkg and Zig. For source-level operations inside a dependency, use `githits code`.");
11827
12883
  registerPkgInfoCommand(pkgCommand);
11828
12884
  registerPkgVulnsCommand(pkgCommand);
11829
12885
  registerPkgDepsCommand(pkgCommand);
@@ -11852,7 +12908,8 @@ async function searchAction(query, options, deps) {
11852
12908
  offset: parseOptionalInt(options.offset, "--offset", 0),
11853
12909
  waitTimeoutMs: parseWaitMs(options.wait)
11854
12910
  });
11855
- const outcome = await service.search(built.params);
12911
+ const spinner = startSpinner(SPINNER_MESSAGES.search, !options.json);
12912
+ const outcome = await service.search(built.params).finally(() => spinner.stop());
11856
12913
  const payload = buildUnifiedSearchSuccessPayload(built.params, built.rawQuery, built.compiledQuery, outcome);
11857
12914
  if (options.json) {
11858
12915
  console.log(JSON.stringify(payload));
@@ -11914,7 +12971,7 @@ Pass the searchRef returned by githits search when the initial request could
11914
12971
  not complete within the wait window. This can return progress, partial hits when
11915
12972
  the original request used --allow-partial, or final results.`;
11916
12973
  function registerSearchCommand(program) {
11917
- program.command("search").summary("Search indexed dependency and repository code, docs, and symbols").description(SEARCH_DESCRIPTION).argument("<query>", "Search query").requiredOption("--in <target>", "Search target: registry:name[@version] or https://github.com/org/repo[#ref]", collectRepeatable3, []).addOption(new Option3("--source <source>", "Source to search (repeatable; default: auto)").choices(["docs", "code", "symbol"]).argParser((value, previous = []) => collectRepeatable3(value.toLowerCase(), previous)).default(undefined)).addOption(new Option3("--kind <kind>", "Precise symbol kind filter").choices([
12974
+ program.command("search").summary("Explore repository code, dependencies, docs and symbols").description(SEARCH_DESCRIPTION).argument("<query>", "Search query").requiredOption("--in <target>", "Search target: registry:name[@version] or https://github.com/org/repo[#ref]", collectRepeatable3, []).addOption(new Option3("--source <source>", "Source to search (repeatable; default: auto)").choices(["docs", "code", "symbol"]).argParser((value, previous = []) => collectRepeatable3(value.toLowerCase(), previous)).default(undefined)).addOption(new Option3("--kind <kind>", "Precise symbol kind filter").choices([
11918
12975
  ...knownSymbolKindList()
11919
12976
  ])).addOption(new Option3("--category <category>", "Broad symbol category filter").choices([...knownSymbolCategoryList()])).option("--path-prefix <prefix>", "Repository path prefix filter").addOption(new Option3("--intent <intent>", "File intent filter (omit to search across all intents)").choices([
11920
12977
  "production",
@@ -11929,7 +12986,7 @@ function registerSearchCommand(program) {
11929
12986
  const deps = await loadContainer2();
11930
12987
  await searchAction(query, options, deps);
11931
12988
  });
11932
- program.command("search-status").summary("Check status of a prior search").description(SEARCH_STATUS_DESCRIPTION).argument("<search-ref>", "Search reference returned by githits search").option("--json", "Output as JSON").action(async (searchRef, options) => {
12989
+ program.command("search-status").summary("Check the status of a previous search").description(SEARCH_STATUS_DESCRIPTION).argument("<search-ref>", "Search reference returned by githits search").option("--json", "Output as JSON").action(async (searchRef, options) => {
11933
12990
  const deps = await loadContainer2();
11934
12991
  await searchStatusAction(searchRef, options, deps);
11935
12992
  });
@@ -11948,7 +13005,7 @@ function requireSearchService(deps) {
11948
13005
  return deps.codeNavigationService;
11949
13006
  }
11950
13007
  async function loadContainer2() {
11951
- const { createContainer: createContainer2 } = await import("./shared/chunk-zzmbjttb.js");
13008
+ const { createContainer: createContainer2 } = await import("./shared/chunk-70bn2pmx.js");
11952
13009
  return createContainer2();
11953
13010
  }
11954
13011
  function parseTargetSpecs(specs) {
@@ -12047,7 +13104,7 @@ function formatUnifiedSearchTerminal(payload) {
12047
13104
  lines.push("");
12048
13105
  lines.push("Partial results:");
12049
13106
  }
12050
- const sourceStatusNotes = formatSourceStatusNotes(payload.sourceStatus);
13107
+ const sourceStatusNotes = formatSourceStatusNotes(payload.sourceStatus, warnings);
12051
13108
  if (payload.results.length === 0) {
12052
13109
  lines.push("No results.");
12053
13110
  if (sourceStatusNotes.length > 0) {
@@ -12159,13 +13216,17 @@ function formatSearchStatusPartialTerminal(payload) {
12159
13216
  sourceStatus: payload.result.sourceStatus
12160
13217
  });
12161
13218
  }
12162
- function formatSourceStatusNotes(sourceStatus) {
13219
+ function formatSourceStatusNotes(sourceStatus, warnings) {
12163
13220
  const useColors = shouldUseColors();
12164
13221
  if (!sourceStatus) {
12165
13222
  return [];
12166
13223
  }
12167
13224
  const lines = [];
12168
13225
  for (const entry of sourceStatus) {
13226
+ const warningPrefix = `Source '${entry.source.toLowerCase()}' for ${entry.targetLabel}:`;
13227
+ if (warnings?.some((warning2) => warning2.startsWith(warningPrefix))) {
13228
+ continue;
13229
+ }
12169
13230
  const label = `${entry.source.toLowerCase()} on ${entry.targetLabel}`;
12170
13231
  if (entry.ignoredFilters && entry.ignoredFilters.length > 0) {
12171
13232
  lines.push(dim(`Note: ${label} ignored filters: ${entry.ignoredFilters.join(", ")}`, useColors));
@@ -12396,7 +13457,7 @@ function mergeRanges2(ranges) {
12396
13457
  }
12397
13458
  return merged;
12398
13459
  }
12399
- function formatUnifiedSearchMetadata(entry, useColors) {
13460
+ function formatUnifiedSearchMetadata(entry, _useColors) {
12400
13461
  if (entry.type !== "documentation_page" && entry.type !== "repository_doc") {
12401
13462
  return [];
12402
13463
  }
@@ -12409,6 +13470,10 @@ function formatUnifiedSearchMetadata(entry, useColors) {
12409
13470
  // src/cli.ts
12410
13471
  var program = new Command;
12411
13472
  var argv = process.argv.slice(2);
13473
+ if (argv.includes("--no-color")) {
13474
+ process.env.NO_COLOR = "1";
13475
+ }
13476
+ var useColors = shouldUseColors();
12412
13477
  var commandSpans = new WeakMap;
12413
13478
  var createUpdateCheckService = () => new NpmRegistryUpdateCheckService({
12414
13479
  currentVersion: version,
@@ -12445,21 +13510,23 @@ var rootCliPreAction = createRootCliPreAction({
12445
13510
  clearAuthSessionMetadata: clearAutoLoginAuthSessionMetadata,
12446
13511
  loginFlow: (options, deps) => loginFlow(options, deps, stderrLoginOutput)
12447
13512
  });
12448
- 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) => {
13513
+ program.name("githits").description("Grounded open-source context for AI coding agents").version(version).option("--no-color", "Disable colored output").configureHelp({
13514
+ styleTitle: (title) => colorizeBrand(title, "primary", useColors, { bold: true })
13515
+ }).hook("preAction", async (thisCommand, actionCommand) => {
12449
13516
  const command = actionCommand ?? thisCommand;
12450
13517
  commandSpans.set(command, startTelemetrySpan(getTelemetryCommandName(command)));
12451
13518
  await rootCliPreAction(thisCommand, actionCommand);
12452
13519
  }).hook("postAction", (_thisCommand, actionCommand) => {
12453
13520
  endTelemetrySpan(commandSpans.get(actionCommand));
12454
13521
  }).addHelpText("after", `
12455
- Getting started:
12456
- githits init Set up MCP for your coding agents
12457
- githits login Authenticate with your GitHits account
13522
+ ${colorizeBrand("Getting started:", "primary", useColors, { bold: true })}
13523
+ githits init Connect GitHits to your coding agents
13524
+ githits login Sign in to your GitHits account
12458
13525
  githits mcp Show MCP setup instructions
12459
- githits example "query" Get code examples
13526
+ githits example "query" Find real-world implementations
12460
13527
 
12461
13528
  Learn more at https://githits.com
12462
- Docs: https://app.githits.com/docs/
13529
+ Docs: https://docs.githits.com
12463
13530
  Support: support@githits.com`);
12464
13531
  registerInitCommand(program);
12465
13532
  registerLoginCommand(program);