githits 0.3.2 → 0.4.0

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
@@ -49,11 +49,11 @@ import {
49
49
  shouldRunUpdateCheck,
50
50
  startTelemetrySpan,
51
51
  withTelemetrySpan
52
- } from "./shared/chunk-zarrkzvq.js";
52
+ } from "./shared/chunk-1cyka46a.js";
53
53
  import {
54
54
  __require,
55
55
  version
56
- } from "./shared/chunk-w5kw8sef.js";
56
+ } from "./shared/chunk-fqzth5mv.js";
57
57
 
58
58
  // src/cli.ts
59
59
  import { Command } from "commander";
@@ -254,9 +254,9 @@ async function authStatusAction(deps) {
254
254
  `);
255
255
  console.log(` Environment: ${mcpUrl}
256
256
  `);
257
- console.log("");
258
- console.log("To authenticate:");
259
- console.log(" githits login");
257
+ console.log(` Storage: ${authStorage.getStorageLocation()}
258
+ `);
259
+ printAuthTroubleshooting();
260
260
  return;
261
261
  }
262
262
  if (auth.expiresAt && new Date(auth.expiresAt) < new Date) {
@@ -276,8 +276,9 @@ async function authStatusAction(deps) {
276
276
  console.log(` Environment: ${mcpUrl}`);
277
277
  console.log(` Expired: ${new Date(auth.expiresAt).toLocaleDateString()}
278
278
  `);
279
- console.log("");
280
- console.log("Run `githits login` to re-authenticate.");
279
+ console.log(` Storage: ${authStorage.getStorageLocation()}
280
+ `);
281
+ printAuthTroubleshooting("expired");
281
282
  return;
282
283
  }
283
284
  console.log(`Authenticated.
@@ -287,6 +288,17 @@ async function authStatusAction(deps) {
287
288
  console.log(`
288
289
  Storage: ${authStorage.getStorageLocation()}`);
289
290
  }
291
+ function printAuthTroubleshooting(reason = "missing") {
292
+ const loginCommand = reason === "expired" ? "githits login --force" : "githits login";
293
+ console.log("Recovery steps:");
294
+ console.log(` ${loginCommand}`);
295
+ if (reason === "missing") {
296
+ console.log(" githits login --force # if a previous login is stale");
297
+ }
298
+ console.log("For CI/automation, set GITHITS_API_TOKEN.");
299
+ console.log("If your system keychain is locked or unavailable, unlock it and retry.");
300
+ console.log("As a last resort, set GITHITS_AUTH_STORAGE=file to use plaintext file storage.");
301
+ }
290
302
  var STATUS_DESCRIPTION = `Show current authentication status.
291
303
 
292
304
  Displays details about the stored token including environment
@@ -353,6 +365,11 @@ function highlight(text, useColors) {
353
365
  return text;
354
366
  return `${colors.bold}${colors.cyan}${text}${colors.reset}`;
355
367
  }
368
+ function highlightMatch(text, useColors) {
369
+ if (!useColors)
370
+ return text;
371
+ return `${colors.bold}${colors.yellow}${text}${colors.reset}`;
372
+ }
356
373
  function highlightRanges(text, ranges, useColors) {
357
374
  if (!useColors || !text || !ranges || ranges.length === 0)
358
375
  return text;
@@ -380,7 +397,7 @@ function highlightRanges(text, ranges, useColors) {
380
397
  for (const [start, end] of merged) {
381
398
  if (cursor < start)
382
399
  result += text.slice(cursor, start);
383
- result += highlight(text.slice(start, end), useColors);
400
+ result += highlightMatch(text.slice(start, end), useColors);
384
401
  cursor = end;
385
402
  }
386
403
  if (cursor < text.length)
@@ -763,7 +780,7 @@ function parseCodeNavigationTargetSpec(spec) {
763
780
  function parseRepoTarget(spec) {
764
781
  const hashIndex = spec.lastIndexOf("#");
765
782
  if (hashIndex === -1) {
766
- return { repoUrl: spec, gitRef: "HEAD" };
783
+ throw new InvalidArgumentError("Repository target must include #gitRef for exact code navigation.");
767
784
  }
768
785
  const repoUrl = spec.slice(0, hashIndex);
769
786
  const gitRef = spec.slice(hashIndex + 1);
@@ -804,7 +821,8 @@ function buildSearchHitFollowUpCommand(hit) {
804
821
  gitRef: loc.gitRef,
805
822
  filePath: loc.filePath,
806
823
  startLine: loc.startLine,
807
- endLine: loc.endLine
824
+ endLine: loc.endLine,
825
+ preferPackageTarget: isPackageTarget(hit)
808
826
  });
809
827
  }
810
828
  if (hit.type === "repository_code" || hit.type === "repository_symbol") {
@@ -833,14 +851,24 @@ function buildCodeReadCommand(input) {
833
851
  return parts.join(" ");
834
852
  }
835
853
  function buildTargetSpec(input) {
854
+ if (input.preferPackageTarget && input.registry && input.packageName) {
855
+ return `${input.registry}:${input.packageName}${input.version ? `@${input.version}` : ""}`;
856
+ }
836
857
  if (input.repoUrl) {
837
- return `${input.repoUrl}#${input.gitRef ?? "HEAD"}`;
858
+ if (!input.gitRef)
859
+ return;
860
+ return `${input.repoUrl}#${input.gitRef}`;
838
861
  }
839
862
  if (input.registry && input.packageName) {
840
863
  return `${input.registry}:${input.packageName}${input.version ? `@${input.version}` : ""}`;
841
864
  }
842
865
  return;
843
866
  }
867
+ function isPackageTarget(hit) {
868
+ const registry = hit.locator.registry;
869
+ const packageName = hit.locator.packageName;
870
+ return Boolean(registry && packageName && hit.target.startsWith(`${registry}:${packageName}`));
871
+ }
844
872
  function appendRange(parts, startLine, endLine) {
845
873
  if (typeof startLine === "number")
846
874
  parts.push(`start_line=${startLine}`);
@@ -1934,7 +1962,7 @@ function renderListPackageDocsText(envelope) {
1934
1962
  if (page.sourceKind === "repo" && page.repoUrl && page.filePath) {
1935
1963
  lines.push(` ${buildCodeReadCommand({
1936
1964
  repoUrl: page.repoUrl,
1937
- gitRef: page.requestedRef ?? page.gitRef,
1965
+ gitRef: page.gitRef,
1938
1966
  filePath: page.filePath,
1939
1967
  startLine: 1,
1940
1968
  endLine: 150
@@ -4110,7 +4138,7 @@ function renderReadPackageDocText(envelope) {
4110
4138
  if (envelope.sourceUrl)
4111
4139
  lines.push(`source: ${envelope.sourceUrl}`);
4112
4140
  if (envelope.filePath) {
4113
- const ref = envelope.requestedRef ?? envelope.gitRef;
4141
+ const ref = envelope.gitRef;
4114
4142
  lines.push(`file: ${envelope.filePath}${ref ? ` @ ${ref}` : ""}`);
4115
4143
  }
4116
4144
  lines.push("");
@@ -4140,6 +4168,121 @@ function buildRange2(envelope) {
4140
4168
  return `${envelope.totalLines} lines`;
4141
4169
  return;
4142
4170
  }
4171
+ // src/shared/auto-login.ts
4172
+ var AUTO_LOGIN_ELIGIBLE_COMMANDS = new Set([
4173
+ "example",
4174
+ "languages",
4175
+ "feedback",
4176
+ "search",
4177
+ "search-status",
4178
+ "code files",
4179
+ "code read",
4180
+ "code grep",
4181
+ "docs list",
4182
+ "docs read",
4183
+ "pkg info",
4184
+ "pkg vulns",
4185
+ "pkg deps",
4186
+ "pkg changelog"
4187
+ ]);
4188
+ function getCommandPath(command) {
4189
+ const names = [];
4190
+ let current = command;
4191
+ while (current) {
4192
+ const name = current.name();
4193
+ if (name && name !== "githits") {
4194
+ names.unshift(name);
4195
+ }
4196
+ current = current.parent ?? null;
4197
+ }
4198
+ return names;
4199
+ }
4200
+ function isAutoLoginEligibleCommand(command, runtime = {
4201
+ stdinIsTTY: Boolean(process.stdin.isTTY),
4202
+ stdoutIsTTY: Boolean(process.stdout.isTTY)
4203
+ }) {
4204
+ const commandPath = getCommandPath(command).join(" ");
4205
+ if (!AUTO_LOGIN_ELIGIBLE_COMMANDS.has(commandPath)) {
4206
+ return false;
4207
+ }
4208
+ if (!runtime.stdinIsTTY || !runtime.stdoutIsTTY) {
4209
+ return false;
4210
+ }
4211
+ return true;
4212
+ }
4213
+ async function maybeAutoLoginBeforeCommand(command, deps) {
4214
+ if (!isAutoLoginEligibleCommand(command, {
4215
+ stdinIsTTY: deps.stdinIsTTY ?? Boolean(process.stdin.isTTY),
4216
+ stdoutIsTTY: deps.stdoutIsTTY ?? Boolean(process.stdout.isTTY)
4217
+ })) {
4218
+ return { status: "skipped" };
4219
+ }
4220
+ const container = await deps.createContainer();
4221
+ if (container.hasValidToken) {
4222
+ return { status: "already-authenticated" };
4223
+ }
4224
+ const result = await deps.loginFlow({}, container);
4225
+ switch (result.status) {
4226
+ case "success":
4227
+ return { status: "authenticated", message: result.message };
4228
+ case "already_authenticated":
4229
+ return { status: "already-authenticated", message: result.message };
4230
+ case "failed":
4231
+ return { status: "failed", message: result.message };
4232
+ }
4233
+ }
4234
+
4235
+ // src/shared/root-cli-pre-action.ts
4236
+ function createRootCliPreAction(deps) {
4237
+ return async (thisCommand, actionCommand) => {
4238
+ if (thisCommand.opts().color === false) {
4239
+ process.env.NO_COLOR = "1";
4240
+ }
4241
+ const command = actionCommand ?? thisCommand;
4242
+ const authResult = await maybeAutoLoginBeforeCommand(command, {
4243
+ ...deps,
4244
+ stdinIsTTY: deps.stdinIsTTY,
4245
+ stdoutIsTTY: deps.stdoutIsTTY
4246
+ });
4247
+ if (authResult.status === "authenticated") {
4248
+ const continuationMessage = getPostLoginContinuationMessage(command);
4249
+ if (continuationMessage) {
4250
+ console.error(continuationMessage);
4251
+ }
4252
+ }
4253
+ if (authResult.status !== "failed") {
4254
+ return;
4255
+ }
4256
+ console.error(`${authResult.message}
4257
+ `);
4258
+ console.error("Run `githits login` to try again.");
4259
+ (deps.exit ?? process.exit)(1);
4260
+ };
4261
+ }
4262
+ function getPostLoginContinuationMessage(command) {
4263
+ switch (getCommandPath(command).join(" ")) {
4264
+ case "example":
4265
+ return "Authentication complete. Running example search...";
4266
+ case "languages":
4267
+ return "Authentication complete. Loading supported languages...";
4268
+ case "feedback":
4269
+ return "Authentication complete. Submitting feedback...";
4270
+ case "search":
4271
+ case "search-status":
4272
+ case "code files":
4273
+ case "code read":
4274
+ case "code grep":
4275
+ case "docs list":
4276
+ case "docs read":
4277
+ case "pkg info":
4278
+ case "pkg vulns":
4279
+ case "pkg deps":
4280
+ case "pkg changelog":
4281
+ return "Authentication complete. Running command...";
4282
+ default:
4283
+ return;
4284
+ }
4285
+ }
4143
4286
  // src/shared/unified-search-request.ts
4144
4287
  var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
4145
4288
  function buildUnifiedSearchParams(input) {
@@ -4192,11 +4335,6 @@ function resolveTargets(target, targets) {
4192
4335
  seen.add(key);
4193
4336
  deduped.push(entry);
4194
4337
  }
4195
- const hasPackageTarget = deduped.some((entry) => entry.packageName);
4196
- const hasRepoTarget = deduped.some((entry) => entry.repoUrl);
4197
- if (hasPackageTarget && hasRepoTarget) {
4198
- throw new InvalidArgumentError("Do not mix package-scoped and repo-scoped targets in one search.");
4199
- }
4200
4338
  return deduped;
4201
4339
  }
4202
4340
  function normaliseRequiredQuery(query) {
@@ -4255,6 +4393,7 @@ var DEFAULT_LIMIT2 = DEFAULT_UNIFIED_SEARCH_LIMIT;
4255
4393
  var DEFAULT_OFFSET = 0;
4256
4394
  function buildUnifiedSearchSuccessPayload(params, rawQuery, compiledQuery, outcome) {
4257
4395
  const warnings = outcome.state === "completed" ? outcome.result.queryWarnings : outcome.result?.queryWarnings ?? outcome.progress?.queryWarnings ?? [];
4396
+ const progress = compactProgress(outcome.progress);
4258
4397
  const query = buildQueryEcho(params, rawQuery, compiledQuery, warnings);
4259
4398
  if (outcome.state === "incomplete") {
4260
4399
  const result = outcome.result;
@@ -4268,13 +4407,12 @@ function buildUnifiedSearchSuccessPayload(params, rawQuery, compiledQuery, outco
4268
4407
  if (result?.page.hasMore === true) {
4269
4408
  payload.nextOffset = result.page.offset + result.page.returned;
4270
4409
  }
4271
- const progress = compactProgress(outcome.progress);
4272
4410
  if (progress)
4273
4411
  payload.progress = progress;
4274
4412
  const sourceStatus2 = compactSourceStatus(result?.sourceStatus);
4275
4413
  if (sourceStatus2)
4276
4414
  payload.sourceStatus = sourceStatus2;
4277
- const combinedWarnings2 = combineWarnings(warnings, sourceStatus2);
4415
+ const combinedWarnings2 = combineWarnings(warnings, sourceStatus2, payload.results, progress);
4278
4416
  if (combinedWarnings2.length > 0)
4279
4417
  payload.warnings = combinedWarnings2;
4280
4418
  return payload;
@@ -4293,15 +4431,17 @@ function buildUnifiedSearchSuccessPayload(params, rawQuery, compiledQuery, outco
4293
4431
  const sourceStatus = compactSourceStatus(outcome.result.sourceStatus);
4294
4432
  if (sourceStatus)
4295
4433
  completed.sourceStatus = sourceStatus;
4296
- const combinedWarnings = combineWarnings(warnings, sourceStatus);
4434
+ const combinedWarnings = combineWarnings(warnings, sourceStatus, completed.results, progress);
4297
4435
  if (combinedWarnings.length > 0)
4298
4436
  completed.warnings = combinedWarnings;
4299
4437
  return completed;
4300
4438
  }
4301
- function combineWarnings(parserWarnings, sourceStatus) {
4439
+ function combineWarnings(parserWarnings, sourceStatus, hits = [], progress) {
4302
4440
  const out = [];
4303
4441
  if (parserWarnings.length > 0)
4304
4442
  out.push(...parserWarnings);
4443
+ out.push(...buildHitFreshnessWarnings(hits));
4444
+ out.push(...buildProgressFreshnessWarnings(progress));
4305
4445
  out.push(...buildSourceStatusWarnings(sourceStatus));
4306
4446
  return out;
4307
4447
  }
@@ -4328,6 +4468,9 @@ function buildUnifiedSearchStatusPayload(outcome) {
4328
4468
  const progress = compactProgress(outcome.progress);
4329
4469
  if (progress)
4330
4470
  payload2.progress = progress;
4471
+ const progressWarnings = buildProgressFreshnessWarnings(progress);
4472
+ if (progressWarnings.length > 0)
4473
+ payload2.warnings = progressWarnings;
4331
4474
  if (outcome.result) {
4332
4475
  payload2.result = buildUnifiedSearchStatusResultPayload(outcome.result);
4333
4476
  }
@@ -4343,6 +4486,7 @@ function buildUnifiedSearchStatusPayload(outcome) {
4343
4486
  }
4344
4487
  function buildUnifiedSearchStatusResultPayload(result) {
4345
4488
  const payload = {
4489
+ query: buildStatusQueryEcho(result),
4346
4490
  hasMore: result.page.hasMore,
4347
4491
  results: result.results.map(buildHitPayload)
4348
4492
  };
@@ -4361,6 +4505,18 @@ function buildUnifiedSearchStatusResultPayload(result) {
4361
4505
  }
4362
4506
  return payload;
4363
4507
  }
4508
+ function buildStatusQueryEcho(result) {
4509
+ const query = {
4510
+ raw: result.query
4511
+ };
4512
+ if (result.queryWarnings.length > 0) {
4513
+ query.warnings = result.queryWarnings;
4514
+ }
4515
+ if (result.sources.length > 0) {
4516
+ query.sources = result.sources.map((entry) => entry.toLowerCase());
4517
+ }
4518
+ return query;
4519
+ }
4364
4520
  function buildQueryEcho(params, rawQuery, compiledQuery, warnings) {
4365
4521
  const echo = {
4366
4522
  raw: rawQuery
@@ -4410,6 +4566,12 @@ function buildHitPayload(hit) {
4410
4566
  target: hit.targetLabel,
4411
4567
  locator: buildLocatorPayload(hit)
4412
4568
  };
4569
+ appendFreshness(payload, {
4570
+ requestedTargetLabel: hit.requestedTargetLabel,
4571
+ freshTargetLabel: hit.freshTargetLabel,
4572
+ servedTargetLabel: hit.servedTargetLabel,
4573
+ freshness: hit.freshness
4574
+ });
4413
4575
  if (hit.title)
4414
4576
  payload.title = hit.title;
4415
4577
  if (hit.summary)
@@ -4417,6 +4579,9 @@ function buildHitPayload(hit) {
4417
4579
  const highlights = buildHighlights(hit.highlights);
4418
4580
  if (highlights)
4419
4581
  payload.highlights = highlights;
4582
+ const followUp = buildSearchHitFollowUpCommand(payload);
4583
+ if (followUp)
4584
+ payload.followUp = followUp;
4420
4585
  return payload;
4421
4586
  }
4422
4587
  function buildLocatorPayload(hit) {
@@ -4478,10 +4643,79 @@ function compactProgress(progress) {
4478
4643
  targetsTotal: progress.targetsTotal,
4479
4644
  elapsedMs: progress.elapsedMs
4480
4645
  };
4646
+ if (progress.query)
4647
+ payload.query = progress.query;
4648
+ if (progress.requestedSources?.length) {
4649
+ payload.requestedSources = progress.requestedSources.map((entry) => entry.toLowerCase());
4650
+ }
4651
+ if (progress.targetMode)
4652
+ payload.targetMode = progress.targetMode;
4653
+ if (progress.requestedTargets?.length) {
4654
+ payload.requestedTargets = progress.requestedTargets;
4655
+ }
4656
+ if (progress.filters)
4657
+ payload.filters = buildFilterEcho3(progress.filters);
4658
+ if (typeof progress.limit === "number")
4659
+ payload.limit = progress.limit;
4660
+ if (typeof progress.offset === "number")
4661
+ payload.offset = progress.offset;
4662
+ const targets = progress.targets?.map(compactProgressTarget).filter(Boolean);
4663
+ if (targets?.length) {
4664
+ payload.targets = targets;
4665
+ }
4481
4666
  if (progress.expiresAt)
4482
4667
  payload.expiresAt = progress.expiresAt;
4668
+ payload.next = `search_status search_ref=${JSON.stringify(progress.searchRef)}`;
4483
4669
  return payload;
4484
4670
  }
4671
+ function appendFreshness(payload, source) {
4672
+ if (!isTrustRelevantFreshness(source.freshness) || !labelsDiverge({
4673
+ requestedTarget: source.requestedTargetLabel,
4674
+ freshTarget: source.freshTargetLabel,
4675
+ servedTarget: source.servedTargetLabel
4676
+ })) {
4677
+ return;
4678
+ }
4679
+ if (source.requestedTargetLabel)
4680
+ payload.requestedTarget = source.requestedTargetLabel;
4681
+ if (source.freshTargetLabel)
4682
+ payload.freshTarget = source.freshTargetLabel;
4683
+ if (source.servedTargetLabel)
4684
+ payload.servedTarget = source.servedTargetLabel;
4685
+ if (source.freshness)
4686
+ payload.freshness = source.freshness;
4687
+ }
4688
+ function compactProgressTarget(target) {
4689
+ const payload = {};
4690
+ if (target.requested)
4691
+ payload.requested = target.requested;
4692
+ if (target.resolvedRequested)
4693
+ payload.resolvedRequested = target.resolvedRequested;
4694
+ if (target.served)
4695
+ payload.served = target.served;
4696
+ if (target.freshness)
4697
+ payload.freshness = target.freshness;
4698
+ if (target.indexingRef)
4699
+ payload.indexingRef = target.indexingRef;
4700
+ if (target.requestedRefKind)
4701
+ payload.requestedRefKind = target.requestedRefKind;
4702
+ return Object.keys(payload).length > 0 ? payload : undefined;
4703
+ }
4704
+ function buildFilterEcho3(filters) {
4705
+ const echo = {};
4706
+ if (filters.kind)
4707
+ echo.kind = filters.kind.toLowerCase();
4708
+ if (filters.category)
4709
+ echo.category = filters.category.toLowerCase();
4710
+ if (filters.pathPrefix)
4711
+ echo.pathPrefix = filters.pathPrefix;
4712
+ if (filters.fileIntent)
4713
+ echo.fileIntent = filters.fileIntent.toLowerCase();
4714
+ if (typeof filters.publicOnly === "boolean") {
4715
+ echo.publicOnly = filters.publicOnly;
4716
+ }
4717
+ return Object.keys(echo).length > 0 ? echo : undefined;
4718
+ }
4485
4719
  function buildSourceStatusWarnings(sourceStatus) {
4486
4720
  if (!sourceStatus || sourceStatus.length === 0)
4487
4721
  return [];
@@ -4493,8 +4727,51 @@ function buildSourceStatusWarnings(sourceStatus) {
4493
4727
  }
4494
4728
  return warnings;
4495
4729
  }
4730
+ function buildHitFreshnessWarnings(hits) {
4731
+ return hits.map((hit) => freshnessWarning({
4732
+ freshness: hit.freshness,
4733
+ requestedTarget: hit.requestedTarget,
4734
+ freshTarget: hit.freshTarget,
4735
+ servedTarget: hit.servedTarget
4736
+ })).filter((entry) => Boolean(entry));
4737
+ }
4738
+ function buildProgressFreshnessWarnings(progress) {
4739
+ return (progress?.targets ?? []).map((target) => freshnessWarning({
4740
+ freshness: target.freshness,
4741
+ requestedTarget: target.requested,
4742
+ freshTarget: target.resolvedRequested,
4743
+ servedTarget: target.served
4744
+ })).filter((entry) => Boolean(entry));
4745
+ }
4746
+ function freshnessWarning(input) {
4747
+ if (!isTrustRelevantFreshness(input.freshness))
4748
+ return;
4749
+ if (!labelsDiverge(input))
4750
+ return;
4751
+ const requested = input.requestedTarget ?? "requested target";
4752
+ const served = input.servedTarget ?? "served target";
4753
+ const fresh = input.freshTarget;
4754
+ return fresh ? `requested ${requested}; served stale ${served} while ${fresh} indexes.` : `requested ${requested}; served stale ${served}.`;
4755
+ }
4756
+ function isTrustRelevantFreshness(value) {
4757
+ return value === "STALE" || value === "INDEXING";
4758
+ }
4759
+ function labelsDiverge(input) {
4760
+ const served = input.servedTarget;
4761
+ if (!served)
4762
+ return false;
4763
+ return Boolean(input.freshTarget && input.freshTarget !== served || input.requestedTarget && input.requestedTarget !== served);
4764
+ }
4496
4765
  function warningForEntry(entry) {
4497
4766
  const reasons = [];
4767
+ const freshness = freshnessWarning({
4768
+ freshness: entry.codeIndexState,
4769
+ requestedTarget: entry.requestedTarget,
4770
+ freshTarget: entry.freshTarget,
4771
+ servedTarget: entry.servedTarget
4772
+ });
4773
+ if (freshness)
4774
+ return freshness;
4498
4775
  if (entry.incompatibleQueryFeatures?.length) {
4499
4776
  reasons.push(`incompatible query features [${entry.incompatibleQueryFeatures.join(", ")}]`);
4500
4777
  }
@@ -4511,7 +4788,9 @@ function warningForEntry(entry) {
4511
4788
  reasons.push(`indexing status ${entry.indexingStatus}`);
4512
4789
  }
4513
4790
  if (entry.codeIndexState) {
4514
- reasons.push(`code index state ${entry.codeIndexState}`);
4791
+ if (entry.codeIndexState !== "STALE") {
4792
+ reasons.push(`code index state ${entry.codeIndexState}`);
4793
+ }
4515
4794
  }
4516
4795
  const prefix = `Source '${entry.source}' for ${entry.targetLabel}`;
4517
4796
  if (reasons.length > 0) {
@@ -4539,11 +4818,23 @@ function compactSourceStatusEntry(entry) {
4539
4818
  targetLabel: entry.targetLabel
4540
4819
  };
4541
4820
  let interesting = false;
4821
+ const staleDiverges = entry.codeIndexState === "STALE" && labelsDiverge({
4822
+ requestedTarget: entry.requestedTargetLabel,
4823
+ freshTarget: entry.freshTargetLabel,
4824
+ servedTarget: entry.servedTargetLabel
4825
+ });
4826
+ if (staleDiverges) {
4827
+ payload.requestedTarget = entry.requestedTargetLabel;
4828
+ payload.freshTarget = entry.freshTargetLabel;
4829
+ payload.servedTarget = entry.servedTargetLabel;
4830
+ payload.codeIndexState = entry.codeIndexState;
4831
+ interesting = true;
4832
+ }
4542
4833
  if (entry.indexingStatus && entry.indexingStatus !== "INDEXED") {
4543
4834
  payload.indexingStatus = entry.indexingStatus;
4544
4835
  interesting = true;
4545
4836
  }
4546
- if (entry.codeIndexState && entry.codeIndexState !== "CURRENT" && entry.codeIndexState !== "STALE") {
4837
+ if (entry.codeIndexState && entry.codeIndexState !== "CURRENT" && (entry.codeIndexState !== "STALE" || staleDiverges)) {
4547
4838
  payload.codeIndexState = entry.codeIndexState;
4548
4839
  interesting = true;
4549
4840
  }
@@ -4718,8 +5009,45 @@ function buildTrailer2(payload) {
4718
5009
  lines.push(` - ${formatSourceStatus(entry)}`);
4719
5010
  }
4720
5011
  }
5012
+ const progress = "progress" in payload ? payload.progress : undefined;
5013
+ if (progress?.targets?.length) {
5014
+ lines.push("progress targets:");
5015
+ for (const target of progress.targets) {
5016
+ lines.push(` - ${formatProgressTarget(target)}`);
5017
+ }
5018
+ }
4721
5019
  return lines;
4722
5020
  }
5021
+ function formatProgressTarget(target) {
5022
+ const parts = [];
5023
+ if (target.requested)
5024
+ parts.push(`requested=${target.requested}`);
5025
+ if (target.resolvedRequested)
5026
+ parts.push(`fresh=${target.resolvedRequested}`);
5027
+ if (target.served)
5028
+ parts.push(`served=${target.served}`);
5029
+ if (target.freshness)
5030
+ parts.push(`state=${describeFreshness(target.freshness)}`);
5031
+ if (target.requestedRefKind)
5032
+ parts.push(`intent=${target.requestedRefKind}`);
5033
+ if (target.indexingRef)
5034
+ parts.push(`indexingRef=${target.indexingRef}`);
5035
+ return parts.length > 0 ? parts.join(SEP6) : "target progress unavailable";
5036
+ }
5037
+ function describeFreshness(value) {
5038
+ switch (value) {
5039
+ case "PENDING":
5040
+ case "INDEXING":
5041
+ return "indexing fresh target";
5042
+ case "STALE":
5043
+ return "served stale evidence";
5044
+ case "CURRENT":
5045
+ case "INDEXED":
5046
+ return "current";
5047
+ default:
5048
+ return value.toLowerCase();
5049
+ }
5050
+ }
4723
5051
  function formatSourceStatus(entry) {
4724
5052
  const parts = [`${entry.source} (${entry.targetLabel})`];
4725
5053
  if (entry.indexingStatus)
@@ -4782,6 +5110,17 @@ function renderUnifiedSearchStatusText(payload) {
4782
5110
  lines.push(buildHeader8(payload));
4783
5111
  if (!payload.completed && payload.progress) {
4784
5112
  lines.push(formatProgress(payload.progress));
5113
+ if (payload.progress.targets?.length) {
5114
+ lines.push("progress targets:");
5115
+ for (const target of payload.progress.targets) {
5116
+ lines.push(` - ${formatProgressTarget(target)}`);
5117
+ }
5118
+ }
5119
+ }
5120
+ if (!payload.completed && payload.warnings && payload.warnings.length > 0) {
5121
+ lines.push("warnings:");
5122
+ for (const warning2 of payload.warnings)
5123
+ lines.push(` - ${warning2}`);
4785
5124
  }
4786
5125
  const result = payload.result;
4787
5126
  if (result)
@@ -4848,14 +5187,39 @@ function formatSourceStatus2(entry) {
4848
5187
  return parts.join(SEP7);
4849
5188
  }
4850
5189
  function formatProgress(progress) {
4851
- return `progress: ${progress.status}, ${progress.targetsReady}/${progress.targetsTotal} targets ready, ${progress.elapsedMs}ms elapsed`;
5190
+ const next = progress.next ? `; next: ${progress.next}` : "";
5191
+ return `progress: ${progress.status}, ${progress.targetsReady}/${progress.targetsTotal} targets ready, ${progress.elapsedMs}ms elapsed${next}`;
4852
5192
  }
4853
5193
  function quote5(value) {
4854
5194
  return JSON.stringify(value);
4855
5195
  }
4856
5196
  // src/shared/unified-search-target.ts
4857
5197
  function parseUnifiedSearchTargetSpec(spec) {
4858
- return parseCodeNavigationTargetSpec(spec);
5198
+ const trimmed = spec.trim();
5199
+ if (trimmed.length === 0) {
5200
+ throw new InvalidArgumentError("Target spec cannot be empty.");
5201
+ }
5202
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
5203
+ return parseDiscoveryRepoTarget(trimmed);
5204
+ }
5205
+ const parsed = parsePackageSpec(trimmed);
5206
+ return {
5207
+ registry: toCodeNavigationRegistry(parsed.registry),
5208
+ packageName: parsed.name,
5209
+ version: parsed.version
5210
+ };
5211
+ }
5212
+ function parseDiscoveryRepoTarget(spec) {
5213
+ const hashIndex = spec.lastIndexOf("#");
5214
+ if (hashIndex === -1) {
5215
+ return { repoUrl: spec };
5216
+ }
5217
+ const repoUrl = spec.slice(0, hashIndex);
5218
+ const gitRef = spec.slice(hashIndex + 1);
5219
+ if (!repoUrl || !gitRef) {
5220
+ throw new InvalidArgumentError("Repository target must be a full URL with optional #gitRef suffix.");
5221
+ }
5222
+ return { repoUrl, gitRef };
4859
5223
  }
4860
5224
  // src/shared/list-files-request.ts
4861
5225
  var LIMIT_MIN2 = 1;
@@ -5613,7 +5977,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
5613
5977
  match in --verbose output; full payload in --json).`;
5614
5978
  function registerCodeGrepCommand(pkgCommand) {
5615
5979
  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) => {
5616
- const { createContainer: createContainer2 } = await import("./shared/chunk-rwcs5gyy.js");
5980
+ const { createContainer: createContainer2 } = await import("./shared/chunk-1h7sanjw.js");
5617
5981
  const deps = await createContainer2();
5618
5982
  await pkgGrepAction(arg1, arg2, arg3, options, {
5619
5983
  codeNavigationService: deps.codeNavigationService,
@@ -6089,6 +6453,10 @@ async function registerDocsCommandGroup(program, options = {}) {
6089
6453
  // src/commands/example.ts
6090
6454
  import { Option } from "commander";
6091
6455
  async function exampleAction(query, options, deps) {
6456
+ if (!deps.hasValidToken && options.json) {
6457
+ printExampleError("Authentication required. Run `githits login`, then retry this command.", "AUTH_REQUIRED", true);
6458
+ process.exit(1);
6459
+ }
6092
6460
  requireAuth(deps);
6093
6461
  try {
6094
6462
  const result = await deps.githitsService.search({
@@ -6105,10 +6473,21 @@ async function exampleAction(query, options, deps) {
6105
6473
  console.log(result);
6106
6474
  }
6107
6475
  } catch (error2) {
6108
- console.error(`Failed to get example: ${error2 instanceof Error ? error2.message : error2}`);
6476
+ if (error2 instanceof AuthenticationError) {
6477
+ printExampleError("Authentication required. Run `githits login`, then retry this command.", "AUTH_REQUIRED", options.json ?? false);
6478
+ process.exit(1);
6479
+ }
6480
+ printExampleError(`Failed to get example: ${error2 instanceof Error ? error2.message : error2}`, "UNKNOWN", options.json ?? false);
6109
6481
  process.exit(1);
6110
6482
  }
6111
6483
  }
6484
+ function printExampleError(error2, code, json) {
6485
+ if (json) {
6486
+ console.error(JSON.stringify({ error: error2, code, retryable: false }));
6487
+ return;
6488
+ }
6489
+ console.error(error2);
6490
+ }
6112
6491
  var EXAMPLE_DESCRIPTION = `Get verified, canonical code examples from global open source.
6113
6492
 
6114
6493
  For dependency, package, or repository source search, use \`githits search\` instead.
@@ -6132,7 +6511,7 @@ function registerExampleCommand(program) {
6132
6511
  });
6133
6512
  }
6134
6513
  async function loadContainer() {
6135
- const { createContainer: createContainer2 } = await import("./shared/chunk-rwcs5gyy.js");
6514
+ const { createContainer: createContainer2 } = await import("./shared/chunk-1h7sanjw.js");
6136
6515
  return createContainer2();
6137
6516
  }
6138
6517
  // src/commands/feedback.ts
@@ -6902,6 +7281,16 @@ class ExitPromptError extends Error {
6902
7281
  name = "ExitPromptError";
6903
7282
  }
6904
7283
  // src/commands/login.ts
7284
+ var stdoutLoginOutput = {
7285
+ write: (message) => {
7286
+ console.log(message);
7287
+ }
7288
+ };
7289
+ var stderrLoginOutput = {
7290
+ write: (message) => {
7291
+ console.error(message);
7292
+ }
7293
+ };
6905
7294
  var TIMEOUT_MS = 5 * 60 * 1000;
6906
7295
  function randomPort() {
6907
7296
  return Math.floor(Math.random() * 2000) + 8000;
@@ -6933,7 +7322,7 @@ async function preflightAuthPersistence(authStorage, mcpUrl) {
6933
7322
  };
6934
7323
  }
6935
7324
  }
6936
- async function loginFlow(options, deps) {
7325
+ async function loginFlow(options, deps, output = stdoutLoginOutput) {
6937
7326
  const { authService, authStorage, browserService, mcpUrl } = deps;
6938
7327
  if (options.port !== undefined && (Number.isNaN(options.port) || options.port < 1 || options.port > 65535)) {
6939
7328
  return {
@@ -6947,10 +7336,10 @@ async function loginFlow(options, deps) {
6947
7336
  if (!isExpired) {
6948
7337
  return { status: "already_authenticated", message: "Already logged in." };
6949
7338
  }
6950
- console.log(`Token expired. Starting new login...
7339
+ output.write(`Token expired. Starting new login...
6951
7340
  `);
6952
7341
  } else if (existing && options.force) {
6953
- console.log(`Re-authenticating (--force flag)...
7342
+ output.write(`Re-authenticating (--force flag)...
6954
7343
  `);
6955
7344
  }
6956
7345
  if (!existing) {
@@ -6959,7 +7348,7 @@ async function loginFlow(options, deps) {
6959
7348
  const persistenceError = await preflightAuthPersistence(authStorage, mcpUrl);
6960
7349
  if (persistenceError)
6961
7350
  return persistenceError;
6962
- console.log("Discovering OAuth endpoints...");
7351
+ output.write("Discovering OAuth endpoints...");
6963
7352
  const metadata = await authService.discoverEndpoints(mcpUrl);
6964
7353
  let client = await authStorage.loadClient(mcpUrl);
6965
7354
  let port;
@@ -6968,7 +7357,7 @@ async function loginFlow(options, deps) {
6968
7357
  if (options.port) {
6969
7358
  redirectUri = `http://127.0.0.1:${options.port}/callback`;
6970
7359
  if (redirectUri !== client.redirectUri) {
6971
- console.log("Registering CLI client with new port...");
7360
+ output.write("Registering CLI client with new port...");
6972
7361
  const registration = await authService.registerClient({
6973
7362
  registrationEndpoint: metadata.registrationEndpoint,
6974
7363
  redirectUri
@@ -6989,7 +7378,7 @@ async function loginFlow(options, deps) {
6989
7378
  } else {
6990
7379
  port = options.port ?? randomPort();
6991
7380
  redirectUri = `http://127.0.0.1:${port}/callback`;
6992
- console.log("Registering CLI client...");
7381
+ output.write("Registering CLI client...");
6993
7382
  const registration = await authService.registerClient({
6994
7383
  registrationEndpoint: metadata.registrationEndpoint,
6995
7384
  redirectUri
@@ -7009,17 +7398,33 @@ async function loginFlow(options, deps) {
7009
7398
  state,
7010
7399
  codeChallenge: challenge
7011
7400
  });
7012
- const serverPromise = authService.startCallbackServer(port, state);
7401
+ let callbackServer;
7402
+ try {
7403
+ callbackServer = await authService.startCallbackServer(port, state);
7404
+ } catch (error2) {
7405
+ const msg = error2 instanceof Error ? error2.message : String(error2);
7406
+ return { status: "failed", message: msg };
7407
+ }
7013
7408
  if (options.browser === false) {
7014
- console.log(`Open this URL in your browser:
7409
+ output.write(`Open this URL in your browser:
7015
7410
  `);
7016
- console.log(` ${authUrl}
7411
+ output.write(` ${authUrl}
7017
7412
  `);
7018
7413
  } else {
7019
- console.log("Opening browser...");
7020
- await browserService.open(authUrl);
7414
+ output.write("Opening browser...");
7415
+ try {
7416
+ await browserService.open(authUrl);
7417
+ } catch (error2) {
7418
+ const msg = error2 instanceof Error ? error2.message : String(error2);
7419
+ output.write(`Could not open browser automatically: ${msg}
7420
+ `);
7421
+ output.write(`Open this URL in your browser:
7422
+ `);
7423
+ output.write(` ${authUrl}
7424
+ `);
7425
+ }
7021
7426
  }
7022
- console.log(`Waiting for authentication...
7427
+ output.write(`Waiting for authentication...
7023
7428
  `);
7024
7429
  let timeoutId;
7025
7430
  const timeoutPromise = new Promise((_, reject) => {
@@ -7027,12 +7432,13 @@ async function loginFlow(options, deps) {
7027
7432
  });
7028
7433
  let callback;
7029
7434
  try {
7030
- callback = await Promise.race([serverPromise, timeoutPromise]);
7435
+ callback = await Promise.race([callbackServer.result, timeoutPromise]);
7031
7436
  if (timeoutId)
7032
7437
  clearTimeout(timeoutId);
7033
7438
  } catch (error2) {
7034
7439
  if (timeoutId)
7035
7440
  clearTimeout(timeoutId);
7441
+ await callbackServer.close().catch(() => {});
7036
7442
  const msg = error2 instanceof Error ? error2.message : "Authentication failed";
7037
7443
  return { status: "failed", message: `${msg}.` };
7038
7444
  }
@@ -7077,7 +7483,7 @@ async function loginFlow(options, deps) {
7077
7483
  };
7078
7484
  }
7079
7485
  async function loginAction(options, deps) {
7080
- const result = await loginFlow(options, deps);
7486
+ const result = await loginFlow(options, deps, stdoutLoginOutput);
7081
7487
  if (result.status === "already_authenticated") {
7082
7488
  console.log(`Already logged in.
7083
7489
  `);
@@ -7089,7 +7495,7 @@ async function loginAction(options, deps) {
7089
7495
  if (result.status === "failed") {
7090
7496
  console.error(`${result.message}
7091
7497
  `);
7092
- console.log("Run `githits login` to try again.");
7498
+ printLoginRecoveryHint(result.message);
7093
7499
  process.exit(1);
7094
7500
  }
7095
7501
  console.log(`Logged in successfully.
@@ -7099,6 +7505,16 @@ async function loginAction(options, deps) {
7099
7505
  console.log(`
7100
7506
  You're ready to use githits with your AI assistant.`);
7101
7507
  }
7508
+ function printLoginRecoveryHint(message) {
7509
+ console.log("Recovery steps:");
7510
+ console.log(" githits auth status");
7511
+ console.log(" githits login --force");
7512
+ if (message.includes("Cannot persist OAuth credentials")) {
7513
+ console.log("If your system keychain is locked or unavailable, unlock it and retry.");
7514
+ console.log("For CI/automation, set GITHITS_API_TOKEN.");
7515
+ console.log("As a last resort, set GITHITS_AUTH_STORAGE=file to use plaintext file storage.");
7516
+ }
7517
+ }
7102
7518
  var LOGIN_DESCRIPTION = `Authenticate with your GitHits account via browser.
7103
7519
 
7104
7520
  Opens your browser to complete authentication securely using OAuth.
@@ -7135,6 +7551,7 @@ async function verifyAgentConfigured(agent, fileSystemService, execService) {
7135
7551
  async function initAction(options, deps) {
7136
7552
  const useColors = shouldUseColors();
7137
7553
  const { fileSystemService, promptService, execService, createLoginDeps } = deps;
7554
+ let continuedWithoutAuth = false;
7138
7555
  console.log(`
7139
7556
  ${colorize("GitHits", "bold", useColors)} — Set up MCP server for your coding agents
7140
7557
  `);
@@ -7158,6 +7575,7 @@ async function initAction(options, deps) {
7158
7575
  } else {
7159
7576
  console.log(` ${warning(`Login failed: ${loginResult.message}`, useColors)}
7160
7577
  `);
7578
+ printAuthRecoveryHint();
7161
7579
  if (!options.yes) {
7162
7580
  try {
7163
7581
  const choice = await promptService.confirm3("Continue without authentication?");
@@ -7175,6 +7593,7 @@ async function initAction(options, deps) {
7175
7593
  throw err;
7176
7594
  }
7177
7595
  }
7596
+ continuedWithoutAuth = true;
7178
7597
  console.log(` Continuing without authentication...
7179
7598
  `);
7180
7599
  }
@@ -7198,6 +7617,11 @@ async function initAction(options, deps) {
7198
7617
  return;
7199
7618
  }
7200
7619
  if (scan.needsSetup.length === 0) {
7620
+ if (continuedWithoutAuth) {
7621
+ console.log(" MCP is already configured, but authentication is still required.");
7622
+ console.log(" Run `githits login` before using GitHits tools.\n");
7623
+ return;
7624
+ }
7201
7625
  console.log(` All detected agents are already configured. Nothing to do.
7202
7626
  `);
7203
7627
  return;
@@ -7270,6 +7694,9 @@ async function initAction(options, deps) {
7270
7694
  const skipped = outcomes.filter((o) => o.status === "skipped").length;
7271
7695
  if (failed > 0) {
7272
7696
  console.log(" Setup completed with errors.");
7697
+ } else if (continuedWithoutAuth && (configured > 0 || alreadyDone > 0)) {
7698
+ console.log(" MCP is configured, but authentication is still required.");
7699
+ console.log(" Run `githits login` before using GitHits tools.");
7273
7700
  } else if (configured > 0 || alreadyDone > 0) {
7274
7701
  console.log(" Done! GitHits is ready.");
7275
7702
  } else if (skipped > 0) {
@@ -7286,6 +7713,15 @@ async function initAction(options, deps) {
7286
7713
  }
7287
7714
  console.log();
7288
7715
  }
7716
+ function printAuthRecoveryHint() {
7717
+ console.log(" You can still configure MCP, but GitHits tools will require auth.");
7718
+ console.log(" Recovery steps:");
7719
+ console.log(" githits auth status");
7720
+ console.log(" githits login --force");
7721
+ console.log(" For CI or locked-down machines, set GITHITS_API_TOKEN.");
7722
+ console.log(` If your system keychain is unavailable, set GITHITS_AUTH_STORAGE=file after accepting plaintext storage.
7723
+ `);
7724
+ }
7289
7725
  var INIT_DESCRIPTION = `Set up GitHits MCP server for your coding agents.
7290
7726
 
7291
7727
  Authenticates with your GitHits account, then scans for available agents
@@ -7516,7 +7952,7 @@ var structuredCodeTargetSchema = z3.object({
7516
7952
  }).describe("Target: provide registry + package_name (package scope) or repo_url + git_ref (repo scope).");
7517
7953
  var codeTargetSchema = z3.union([
7518
7954
  structuredCodeTargetSchema,
7519
- z3.string().min(1).describe("Compact target string. Package: `npm:react@18.2.0`. Repository: `https://github.com/facebook/react#HEAD` (git ref suffix optional, defaults to HEAD).")
7955
+ z3.string().min(1).describe("Compact target string. Package: `npm:react@18.2.0`. Repository: `https://github.com/facebook/react#HEAD` (git ref suffix required for exact code navigation).")
7520
7956
  ]);
7521
7957
  function resolveCodeTarget(target) {
7522
7958
  if (typeof target === "string") {
@@ -7548,10 +7984,7 @@ function resolveCodeTarget(target) {
7548
7984
  if (!target.repo_url) {
7549
7985
  return invalidTargetResult("Incomplete repository target: repo_url is required.");
7550
7986
  }
7551
- return {
7552
- repoUrl: target.repo_url,
7553
- gitRef: target.git_ref ?? "HEAD"
7554
- };
7987
+ return invalidTargetResult("Incomplete repository target: git_ref is required for exact code navigation.");
7555
7988
  }
7556
7989
  return {
7557
7990
  repoUrl: target.repo_url,
@@ -8507,10 +8940,14 @@ function buildRange3(args, textMode) {
8507
8940
  }
8508
8941
  // src/tools/search.ts
8509
8942
  import { z as z13 } from "zod";
8943
+ var searchTargetSchema = z13.union([
8944
+ structuredCodeTargetSchema,
8945
+ z13.string().min(1).describe("Compact discovery target string. Package: `npm:react@18.2.0`. Repository: `https://github.com/facebook/react` for backend default branch or `https://github.com/facebook/react#HEAD` for an explicit ref.")
8946
+ ]);
8510
8947
  var schema12 = {
8511
8948
  query: z13.string().min(1).describe("Discovery query string. Supports implicit AND, uppercase OR, parentheses, unary -, quoted phrases, semantic qualifiers (kind:, category:, path:, lang:, name:, intent:), and routing qualifiers (registry:, package:, version:, repo:). Parsed once and compiled per source; it is not forwarded as a raw backend query."),
8512
- target: codeTargetSchema.optional(),
8513
- targets: z13.array(codeTargetSchema).max(20).optional(),
8949
+ target: searchTargetSchema.optional(),
8950
+ targets: z13.array(searchTargetSchema).max(20).optional(),
8514
8951
  sources: z13.array(z13.enum(["docs", "code", "symbol"])).optional().describe("Optional source selection. Omit for backend AUTO."),
8515
8952
  category: z13.enum(["callable", "type", "module", "data", "documentation"]).optional(),
8516
8953
  kind: z13.enum([
@@ -8572,17 +9009,17 @@ function createSearchTool(service) {
8572
9009
  annotations: { readOnlyHint: true },
8573
9010
  handler: async (args) => {
8574
9011
  try {
8575
- const resolvedTarget = args.target ? resolveCodeTarget(args.target) : undefined;
9012
+ const resolvedTarget = args.target ? resolveSearchTarget(args.target) : undefined;
8576
9013
  if (resolvedTarget && "content" in resolvedTarget)
8577
9014
  return resolvedTarget;
8578
- const resolvedTargets = args.targets?.map((entry) => resolveCodeTarget(entry));
9015
+ const resolvedTargets = args.targets?.map((entry) => resolveSearchTarget(entry));
8579
9016
  const resolvedTargetsError = resolvedTargets?.find((entry) => ("content" in entry));
8580
9017
  if (resolvedTargetsError) {
8581
9018
  return resolvedTargetsError;
8582
9019
  }
8583
9020
  const built = buildUnifiedSearchParams({
8584
9021
  target: resolvedTarget && !("content" in resolvedTarget) ? resolvedTarget : undefined,
8585
- targets: resolvedTargets?.filter(isResolvedCodeTarget),
9022
+ targets: resolvedTargets?.filter(isResolvedSearchTarget),
8586
9023
  query: args.query,
8587
9024
  sources: args.sources?.map((entry) => entry.toUpperCase()),
8588
9025
  kind: toSymbolKind(args.kind),
@@ -8613,9 +9050,53 @@ function createSearchTool(service) {
8613
9050
  }
8614
9051
  };
8615
9052
  }
8616
- function isResolvedCodeTarget(target) {
9053
+ function isResolvedSearchTarget(target) {
8617
9054
  return !("content" in target);
8618
9055
  }
9056
+ function resolveSearchTarget(target) {
9057
+ if (typeof target === "string") {
9058
+ try {
9059
+ return parseUnifiedSearchTargetSpec(target);
9060
+ } catch (error2) {
9061
+ const mapped = mapCodeNavigationError(error2);
9062
+ return errorResult(JSON.stringify({
9063
+ error: mapped.message,
9064
+ code: mapped.code,
9065
+ retryable: mapped.retryable ?? false,
9066
+ ...mapped.details ? { details: mapped.details } : {}
9067
+ }));
9068
+ }
9069
+ }
9070
+ const hasPackageTarget = Boolean(target.registry || target.package_name);
9071
+ const hasRepoTarget = Boolean(target.repo_url || target.git_ref);
9072
+ if (hasPackageTarget && hasRepoTarget) {
9073
+ return invalidSearchTargetResult("Invalid target: provide either registry + package_name or repo_url + git_ref, not both.");
9074
+ }
9075
+ if (!hasPackageTarget && !hasRepoTarget) {
9076
+ return invalidSearchTargetResult("Missing target: provide registry + package_name or repo_url.");
9077
+ }
9078
+ if (hasPackageTarget) {
9079
+ if (!target.registry || !target.package_name) {
9080
+ return invalidSearchTargetResult("Incomplete package target: both registry and package_name are required.");
9081
+ }
9082
+ return {
9083
+ registry: toCodeNavigationRegistry(target.registry),
9084
+ packageName: target.package_name,
9085
+ version: target.version
9086
+ };
9087
+ }
9088
+ if (!target.repo_url) {
9089
+ return invalidSearchTargetResult("Incomplete repository target: repo_url is required.");
9090
+ }
9091
+ return { repoUrl: target.repo_url, gitRef: target.git_ref };
9092
+ }
9093
+ function invalidSearchTargetResult(message) {
9094
+ return errorResult(JSON.stringify({
9095
+ error: message,
9096
+ code: "INVALID_ARGUMENT",
9097
+ retryable: false
9098
+ }));
9099
+ }
8619
9100
  function isTextFormat11(format) {
8620
9101
  return format === undefined || format === "text" || format === "text-v1";
8621
9102
  }
@@ -9375,7 +9856,7 @@ function requireSearchService(deps) {
9375
9856
  return deps.codeNavigationService;
9376
9857
  }
9377
9858
  async function loadContainer2() {
9378
- const { createContainer: createContainer2 } = await import("./shared/chunk-rwcs5gyy.js");
9859
+ const { createContainer: createContainer2 } = await import("./shared/chunk-1h7sanjw.js");
9379
9860
  return createContainer2();
9380
9861
  }
9381
9862
  function parseTargetSpecs(specs) {
@@ -9454,8 +9935,9 @@ function formatSearchErrorTerminal(payload, context) {
9454
9935
  function formatUnifiedSearchTerminal(payload) {
9455
9936
  const lines = [];
9456
9937
  const useColors = shouldUseColors();
9457
- if (payload.query.warnings && payload.query.warnings.length > 0) {
9458
- for (const warning2 of payload.query.warnings) {
9938
+ const warnings = payload.warnings ?? payload.query.warnings;
9939
+ if (warnings && warnings.length > 0) {
9940
+ for (const warning2 of warnings) {
9459
9941
  lines.push(`Warning: ${warning2}`);
9460
9942
  }
9461
9943
  lines.push("");
@@ -9494,7 +9976,7 @@ function formatUnifiedSearchTerminal(payload) {
9494
9976
  lines.push("");
9495
9977
  for (const entry of display) {
9496
9978
  const location = formatUnifiedSearchLocation(entry.locator);
9497
- const header = formatUnifiedSearchHeader(entry, useColors, location);
9979
+ const header = formatUnifiedSearchHeader(entry, useColors, location, payload.query.raw);
9498
9980
  lines.push(header);
9499
9981
  const metadata = formatUnifiedSearchMetadata(entry, useColors);
9500
9982
  if (metadata.length > 0) {
@@ -9561,7 +10043,11 @@ function formatSearchStatusCompletedTerminal(payload) {
9561
10043
  results: payload.result.results,
9562
10044
  searchRef: payload.searchRef,
9563
10045
  progress: undefined,
9564
- query: { warnings: payload.result.warnings },
10046
+ query: {
10047
+ raw: payload.result.query?.raw,
10048
+ warnings: payload.result.warnings
10049
+ },
10050
+ warnings: payload.result.warnings,
9565
10051
  sourceStatus: payload.result.sourceStatus
9566
10052
  });
9567
10053
  }
@@ -9573,7 +10059,11 @@ function formatSearchStatusPartialTerminal(payload) {
9573
10059
  results: payload.result.results,
9574
10060
  searchRef: payload.searchRef,
9575
10061
  progress: payload.progress,
9576
- query: { warnings: payload.result.warnings },
10062
+ query: {
10063
+ raw: payload.result.query?.raw,
10064
+ warnings: payload.result.warnings
10065
+ },
10066
+ warnings: payload.result.warnings,
9577
10067
  sourceStatus: payload.result.sourceStatus
9578
10068
  });
9579
10069
  }
@@ -9684,11 +10174,113 @@ function formatUnifiedSearchLocation(locator) {
9684
10174
  }
9685
10175
  return `${locator.filePath}:${locator.startLine}${locator.endLine && locator.endLine !== locator.startLine ? `-${locator.endLine}` : ""}`;
9686
10176
  }
9687
- function formatUnifiedSearchHeader(entry, useColors, location) {
9688
- const primary = entry.type === "documentation_page" ? entry.target : location ? `${entry.target} ${location}` : entry.target;
10177
+ function formatUnifiedSearchHeader(entry, useColors, location, rawQuery) {
10178
+ const primary = formatUnifiedSearchPrimary(entry.type, entry.target, location, rawQuery, useColors);
9689
10179
  const badge = `[${formatUnifiedSearchResultLabel(entry.type)}]`;
9690
10180
  const title = entry.title ? highlightRanges(entry.title, entry.highlights?.title, useColors) : undefined;
9691
- return `${highlight(primary, useColors)} ${dim(badge, useColors)}${title ? ` - ${title}` : ""}`;
10181
+ return `${primary} ${dim(badge, useColors)}${title ? ` - ${title}` : ""}`;
10182
+ }
10183
+ function formatUnifiedSearchPrimary(type, target, location, rawQuery, useColors) {
10184
+ const formattedTarget = highlight(target, useColors);
10185
+ if (type === "documentation_page" || !location) {
10186
+ return formattedTarget;
10187
+ }
10188
+ return `${formattedTarget} ${formatLocationWithQueryHighlights(location, rawQuery, useColors)}`;
10189
+ }
10190
+ function formatLocationWithQueryHighlights(location, rawQuery, useColors) {
10191
+ const ranges = buildQueryTermRanges(location, rawQuery);
10192
+ if (ranges.length === 0)
10193
+ return highlight(location, useColors);
10194
+ if (!useColors)
10195
+ return location;
10196
+ let result = "";
10197
+ let cursor2 = 0;
10198
+ for (const [start, end] of ranges) {
10199
+ if (cursor2 < start)
10200
+ result += highlight(location.slice(cursor2, start), true);
10201
+ result += highlightMatch(location.slice(start, end), true);
10202
+ cursor2 = end;
10203
+ }
10204
+ if (cursor2 < location.length)
10205
+ result += highlight(location.slice(cursor2), true);
10206
+ return result;
10207
+ }
10208
+ function buildQueryTermRanges(text, rawQuery) {
10209
+ const terms = extractQueryHighlightTerms(rawQuery);
10210
+ if (terms.length === 0)
10211
+ return [];
10212
+ const lowerText = text.toLowerCase();
10213
+ const ranges = [];
10214
+ const orderedTerms = [...terms].sort((left, right) => right.length - left.length);
10215
+ for (const term of orderedTerms) {
10216
+ const lowerTerm = term.toLowerCase();
10217
+ let cursor2 = 0;
10218
+ while (cursor2 < lowerText.length) {
10219
+ const start = lowerText.indexOf(lowerTerm, cursor2);
10220
+ if (start === -1)
10221
+ break;
10222
+ const end = start + lowerTerm.length;
10223
+ if (!ranges.some((range) => rangesOverlap(range, [start, end]))) {
10224
+ ranges.push([start, end]);
10225
+ }
10226
+ cursor2 = end;
10227
+ }
10228
+ }
10229
+ return mergeRanges2(ranges);
10230
+ }
10231
+ function extractQueryHighlightTerms(rawQuery) {
10232
+ if (!rawQuery)
10233
+ return [];
10234
+ const booleanOperators = new Set(["AND", "OR", "NOT"]);
10235
+ const terms = new Set;
10236
+ const quotedRanges = [];
10237
+ for (const match of rawQuery.matchAll(/"([^"]+)"/g)) {
10238
+ const phrase = match[1];
10239
+ if (phrase) {
10240
+ addQueryHighlightTerm(phrase, terms, booleanOperators, {
10241
+ stripQualifier: false
10242
+ });
10243
+ }
10244
+ if (typeof match.index === "number") {
10245
+ quotedRanges.push([match.index, match.index + match[0].length]);
10246
+ }
10247
+ }
10248
+ for (const match of rawQuery.matchAll(/[A-Za-z0-9_./@:-]+/g)) {
10249
+ const index = match.index ?? 0;
10250
+ if (quotedRanges.some(([start, end]) => index >= start && index < end)) {
10251
+ continue;
10252
+ }
10253
+ addQueryHighlightTerm(match[0], terms, booleanOperators);
10254
+ }
10255
+ return Array.from(terms);
10256
+ }
10257
+ function addQueryHighlightTerm(candidate, terms, booleanOperators, options = { stripQualifier: true }) {
10258
+ const normalised = options.stripQualifier && /^[A-Za-z]+:.+/.test(candidate) ? candidate.split(":").slice(1).join(":") : candidate;
10259
+ const term = normalised.replace(/^[-+]+/, "").replace(/[-+]+$/, "");
10260
+ if (term.length < 2)
10261
+ return;
10262
+ if (booleanOperators.has(term.toUpperCase()))
10263
+ return;
10264
+ terms.add(term);
10265
+ }
10266
+ function rangesOverlap(left, right) {
10267
+ return left[0] < right[1] && right[0] < left[1];
10268
+ }
10269
+ function mergeRanges2(ranges) {
10270
+ const sorted = ranges.filter(([start, end]) => end > start).sort((left, right) => left[0] - right[0] || left[1] - right[1]);
10271
+ const merged = [];
10272
+ for (const current of sorted) {
10273
+ const previous = merged[merged.length - 1];
10274
+ if (!previous || current[0] > previous[1]) {
10275
+ merged.push(current);
10276
+ continue;
10277
+ }
10278
+ merged[merged.length - 1] = [
10279
+ previous[0],
10280
+ Math.max(previous[1], current[1])
10281
+ ];
10282
+ }
10283
+ return merged;
9692
10284
  }
9693
10285
  function formatUnifiedSearchMetadata(entry, useColors) {
9694
10286
  if (entry.type !== "documentation_page" && entry.type !== "repository_doc") {
@@ -9696,16 +10288,14 @@ function formatUnifiedSearchMetadata(entry, useColors) {
9696
10288
  }
9697
10289
  const lines = [];
9698
10290
  if (entry.locator.pageId) {
9699
- lines.push(` ${dim("pageId:", useColors)} ${entry.locator.pageId}`);
10291
+ if (entry.type === "documentation_page") {
10292
+ lines.push(` ${dim("pageId:", useColors)} ${entry.locator.pageId}`);
10293
+ }
9700
10294
  }
9701
10295
  const sourceBadge = entry.locator.sourceKind?.toLowerCase() === "repository" ? "[repo]" : entry.locator.sourceKind?.toLowerCase() === "crawled" ? "[crawled]" : undefined;
9702
- if (entry.locator.sourceUrl) {
10296
+ if (entry.locator.sourceUrl && entry.type === "documentation_page") {
9703
10297
  lines.push(` ${dim("source:", useColors)} ${sourceBadge ? `${sourceBadge} ` : ""}${entry.locator.sourceUrl}`);
9704
10298
  }
9705
- if (entry.type === "repository_doc" && entry.locator.filePath) {
9706
- const ref = entry.locator.requestedRef ?? entry.locator.gitRef;
9707
- lines.push(` ${dim("file:", useColors)} ${entry.locator.filePath}${ref ? ` @ ${ref}` : ""}`);
9708
- }
9709
10299
  return lines;
9710
10300
  }
9711
10301
  // src/cli.ts
@@ -9741,12 +10331,14 @@ if (isTelemetryEnabled()) {
9741
10331
  flushTelemetry(exitCode);
9742
10332
  });
9743
10333
  }
9744
- program.name("githits").description("Code examples from global open source for your AI assistant").version(version).option("--no-color", "Disable colored output").hook("preAction", (thisCommand, actionCommand) => {
9745
- if (thisCommand.opts().color === false) {
9746
- process.env.NO_COLOR = "1";
9747
- }
10334
+ var rootCliPreAction = createRootCliPreAction({
10335
+ createContainer,
10336
+ loginFlow: (options, deps) => loginFlow(options, deps, stderrLoginOutput)
10337
+ });
10338
+ 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) => {
9748
10339
  const command = actionCommand ?? thisCommand;
9749
10340
  commandSpans.set(command, startTelemetrySpan(getTelemetryCommandName(command)));
10341
+ await rootCliPreAction(thisCommand, actionCommand);
9750
10342
  }).hook("postAction", (_thisCommand, actionCommand) => {
9751
10343
  endTelemetrySpan(commandSpans.get(actionCommand));
9752
10344
  }).addHelpText("after", `