githits 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  flushTelemetry,
33
33
  getCodeNavigationCapability,
34
34
  getCodeNavigationUrl,
35
+ getEnvApiToken,
35
36
  isCodeNavigationCliOverrideEnabled,
36
37
  isTelemetryEnabled,
37
38
  refreshExpiredToken,
@@ -40,11 +41,11 @@ import {
40
41
  setMcpClientVersionProvider,
41
42
  startTelemetrySpan,
42
43
  withTelemetrySpan
43
- } from "./shared/chunk-1t81jdax.js";
44
+ } from "./shared/chunk-ns1j9dan.js";
44
45
  import {
45
46
  __require,
46
47
  version
47
- } from "./shared/chunk-fa4571ch.js";
48
+ } from "./shared/chunk-g6ay6x9v.js";
48
49
 
49
50
  // src/cli.ts
50
51
  import { Command } from "commander";
@@ -80,7 +81,6 @@ async function authStatusAction(deps) {
80
81
  console.log(`Authenticated via environment variable.
81
82
  `);
82
83
  console.log(` Source: GITHITS_API_TOKEN`);
83
- console.log(` Token: ${envApiToken.slice(0, 8)}...`);
84
84
  displayCodeNavigationStatus(getCodeNavigationCapability(envApiToken), codeNavigationCliOverrideEnabled);
85
85
  return;
86
86
  }
@@ -138,10 +138,27 @@ function registerAuthStatusCommand(program) {
138
138
  await authStatusAction(deps);
139
139
  });
140
140
  }
141
+ // src/commands/gated-command-group.ts
142
+ async function resolveGatedCommandGroupRegistrationState(options = {}) {
143
+ const codeNavigationUrl = options.codeNavigationUrl ?? getCodeNavigationUrl();
144
+ if (!codeNavigationUrl) {
145
+ return { codeNavigationUrl: undefined, shouldRegister: false };
146
+ }
147
+ const overrideEnabled = options.overrideEnabled ?? isCodeNavigationCliOverrideEnabled();
148
+ const registrationState = options.capability !== undefined || options.expiredStoredAuth !== undefined ? {
149
+ capability: options.capability ?? "unknown",
150
+ expiredStoredAuth: options.expiredStoredAuth ?? false
151
+ } : await resolveStartupCodeNavigationRegistrationState();
152
+ const envTokenPresent = options.envTokenPresent ?? Boolean(getEnvApiToken());
153
+ return {
154
+ codeNavigationUrl,
155
+ shouldRegister: overrideEnabled || registrationState.capability === "enabled" || envTokenPresent || registrationState.expiredStoredAuth
156
+ };
157
+ }
158
+
141
159
  // src/shared/code-navigation-defaults.ts
142
160
  var DEFAULT_WAIT_TIMEOUT_MS = 20000;
143
161
  var MAX_WAIT_TIMEOUT_MS = 60000;
144
- var SEARCH_SYMBOLS_DEFAULT_FILE_INTENT = "PRODUCTION";
145
162
  var FILE_INTENT_ALL = Symbol("FILE_INTENT_ALL");
146
163
 
147
164
  // src/shared/colors.ts
@@ -299,17 +316,10 @@ var fileIntentMap = {
299
316
  build: "BUILD",
300
317
  vendor: "VENDOR"
301
318
  };
302
- var matchModeMap = {
303
- or: "OR",
304
- and: "AND"
305
- };
306
319
  function toCodeNavigationRegistry(registry) {
307
320
  return toPkgseerRegistry(registry);
308
321
  }
309
- function toSearchSymbolsMatchMode(mode) {
310
- return mode ? matchModeMap[mode] : undefined;
311
- }
312
- function toSearchSymbolsKind(kind) {
322
+ function toSymbolKind(kind) {
313
323
  return kind ? symbolKindMap[kind] : undefined;
314
324
  }
315
325
  function toSymbolCategory(category) {
@@ -321,7 +331,7 @@ function knownSymbolKindList() {
321
331
  function knownSymbolCategoryList() {
322
332
  return Object.keys(symbolCategoryMap);
323
333
  }
324
- function toSearchSymbolsFileIntent(intent) {
334
+ function toFileIntent(intent) {
325
335
  return intent ? fileIntentMap[intent] : undefined;
326
336
  }
327
337
  // src/shared/code-navigation-error-map.ts
@@ -478,6 +488,23 @@ function isInvalidArgumentError(error2) {
478
488
  return false;
479
489
  return error2.name.startsWith("Invalid") || error2.name.startsWith("Unsupported");
480
490
  }
491
+ // src/shared/docs-follow-up.ts
492
+ function lowerDocSourceKind(value) {
493
+ switch (value) {
494
+ case "CRAWLED":
495
+ return "crawled";
496
+ case "REPOSITORY":
497
+ return "repo";
498
+ default:
499
+ return;
500
+ }
501
+ }
502
+ // src/shared/extract-solution-id.ts
503
+ var SOLUTION_URL_RE = /solutions\/([0-9a-fA-F-]{36})/;
504
+ function extractSolutionId(markdown) {
505
+ const match = SOLUTION_URL_RE.exec(markdown);
506
+ return match?.[1];
507
+ }
481
508
  // src/shared/package-spec.ts
482
509
  var KNOWN_REGISTRIES = [
483
510
  "npm",
@@ -729,19 +756,27 @@ function normalizeWaitTimeoutMs(value) {
729
756
  function buildGrepRepoSuccessPayload(result, options) {
730
757
  const envelope = {
731
758
  pattern: options.pattern,
732
- patternType: options.patternType,
733
- caseSensitive: options.caseSensitive,
734
759
  matches: result.matches.map(projectMatch),
735
760
  hasMore: result.hasMore,
736
- truncatedReason: result.truncatedReason.toLowerCase(),
737
- routeTaken: result.routeTaken?.toLowerCase(),
738
761
  filesScanned: result.filesScanned,
739
762
  filesInScope: result.filesInScope,
740
- binaryFilesSkipped: result.binaryFilesSkipped,
741
- filesTooLargeSkipped: result.filesTooLargeSkipped,
742
763
  totalMatches: result.totalMatches,
743
764
  uniqueFilesMatched: result.uniqueFilesMatched
744
765
  };
766
+ if (options.patternType !== "literal") {
767
+ envelope.patternType = options.patternType;
768
+ }
769
+ if (options.caseSensitive)
770
+ envelope.caseSensitive = true;
771
+ if (result.binaryFilesSkipped > 0) {
772
+ envelope.binaryFilesSkipped = result.binaryFilesSkipped;
773
+ }
774
+ if (result.filesTooLargeSkipped > 0) {
775
+ envelope.filesTooLargeSkipped = result.filesTooLargeSkipped;
776
+ }
777
+ if (result.truncatedReason && result.truncatedReason !== "NONE") {
778
+ envelope.truncatedReason = result.truncatedReason.toLowerCase();
779
+ }
745
780
  if (options.registry)
746
781
  envelope.registry = options.registry;
747
782
  if (options.name)
@@ -763,19 +798,26 @@ function buildGrepRepoSuccessPayload(result, options) {
763
798
  return envelope;
764
799
  }
765
800
  function projectMatch(match) {
766
- return {
801
+ const projected = {
767
802
  filePath: match.filePath,
768
803
  line: match.line,
769
804
  matchStartByte: match.matchStartByte,
770
805
  matchEndByte: match.matchEndByte,
771
- lineContent: match.lineContent,
772
- contextBefore: match.contextBefore,
773
- contextAfter: match.contextAfter,
774
- fileContentHash: match.fileContentHash,
775
- fileIntent: match.fileIntent,
776
- symbolRowId: match.symbolRowId,
777
- symbol: match.symbol
806
+ lineContent: match.lineContent
778
807
  };
808
+ if (match.contextBefore && match.contextBefore.length > 0) {
809
+ projected.contextBefore = match.contextBefore;
810
+ }
811
+ if (match.contextAfter && match.contextAfter.length > 0) {
812
+ projected.contextAfter = match.contextAfter;
813
+ }
814
+ if (match.fileContentHash)
815
+ projected.fileContentHash = match.fileContentHash;
816
+ if (match.fileIntent)
817
+ projected.fileIntent = match.fileIntent;
818
+ if (match.symbol)
819
+ projected.symbol = match.symbol;
820
+ return projected;
779
821
  }
780
822
  function projectResolution(resolution) {
781
823
  if (!resolution)
@@ -1110,39 +1152,191 @@ function filterLanguages(languages, query, limit = DEFAULT_LIMIT) {
1110
1152
  const lowerQuery = query.toLowerCase();
1111
1153
  return languages.filter((lang) => lang.name.toLowerCase().includes(lowerQuery) || lang.display_name.toLowerCase().includes(lowerQuery) || lang.aliases.some((a) => a.toLowerCase().includes(lowerQuery))).slice(0, limit).map(({ name, display_name }) => ({ name, display_name }));
1112
1154
  }
1113
- // src/shared/normalise-keywords.ts
1114
- var MAX_KEYWORDS = 20;
1115
-
1116
- class InvalidKeywordsError extends Error {
1117
- constructor(message) {
1118
- super(message);
1119
- this.name = "InvalidKeywordsError";
1155
+ // src/shared/list-package-docs-request.ts
1156
+ function buildListPackageDocsParams(input) {
1157
+ const packageName = input.packageName?.trim() ?? "";
1158
+ if (!packageName) {
1159
+ throw new InvalidPackageSpecError("Package name is required.");
1120
1160
  }
1161
+ const registry = input.registry?.trim().toLowerCase() ?? "";
1162
+ if (!isKnownPkgseerRegistryArg(registry)) {
1163
+ throw new UnsupportedRegistryError(`Unsupported registry '${input.registry}'. Supported: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, packagist.`);
1164
+ }
1165
+ const params = {
1166
+ registry: toPkgseerRegistry(registry),
1167
+ packageName
1168
+ };
1169
+ const version2 = input.version?.trim();
1170
+ if (version2)
1171
+ params.version = version2;
1172
+ const after = input.after?.trim();
1173
+ if (after)
1174
+ params.after = after;
1175
+ if (input.limit !== undefined) {
1176
+ if (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 500) {
1177
+ throw new InvalidPackageSpecError("Limit must be an integer between 1 and 500.");
1178
+ }
1179
+ params.limit = input.limit;
1180
+ }
1181
+ return {
1182
+ params,
1183
+ limitExplicit: input.limit !== undefined,
1184
+ afterExplicit: Boolean(after)
1185
+ };
1121
1186
  }
1122
- function normaliseKeywords(rawComma, rawRepeated) {
1123
- const seen = new Set;
1124
- const out = [];
1125
- const add = (value) => {
1126
- const trimmed = value.trim();
1127
- if (trimmed === "")
1128
- return;
1129
- if (seen.has(trimmed))
1130
- return;
1131
- seen.add(trimmed);
1132
- out.push(trimmed);
1187
+ // src/shared/format-date.ts
1188
+ function toIsoDate(iso) {
1189
+ if (!iso)
1190
+ return null;
1191
+ const parsed = new Date(iso);
1192
+ if (Number.isNaN(parsed.getTime()))
1193
+ return null;
1194
+ return parsed.toISOString().slice(0, 10);
1195
+ }
1196
+ var MINUTE = 60;
1197
+ var HOUR = 60 * MINUTE;
1198
+ var DAY = 24 * HOUR;
1199
+ var MONTH = 30 * DAY;
1200
+ var YEAR = 365 * DAY;
1201
+ function toRelativeDate(iso, now = new Date) {
1202
+ if (!iso)
1203
+ return null;
1204
+ const parsed = new Date(iso);
1205
+ if (Number.isNaN(parsed.getTime()))
1206
+ return null;
1207
+ const deltaSeconds = Math.floor((now.getTime() - parsed.getTime()) / 1000);
1208
+ if (deltaSeconds < 0) {
1209
+ return toIsoDate(iso);
1210
+ }
1211
+ if (deltaSeconds < MINUTE)
1212
+ return "just now";
1213
+ if (deltaSeconds < HOUR)
1214
+ return formatUnit(deltaSeconds, MINUTE, "minute");
1215
+ if (deltaSeconds < DAY)
1216
+ return formatUnit(deltaSeconds, HOUR, "hour");
1217
+ if (deltaSeconds < MONTH)
1218
+ return formatUnit(deltaSeconds, DAY, "day");
1219
+ if (deltaSeconds < YEAR)
1220
+ return formatUnit(deltaSeconds, MONTH, "month");
1221
+ return formatUnit(deltaSeconds, YEAR, "year");
1222
+ }
1223
+ function formatUnit(deltaSeconds, unit, label) {
1224
+ const n = Math.floor(deltaSeconds / unit);
1225
+ return `${n} ${label}${n === 1 ? "" : "s"} ago`;
1226
+ }
1227
+
1228
+ // src/shared/list-package-docs-response.ts
1229
+ function buildListPackageDocsSuccessPayload(result, options) {
1230
+ const envelope = {
1231
+ hasMore: result.pageInfo?.hasNextPage ?? false,
1232
+ pages: result.pages.map((page) => {
1233
+ assertDocListEntry(page);
1234
+ const pageId = page.id;
1235
+ const lastUpdatedAt = toIsoDate(page.lastUpdatedAt);
1236
+ const entry = { pageId };
1237
+ if (page.title)
1238
+ entry.title = page.title;
1239
+ const sourceKind = lowerDocSourceKind(page.sourceKind);
1240
+ if (sourceKind)
1241
+ entry.sourceKind = sourceKind;
1242
+ if (page.sourceUrl)
1243
+ entry.sourceUrl = page.sourceUrl;
1244
+ if (page.repoUrl)
1245
+ entry.repoUrl = page.repoUrl;
1246
+ if (page.gitRef)
1247
+ entry.gitRef = page.gitRef;
1248
+ if (page.requestedRef)
1249
+ entry.requestedRef = page.requestedRef;
1250
+ if (page.filePath)
1251
+ entry.filePath = page.filePath;
1252
+ if (page.linkName)
1253
+ entry.linkName = page.linkName;
1254
+ if (lastUpdatedAt)
1255
+ entry.lastUpdatedAt = lastUpdatedAt;
1256
+ return entry;
1257
+ })
1133
1258
  };
1134
- if (rawComma !== undefined && rawComma !== "") {
1135
- for (const piece of rawComma.split(","))
1136
- add(piece);
1259
+ if (result.registry)
1260
+ envelope.registry = result.registry.toLowerCase();
1261
+ if (result.packageName)
1262
+ envelope.name = result.packageName;
1263
+ if (result.version)
1264
+ envelope.version = result.version;
1265
+ if (typeof result.stale === "boolean")
1266
+ envelope.stale = result.stale;
1267
+ if (result.pageInfo?.totalCount !== undefined)
1268
+ envelope.total = result.pageInfo.totalCount;
1269
+ if (result.pageInfo?.endCursor)
1270
+ envelope.nextCursor = result.pageInfo.endCursor;
1271
+ const filter = {};
1272
+ if (options.limitExplicit && options.limit !== undefined)
1273
+ filter.limit = options.limit;
1274
+ if (options.afterExplicit && options.after)
1275
+ filter.after = options.after;
1276
+ if (Object.keys(filter).length > 0)
1277
+ envelope.filter = filter;
1278
+ return envelope;
1279
+ }
1280
+ function assertDocListEntry(page) {
1281
+ if (!page.id) {
1282
+ throw new MalformedPackageIntelligenceResponseError("Documentation page list entry missing required id.");
1283
+ }
1284
+ if (page.sourceKind === "REPOSITORY" && (!page.repoUrl || !page.gitRef || !page.filePath)) {
1285
+ throw new MalformedPackageIntelligenceResponseError("Repository-backed documentation list entry missing repo locator fields.");
1286
+ }
1287
+ }
1288
+ function formatListPackageDocsTerminal(envelope, options) {
1289
+ const lines = [];
1290
+ lines.push(buildSummaryHeader(envelope, options.useColors));
1291
+ lines.push("");
1292
+ if (envelope.pages.length === 0) {
1293
+ lines.push(dim("No documentation pages found.", options.useColors));
1294
+ lines.push("");
1295
+ return lines.join(`
1296
+ `);
1137
1297
  }
1138
- if (rawRepeated !== undefined) {
1139
- for (const piece of rawRepeated)
1140
- add(piece);
1298
+ for (const page of envelope.pages) {
1299
+ lines.push(formatPageHeader(page, options.useColors));
1300
+ const meta = formatPageMeta(page, options.useColors, options.verbose ?? false);
1301
+ if (meta.length > 0)
1302
+ lines.push(...meta);
1303
+ lines.push("");
1141
1304
  }
1142
- if (out.length > MAX_KEYWORDS) {
1143
- throw new InvalidKeywordsError(`Too many keywords: got ${out.length}, maximum is ${MAX_KEYWORDS}.`);
1305
+ if (envelope.nextCursor) {
1306
+ lines.push(dim(`Next cursor: ${envelope.nextCursor}`, options.useColors));
1144
1307
  }
1145
- return out;
1308
+ if (envelope.stale) {
1309
+ lines.push(dim("Documentation may be stale.", options.useColors));
1310
+ }
1311
+ if (envelope.nextCursor || envelope.stale)
1312
+ lines.push("");
1313
+ return lines.join(`
1314
+ `);
1315
+ }
1316
+ function buildSummaryHeader(envelope, useColors) {
1317
+ const target = envelope.registry && envelope.name ? `${envelope.registry}:${envelope.name}${envelope.version ? `@${envelope.version}` : ""}` : "package docs";
1318
+ const summary = `${target} · ${envelope.pages.length} page${envelope.pages.length === 1 ? "" : "s"}`;
1319
+ const suffix = envelope.total !== undefined ? ` of ${envelope.total}` : "";
1320
+ return `${colorize(summary, "bold", useColors)}${dim(suffix, useColors)}`;
1321
+ }
1322
+ function formatPageHeader(page, useColors) {
1323
+ const badge = page.sourceKind === "repo" ? "[repo]" : "[crawled]";
1324
+ const title = page.title ?? page.pageId;
1325
+ return `${colorize(page.pageId, "bold", useColors)} ${dim(badge, useColors)} - ${title}`;
1326
+ }
1327
+ function formatPageMeta(page, useColors, verbose) {
1328
+ const lines = [];
1329
+ if (page.sourceUrl) {
1330
+ lines.push(` ${dim("source:", useColors)} ${page.sourceUrl}`);
1331
+ }
1332
+ if (page.filePath) {
1333
+ const ref = page.requestedRef ?? page.gitRef;
1334
+ lines.push(` ${dim("file:", useColors)} ${page.filePath}${ref ? ` @ ${ref}` : ""}`);
1335
+ }
1336
+ if (verbose && page.lastUpdatedAt) {
1337
+ lines.push(` ${dim("updated:", useColors)} ${page.lastUpdatedAt}`);
1338
+ }
1339
+ return lines;
1146
1340
  }
1147
1341
  // src/shared/package-dependencies-request.ts
1148
1342
  class UnsupportedDependenciesRegistryError extends Error {
@@ -1969,47 +2163,6 @@ function buildPackageSummaryParams(input) {
1969
2163
  }
1970
2164
  };
1971
2165
  }
1972
- // src/shared/format-date.ts
1973
- function toIsoDate(iso) {
1974
- if (!iso)
1975
- return null;
1976
- const parsed = new Date(iso);
1977
- if (Number.isNaN(parsed.getTime()))
1978
- return null;
1979
- return parsed.toISOString().slice(0, 10);
1980
- }
1981
- var MINUTE = 60;
1982
- var HOUR = 60 * MINUTE;
1983
- var DAY = 24 * HOUR;
1984
- var MONTH = 30 * DAY;
1985
- var YEAR = 365 * DAY;
1986
- function toRelativeDate(iso, now = new Date) {
1987
- if (!iso)
1988
- return null;
1989
- const parsed = new Date(iso);
1990
- if (Number.isNaN(parsed.getTime()))
1991
- return null;
1992
- const deltaSeconds = Math.floor((now.getTime() - parsed.getTime()) / 1000);
1993
- if (deltaSeconds < 0) {
1994
- return toIsoDate(iso);
1995
- }
1996
- if (deltaSeconds < MINUTE)
1997
- return "just now";
1998
- if (deltaSeconds < HOUR)
1999
- return formatUnit(deltaSeconds, MINUTE, "minute");
2000
- if (deltaSeconds < DAY)
2001
- return formatUnit(deltaSeconds, HOUR, "hour");
2002
- if (deltaSeconds < MONTH)
2003
- return formatUnit(deltaSeconds, DAY, "day");
2004
- if (deltaSeconds < YEAR)
2005
- return formatUnit(deltaSeconds, MONTH, "month");
2006
- return formatUnit(deltaSeconds, YEAR, "year");
2007
- }
2008
- function formatUnit(deltaSeconds, unit, label) {
2009
- const n = Math.floor(deltaSeconds / unit);
2010
- return `${n} ${label}${n === 1 ? "" : "s"} ago`;
2011
- }
2012
-
2013
2166
  // src/shared/format-number.ts
2014
2167
  function formatCompactNumber(n) {
2015
2168
  if (!Number.isFinite(n)) {
@@ -2836,6 +2989,157 @@ function formatUpgradeFooter(paths) {
2836
2989
  return `Upgrade to ${paths[0]}.`;
2837
2990
  return `Upgrade options: ${paths.join(", ")}.`;
2838
2991
  }
2992
+ // src/shared/parse-lines-option.ts
2993
+ function parseLinesOption(raw) {
2994
+ const trimmed = raw.trim();
2995
+ const dashIndex = trimmed.indexOf("-");
2996
+ if (dashIndex < 0) {
2997
+ throw new InvalidPackageSpecError(`--lines expects a range like \`10-40\`, \`10-\`, or \`-40\`. Single-line form isn't accepted — use --start ${trimmed}.`);
2998
+ }
2999
+ const startRaw = trimmed.slice(0, dashIndex).trim();
3000
+ const endRaw = trimmed.slice(dashIndex + 1).trim();
3001
+ if (startRaw.length === 0 && endRaw.length === 0) {
3002
+ throw new InvalidPackageSpecError("--lines requires at least one bound. Use `10-40`, `10-` for open end, or `-40` for open start.");
3003
+ }
3004
+ const startLine = startRaw.length > 0 ? requirePositiveInteger(startRaw, "--lines start") : undefined;
3005
+ const endLine = endRaw.length > 0 ? requirePositiveInteger(endRaw, "--lines end") : undefined;
3006
+ if (startLine !== undefined && endLine !== undefined && startLine > endLine) {
3007
+ throw new InvalidPackageSpecError(`--lines range is reversed: ${startLine} > ${endLine}.`);
3008
+ }
3009
+ if (startLine === undefined && endLine !== undefined) {
3010
+ return { startLine: 1, endLine };
3011
+ }
3012
+ return { startLine, endLine };
3013
+ }
3014
+ function requirePositiveInteger(raw, label) {
3015
+ if (!/^\d+$/.test(raw)) {
3016
+ throw new InvalidPackageSpecError(`${label} must be a positive integer. Got '${raw}'.`);
3017
+ }
3018
+ const parsed = Number.parseInt(raw, 10);
3019
+ if (parsed < 1) {
3020
+ throw new InvalidPackageSpecError(`${label} must be ≥ 1 (lines are 1-indexed). Got ${parsed}.`);
3021
+ }
3022
+ return parsed;
3023
+ }
3024
+ // src/shared/read-package-doc-request.ts
3025
+ function buildReadPackageDocParams(input) {
3026
+ const pageId = input.pageId?.trim() ?? "";
3027
+ if (!pageId) {
3028
+ throw new InvalidPackageSpecError("Page ID is required.");
3029
+ }
3030
+ return {
3031
+ params: { pageId }
3032
+ };
3033
+ }
3034
+ // src/shared/read-package-doc-response.ts
3035
+ function buildReadPackageDocSuccessPayload(result, requestedPageId, range) {
3036
+ const pageId = result.page?.id;
3037
+ if (!pageId) {
3038
+ throw new MalformedPackageIntelligenceResponseError(`Documentation page '${requestedPageId}' missing required id in response.`);
3039
+ }
3040
+ if ((result.page?.sourceKind ?? result.sourceKind) === "REPOSITORY" && (!result.page?.repoUrl || !result.page?.gitRef || !result.page?.filePath)) {
3041
+ throw new MalformedPackageIntelligenceResponseError(`Repository-backed documentation page '${pageId}' missing repo locator fields.`);
3042
+ }
3043
+ const envelope = { pageId };
3044
+ if (result.registry)
3045
+ envelope.registry = result.registry.toLowerCase();
3046
+ if (result.packageName)
3047
+ envelope.name = result.packageName;
3048
+ if (result.version)
3049
+ envelope.version = result.version;
3050
+ if (result.page?.title)
3051
+ envelope.title = result.page.title;
3052
+ if (result.page?.contentFormat)
3053
+ envelope.format = result.page.contentFormat;
3054
+ if (result.page?.content !== undefined) {
3055
+ const sliced = sliceContent(result.page.content, range);
3056
+ envelope.content = sliced.content;
3057
+ if (sliced.totalLines !== undefined)
3058
+ envelope.totalLines = sliced.totalLines;
3059
+ if (sliced.startLine !== undefined)
3060
+ envelope.startLine = sliced.startLine;
3061
+ if (sliced.endLine !== undefined)
3062
+ envelope.endLine = sliced.endLine;
3063
+ }
3064
+ if (result.page?.breadcrumbs && result.page.breadcrumbs.length > 0) {
3065
+ envelope.breadcrumbs = result.page.breadcrumbs;
3066
+ }
3067
+ if (result.page?.linkName)
3068
+ envelope.linkName = result.page.linkName;
3069
+ if (result.page?.lastUpdatedAt) {
3070
+ envelope.lastUpdatedAt = toIsoDate(result.page.lastUpdatedAt) ?? undefined;
3071
+ }
3072
+ const sourceKind = lowerDocSourceKind(result.page?.sourceKind ?? result.sourceKind);
3073
+ if (sourceKind)
3074
+ envelope.sourceKind = sourceKind;
3075
+ if (result.page?.source?.url)
3076
+ envelope.sourceUrl = result.page.source.url;
3077
+ if (result.page?.source?.label)
3078
+ envelope.sourceLabel = result.page.source.label;
3079
+ if (result.page?.repoUrl)
3080
+ envelope.repoUrl = result.page.repoUrl;
3081
+ if (result.page?.gitRef)
3082
+ envelope.gitRef = result.page.gitRef;
3083
+ if (result.page?.requestedRef)
3084
+ envelope.requestedRef = result.page.requestedRef;
3085
+ if (result.page?.filePath)
3086
+ envelope.filePath = result.page.filePath;
3087
+ if (result.page?.baseUrl)
3088
+ envelope.baseUrl = result.page.baseUrl;
3089
+ return envelope;
3090
+ }
3091
+ function sliceContent(content, range) {
3092
+ if (content.length === 0) {
3093
+ return { content };
3094
+ }
3095
+ const trimmed = content.endsWith(`
3096
+ `) ? content.slice(0, -1) : content;
3097
+ const lines = trimmed.split(`
3098
+ `);
3099
+ const totalLines = lines.length;
3100
+ if (!range || range.startLine === undefined && range.endLine === undefined) {
3101
+ return { content, totalLines };
3102
+ }
3103
+ const startLine = Math.max(1, range.startLine ?? 1);
3104
+ const endLine = Math.min(totalLines, range.endLine ?? totalLines);
3105
+ if (startLine > totalLines) {
3106
+ return { content: "", totalLines, startLine, endLine: startLine - 1 };
3107
+ }
3108
+ const sliced = lines.slice(startLine - 1, endLine).join(`
3109
+ `);
3110
+ return { content: sliced, totalLines, startLine, endLine };
3111
+ }
3112
+ function formatReadPackageDocTerminal(envelope, options) {
3113
+ if (!(options.verbose ?? false)) {
3114
+ return envelope.content ?? "";
3115
+ }
3116
+ const lines = [];
3117
+ lines.push(buildHeader(envelope, options.useColors));
3118
+ lines.push(`pageId: ${envelope.pageId}`);
3119
+ if (envelope.sourceUrl)
3120
+ lines.push(`source: ${envelope.sourceUrl}`);
3121
+ if (envelope.filePath) {
3122
+ const ref = envelope.requestedRef ?? envelope.gitRef;
3123
+ lines.push(`file: ${envelope.filePath}${ref ? ` @ ${ref}` : ""}`);
3124
+ }
3125
+ if (envelope.lastUpdatedAt)
3126
+ lines.push(`updated: ${envelope.lastUpdatedAt}`);
3127
+ if (envelope.breadcrumbs && envelope.breadcrumbs.length > 0) {
3128
+ lines.push(`breadcrumbs: ${envelope.breadcrumbs.join(" > ")}`);
3129
+ }
3130
+ lines.push("");
3131
+ if (envelope.content)
3132
+ lines.push(envelope.content);
3133
+ return `${lines.join(`
3134
+ `)}
3135
+ `;
3136
+ }
3137
+ function buildHeader(envelope, useColors) {
3138
+ const badge = envelope.sourceKind === "repo" ? "[repo]" : "[crawled]";
3139
+ const title = envelope.title ?? envelope.pageId;
3140
+ const prefix = envelope.registry && envelope.name ? `${envelope.registry}:${envelope.name}${envelope.version ? `@${envelope.version}` : ""}` : "documentation";
3141
+ return `${colorize(`${prefix} ${badge}`, "bold", useColors)}${title ? ` - ${title}` : ""}`;
3142
+ }
2839
3143
  // src/shared/require-auth.ts
2840
3144
  class AuthRequiredError extends Error {
2841
3145
  constructor(message) {
@@ -2862,106 +3166,13 @@ function requireAuth(deps, context) {
2862
3166
  Need help? support@githits.com`);
2863
3167
  throw new AuthRequiredError(`Authentication required${suffix}`);
2864
3168
  }
2865
- // src/shared/search-symbols-request.ts
2866
- function buildSearchSymbolsParams(input) {
2867
- const defaulted = [];
2868
- const resolvedFileIntent = resolveFileIntent(input.fileIntent, defaulted);
2869
- const resolvedWaitTimeoutMs = resolveWaitTimeoutMs(input.waitTimeoutMs, defaulted);
2870
- return {
2871
- params: {
2872
- target: input.target,
2873
- query: input.query,
2874
- keywords: input.keywords,
2875
- matchMode: input.matchMode,
2876
- kind: input.kind,
2877
- category: input.category,
2878
- filePath: input.filePath,
2879
- limit: input.limit,
2880
- fileIntent: resolvedFileIntent,
2881
- waitTimeoutMs: resolvedWaitTimeoutMs
2882
- },
2883
- defaulted
2884
- };
2885
- }
2886
- function resolveFileIntent(input, defaulted) {
2887
- if (input === FILE_INTENT_ALL) {
2888
- return;
2889
- }
2890
- if (input === undefined) {
2891
- defaulted.push("fileIntent");
2892
- return SEARCH_SYMBOLS_DEFAULT_FILE_INTENT;
2893
- }
2894
- return input;
2895
- }
2896
- function resolveWaitTimeoutMs(input, defaulted) {
2897
- if (input === undefined) {
2898
- defaulted.push("waitTimeoutMs");
2899
- return DEFAULT_WAIT_TIMEOUT_MS;
2900
- }
2901
- return input;
2902
- }
2903
- // src/shared/search-symbols-response.ts
2904
- function buildSearchSymbolsSuccessPayload(params, defaulted, result) {
2905
- const payload = {
2906
- query: {
2907
- target: params.target,
2908
- query: params.query,
2909
- keywords: params.keywords,
2910
- matchMode: params.matchMode?.toLowerCase(),
2911
- kind: params.kind?.toLowerCase(),
2912
- category: params.category?.toLowerCase(),
2913
- filePath: params.filePath,
2914
- fileIntent: echoFileIntent(params.fileIntent),
2915
- limit: params.limit,
2916
- waitTimeoutMs: params.waitTimeoutMs ?? 0,
2917
- defaulted
2918
- },
2919
- results: result.results,
2920
- returnedCount: result.results.length,
2921
- totalMatches: result.totalMatches,
2922
- hasMore: result.hasMore
2923
- };
2924
- if (result.version)
2925
- payload.version = result.version;
2926
- if (result.resolution)
2927
- payload.resolution = result.resolution;
2928
- if (result.warning)
2929
- payload.warning = result.warning;
2930
- if (result.hint && !isLegacyZeroChunksHint(result.hint)) {
2931
- payload.hint = result.hint;
2932
- }
2933
- return payload;
2934
- }
2935
- function isLegacyZeroChunksHint(hint) {
2936
- return hint.toLowerCase().includes("0 searchable chunks");
2937
- }
2938
- function buildSearchSymbolsErrorPayload(error2) {
2939
- const mapped = mapCodeNavigationError(error2);
2940
- const payload = {
2941
- error: mapped.message,
2942
- code: mapped.code
2943
- };
2944
- if (typeof mapped.retryable === "boolean") {
2945
- payload.retryable = mapped.retryable;
2946
- }
2947
- if (mapped.details && Object.keys(mapped.details).length > 0) {
2948
- payload.details = mapped.details;
2949
- }
2950
- return payload;
2951
- }
2952
- function echoFileIntent(resolved) {
2953
- if (resolved === undefined)
2954
- return "all";
2955
- return resolved.toLowerCase();
2956
- }
2957
3169
  // src/shared/unified-search-request.ts
2958
3170
  function buildUnifiedSearchParams(input) {
2959
3171
  const targets = resolveTargets(input.target, input.targets);
2960
3172
  const rawQuery = normaliseRequiredQuery(input.query);
2961
- const defaulted = [];
2962
- const limit = resolveNumber(input.limit, 20, "limit", defaulted);
2963
- const offset = resolveNumber(input.offset, 0, "offset", defaulted);
2964
- const waitTimeoutMs = resolveNumber(input.waitTimeoutMs, DEFAULT_WAIT_TIMEOUT_MS, "waitTimeoutMs", defaulted);
3173
+ const limit = input.limit ?? 20;
3174
+ const offset = input.offset ?? 0;
3175
+ const waitTimeoutMs = input.waitTimeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
2965
3176
  const qualifierClauses = buildQualifierClauses({
2966
3177
  name: input.name,
2967
3178
  language: input.language
@@ -2971,7 +3182,7 @@ function buildUnifiedSearchParams(input) {
2971
3182
  kind: input.kind,
2972
3183
  category: input.category,
2973
3184
  pathPrefix: input.pathPrefix,
2974
- fileIntent: resolveFileIntent2(input.fileIntent, input.sources, defaulted),
3185
+ fileIntent: input.fileIntent,
2975
3186
  publicOnly: input.publicOnly
2976
3187
  });
2977
3188
  return {
@@ -2986,8 +3197,7 @@ function buildUnifiedSearchParams(input) {
2986
3197
  waitTimeoutMs
2987
3198
  },
2988
3199
  rawQuery,
2989
- compiledQuery,
2990
- defaulted
3200
+ compiledQuery
2991
3201
  };
2992
3202
  }
2993
3203
  function resolveTargets(target, targets) {
@@ -3021,27 +3231,10 @@ function normaliseRequiredQuery(query) {
3021
3231
  }
3022
3232
  return trimmed;
3023
3233
  }
3024
- function resolveNumber(value, fallback, field, defaulted) {
3025
- if (value === undefined) {
3026
- defaulted.push(field);
3027
- return fallback;
3028
- }
3029
- return value;
3030
- }
3031
- function resolveFileIntent2(value, sources, defaulted) {
3032
- if (value === undefined) {
3033
- if (sources && sources.length > 0 && sources.every((entry) => entry === "DOCS")) {
3034
- return;
3035
- }
3036
- defaulted.push("fileIntent");
3037
- return SEARCH_SYMBOLS_DEFAULT_FILE_INTENT;
3038
- }
3039
- return value;
3040
- }
3041
- function buildQualifierClauses(input) {
3042
- const clauses = [];
3043
- if (input.name) {
3044
- clauses.push(`name:${quoteQualifierValue(input.name)}`);
3234
+ function buildQualifierClauses(input) {
3235
+ const clauses = [];
3236
+ if (input.name) {
3237
+ clauses.push(`name:${quoteQualifierValue(input.name)}`);
3045
3238
  }
3046
3239
  if (input.language) {
3047
3240
  clauses.push(`lang:${quoteQualifierValue(input.language)}`);
@@ -3083,39 +3276,46 @@ function buildFilters(input) {
3083
3276
  return Object.keys(filters).length > 0 ? filters : undefined;
3084
3277
  }
3085
3278
  // src/shared/unified-search-response.ts
3086
- function buildUnifiedSearchSuccessPayload(params, rawQuery, compiledQuery, defaulted, outcome) {
3279
+ var DEFAULT_LIMIT2 = 20;
3280
+ var DEFAULT_OFFSET = 0;
3281
+ function buildUnifiedSearchSuccessPayload(params, rawQuery, compiledQuery, outcome) {
3087
3282
  const warnings = outcome.state === "completed" ? outcome.result.queryWarnings : outcome.result?.queryWarnings ?? outcome.progress?.queryWarnings ?? [];
3088
- const query = buildQueryEcho(params, rawQuery, compiledQuery, defaulted, warnings);
3283
+ const query = buildQueryEcho(params, rawQuery, compiledQuery, warnings);
3089
3284
  if (outcome.state === "incomplete") {
3090
3285
  const result = outcome.result;
3091
3286
  const payload = {
3092
3287
  query,
3093
3288
  completed: false,
3094
- returnedCount: result?.results.length ?? 0,
3095
3289
  hasMore: result?.page.hasMore ?? false,
3096
3290
  results: result?.results.map(buildHitPayload) ?? [],
3097
- searchRef: outcome.searchRef,
3098
- progress: outcome.progress
3291
+ searchRef: outcome.searchRef
3099
3292
  };
3100
3293
  if (result?.page.hasMore === true) {
3101
3294
  payload.nextOffset = result.page.offset + result.page.returned;
3102
3295
  }
3103
- if (result) {
3104
- payload.sourceStatus = result.sourceStatus;
3105
- }
3296
+ const progress = compactProgress(outcome.progress);
3297
+ if (progress)
3298
+ payload.progress = progress;
3299
+ const sourceStatus2 = compactSourceStatus(result?.sourceStatus);
3300
+ if (sourceStatus2)
3301
+ payload.sourceStatus = sourceStatus2;
3106
3302
  return payload;
3107
3303
  }
3108
- return {
3304
+ const completed = {
3109
3305
  query,
3110
3306
  completed: true,
3111
- returnedCount: outcome.result.results.length,
3112
3307
  hasMore: outcome.result.page.hasMore,
3113
- nextOffset: outcome.result.page.hasMore ? outcome.result.page.offset + outcome.result.page.returned : undefined,
3114
- results: outcome.result.results.map(buildHitPayload),
3115
- searchRef: outcome.searchRef,
3116
- progress: outcome.progress,
3117
- sourceStatus: outcome.result.sourceStatus
3308
+ results: outcome.result.results.map(buildHitPayload)
3118
3309
  };
3310
+ if (outcome.result.page.hasMore) {
3311
+ completed.nextOffset = outcome.result.page.offset + outcome.result.page.returned;
3312
+ }
3313
+ if (outcome.searchRef)
3314
+ completed.searchRef = outcome.searchRef;
3315
+ const sourceStatus = compactSourceStatus(outcome.result.sourceStatus);
3316
+ if (sourceStatus)
3317
+ completed.sourceStatus = sourceStatus;
3318
+ return completed;
3119
3319
  }
3120
3320
  function buildUnifiedSearchErrorPayload(error2) {
3121
3321
  const mapped = mapCodeNavigationError(error2);
@@ -3133,82 +3333,225 @@ function buildUnifiedSearchErrorPayload(error2) {
3133
3333
  }
3134
3334
  function buildUnifiedSearchStatusPayload(outcome) {
3135
3335
  if (outcome.state === "incomplete") {
3136
- const payload = {
3336
+ const payload2 = {
3137
3337
  completed: false,
3138
- searchRef: outcome.searchRef,
3139
- progress: outcome.progress
3338
+ searchRef: outcome.searchRef
3140
3339
  };
3340
+ const progress = compactProgress(outcome.progress);
3341
+ if (progress)
3342
+ payload2.progress = progress;
3141
3343
  if (outcome.result) {
3142
- payload.result = buildUnifiedSearchStatusResultPayload(outcome.result);
3344
+ payload2.result = buildUnifiedSearchStatusResultPayload(outcome.result);
3143
3345
  }
3144
- return payload;
3346
+ return payload2;
3145
3347
  }
3146
- return {
3348
+ const payload = {
3147
3349
  completed: true,
3148
- searchRef: outcome.searchRef,
3149
- progress: outcome.progress,
3150
3350
  result: buildUnifiedSearchStatusResultPayload(outcome.result)
3151
3351
  };
3352
+ if (outcome.searchRef)
3353
+ payload.searchRef = outcome.searchRef;
3354
+ return payload;
3152
3355
  }
3153
3356
  function buildUnifiedSearchStatusResultPayload(result) {
3154
- return {
3155
- query: result.query,
3156
- queryWarnings: result.queryWarnings,
3157
- sources: result.sources.map((entry) => entry.toLowerCase()),
3158
- returnedCount: result.results.length,
3357
+ const payload = {
3159
3358
  hasMore: result.page.hasMore,
3160
- nextOffset: result.page.hasMore ? result.page.offset + result.page.returned : undefined,
3161
- results: result.results.map(buildHitPayload),
3162
- sourceStatus: result.sourceStatus
3359
+ results: result.results.map(buildHitPayload)
3163
3360
  };
3361
+ if (result.page.hasMore) {
3362
+ payload.nextOffset = result.page.offset + result.page.returned;
3363
+ }
3364
+ if (result.queryWarnings.length > 0) {
3365
+ payload.warnings = result.queryWarnings;
3366
+ }
3367
+ if (result.sources.length > 0) {
3368
+ payload.sources = result.sources.map((entry) => entry.toLowerCase());
3369
+ }
3370
+ const sourceStatus = compactSourceStatus(result.sourceStatus);
3371
+ if (sourceStatus)
3372
+ payload.sourceStatus = sourceStatus;
3373
+ return payload;
3164
3374
  }
3165
- function buildQueryEcho(params, rawQuery, compiledQuery, defaulted, warnings) {
3166
- return {
3167
- raw: rawQuery,
3168
- compiled: compiledQuery,
3169
- warnings,
3170
- targets: params.targets,
3171
- sources: params.sources?.map((entry) => entry.toLowerCase()),
3172
- filters: params.filters ? {
3173
- kind: params.filters.kind?.toLowerCase(),
3174
- category: params.filters.category?.toLowerCase(),
3175
- pathPrefix: params.filters.pathPrefix,
3176
- fileIntent: params.filters.fileIntent?.toLowerCase(),
3177
- publicOnly: params.filters.publicOnly
3178
- } : undefined,
3179
- allowPartialResults: params.allowPartialResults ?? false,
3180
- limit: params.limit ?? 0,
3181
- offset: params.offset ?? 0,
3182
- waitTimeoutMs: params.waitTimeoutMs ?? 0,
3183
- defaulted
3375
+ function buildQueryEcho(params, rawQuery, compiledQuery, warnings) {
3376
+ const echo = {
3377
+ raw: rawQuery
3184
3378
  };
3379
+ if (compiledQuery !== rawQuery) {
3380
+ echo.compiled = compiledQuery;
3381
+ }
3382
+ if (warnings.length > 0) {
3383
+ echo.warnings = warnings;
3384
+ }
3385
+ if (params.sources && params.sources.length > 0) {
3386
+ echo.sources = params.sources.map((entry) => entry.toLowerCase());
3387
+ }
3388
+ if (params.filters) {
3389
+ const filters = {};
3390
+ if (params.filters.kind)
3391
+ filters.kind = params.filters.kind.toLowerCase();
3392
+ if (params.filters.category)
3393
+ filters.category = params.filters.category.toLowerCase();
3394
+ if (params.filters.pathPrefix)
3395
+ filters.pathPrefix = params.filters.pathPrefix;
3396
+ if (params.filters.fileIntent)
3397
+ filters.fileIntent = params.filters.fileIntent.toLowerCase();
3398
+ if (typeof params.filters.publicOnly === "boolean")
3399
+ filters.publicOnly = params.filters.publicOnly;
3400
+ if (Object.keys(filters).length > 0)
3401
+ echo.filters = filters;
3402
+ }
3403
+ if (params.allowPartialResults === true) {
3404
+ echo.allowPartialResults = true;
3405
+ }
3406
+ if (params.limit !== undefined && params.limit !== DEFAULT_LIMIT2) {
3407
+ echo.limit = params.limit;
3408
+ }
3409
+ if (params.offset !== undefined && params.offset !== DEFAULT_OFFSET) {
3410
+ echo.offset = params.offset;
3411
+ }
3412
+ if (params.waitTimeoutMs !== undefined && params.waitTimeoutMs !== DEFAULT_WAIT_TIMEOUT_MS) {
3413
+ echo.waitTimeoutMs = params.waitTimeoutMs;
3414
+ }
3415
+ return echo;
3185
3416
  }
3186
3417
  function buildHitPayload(hit) {
3187
- return {
3418
+ assertSearchFollowUpInvariant(hit);
3419
+ const payload = {
3188
3420
  type: hit.resultType.toLowerCase(),
3189
3421
  target: hit.targetLabel,
3190
- title: hit.title,
3191
- summary: hit.summary,
3192
- score: hit.score,
3193
- highlights: hit.highlights,
3194
- locator: {
3195
- registry: hit.locator.registry,
3196
- packageName: hit.locator.packageName,
3197
- version: hit.locator.version,
3198
- pageId: hit.locator.pageId,
3199
- repoUrl: hit.locator.repoUrl,
3200
- gitRef: hit.locator.gitRef,
3201
- filePath: hit.locator.filePath,
3202
- startLine: hit.locator.startLine,
3203
- endLine: hit.locator.endLine,
3204
- fileContentHash: hit.locator.fileContentHash,
3205
- symbolRef: hit.locator.symbolRef,
3206
- qualifiedPath: hit.locator.qualifiedPath,
3207
- kind: hit.locator.kind,
3208
- category: hit.locator.category,
3209
- language: hit.locator.language
3210
- }
3422
+ locator: buildLocatorPayload(hit)
3211
3423
  };
3424
+ if (hit.title)
3425
+ payload.title = hit.title;
3426
+ if (hit.summary)
3427
+ payload.summary = hit.summary;
3428
+ if (typeof hit.score === "number")
3429
+ payload.score = hit.score;
3430
+ const highlights = buildHighlights(hit.highlights);
3431
+ if (highlights)
3432
+ payload.highlights = highlights;
3433
+ return payload;
3434
+ }
3435
+ function buildLocatorPayload(hit) {
3436
+ const locator = {};
3437
+ const src = hit.locator;
3438
+ if (src.registry)
3439
+ locator.registry = src.registry;
3440
+ if (src.packageName)
3441
+ locator.packageName = src.packageName;
3442
+ if (src.version)
3443
+ locator.version = src.version;
3444
+ if (src.pageId)
3445
+ locator.pageId = src.pageId;
3446
+ if (src.sourceKind)
3447
+ locator.sourceKind = src.sourceKind;
3448
+ if (src.sourceUrl)
3449
+ locator.sourceUrl = src.sourceUrl;
3450
+ if (src.repoUrl)
3451
+ locator.repoUrl = src.repoUrl;
3452
+ if (src.gitRef)
3453
+ locator.gitRef = src.gitRef;
3454
+ if (src.requestedRef)
3455
+ locator.requestedRef = src.requestedRef;
3456
+ if (src.filePath)
3457
+ locator.filePath = src.filePath;
3458
+ if (typeof src.startLine === "number")
3459
+ locator.startLine = src.startLine;
3460
+ if (typeof src.endLine === "number")
3461
+ locator.endLine = src.endLine;
3462
+ if (src.qualifiedPath && src.qualifiedPath !== hit.title) {
3463
+ locator.qualifiedPath = src.qualifiedPath;
3464
+ }
3465
+ if (src.kind)
3466
+ locator.kind = src.kind;
3467
+ if (src.category)
3468
+ locator.category = src.category;
3469
+ if (src.language)
3470
+ locator.language = src.language;
3471
+ return locator;
3472
+ }
3473
+ function buildHighlights(highlights) {
3474
+ if (!highlights)
3475
+ return;
3476
+ const compact = {};
3477
+ if (highlights.title && highlights.title.length > 0) {
3478
+ compact.title = highlights.title;
3479
+ }
3480
+ if (highlights.summary && highlights.summary.length > 0) {
3481
+ compact.summary = highlights.summary;
3482
+ }
3483
+ return Object.keys(compact).length > 0 ? compact : undefined;
3484
+ }
3485
+ function compactProgress(progress) {
3486
+ if (!progress)
3487
+ return;
3488
+ const payload = {
3489
+ status: progress.status,
3490
+ targetsReady: progress.targetsReady,
3491
+ targetsTotal: progress.targetsTotal,
3492
+ elapsedMs: progress.elapsedMs
3493
+ };
3494
+ if (progress.expiresAt)
3495
+ payload.expiresAt = progress.expiresAt;
3496
+ return payload;
3497
+ }
3498
+ function compactSourceStatus(sourceStatus) {
3499
+ if (!sourceStatus || sourceStatus.length === 0)
3500
+ return;
3501
+ const compact = [];
3502
+ for (const entry of sourceStatus) {
3503
+ const slim = compactSourceStatusEntry(entry);
3504
+ if (slim)
3505
+ compact.push(slim);
3506
+ }
3507
+ return compact.length > 0 ? compact : undefined;
3508
+ }
3509
+ function compactSourceStatusEntry(entry) {
3510
+ const payload = {
3511
+ source: entry.source.toLowerCase(),
3512
+ targetLabel: entry.targetLabel
3513
+ };
3514
+ let interesting = false;
3515
+ if (entry.indexingStatus && entry.indexingStatus !== "INDEXED") {
3516
+ payload.indexingStatus = entry.indexingStatus;
3517
+ interesting = true;
3518
+ }
3519
+ if (entry.codeIndexState && entry.codeIndexState !== "CURRENT" && entry.codeIndexState !== "STALE") {
3520
+ payload.codeIndexState = entry.codeIndexState;
3521
+ interesting = true;
3522
+ }
3523
+ if (typeof entry.resultCount === "number" && entry.resultCount > 0) {
3524
+ payload.resultCount = entry.resultCount;
3525
+ }
3526
+ if (entry.ignoredFilters.length > 0) {
3527
+ payload.ignoredFilters = entry.ignoredFilters;
3528
+ interesting = true;
3529
+ }
3530
+ if (entry.incompatibleFilters.length > 0) {
3531
+ payload.incompatibleFilters = entry.incompatibleFilters;
3532
+ interesting = true;
3533
+ }
3534
+ if (entry.ignoredQueryFeatures.length > 0) {
3535
+ payload.ignoredQueryFeatures = entry.ignoredQueryFeatures;
3536
+ interesting = true;
3537
+ }
3538
+ if (entry.incompatibleQueryFeatures.length > 0) {
3539
+ payload.incompatibleQueryFeatures = entry.incompatibleQueryFeatures;
3540
+ interesting = true;
3541
+ }
3542
+ if (entry.note) {
3543
+ payload.note = entry.note;
3544
+ interesting = true;
3545
+ }
3546
+ return interesting ? payload : undefined;
3547
+ }
3548
+ function assertSearchFollowUpInvariant(hit) {
3549
+ if ((hit.resultType === "DOCUMENTATION_PAGE" || hit.resultType === "REPOSITORY_DOC") && !hit.locator.pageId) {
3550
+ throw new MalformedCodeNavigationResponseError(`${hit.resultType} search hit missing required pageId.`);
3551
+ }
3552
+ if (hit.resultType === "REPOSITORY_DOC" && (!hit.locator.repoUrl || !hit.locator.gitRef || !hit.locator.filePath)) {
3553
+ throw new MalformedCodeNavigationResponseError("REPOSITORY_DOC search hit missing repo locator fields.");
3554
+ }
3212
3555
  }
3213
3556
  // src/shared/unified-search-target.ts
3214
3557
  function parseUnifiedSearchTargetSpec(spec) {
@@ -3378,7 +3721,7 @@ function formatPlain2(envelope, options) {
3378
3721
  }
3379
3722
  function formatVerbose2(envelope, options) {
3380
3723
  const lines = [];
3381
- lines.push(buildSummaryHeader(envelope, options));
3724
+ lines.push(buildSummaryHeader2(envelope, options));
3382
3725
  if (envelope.resolution || envelope.indexedVersion) {
3383
3726
  lines.push(buildResolutionLine(envelope, options));
3384
3727
  }
@@ -3406,7 +3749,7 @@ function formatEmpty(envelope, options, verbose) {
3406
3749
  ` };
3407
3750
  }
3408
3751
  const lines = [];
3409
- lines.push(buildSummaryHeader(envelope, options));
3752
+ lines.push(buildSummaryHeader2(envelope, options));
3410
3753
  if (envelope.resolution || envelope.indexedVersion) {
3411
3754
  lines.push(buildResolutionLine(envelope, options));
3412
3755
  }
@@ -3416,7 +3759,7 @@ function formatEmpty(envelope, options, verbose) {
3416
3759
  return { stdout: lines.join(`
3417
3760
  `) };
3418
3761
  }
3419
- function buildSummaryHeader(envelope, options) {
3762
+ function buildSummaryHeader2(envelope, options) {
3420
3763
  const identity = buildIdentityLabel(envelope);
3421
3764
  const countValue = envelope.hasMore ? `${envelope.files.length}+` : String(envelope.total);
3422
3765
  const counts = `${countValue} ${plural("file", "files", envelope.files.length)}`;
@@ -3654,7 +3997,7 @@ On an INDEXING response, the dependency is being indexed on-demand
3654
3997
  — retry with a longer --wait (up to 60000 ms) or pick one of the
3655
3998
  already-indexed versions surfaced in the error detail.`;
3656
3999
  function registerCodeFilesCommand(pkgCommand) {
3657
- return pkgCommand.command("files").summary("List files in an indexed dependency").description(PKG_FILES_DESCRIPTION).argument("[arg1]", "In spec mode: package spec (e.g. npm:express). In --repo-url mode: the path-prefix.").argument("[arg2]", "In spec mode: the path-prefix (literal directory, not a glob). Unused in --repo-url mode.").option("--repo-url <url>", "Repository URL addressing (requires --git-ref)").option("--git-ref <ref>", "Tag, commit, branch, or HEAD. Required with --repo-url.").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) => {
4000
+ 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("--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) => {
3658
4001
  const deps = await createContainer();
3659
4002
  await pkgFilesAction(arg1, arg2, options, {
3660
4003
  codeNavigationService: deps.codeNavigationService,
@@ -3796,8 +4139,8 @@ for context, --verbose for grouped output, and --cursor to continue a paginated
3796
4139
  grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
3797
4140
  match in --verbose output; full payload in --json).`;
3798
4141
  function registerCodeGrepCommand(pkgCommand) {
3799
- return pkgCommand.command("grep").summary("Deterministic text grep over indexed dependency source").description(PKG_GREP_DESCRIPTION).argument("[arg1]", "In spec mode: package spec (e.g. npm:express). In --repo-url mode: the pattern.").argument("[arg2]", "In spec mode: the pattern. In --repo-url mode: optional path-prefix.").argument("[arg3]", "In spec mode: optional path-prefix. Unused in --repo-url mode.").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)", collectRepeatable, []).option("--ext <ext>", "Extension filter without leading dot (repeatable)", collectRepeatable, []).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}`, collectRepeatable, []).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) => {
3800
- const { createContainer: createContainer2 } = await import("./shared/chunk-cdx2gnrs.js");
4142
+ 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)", collectRepeatable, []).option("--ext <ext>", "Extension filter without leading dot (repeatable)", collectRepeatable, []).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}`, collectRepeatable, []).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) => {
4143
+ const { createContainer: createContainer2 } = await import("./shared/chunk-4drb8s8v.js");
3801
4144
  const deps = await createContainer2();
3802
4145
  await pkgGrepAction(arg1, arg2, arg3, options, {
3803
4146
  codeNavigationService: deps.codeNavigationService,
@@ -3895,7 +4238,7 @@ function formatReadFileTerminal(envelope, options) {
3895
4238
  function formatBinary(envelope, options, verbose) {
3896
4239
  const sentinel = dim("Binary file — cannot display as text.", options.useColors);
3897
4240
  if (verbose) {
3898
- return `${buildHeader(envelope, options)}
4241
+ return `${buildHeader2(envelope, options)}
3899
4242
 
3900
4243
  ${sentinel}
3901
4244
  `;
@@ -3906,7 +4249,7 @@ ${sentinel}
3906
4249
  function formatNoContent(envelope, options, verbose) {
3907
4250
  const sentinel = dim("(no content returned)", options.useColors);
3908
4251
  if (verbose) {
3909
- return `${buildHeader(envelope, options)}
4252
+ return `${buildHeader2(envelope, options)}
3910
4253
 
3911
4254
  ${sentinel}
3912
4255
  `;
@@ -3916,7 +4259,7 @@ ${sentinel}
3916
4259
  }
3917
4260
  function formatVerboseBody(envelope, options) {
3918
4261
  const lines = [];
3919
- lines.push(buildHeader(envelope, options));
4262
+ lines.push(buildHeader2(envelope, options));
3920
4263
  lines.push("");
3921
4264
  const content = envelope.content ?? "";
3922
4265
  const bodyLines = content.split(`
@@ -3936,7 +4279,7 @@ function formatVerboseBody(envelope, options) {
3936
4279
  return lines.join(`
3937
4280
  `);
3938
4281
  }
3939
- function buildHeader(envelope, options) {
4282
+ function buildHeader2(envelope, options) {
3940
4283
  const parts = [envelope.path];
3941
4284
  if (envelope.language)
3942
4285
  parts.push(envelope.language);
@@ -4026,14 +4369,14 @@ function resolveLineRange(options, pathWithRange) {
4026
4369
  };
4027
4370
  }
4028
4371
  if (hasLines) {
4029
- return parseLinesOption(options.lines);
4372
+ return parseLinesOption2(options.lines);
4030
4373
  }
4031
4374
  return {
4032
4375
  startLine: parseIntCliOption(options.start, "--start", 1, Number.MAX_SAFE_INTEGER),
4033
4376
  endLine: parseIntCliOption(options.end, "--end", 1, Number.MAX_SAFE_INTEGER)
4034
4377
  };
4035
4378
  }
4036
- function parseLinesOption(raw) {
4379
+ function parseLinesOption2(raw) {
4037
4380
  const trimmed = raw.trim();
4038
4381
  const dashIndex = trimmed.indexOf("-");
4039
4382
  if (dashIndex < 0) {
@@ -4044,8 +4387,8 @@ function parseLinesOption(raw) {
4044
4387
  if (startRaw.length === 0 && endRaw.length === 0) {
4045
4388
  throw new InvalidPackageSpecError("--lines requires at least one bound. Use `10-40`, `10-` for open end, or `-40` for open start.");
4046
4389
  }
4047
- const startLine = startRaw.length > 0 ? requirePositiveInteger(startRaw, "--lines start") : undefined;
4048
- const endLine = endRaw.length > 0 ? requirePositiveInteger(endRaw, "--lines end") : undefined;
4390
+ const startLine = startRaw.length > 0 ? requirePositiveInteger2(startRaw, "--lines start") : undefined;
4391
+ const endLine = endRaw.length > 0 ? requirePositiveInteger2(endRaw, "--lines end") : undefined;
4049
4392
  if (startLine !== undefined && endLine !== undefined && startLine > endLine) {
4050
4393
  throw new InvalidPackageSpecError(`--lines range is reversed: ${startLine} > ${endLine}.`);
4051
4394
  }
@@ -4068,14 +4411,14 @@ function parsePathWithOptionalRange(path) {
4068
4411
  if (!startRaw) {
4069
4412
  throw new InvalidPackageSpecError(`Invalid path with range: '${path}'. Use <path>:<start>-<end>.`);
4070
4413
  }
4071
- const startLine = requirePositiveInteger(startRaw, "path range start");
4072
- const endLine = endRaw !== undefined && endRaw.length > 0 ? requirePositiveInteger(endRaw, "path range end") : startLine;
4414
+ const startLine = requirePositiveInteger2(startRaw, "path range start");
4415
+ const endLine = endRaw !== undefined && endRaw.length > 0 ? requirePositiveInteger2(endRaw, "path range end") : startLine;
4073
4416
  if (startLine > endLine) {
4074
4417
  throw new InvalidPackageSpecError(`Path range is reversed: ${startLine} > ${endLine}.`);
4075
4418
  }
4076
4419
  return { filePath, startLine, endLine };
4077
4420
  }
4078
- function requirePositiveInteger(raw, label) {
4421
+ function requirePositiveInteger2(raw, label) {
4079
4422
  if (!/^\d+$/.test(raw)) {
4080
4423
  throw new InvalidPackageSpecError(`${label} must be a positive integer. Got '${raw}'.`);
4081
4424
  }
@@ -4114,308 +4457,211 @@ function registerCodeReadCommand(pkgCommand) {
4114
4457
  });
4115
4458
  }
4116
4459
 
4117
- // src/commands/code/search-symbols.ts
4118
- async function searchSymbolsAction(packageArg, query, options, deps) {
4460
+ // src/commands/code/index.ts
4461
+ async function registerCodeCommandGroup(program, options = {}) {
4462
+ const registration = await resolveGatedCommandGroupRegistrationState(options);
4463
+ if (!registration.shouldRegister) {
4464
+ return;
4465
+ }
4466
+ 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>`. For symbol or unified discovery search use `githits search`; for package-level metadata use `githits pkg`.");
4467
+ registerCodeFilesCommand(codeCommand);
4468
+ registerCodeReadCommand(codeCommand);
4469
+ registerCodeGrepCommand(codeCommand);
4470
+ }
4471
+ // src/commands/docs/list.ts
4472
+ async function docsListAction(spec, options, deps) {
4119
4473
  requireAuth(deps);
4120
4474
  try {
4121
- if (!deps.codeNavigationUrl || !deps.codeNavigationService) {
4122
- throw new InvalidArgumentError("Code navigation is not configured for this environment.");
4123
- }
4124
- const keywords = normaliseKeywords(options.keywords, options.keyword);
4125
- if (!query && keywords.length === 0) {
4126
- throw new InvalidArgumentError("Provide a query argument, or pass keywords via --keywords or repeated --keyword.");
4127
- }
4128
- const parsed = parsePackageSpec(packageArg);
4129
- const { params, defaulted } = buildSearchSymbolsParams({
4130
- target: {
4131
- registry: toCodeNavigationRegistry(parsed.registry),
4132
- packageName: parsed.name,
4133
- version: parsed.version
4134
- },
4135
- query,
4136
- keywords: keywords.length > 0 ? keywords : undefined,
4137
- matchMode: parseMatchMode(options.matchMode),
4138
- kind: parseKind(options.kind),
4139
- category: parseCategory(options.category),
4140
- filePath: options.file,
4141
- limit: parseOptionalInt(options.limit, "--limit", 1, 50),
4142
- fileIntent: parseIntent(options.intent),
4143
- waitTimeoutMs: parseWaitSeconds(options.wait)
4475
+ if (!deps.codeNavigationUrl || !deps.packageIntelligenceService) {
4476
+ throw new InvalidPackageSpecError("Package intelligence is not configured for this environment.");
4477
+ }
4478
+ const parsed = parsePackageSpec(spec);
4479
+ const limit = parseLimitOption(options.limit);
4480
+ const build = buildListPackageDocsParams({
4481
+ registry: parsed.registry,
4482
+ packageName: parsed.name,
4483
+ version: parsed.version,
4484
+ limit,
4485
+ after: options.after
4486
+ });
4487
+ const result = await deps.packageIntelligenceService.listPackageDocs(build.params);
4488
+ const payload = buildListPackageDocsSuccessPayload(result, {
4489
+ limitExplicit: build.limitExplicit,
4490
+ afterExplicit: build.afterExplicit,
4491
+ limit: build.params.limit,
4492
+ after: build.params.after
4144
4493
  });
4145
- const result = await deps.codeNavigationService.searchSymbols(params);
4146
- const payload = buildSearchSymbolsSuccessPayload(params, defaulted, result);
4147
4494
  if (options.json) {
4148
4495
  console.log(JSON.stringify(payload));
4149
4496
  return;
4150
4497
  }
4151
- console.log(formatSearchSymbolsTerminal(payload, parsed.registry, parsed.name, parsed.version, query));
4498
+ process.stdout.write(formatListPackageDocsTerminal(payload, {
4499
+ verbose: options.verbose ?? false,
4500
+ useColors: shouldUseColors()
4501
+ }));
4152
4502
  } catch (error2) {
4153
- handleSearchSymbolsCommandError(error2, options.json ?? false);
4503
+ handleDocsListError(error2, options.json ?? false);
4154
4504
  }
4155
4505
  }
4156
- var SEARCH_SYMBOLS_DESCRIPTION = `Find functions, classes, modules, and doc sections inside an indexed dependency by exact-token search.
4157
-
4158
- Prefer top-level \`githits search --source symbol\` for new symbol-shaped workflows. \`githits code search\` remains available for the older dedicated symbol-search UX and JSON parity contract.
4159
-
4160
- Package spec: <registry>:<name>[@<version>]. Omit the registry to default to
4161
- npm. Supported registries: npm, pypi, hex, crates, nuget, maven, zig, vcpkg,
4162
- packagist.
4163
-
4164
- Filter by --category (broad: callable, type, module, data, documentation)
4165
- or --kind (precise: function, method, class, trait, …). Prefer --category
4166
- for most use cases; reach for --kind when you need a specific construct.
4167
-
4168
- Default file intent is production source. Pass --intent all to include tests,
4169
- examples, benchmarks, generated files, and other non-production code.
4170
-
4171
- Examples:
4172
- githits code search npm:express middleware
4173
- githits code search npm:express middleware --intent all
4174
- githits code search pypi:requests timeout --category callable --limit 10
4175
- githits code search crates:serde Serialize --kind trait --limit 5
4176
- githits code search npm:@types/node Buffer --file src/ --json
4177
- githits code search npm:express --keywords "router,handler" --match-mode and`;
4178
- function registerCodeSearchSymbolsCommand(program) {
4179
- program.command("search <package> [query]").alias("search-symbols").summary("Legacy symbol search over indexed package source").description(SEARCH_SYMBOLS_DESCRIPTION).option("--category <category>", "Filter by broad symbol category: callable, type, module, data, documentation. Preferred over --kind for most use cases.").option("--kind <kind>", "Filter by precise symbol kind (function, method, constructor, getter, setter, class, interface, trait, struct, enum, record, module, namespace, property, event, etc.). Prefer --category for broad filtering.").option("--limit <n>", "Max results (1-50; default: 25)").option("--keywords <words>", "Comma-separated keywords (alternative to the query argument)").option("--keyword <word>", "Single keyword (repeatable; combines with --keywords)", collectRepeatable2, []).option("--match-mode <mode>", "How to combine keywords: or (any match) or and (all match)").option("--file <prefix>", "Filter to files matching path prefix").option("--intent <intent>", "File intent filter (production, test, benchmark, example, generated, fixture, build, vendor, all). Default: production.").option("--wait <seconds>", "Max seconds to wait for indexing (0-60; default: 20). Accepts `10` or `10s`. Indexing usually completes within 30 seconds; pass `--wait 60` to block on a first-time request.").option("--json", "Output as JSON").action(async (packageArg, query, options) => {
4180
- try {
4181
- const deps = await createContainer();
4182
- await searchSymbolsAction(packageArg, query, options, deps);
4183
- } catch (error2) {
4184
- if (error2 instanceof AuthRequiredError) {
4185
- process.exit(1);
4186
- }
4187
- throw error2;
4188
- }
4189
- });
4190
- }
4191
- function collectRepeatable2(value, previous) {
4192
- return [...previous, value];
4193
- }
4194
- function formatSearchSymbolsTerminal(payload, registry, packageName, version2, query) {
4195
- const lines = [];
4196
- if (payload.warning) {
4197
- lines.push(`Warning: ${payload.warning}`);
4198
- lines.push("");
4199
- }
4200
- lines.push(formatHeader2(payload));
4201
- lines.push("");
4202
- if (payload.results.length === 0) {
4203
- lines.push(formatZeroResultMessage(query, registry, packageName, version2, payload.query, payload.hint));
4204
- return lines.join(`
4205
- `).trimEnd();
4206
- }
4207
- for (const entry of payload.results) {
4208
- lines.push(...formatEntry(entry));
4209
- lines.push("");
4210
- }
4211
- if (payload.hint) {
4212
- lines.push(`Note: ${payload.hint}`);
4213
- }
4214
- return lines.join(`
4215
- `).trimEnd();
4216
- }
4217
- function formatHeader2(payload) {
4218
- let summary = `${payload.returnedCount} match(es)`;
4219
- if (payload.hasMore)
4220
- summary += " (more available)";
4221
- if (payload.version) {
4222
- summary += ` · indexed ${displayVersion(payload.version)}`;
4223
- const requested = payload.resolution?.requestedVersion ?? payload.resolution?.requestedRef;
4224
- if (requested && !isTrivialRefDifference(requested, payload.version) && !isTrivialRefDifference(requested, payload.resolution?.resolvedRef)) {
4225
- summary += ` (requested ${requested})`;
4226
- }
4227
- }
4228
- return summary;
4229
- }
4230
- function displayVersion(version2) {
4231
- if (/^[0-9a-f]{40}$/i.test(version2))
4232
- return version2.slice(0, 7);
4233
- return version2;
4234
- }
4235
- function isTrivialRefDifference(requested, resolved) {
4236
- if (!resolved)
4237
- return false;
4238
- if (requested === resolved)
4239
- return true;
4240
- const stripV = (v) => v.startsWith("v") ? v.slice(1) : v;
4241
- return stripV(requested) === stripV(resolved);
4242
- }
4243
- function formatEntry(entry) {
4244
- const out = [];
4245
- const locationParts = [];
4246
- if (entry.filePath) {
4247
- if (entry.startLine) {
4248
- locationParts.push(entry.endLine && entry.endLine !== entry.startLine ? `${entry.filePath}:${entry.startLine}-${entry.endLine}` : `${entry.filePath}:${entry.startLine}`);
4249
- } else {
4250
- locationParts.push(entry.filePath);
4251
- }
4252
- }
4253
- const kindLabel = resolveKindLabel(entry);
4254
- if (kindLabel)
4255
- locationParts.push(`[${kindLabel}]`);
4256
- out.push(locationParts.length > 0 ? locationParts.join(" ") : "unnamed match");
4257
- if (entry.name)
4258
- out.push(` ${entry.name}`);
4259
- const snippet = buildSnippet(entry.code);
4260
- for (const snippetLine of snippet)
4261
- out.push(snippetLine);
4262
- return out;
4263
- }
4264
- function resolveKindLabel(entry) {
4265
- if (!entry.kind)
4266
- return;
4267
- const normalised = entry.kind.toLowerCase();
4268
- if (normalised === "fallback")
4506
+ function parseLimitOption(value) {
4507
+ if (value === undefined)
4269
4508
  return;
4270
- return normalised;
4271
- }
4272
- function buildSnippet(code, maxLines = 3) {
4273
- if (!code)
4274
- return [];
4275
- const raw = code.split(`
4276
- `);
4277
- while (raw.length > 0 && raw[0]?.trim() === "")
4278
- raw.shift();
4279
- while (raw.length > 0 && raw[raw.length - 1]?.trim() === "")
4280
- raw.pop();
4281
- if (raw.length === 0)
4282
- return [];
4283
- const indent = commonLeadingIndent(raw);
4284
- const dedented = raw.map((line) => indent > 0 ? line.slice(indent) : line);
4285
- const truncated = dedented.length > maxLines;
4286
- const selected = dedented.slice(0, maxLines);
4287
- const visible = truncated ? [...selected, "…"] : selected;
4288
- return visible.map((line) => ` ${line}`);
4289
- }
4290
- function commonLeadingIndent(lines) {
4291
- let min = Number.POSITIVE_INFINITY;
4292
- for (const line of lines) {
4293
- if (line.trim() === "")
4294
- continue;
4295
- const match = line.match(/^(\s*)/);
4296
- const len = match?.[1]?.length ?? 0;
4297
- if (len < min)
4298
- min = len;
4299
- }
4300
- return Number.isFinite(min) ? min : 0;
4301
- }
4302
- function formatZeroResultMessage(query, registry, packageName, version2, echo, serverHint) {
4303
- const target = version2 ? `${registry}:${packageName}@${version2}` : `${registry}:${packageName}`;
4304
- const queryText = query ? `"${query}"` : "the given keywords";
4305
- const header = `No matches for ${queryText} in ${target}.`;
4306
- if (serverHint) {
4307
- return [header, serverHint].join(`
4308
- `);
4509
+ const parsed = Number(value);
4510
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 500) {
4511
+ throw new InvalidPackageSpecError("--limit must be an integer between 1 and 500.");
4309
4512
  }
4310
- const suggestions = [];
4311
- if (echo.kind)
4312
- suggestions.push("drop --kind");
4313
- if (echo.category)
4314
- suggestions.push("drop --category");
4315
- if (echo.filePath)
4316
- suggestions.push("broaden or remove --file");
4317
- if (echo.fileIntent !== "all")
4318
- suggestions.push("try --intent all");
4319
- if (echo.matchMode === "and")
4320
- suggestions.push("try --match-mode or");
4321
- suggestions.push("try broader keywords");
4322
- return [header, `Try: ${suggestions.join(", ")}.`].join(`
4323
- `);
4513
+ return parsed;
4324
4514
  }
4325
- function handleSearchSymbolsCommandError(error2, json) {
4515
+ function handleDocsListError(error2, json) {
4516
+ const mapped = mapPackageIntelligenceError(error2);
4326
4517
  if (json) {
4327
- console.error(JSON.stringify(buildSearchSymbolsErrorPayload(error2)));
4328
- process.exit(1);
4518
+ console.error(JSON.stringify({
4519
+ error: mapped.message,
4520
+ code: mapped.code,
4521
+ retryable: mapped.retryable ?? false,
4522
+ ...mapped.details ? { details: mapped.details } : {}
4523
+ }));
4524
+ } else {
4525
+ console.error(mapped.message);
4329
4526
  }
4330
- const mapped = mapCodeNavigationError(error2);
4331
- console.error(`Failed to search symbols: ${mapped.message}`);
4332
4527
  process.exit(1);
4333
4528
  }
4334
- function parseOptionalInt(value, optionName, min, max) {
4335
- if (value === undefined)
4336
- return;
4337
- const parsed = Number.parseInt(value, 10);
4338
- if (Number.isNaN(parsed) || parsed < min || parsed > max) {
4339
- throw new InvalidArgumentError(`${optionName} must be a number between ${min} and ${max}`);
4340
- }
4341
- return parsed;
4529
+ var DOCS_LIST_DESCRIPTION = `List package documentation pages from mixed sources.
4530
+
4531
+ Docs are mixed by default: hosted/crawled docs and repository-backed docs
4532
+ appear together. Every entry shows its page ID, source badge, and source
4533
+ location. Repo-backed docs also carry exact file follow-up metadata in JSON.
4534
+
4535
+ Package spec: <registry>:<name>[@version].`;
4536
+ function registerDocsListCommand(docsCommand) {
4537
+ return docsCommand.command("list").summary("List documentation pages for a package").description(DOCS_LIST_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express@5.2.1").option("--limit <n>", "Max pages (1-500, default 100)").option("--after <cursor>", "Pagination cursor from a prior response").option("-v, --verbose", "Show updated timestamps when available").option("--json", "Emit the JSON envelope").action(async (spec, options) => {
4538
+ const deps = await createContainer();
4539
+ await docsListAction(spec, options, {
4540
+ packageIntelligenceService: deps.packageIntelligenceService,
4541
+ codeNavigationUrl: deps.codeNavigationUrl,
4542
+ hasValidToken: deps.hasValidToken,
4543
+ mcpUrl: deps.mcpUrl
4544
+ });
4545
+ });
4342
4546
  }
4343
- function parseMatchMode(value) {
4344
- if (!value)
4345
- return;
4346
- const parsed = toSearchSymbolsMatchMode(value.toLowerCase());
4347
- if (!parsed) {
4348
- throw new InvalidArgumentError("--match-mode must be 'or' or 'and'");
4547
+
4548
+ // src/commands/docs/read.ts
4549
+ async function docsReadAction(pageId, options, deps) {
4550
+ requireAuth(deps);
4551
+ try {
4552
+ if (!deps.codeNavigationUrl || !deps.packageIntelligenceService) {
4553
+ throw new InvalidPackageSpecError("Package intelligence is not configured for this environment.");
4554
+ }
4555
+ const range = options.lines ? parseLinesOption(options.lines) : undefined;
4556
+ const build = buildReadPackageDocParams({ pageId });
4557
+ const result = await deps.packageIntelligenceService.readPackageDoc(build.params);
4558
+ const payload = buildReadPackageDocSuccessPayload(result, build.params.pageId, range);
4559
+ if (options.json) {
4560
+ console.log(JSON.stringify(payload));
4561
+ return;
4562
+ }
4563
+ process.stdout.write(formatReadPackageDocTerminal(payload, {
4564
+ verbose: options.verbose ?? false,
4565
+ useColors: shouldUseColors()
4566
+ }));
4567
+ } catch (error2) {
4568
+ handleDocsReadError(error2, options.json ?? false);
4349
4569
  }
4350
- return parsed;
4351
4570
  }
4352
- function parseKind(value) {
4353
- if (!value)
4354
- return;
4355
- const parsed = toSearchSymbolsKind(value.toLowerCase());
4356
- if (!parsed) {
4357
- throw new InvalidArgumentError(`--kind must be one of ${knownSymbolKindList().join(", ")}`);
4571
+ function handleDocsReadError(error2, json) {
4572
+ const mapped = mapPackageIntelligenceError(error2);
4573
+ if (json) {
4574
+ console.error(JSON.stringify({
4575
+ error: mapped.message,
4576
+ code: mapped.code,
4577
+ retryable: mapped.retryable ?? false,
4578
+ ...mapped.details ? { details: mapped.details } : {}
4579
+ }));
4580
+ } else {
4581
+ console.error(mapped.message);
4358
4582
  }
4359
- return parsed;
4583
+ process.exit(1);
4360
4584
  }
4361
- function parseCategory(value) {
4362
- if (!value)
4363
- return;
4364
- const parsed = toSymbolCategory(value.toLowerCase());
4365
- if (!parsed) {
4366
- throw new InvalidArgumentError(`--category must be one of ${knownSymbolCategoryList().join(", ")}`);
4367
- }
4368
- return parsed;
4585
+ var DOCS_READ_DESCRIPTION = `Read a documentation page by page ID.
4586
+
4587
+ Use page IDs from githits docs list, githits search --json, or MCP doc/search
4588
+ results. Default output is content-only for easy piping; pass --verbose for a
4589
+ metadata header. Use --lines for a bounded line range (e.g. \`--lines 10-40\`,
4590
+ \`--lines 10-\` for open-ended, or \`--lines -40\` for the first 40 lines)
4591
+ useful when a page is too long to read whole.`;
4592
+ function registerDocsReadCommand(docsCommand) {
4593
+ return docsCommand.command("read").summary("Read a documentation page by page ID").description(DOCS_READ_DESCRIPTION).argument("<page-id>", "Documentation page ID from docs/search results").option("--lines <range>", "Bounded line range, e.g. 10-40, 10-, or -40 (1-indexed inclusive)").option("-v, --verbose", "Show metadata header before content").option("--json", "Emit the JSON envelope").action(async (pageId, options) => {
4594
+ const deps = await createContainer();
4595
+ await docsReadAction(pageId, options, {
4596
+ packageIntelligenceService: deps.packageIntelligenceService,
4597
+ codeNavigationUrl: deps.codeNavigationUrl,
4598
+ hasValidToken: deps.hasValidToken,
4599
+ mcpUrl: deps.mcpUrl
4600
+ });
4601
+ });
4369
4602
  }
4370
- function parseIntent(value) {
4371
- if (value === undefined)
4603
+
4604
+ // src/commands/docs/index.ts
4605
+ async function registerDocsCommandGroup(program, options = {}) {
4606
+ const registration = await resolveGatedCommandGroupRegistrationState(options);
4607
+ if (!registration.shouldRegister) {
4372
4608
  return;
4373
- const lower = value.toLowerCase();
4374
- if (lower === "all")
4375
- return FILE_INTENT_ALL;
4376
- const parsed = toSearchSymbolsFileIntent(lower);
4377
- if (!parsed) {
4378
- throw new InvalidArgumentError("--intent must be one of production, test, benchmark, example, generated, fixture, build, vendor, or all");
4379
4609
  }
4380
- return parsed;
4610
+ 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.");
4611
+ registerDocsListCommand(docsCommand);
4612
+ registerDocsReadCommand(docsCommand);
4381
4613
  }
4382
- function parseWaitSeconds(value) {
4383
- if (value === undefined)
4384
- return;
4385
- const trimmed = value.trim();
4386
- const withoutSuffix = trimmed.endsWith("s") ? trimmed.slice(0, -1) : trimmed;
4387
- if (trimmed.endsWith("ms")) {
4388
- throw new InvalidArgumentError("--wait is specified in seconds (e.g. `10` or `10s`), not milliseconds.");
4389
- }
4390
- const parsed = Number.parseInt(withoutSuffix, 10);
4391
- if (Number.isNaN(parsed) || parsed < 0 || parsed > 60 || withoutSuffix !== String(parsed)) {
4392
- throw new InvalidArgumentError("--wait must be a number of seconds between 0 and 60 (e.g. `10` or `10s`).");
4614
+ // src/commands/example.ts
4615
+ import { Option } from "commander";
4616
+ async function exampleAction(query, options, deps) {
4617
+ requireAuth(deps);
4618
+ try {
4619
+ const result = await deps.githitsService.search({
4620
+ query,
4621
+ language: options.lang,
4622
+ licenseMode: options.license,
4623
+ includeExplanation: options.explain
4624
+ });
4625
+ if (options.json) {
4626
+ const solutionId = extractSolutionId(result);
4627
+ const payload = solutionId ? { result, solution_id: solutionId } : { result };
4628
+ console.log(JSON.stringify(payload));
4629
+ } else {
4630
+ console.log(result);
4631
+ }
4632
+ } catch (error2) {
4633
+ console.error(`Failed to get example: ${error2 instanceof Error ? error2.message : error2}`);
4634
+ process.exit(1);
4393
4635
  }
4394
- return parsed * 1000;
4395
4636
  }
4637
+ var EXAMPLE_DESCRIPTION = `Get verified, canonical code examples from global open source.
4396
4638
 
4397
- // src/commands/code/index.ts
4398
- async function registerCodeCommandGroup(program, options = {}) {
4399
- const codeNavigationUrl = options.codeNavigationUrl ?? getCodeNavigationUrl();
4400
- if (!codeNavigationUrl) {
4401
- return;
4402
- }
4403
- const overrideEnabled = options.overrideEnabled ?? isCodeNavigationCliOverrideEnabled();
4404
- const registrationState = options.capability !== undefined || options.expiredStoredAuth !== undefined ? {
4405
- capability: options.capability ?? "unknown",
4406
- expiredStoredAuth: options.expiredStoredAuth ?? false
4407
- } : await resolveStartupCodeNavigationRegistrationState();
4408
- if (!overrideEnabled && registrationState.capability !== "enabled" && !registrationState.expiredStoredAuth) {
4409
- return;
4410
- }
4411
- const codeCommand = program.command("code").summary("Source-level operations on indexed dependencies").description("Search exact tokens, 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>`. For package-level metadata (versions, vulnerabilities, dependencies, changelog) use `githits pkg`.");
4412
- registerCodeSearchSymbolsCommand(codeCommand);
4413
- registerCodeFilesCommand(codeCommand);
4414
- registerCodeReadCommand(codeCommand);
4415
- registerCodeGrepCommand(codeCommand);
4639
+ For dependency, package, or repository source search, use \`githits search\` instead.
4640
+
4641
+ Examples:
4642
+ githits example "how to use express middleware"
4643
+ githits example "how to use express middleware" --lang javascript
4644
+ githits example "async file reading" -l python --license yolo
4645
+ githits example "react hooks patterns" -l typescript --explain
4646
+ githits example "react hooks patterns" -l typescript --json`;
4647
+ function registerExampleCommand(program) {
4648
+ 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) => {
4649
+ try {
4650
+ const deps = await loadContainer();
4651
+ await exampleAction(query, options, deps);
4652
+ } catch (error2) {
4653
+ if (error2 instanceof AuthRequiredError)
4654
+ process.exit(1);
4655
+ throw error2;
4656
+ }
4657
+ });
4658
+ }
4659
+ async function loadContainer() {
4660
+ const { createContainer: createContainer2 } = await import("./shared/chunk-4drb8s8v.js");
4661
+ return createContainer2();
4416
4662
  }
4417
4663
  // src/commands/feedback.ts
4418
- import { Option } from "commander";
4664
+ import { Option as Option2 } from "commander";
4419
4665
  async function feedbackAction(solutionId, options, deps) {
4420
4666
  requireAuth(deps);
4421
4667
  if (!options.accept && !options.reject) {
@@ -4449,7 +4695,7 @@ Examples:
4449
4695
  githits feedback abc123 --reject -m "Example was outdated"
4450
4696
  githits feedback abc123 --accept --message "Solved my problem" --json`;
4451
4697
  function registerFeedbackCommand(program) {
4452
- program.command("feedback").summary("Submit feedback on a search result").description(FEEDBACK_DESCRIPTION).argument("<solution_id>", "Solution ID from search result").addOption(new Option("--accept", "Mark as helpful").conflicts("reject")).addOption(new Option("--reject", "Mark as unhelpful").conflicts("accept")).option("-m, --message <text>", "Feedback explanation").option("--json", "Output as JSON for piping").action(async (solutionId, options) => {
4698
+ program.command("feedback").summary("Submit feedback on a search result").description(FEEDBACK_DESCRIPTION).argument("<solution_id>", "Solution ID from search result").addOption(new Option2("--accept", "Mark as helpful").conflicts("reject")).addOption(new Option2("--reject", "Mark as unhelpful").conflicts("accept")).option("-m, --message <text>", "Feedback explanation").option("--json", "Output as JSON for piping").action(async (solutionId, options) => {
4453
4699
  try {
4454
4700
  const deps = await createContainer();
4455
4701
  await feedbackAction(solutionId, options, deps);
@@ -4461,6 +4707,62 @@ function registerFeedbackCommand(program) {
4461
4707
  });
4462
4708
  }
4463
4709
  // src/commands/init/setup-handlers.ts
4710
+ import {
4711
+ parse as parseJsonc,
4712
+ printParseErrorCode
4713
+ } from "jsonc-parser";
4714
+ function parseConfigObject(content) {
4715
+ let normalizedContent = content;
4716
+ if (normalizedContent.charCodeAt(0) === 65279) {
4717
+ normalizedContent = normalizedContent.slice(1);
4718
+ }
4719
+ const trimmed = normalizedContent.trim();
4720
+ if (trimmed === "") {
4721
+ return {
4722
+ format: "json",
4723
+ value: {}
4724
+ };
4725
+ }
4726
+ try {
4727
+ const parsed = JSON.parse(normalizedContent);
4728
+ if (!isPlainObject(parsed)) {
4729
+ return {
4730
+ format: "invalid",
4731
+ error: "Config file root is not a JSON object"
4732
+ };
4733
+ }
4734
+ return {
4735
+ format: "json",
4736
+ value: parsed
4737
+ };
4738
+ } catch (jsonError) {
4739
+ const parseErrors = [];
4740
+ const parsed = parseJsonc(normalizedContent, parseErrors, {
4741
+ allowTrailingComma: true,
4742
+ disallowComments: false,
4743
+ allowEmptyContent: false
4744
+ });
4745
+ if (parseErrors.length > 0) {
4746
+ const firstParseError = parseErrors[0];
4747
+ const strictErrorMessage = jsonError instanceof Error ? jsonError.message : String(jsonError);
4748
+ const jsoncDetail = firstParseError ? `${printParseErrorCode(firstParseError.error)} at offset ${firstParseError.offset}` : "Unknown parse error";
4749
+ return {
4750
+ format: "invalid",
4751
+ error: `Invalid JSON: ${strictErrorMessage}. JSONC parse error: ${jsoncDetail}`
4752
+ };
4753
+ }
4754
+ if (!isPlainObject(parsed)) {
4755
+ return {
4756
+ format: "invalid",
4757
+ error: "Config file root is not a JSON object"
4758
+ };
4759
+ }
4760
+ return {
4761
+ format: "jsonc",
4762
+ value: parsed
4763
+ };
4764
+ }
4765
+ }
4464
4766
  function isPlainObject(value) {
4465
4767
  return typeof value === "object" && value !== null && !Array.isArray(value);
4466
4768
  }
@@ -4569,29 +4871,14 @@ function getMatchingServerKeys(servers, serverName) {
4569
4871
  return Object.keys(servers).filter((key) => key.toLowerCase() === normalizedTarget);
4570
4872
  }
4571
4873
  function mergeServerConfig(existingContent, serversKey, serverName, serverConfig) {
4572
- let content = existingContent;
4573
- if (content.charCodeAt(0) === 65279) {
4574
- content = content.slice(1);
4575
- }
4576
- const trimmed = content.trim();
4577
- if (trimmed === "") {
4578
- content = "{}";
4579
- }
4580
- let config;
4581
- try {
4582
- config = JSON.parse(content);
4583
- } catch (err) {
4874
+ const parsedConfig = parseConfigObject(existingContent);
4875
+ if (parsedConfig.format === "invalid") {
4584
4876
  return {
4585
4877
  status: "parse_error",
4586
- error: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}`
4587
- };
4588
- }
4589
- if (typeof config !== "object" || config === null || Array.isArray(config)) {
4590
- return {
4591
- status: "parse_error",
4592
- error: "Config file root is not a JSON object"
4878
+ error: parsedConfig.error
4593
4879
  };
4594
4880
  }
4881
+ const config = parsedConfig.value;
4595
4882
  if (!(serversKey in config)) {
4596
4883
  config[serversKey] = {};
4597
4884
  }
@@ -4629,24 +4916,13 @@ function formatSetupPreview(config) {
4629
4916
  ${snippet}`;
4630
4917
  }
4631
4918
  async function isAlreadyConfigured(config, fs) {
4632
- try {
4633
- let content;
4634
- try {
4635
- content = await fs.readFile(config.configPath);
4636
- } catch {
4637
- return false;
4638
- }
4639
- if (content.charCodeAt(0) === 65279) {
4640
- content = content.slice(1);
4641
- }
4642
- const trimmed = content.trim();
4643
- if (trimmed === "") {
4644
- return false;
4645
- }
4646
- const parsed = JSON.parse(trimmed);
4647
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
4919
+ try {
4920
+ const content = await fs.readFile(config.configPath);
4921
+ const parsedConfig = parseConfigObject(content);
4922
+ if (parsedConfig.format === "invalid") {
4648
4923
  return false;
4649
4924
  }
4925
+ const parsed = parsedConfig.value;
4650
4926
  const servers = parsed[config.serversKey];
4651
4927
  if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
4652
4928
  return false;
@@ -4804,6 +5080,32 @@ function getAppDataPath(fs, appName) {
4804
5080
  return fs.joinPath(home, ".config", appName);
4805
5081
  }
4806
5082
  }
5083
+ function getUserDataRoot(fs) {
5084
+ const home = fs.getHomeDir();
5085
+ switch (process.platform) {
5086
+ case "win32":
5087
+ return process.env.APPDATA ?? fs.joinPath(home, "AppData", "Roaming");
5088
+ case "darwin":
5089
+ return fs.joinPath(home, "Library", "Application Support");
5090
+ default:
5091
+ return process.env.XDG_DATA_HOME ?? fs.joinPath(home, ".local", "share");
5092
+ }
5093
+ }
5094
+ function getOpenCodeConfigDir(fs) {
5095
+ if (process.platform === "win32") {
5096
+ return fs.joinPath(getUserDataRoot(fs), "opencode");
5097
+ }
5098
+ return fs.joinPath(fs.getHomeDir(), ".config", "opencode");
5099
+ }
5100
+ function getOpenCodeDesktopDetectPaths(fs) {
5101
+ const userDataRoot = getUserDataRoot(fs);
5102
+ return [
5103
+ fs.joinPath(userDataRoot, "ai.opencode.desktop"),
5104
+ fs.joinPath(userDataRoot, "ai.opencode.desktop.beta"),
5105
+ fs.joinPath(userDataRoot, "ai.opencode.desktop.dev"),
5106
+ getOpenCodeConfigDir(fs)
5107
+ ];
5108
+ }
4807
5109
  async function isExecutableAvailable(exec, executable) {
4808
5110
  try {
4809
5111
  const lookupCommand = process.platform === "win32" ? "where" : "which";
@@ -5016,12 +5318,13 @@ var googleAntigravity = {
5016
5318
  var openCode = {
5017
5319
  name: "OpenCode",
5018
5320
  id: "opencode",
5019
- detectionMethod: "binary",
5321
+ detectionMethod: "hybrid",
5020
5322
  setupMethod: "config-file",
5323
+ detectPaths: (fs) => getOpenCodeDesktopDetectPaths(fs),
5021
5324
  detectBinary: async (exec) => isExecutableAvailable(exec, "opencode"),
5022
5325
  getSetupConfig: (fs) => ({
5023
5326
  method: "config-file",
5024
- configPath: process.platform === "win32" ? fs.joinPath(process.env.APPDATA ?? fs.joinPath(fs.getHomeDir(), "AppData", "Roaming"), "opencode", "opencode.json") : fs.joinPath(fs.getHomeDir(), ".config", "opencode", "opencode.json"),
5327
+ configPath: fs.joinPath(getOpenCodeConfigDir(fs), "opencode.json"),
5025
5328
  serversKey: "mcp",
5026
5329
  serverName: GITHITS_SERVER_NAME,
5027
5330
  serverConfig: {
@@ -5065,6 +5368,26 @@ async function scanAgents(definitions, fs, execService) {
5065
5368
  break;
5066
5369
  }
5067
5370
  }
5371
+ } else if (agent.detectionMethod === "hybrid") {
5372
+ let binaryDetected = false;
5373
+ let pathDetected = false;
5374
+ if (agent.detectBinary) {
5375
+ try {
5376
+ binaryDetected = await agent.detectBinary(execService);
5377
+ } catch {
5378
+ binaryDetected = false;
5379
+ }
5380
+ }
5381
+ if (!binaryDetected && agent.detectPaths) {
5382
+ const paths = agent.detectPaths(fs);
5383
+ for (const path of paths) {
5384
+ if (await fs.isDirectory(path)) {
5385
+ pathDetected = true;
5386
+ break;
5387
+ }
5388
+ }
5389
+ }
5390
+ detected = binaryDetected || pathDetected;
5068
5391
  }
5069
5392
  if (!detected) {
5070
5393
  result.notDetected.push(agent);
@@ -5586,9 +5909,23 @@ async function withErrorHandling(operation, fn) {
5586
5909
  try {
5587
5910
  return await fn();
5588
5911
  } catch (error2) {
5589
- const message = error2 instanceof Error ? error2.message : "Unknown error";
5590
- return errorResult(`Failed to ${operation}: ${message}`);
5912
+ return errorResult(JSON.stringify(classify3(operation, error2)));
5913
+ }
5914
+ }
5915
+ function classify3(operation, error2) {
5916
+ if (error2 instanceof AuthenticationError) {
5917
+ return {
5918
+ error: error2.message,
5919
+ code: "UNAUTHENTICATED",
5920
+ retryable: false
5921
+ };
5591
5922
  }
5923
+ const message = error2 instanceof Error ? error2.message : "Unknown error";
5924
+ return {
5925
+ error: `Failed to ${operation}: ${message}`,
5926
+ code: "UNKNOWN",
5927
+ retryable: false
5928
+ };
5592
5929
  }
5593
5930
 
5594
5931
  // src/tools/feedback.ts
@@ -5597,24 +5934,9 @@ var schema = {
5597
5934
  accepted: z.boolean().describe("True if the example was helpful/good, False if unhelpful/bad"),
5598
5935
  feedback_text: z.string().optional().describe('Optional text explaining why (e.g., "This solved problem X" or "Example was outdated")')
5599
5936
  };
5600
- var DESCRIPTION = `Submit feedback on a GitHits search result.
5937
+ var DESCRIPTION = `Submit feedback on a GitHits example result.
5601
5938
 
5602
- Use this tool after receiving a search result to indicate whether the example was helpful.
5603
- This feedback helps improve GitHits' search quality.
5604
-
5605
- **When to use**:
5606
- - After using the search tool, provide feedback on whether the result was useful
5607
- - Use \`accepted=true\` if the example solved your problem or was helpful, and you used it
5608
- - Use \`accepted=false\` if the example was not relevant or unhelpful, and you did not use it
5609
- - Optionally provide textual feedback explaining why
5610
-
5611
- Args:
5612
- solution_id: The solution ID from a previous search result (shown in the result)
5613
- accepted: True if the example was helpful/good, False if unhelpful/bad
5614
- feedback_text: Optional text explaining why (e.g., "This solved problem X" or "Example was outdated")
5615
-
5616
- Returns:
5617
- Confirmation message or error`;
5939
+ Call after \`get_example\` to record whether the returned example was used. \`accepted=true\` when it solved the problem or was useful; \`accepted=false\` when it was irrelevant or wrong. Use \`feedback_text\` to add a short reason. Feeds back into ranking quality.`;
5618
5940
  function createFeedbackTool(service) {
5619
5941
  return {
5620
5942
  name: "feedback",
@@ -5636,13 +5958,12 @@ function createFeedbackTool(service) {
5636
5958
  import { z as z2 } from "zod";
5637
5959
  var schema2 = {
5638
5960
  query: z2.string().min(1).describe("Natural-language example-search query for canonical code examples."),
5639
- language: z2.string().min(1).describe("Programming language. Use search_language first if the exact name is uncertain."),
5961
+ language: z2.string().min(1).optional().describe("Optional programming language. If omitted, GitHits tries to infer it automatically. Use search_language first only when you need to force a specific language and the exact name is uncertain."),
5640
5962
  license_mode: z2.enum(["strict", "yolo", "custom"]).optional().describe("License filtering mode: strict (default), yolo, or custom.")
5641
5963
  };
5642
5964
  var DESCRIPTION2 = `Get verified, canonical code examples from global open source.
5643
5965
 
5644
- Use this for example retrieval. For searching indexed dependency and repository code/docs,
5645
- use the unified \`search\` tool instead.`;
5966
+ Returns JSON \`{result, solution_id?}\`. \`result\` is markdown — render or quote it directly. Pass \`solution_id\` to \`feedback\` after using or rejecting the example. For searching indexed dependency and repository code/docs, use the unified \`search\` tool instead.`;
5646
5967
  function createGetExampleTool(service) {
5647
5968
  return {
5648
5969
  name: "get_example",
@@ -5650,13 +5971,15 @@ function createGetExampleTool(service) {
5650
5971
  schema: schema2,
5651
5972
  handler: async (args) => {
5652
5973
  return withErrorHandling("get example", async () => {
5653
- const result = await service.search({
5974
+ const markdown = await service.search({
5654
5975
  query: args.query,
5655
5976
  language: args.language,
5656
5977
  licenseMode: args.license_mode,
5657
5978
  includeExplanation: false
5658
5979
  });
5659
- return textResult(result);
5980
+ const solutionId = extractSolutionId(markdown);
5981
+ const payload = solutionId ? { result: markdown, solution_id: solutionId } : { result: markdown };
5982
+ return textResult(JSON.stringify(payload));
5660
5983
  });
5661
5984
  }
5662
5985
  };
@@ -5725,15 +6048,11 @@ function invalidTargetResult(message) {
5725
6048
  }
5726
6049
 
5727
6050
  // src/tools/grep-repo.ts
5728
- var pathSelectorSchema = z4.object({
5729
- kind: z4.enum(["exact", "prefix", "glob"]),
5730
- value: z4.string()
5731
- });
5732
6051
  var schema3 = {
5733
6052
  target: codeTargetSchema,
5734
6053
  pattern: z4.string().describe(GREP_REPO_PATTERN_NOTE),
5735
- path: z4.string().optional().describe("Exact file path to grep. Shares the same path vocabulary as `read_file`."),
5736
- path_prefix: z4.string().optional().describe("Literal directory prefix to scope grep, matching `list_files` / `search` naming."),
6054
+ path: z4.string().optional().describe("Exact file path to grep. Shares the same path vocabulary as `code_read`."),
6055
+ path_prefix: z4.string().optional().describe("Literal directory prefix to scope grep, matching `code_files` / `search` naming."),
5737
6056
  globs: z4.array(z4.string()).optional().describe("Repeatable glob scopes with real glob semantics (e.g. `src/**/*.ts`)."),
5738
6057
  extensions: z4.array(z4.string()).optional().describe("Extensions to include, without a leading dot."),
5739
6058
  pattern_type: z4.enum(["literal", "regex"]).optional(),
@@ -5749,10 +6068,10 @@ var schema3 = {
5749
6068
  symbol_fields: z4.array(z4.enum(GREP_REPO_SYMBOL_FIELDS)).optional().describe(GREP_REPO_SYMBOL_FIELDS_NOTE),
5750
6069
  wait_timeout_ms: z4.number().optional()
5751
6070
  };
5752
- var DESCRIPTION3 = "Deterministic text grep over indexed dependency and repository source files. " + "Use this when you know the text pattern you want; use `search` for discovery. " + "Whole-target grep is the default. Narrow with `path`, `path_prefix`, `globs`, or `extensions`. " + "Matches chain directly into `read_file` via `matches[].filePath`.";
6071
+ var DESCRIPTION3 = "Deterministic text grep over indexed dependency and repository source files. " + "Use this when you know the text pattern you want; use `search` for discovery. " + "Whole-target grep is the default. Narrow with `path`, `path_prefix`, `globs`, or `extensions`. " + "Matches chain directly into `code_read` via `matches[].filePath`.";
5753
6072
  function createGrepRepoTool(service) {
5754
6073
  return {
5755
- name: "grep_repo",
6074
+ name: "code_grep",
5756
6075
  description: DESCRIPTION3,
5757
6076
  schema: schema3,
5758
6077
  annotations: { readOnlyHint: true },
@@ -5826,10 +6145,10 @@ var schema4 = {
5826
6145
  limit: z5.number().optional().describe("Max entries to return (1–1000, default 200). Out-of-range values return an `INVALID_ARGUMENT` envelope."),
5827
6146
  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`.")
5828
6147
  };
5829
- var DESCRIPTION4 = "List files in an indexed dependency. Response: " + "`{total, hasMore, files: [{path, name, language, fileType, byteSize}], " + "resolution, indexedVersion}`. Address via `target.registry` + " + "`target.package_name` (package scope) or `target.repo_url` + " + "`target.git_ref` (repo scope), mutually exclusive. `path_prefix` " + "is a literal directory prefix — it does NOT accept globs " + "(`*.ts`) or extension filters. The returned `path` values feed " + "directly into `read_file` and help scope `grep_repo`. 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`.";
6148
+ var DESCRIPTION4 = "List files in an indexed dependency. Response: " + "`{total, hasMore, files: [{path, name, language, fileType, byteSize}], " + "resolution, indexedVersion}`. Address via `target.registry` + " + "`target.package_name` (package scope) or `target.repo_url` + " + "`target.git_ref` (repo scope), mutually exclusive. `path_prefix` " + "is a literal directory prefix — it does NOT accept globs " + "(`*.ts`) or extension filters. The returned `path` values feed " + "directly into `code_read` and help scope `code_grep`. Returns an `INDEXING` " + "error envelope when the dependency is being indexed on-demand — " + "retry with a longer `wait_timeout_ms` or use a version from " + "`details.availableVersions`.";
5830
6149
  function createListFilesTool(service) {
5831
6150
  return {
5832
- name: "list_files",
6151
+ name: "code_files",
5833
6152
  description: DESCRIPTION4,
5834
6153
  schema: schema4,
5835
6154
  annotations: { readOnlyHint: true },
@@ -5868,8 +6187,53 @@ function createListFilesTool(service) {
5868
6187
  }
5869
6188
  };
5870
6189
  }
5871
- // src/tools/package-changelog.ts
6190
+ // src/tools/list-package-docs.ts
5872
6191
  import { z as z6 } from "zod";
6192
+ var schema5 = {
6193
+ registry: z6.string().describe("Package registry. One of: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, packagist."),
6194
+ package_name: z6.string().describe("Package name (scoped names ok: @types/node)."),
6195
+ version: z6.string().optional().describe("Optional package version."),
6196
+ limit: z6.number().optional().describe("Max pages to return (1-500, default 100)."),
6197
+ after: z6.string().optional().describe("Pagination cursor from a prior response.")
6198
+ };
6199
+ var DESCRIPTION5 = "List mixed package documentation pages from hosted docs and repository-backed docs. " + "Every entry includes a stable `pageId`, `sourceKind` (`crawled` or `repo`), and source URL; repo-backed entries also expose `repoUrl` / `gitRef` / `filePath` for exact file reads. " + "Pass a returned `pageId` to `docs_read`. Use this to browse before reading a full page.";
6200
+ function createListPackageDocsTool(service) {
6201
+ return {
6202
+ name: "docs_list",
6203
+ description: DESCRIPTION5,
6204
+ schema: schema5,
6205
+ annotations: { readOnlyHint: true },
6206
+ handler: async (args) => {
6207
+ try {
6208
+ const build = buildListPackageDocsParams({
6209
+ registry: args.registry,
6210
+ packageName: args.package_name,
6211
+ version: args.version,
6212
+ limit: args.limit,
6213
+ after: args.after
6214
+ });
6215
+ const result = await service.listPackageDocs(build.params);
6216
+ const payload = buildListPackageDocsSuccessPayload(result, {
6217
+ limitExplicit: build.limitExplicit,
6218
+ afterExplicit: build.afterExplicit,
6219
+ limit: build.params.limit,
6220
+ after: build.params.after
6221
+ });
6222
+ return textResult(JSON.stringify(payload));
6223
+ } catch (error2) {
6224
+ const mapped = mapPackageIntelligenceError(error2);
6225
+ return errorResult(JSON.stringify({
6226
+ error: mapped.message,
6227
+ code: mapped.code,
6228
+ retryable: mapped.retryable ?? false,
6229
+ ...mapped.details ? { details: mapped.details } : {}
6230
+ }));
6231
+ }
6232
+ }
6233
+ };
6234
+ }
6235
+ // src/tools/package-changelog.ts
6236
+ import { z as z7 } from "zod";
5873
6237
 
5874
6238
  // src/shared/package-changelog-request.ts
5875
6239
  function buildPackageChangelogParams(input) {
@@ -6134,22 +6498,22 @@ function stripAnsi(text) {
6134
6498
  }
6135
6499
 
6136
6500
  // src/tools/package-changelog.ts
6137
- var schema5 = {
6138
- registry: z6.string().optional().describe("Package registry (with `package_name`). Mutually exclusive with `repo_url`. Supported: npm, pypi, hex, crates, vcpkg, zig, nuget, maven, packagist."),
6139
- package_name: z6.string().optional().describe("Package name (with `registry`). Scoped names ok (`@types/node`). Mutually exclusive with `repo_url`."),
6140
- repo_url: z6.string().optional().describe("GitHub repository URL (https://…). Mutually exclusive with `registry` + `package_name`. Use when agents have a repo URL without a registry mapping."),
6141
- from_version: z6.string().optional().describe("Start of version range. When set, the response returns every entry between `from_version` and `to_version` (or latest) with no count cap — range mode. Mutually exclusive with `limit`. Tag-style `v`-prefixed inputs are rejected."),
6142
- to_version: z6.string().optional().describe("End of range / latest-mode cap. Works in either mode. Defaults to latest on the wire. Tag-style `v`-prefixed inputs are rejected."),
6143
- limit: z6.number().optional().describe("Latest-mode cap on entry count (1–50, default 10). Rejected with `INVALID_ARGUMENT` when `from_version` is also set or when out of range."),
6144
- git_ref: z6.string().optional().describe("Git branch or tag for CHANGELOG.md source (no effect on GitHub Releases or HexDocs). Defaults to the repository's default branch."),
6145
- include_bodies: z6.boolean().optional().describe("When false, each entry in `entries.items[]` omits its `body` field. Default true. Set false when you only need the version / date / URL timeline — drops 10 KB+ per entry on large release notes.")
6501
+ var schema6 = {
6502
+ registry: z7.string().optional().describe("Package registry (with `package_name`). Mutually exclusive with `repo_url`. Supported: npm, pypi, hex, crates, vcpkg, zig, nuget, maven, packagist."),
6503
+ package_name: z7.string().optional().describe("Package name (with `registry`). Scoped names ok (`@types/node`). Mutually exclusive with `repo_url`."),
6504
+ repo_url: z7.string().optional().describe("GitHub repository URL (https://…). Mutually exclusive with `registry` + `package_name`. Use when agents have a repo URL without a registry mapping."),
6505
+ from_version: z7.string().optional().describe("Start of version range. When set, the response returns every entry between `from_version` and `to_version` (or latest) with no count cap — range mode. Mutually exclusive with `limit`. Tag-style `v`-prefixed inputs are rejected."),
6506
+ to_version: z7.string().optional().describe("End of range / latest-mode cap. Works in either mode. Defaults to latest on the wire. Tag-style `v`-prefixed inputs are rejected."),
6507
+ limit: z7.number().optional().describe("Latest-mode cap on entry count (1–50, default 10). Rejected with `INVALID_ARGUMENT` when `from_version` is also set or when out of range."),
6508
+ git_ref: z7.string().optional().describe("Git branch or tag for CHANGELOG.md source (no effect on GitHub Releases or HexDocs). Defaults to the repository's default branch."),
6509
+ include_bodies: z7.boolean().optional().describe("When false, each entry in `entries.items[]` omits its `body` field. Default true. Set false when you only need the version / date / URL timeline — drops 10 KB+ per entry on large release notes.")
6146
6510
  };
6147
- var DESCRIPTION5 = "Release notes for a package or GitHub repo, newest-first. Default " + "latest mode returns the ten most recent entries (`limit` 1–50). " + "With `from_version`, returns every entry in the " + "`[from_version, to_version]` range (range mode, no count cap). " + "Address via `registry` + `package_name` or `repo_url` (mutually " + 'exclusive). Response: `source` (`"releases"` / `"changelog_file"` ' + '/ `"hexdocs"`), `mode` (`"latest"` or `"range"`), ' + "`entries: { count, items }` with full markdown bodies. Set " + "`include_bodies: false` for a version / date / URL timeline only. " + "Supports npm, PyPI, Hex, Crates, vcpkg, Zig, NuGet, Maven, " + "Packagist; returns `NOT_FOUND` when a package has no changelog " + "source.";
6511
+ var DESCRIPTION6 = "Release notes for a package or GitHub repo, newest-first. Default " + "latest mode returns the ten most recent entries (`limit` 1–50). " + "With `from_version`, returns every entry in the " + "`[from_version, to_version]` range (range mode, no count cap). " + "Address via `registry` + `package_name` or `repo_url` (mutually " + 'exclusive). Response: `source` (`"releases"` / `"changelog_file"` ' + '/ `"hexdocs"`), `mode` (`"latest"` or `"range"`), ' + "`entries: { count, items }` with full markdown bodies. Set " + "`include_bodies: false` for a version / date / URL timeline only. " + "Supports npm, PyPI, Hex, Crates, vcpkg, Zig, NuGet, Maven, " + "Packagist; returns `NOT_FOUND` when a package has no changelog " + "source.";
6148
6512
  function createPackageChangelogTool(service) {
6149
6513
  return {
6150
- name: "package_changelog",
6151
- description: DESCRIPTION5,
6152
- schema: schema5,
6514
+ name: "pkg_changelog",
6515
+ description: DESCRIPTION6,
6516
+ schema: schema6,
6153
6517
  annotations: { readOnlyHint: true },
6154
6518
  handler: async (args) => {
6155
6519
  try {
@@ -6197,22 +6561,22 @@ function createPackageChangelogTool(service) {
6197
6561
  };
6198
6562
  }
6199
6563
  // src/tools/package-dependencies.ts
6200
- import { z as z7 } from "zod";
6201
- var schema6 = {
6202
- registry: z7.string().describe("Package registry. Dependency data is available on npm, pypi, hex, crates, vcpkg, and zig."),
6203
- package_name: z7.string().describe("Package name (scoped names ok: @types/node)."),
6204
- version: z7.string().optional().describe("Specific version to inspect. Defaults to latest when omitted. Tag-style inputs with a leading `v` (for example `v4.18.0`) are rejected — pass the canonical version (`4.18.0`)."),
6205
- lifecycle: z7.union([z7.string(), z7.array(z7.string())]).optional().describe('Filter the `groups` block server-side by lifecycle phase. Accepts a single value, a comma-separated string (e.g. `"runtime,development"`), or an array of strings. Canonical values: `runtime`, `development`, `build`, `peer`, `optional`. Uppercase is tolerated. When the filter matches nothing the response still includes `groups: { items: [] }` so you can tell an empty-match apart from a registry that has no groups concept.'),
6206
- include_transitive: z7.boolean().optional().describe("When true the response gains a `transitive` block with aggregate counts (`edges`, `uniquePackages`), the preprocessed `packages[]` list (each `{name, version}` — the complete install footprint), plus typed `conflicts[]` (`{name, requiredVersions}`) and `circularDependencies[]` (`{cycle: string[]}`) when the backend reported any. Off by default."),
6207
- include_importers: z7.boolean().optional().describe("Requires `include_transitive: true`. When true, each entry in `transitive.packages[]` also carries an `importers` array — every upstream package that pulls it in, with that importer's own resolved version and the constraint it declared. Off by default because adding provenance roughly quadruples the envelope size on heavy graphs. Turn on when you need to trace why a specific transitive dep is present."),
6208
- max_depth: z7.number().int().min(1).max(10).optional().describe("Cap the transitive traversal at this depth (1–10). Omit to get the backend's full graph. Requires `include_transitive: true` — passing `max_depth` without the transitive flag is rejected with `INVALID_ARGUMENT`.")
6564
+ import { z as z8 } from "zod";
6565
+ var schema7 = {
6566
+ registry: z8.string().describe("Package registry. Dependency data is available on npm, pypi, hex, crates, vcpkg, and zig."),
6567
+ package_name: z8.string().describe("Package name (scoped names ok: @types/node)."),
6568
+ version: z8.string().optional().describe("Specific version to inspect. Defaults to latest when omitted. Tag-style inputs with a leading `v` (for example `v4.18.0`) are rejected — pass the canonical version (`4.18.0`)."),
6569
+ lifecycle: z8.union([z8.string(), z8.array(z8.string())]).optional().describe('Filter the `groups` block server-side by lifecycle phase. Accepts a single value, a comma-separated string (e.g. `"runtime,development"`), or an array of strings. Canonical values: `runtime`, `development`, `build`, `peer`, `optional`. Uppercase is tolerated. When the filter matches nothing the response still includes `groups: { items: [] }` so you can tell an empty-match apart from a registry that has no groups concept.'),
6570
+ include_transitive: z8.boolean().optional().describe("When true the response gains a `transitive` block with aggregate counts (`edges`, `uniquePackages`), the preprocessed `packages[]` list (each `{name, version}` — the complete install footprint), plus typed `conflicts[]` (`{name, requiredVersions}`) and `circularDependencies[]` (`{cycle: string[]}`) when the backend reported any. Off by default."),
6571
+ include_importers: z8.boolean().optional().describe("Requires `include_transitive: true`. When true, each entry in `transitive.packages[]` also carries an `importers` array — every upstream package that pulls it in, with that importer's own resolved version and the constraint it declared. Off by default because adding provenance roughly quadruples the envelope size on heavy graphs. Turn on when you need to trace why a specific transitive dep is present."),
6572
+ max_depth: z8.number().int().min(1).max(10).optional().describe("Cap the transitive traversal at this depth (1–10). Omit to get the backend's full graph. Requires `include_transitive: true` — passing `max_depth` without the transitive flag is rejected with `INVALID_ARGUMENT`.")
6209
6573
  };
6210
- var DESCRIPTION6 = "Analyze a package's dependency graph. The response always includes " + "a `runtime` block listing the direct runtime dependencies as " + "`{name, version, constraint}` records (the backend resolves each " + "constraint to a concrete version for you). It also always includes " + "a structured `groups` block whenever the backend returns group " + "metadata — one group per lifecycle (`runtime`, `development`, " + "`build`, `peer`, `optional`) plus feature-conditional groups for " + "registries that have them (PyPI extras, Crates features). Use " + "`lifecycle` to filter `groups` server-side. Set " + "`include_transitive: true` to add a `transitive` block with the " + "full install footprint, conflict detection, and circular-" + "dependency flags; layer `include_importers: true` on top when you " + "also need per-package provenance. Supports npm, PyPI, Hex, Crates, " + "vcpkg, and Zig.";
6574
+ var DESCRIPTION7 = "Analyze a package's dependency graph. The response always includes " + "a `runtime` block listing the direct runtime dependencies as " + "`{name, version, constraint}` records (the backend resolves each " + "constraint to a concrete version for you). It also always includes " + "a structured `groups` block whenever the backend returns group " + "metadata — one group per lifecycle (`runtime`, `development`, " + "`build`, `peer`, `optional`) plus feature-conditional groups for " + "registries that have them (PyPI extras, Crates features). Use " + "`lifecycle` to filter `groups` server-side. Set " + "`include_transitive: true` to add a `transitive` block with the " + "full install footprint, conflict detection, and circular-" + "dependency flags; layer `include_importers: true` on top when you " + "also need per-package provenance. Supports npm, PyPI, Hex, Crates, " + "vcpkg, and Zig.";
6211
6575
  function createPackageDependenciesTool(service) {
6212
6576
  return {
6213
- name: "package_dependencies",
6214
- description: DESCRIPTION6,
6215
- schema: schema6,
6577
+ name: "pkg_deps",
6578
+ description: DESCRIPTION7,
6579
+ schema: schema7,
6216
6580
  annotations: { readOnlyHint: true },
6217
6581
  handler: async (args) => {
6218
6582
  try {
@@ -6261,17 +6625,17 @@ function createPackageDependenciesTool(service) {
6261
6625
  };
6262
6626
  }
6263
6627
  // src/tools/package-summary.ts
6264
- import { z as z8 } from "zod";
6265
- var schema7 = {
6266
- registry: z8.string().describe("Package registry. One of: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, packagist."),
6267
- package_name: z8.string().describe("Package name (scoped names ok: @types/node).")
6628
+ import { z as z9 } from "zod";
6629
+ var schema8 = {
6630
+ registry: z9.string().describe("Package registry. One of: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, packagist."),
6631
+ package_name: z9.string().describe("Package name (scoped names ok: @types/node).")
6268
6632
  };
6269
- var DESCRIPTION7 = "Get a package overview — latest version, license, description, " + "repository, downloads, GitHub stars, install command, recent " + "changes, and a count of known vulnerabilities. Use before " + "recommending a package or to orient on what a dependency is. " + "Works across npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, " + "vcpkg, and Zig. Always returns data for the latest published " + "version.";
6633
+ var DESCRIPTION8 = "Get a package overview — latest version, license, description, " + "repository, downloads, GitHub stars, install command, recent " + "changes, and a count of known vulnerabilities. Use before " + "recommending a package or to orient on what a dependency is. " + "Works across npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, " + "vcpkg, and Zig. Always returns data for the latest published " + "version.";
6270
6634
  function createPackageSummaryTool(service) {
6271
6635
  return {
6272
- name: "package_summary",
6273
- description: DESCRIPTION7,
6274
- schema: schema7,
6636
+ name: "pkg_info",
6637
+ description: DESCRIPTION8,
6638
+ schema: schema8,
6275
6639
  annotations: { readOnlyHint: true },
6276
6640
  handler: async (args) => {
6277
6641
  try {
@@ -6303,20 +6667,20 @@ function createPackageSummaryTool(service) {
6303
6667
  };
6304
6668
  }
6305
6669
  // src/tools/package-vulnerabilities.ts
6306
- import { z as z9 } from "zod";
6307
- var schema8 = {
6308
- registry: z9.string().describe("Package registry. Vulnerability data available on npm, pypi, hex, and crates only."),
6309
- package_name: z9.string().describe("Package name (scoped names ok: @types/node)."),
6310
- version: z9.string().optional().describe("Specific version to check. Defaults to latest when omitted."),
6311
- min_severity: z9.string().optional().describe("Only return advisories at or above this severity (`low`, `medium`, `high`, `critical`; uppercase tolerated). Omit to see all, including null-severity advisories."),
6312
- include_withdrawn: z9.boolean().optional().describe("Include retracted advisories (default: false).")
6670
+ import { z as z10 } from "zod";
6671
+ var schema9 = {
6672
+ registry: z10.string().describe("Package registry. Vulnerability data available on npm, pypi, hex, and crates only."),
6673
+ package_name: z10.string().describe("Package name (scoped names ok: @types/node)."),
6674
+ version: z10.string().optional().describe("Specific version to check. Defaults to latest when omitted."),
6675
+ min_severity: z10.string().optional().describe("Only return advisories at or above this severity (`low`, `medium`, `high`, `critical`; uppercase tolerated). Omit to see all, including null-severity advisories."),
6676
+ include_withdrawn: z10.boolean().optional().describe("Include retracted advisories (default: false).")
6313
6677
  };
6314
- var DESCRIPTION8 = "Check known vulnerabilities for a package on npm, PyPI, Hex, or " + "Crates (other registries are not yet supported for vulnerability " + "data). Returns a count summary, each advisory with OSV ID, " + "severity, affected ranges and fix versions, plus suggested " + "upgrade paths. Malicious-package advisories surface in a separate " + "bucket. Pass `version` to inspect a specific release; otherwise " + "the latest is checked. Use `min_severity` to filter to a threshold " + "(`low`, `medium`, `high`, `critical`) and `include_withdrawn` to " + "also see retracted advisories.";
6678
+ var DESCRIPTION9 = "Check known vulnerabilities for a package on npm, PyPI, Hex, or " + "Crates (other registries are not yet supported for vulnerability " + "data). Returns a count summary, each advisory with OSV ID, " + "severity, affected ranges and fix versions, plus suggested " + "upgrade paths. Malicious-package advisories surface in a separate " + "bucket. Pass `version` to inspect a specific release; otherwise " + "the latest is checked. Use `min_severity` to filter to a threshold " + "(`low`, `medium`, `high`, `critical`) and `include_withdrawn` to " + "also see retracted advisories.";
6315
6679
  function createPackageVulnerabilitiesTool(service) {
6316
6680
  return {
6317
- name: "package_vulnerabilities",
6318
- description: DESCRIPTION8,
6319
- schema: schema8,
6681
+ name: "pkg_vulns",
6682
+ description: DESCRIPTION9,
6683
+ schema: schema9,
6320
6684
  annotations: { readOnlyHint: true },
6321
6685
  handler: async (args) => {
6322
6686
  try {
@@ -6353,20 +6717,20 @@ function createPackageVulnerabilitiesTool(service) {
6353
6717
  };
6354
6718
  }
6355
6719
  // src/tools/read-file.ts
6356
- import { z as z10 } from "zod";
6357
- var schema9 = {
6720
+ import { z as z11 } from "zod";
6721
+ var schema10 = {
6358
6722
  target: codeTargetSchema,
6359
- path: z10.string().describe("Path to the file. Package addressing: package-relative. Repo addressing: repo-relative. This is the same `path` key that `list_files` emits for each entry, so the `list_files` → `read_file` chain needs no renaming."),
6360
- start_line: z10.number().optional().describe("Starting line (1-indexed). Omit for the full file from line 1."),
6361
- end_line: z10.number().optional().describe("Ending line (inclusive). Omit for end of file. Must be ≥ `start_line` when both are set."),
6362
- wait_timeout_ms: z10.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`.")
6723
+ path: z11.string().describe("Path to the file. Package addressing: package-relative. Repo addressing: repo-relative. This is the same `path` key that `code_files` emits for each entry, so chaining needs no renaming."),
6724
+ start_line: z11.number().optional().describe("Starting line (1-indexed). Omit for the full file from line 1."),
6725
+ end_line: z11.number().optional().describe("Ending line (inclusive). Omit for end of file. Must be ≥ `start_line` when both are set."),
6726
+ wait_timeout_ms: z11.number().optional().describe("Max milliseconds to wait for indexing (0–60000, default 20000). On an `INDEXING` error envelope, retry with a longer timeout or pass a version from `details.availableVersions`.")
6363
6727
  };
6364
- var DESCRIPTION9 = "Read a file from an indexed dependency. Default returns the full " + "file; use `start_line` / `end_line` for a bounded range. Response: " + "`{path, language, totalLines, startLine, endLine, content, " + "isBinary}`. Binary files set `isBinary: true` and omit `content` — " + "agents branch on the flag rather than checking null. Pass the same " + "`path` emitted by `list_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` " + "(note: `fetchCodeContext` doesn't emit `availableVersions` in " + "details, only `indexingRef`). When the path doesn't resolve the " + "response is a `NOT_FOUND` (or `FILE_NOT_FOUND`) error — call " + "`list_files` to discover the actual paths.";
6728
+ var DESCRIPTION10 = "Read a file from an indexed dependency. Default returns the full " + "file; use `start_line` / `end_line` for a bounded range. Response: " + "`{path, language, totalLines, startLine, endLine, content, " + "isBinary}`. Binary files set `isBinary: true` and omit `content` — " + "agents branch on the flag rather than checking null. 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`. " + "When the path doesn't resolve the response is a `NOT_FOUND` (or " + "`FILE_NOT_FOUND`) error — call `code_files` to discover the " + "actual paths.";
6365
6729
  function createReadFileTool(service) {
6366
6730
  return {
6367
- name: "read_file",
6368
- description: DESCRIPTION9,
6369
- schema: schema9,
6731
+ name: "code_read",
6732
+ description: DESCRIPTION10,
6733
+ schema: schema10,
6370
6734
  annotations: { readOnlyHint: true },
6371
6735
  handler: async (args) => {
6372
6736
  const target = resolveCodeTarget(args.target);
@@ -6401,15 +6765,48 @@ function createReadFileTool(service) {
6401
6765
  }
6402
6766
  };
6403
6767
  }
6768
+ // src/tools/read-package-doc.ts
6769
+ import { z as z12 } from "zod";
6770
+ var schema11 = {
6771
+ page_id: z12.string().describe("Documentation page ID from `docs_list` or `search` results. Pass through unchanged; repo-backed IDs are snapshot-pinned."),
6772
+ start_line: z12.number().optional().describe("Starting line (1-indexed). Omit for the full page. Use with `end_line` to bound how much content the tool returns when a page is large."),
6773
+ end_line: z12.number().optional().describe("Ending line (inclusive). Omit for end of page. Must be ≥ `start_line` when both are set.")
6774
+ };
6775
+ var DESCRIPTION11 = "Read a documentation page by page ID. Works for both hosted/crawled docs and repository-backed docs. " + "Pass `start_line` / `end_line` to fetch only a slice when a page is too long — response carries `totalLines` so you can target the next slice. " + "Repo-backed results additionally include exact file follow-up metadata for `code_read`.";
6776
+ function createReadPackageDocTool(service) {
6777
+ return {
6778
+ name: "docs_read",
6779
+ description: DESCRIPTION11,
6780
+ schema: schema11,
6781
+ annotations: { readOnlyHint: true },
6782
+ handler: async (args) => {
6783
+ try {
6784
+ const build = buildReadPackageDocParams({ pageId: args.page_id });
6785
+ const result = await service.readPackageDoc(build.params);
6786
+ const range = args.start_line !== undefined || args.end_line !== undefined ? { startLine: args.start_line, endLine: args.end_line } : undefined;
6787
+ const payload = buildReadPackageDocSuccessPayload(result, build.params.pageId, range);
6788
+ return textResult(JSON.stringify(payload));
6789
+ } catch (error2) {
6790
+ const mapped = mapPackageIntelligenceError(error2);
6791
+ return errorResult(JSON.stringify({
6792
+ error: mapped.message,
6793
+ code: mapped.code,
6794
+ retryable: mapped.retryable ?? false,
6795
+ ...mapped.details ? { details: mapped.details } : {}
6796
+ }));
6797
+ }
6798
+ }
6799
+ };
6800
+ }
6404
6801
  // src/tools/search.ts
6405
- import { z as z11 } from "zod";
6406
- var schema10 = {
6407
- query: z11.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."),
6802
+ import { z as z13 } from "zod";
6803
+ var schema12 = {
6804
+ 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."),
6408
6805
  target: codeTargetSchema.optional(),
6409
- targets: z11.array(codeTargetSchema).max(20).optional(),
6410
- sources: z11.array(z11.enum(["docs", "code", "symbol"])).optional().describe("Optional source selection. Omit for backend AUTO."),
6411
- category: z11.enum(["callable", "type", "module", "data", "documentation"]).optional(),
6412
- kind: z11.enum([
6806
+ targets: z13.array(codeTargetSchema).max(20).optional(),
6807
+ sources: z13.array(z13.enum(["docs", "code", "symbol"])).optional().describe("Optional source selection. Omit for backend AUTO."),
6808
+ category: z13.enum(["callable", "type", "module", "data", "documentation"]).optional(),
6809
+ kind: z13.enum([
6413
6810
  "function",
6414
6811
  "method",
6415
6812
  "constructor",
@@ -6439,8 +6836,8 @@ var schema10 = {
6439
6836
  "constant",
6440
6837
  "doc_section"
6441
6838
  ]).optional(),
6442
- path_prefix: z11.string().optional(),
6443
- file_intent: z11.enum([
6839
+ path_prefix: z13.string().optional(),
6840
+ file_intent: z13.enum([
6444
6841
  "production",
6445
6842
  "test",
6446
6843
  "benchmark",
@@ -6449,21 +6846,21 @@ var schema10 = {
6449
6846
  "fixture",
6450
6847
  "build",
6451
6848
  "vendor"
6452
- ]).optional().describe("Optional file-intent filter. When omitted, AUTO/code/symbol searches default to production; explicit docs-only searches omit the filter because docs do not support it."),
6453
- public_only: z11.boolean().optional(),
6454
- name: z11.string().optional(),
6455
- language: z11.string().optional(),
6456
- allow_partial_results: z11.boolean().optional().describe("Default false waits for all sources; if the wait window expires, returns only searchRef/progress. When true, includes hits from sources that finished so far and still returns searchRef for continuation. Partial payloads support normal pagination via nextOffset."),
6457
- limit: z11.coerce.number().int().min(1).max(100).optional(),
6458
- offset: z11.coerce.number().int().min(0).optional(),
6459
- wait_timeout_ms: z11.coerce.number().int().min(0).max(60000).optional()
6849
+ ]).optional().describe("Optional file-intent filter. Omit it to search across all intents; some sources may ignore this filter and report that in sourceStatus."),
6850
+ public_only: z13.boolean().optional(),
6851
+ name: z13.string().optional(),
6852
+ language: z13.string().optional(),
6853
+ allow_partial_results: z13.boolean().optional().describe("Default false waits for all sources; if the wait window expires, returns only searchRef/progress. When true, includes hits from sources that finished so far and still returns searchRef for continuation. Partial payloads support normal pagination via nextOffset."),
6854
+ limit: z13.coerce.number().int().min(1).max(100).optional(),
6855
+ offset: z13.coerce.number().int().min(0).optional(),
6856
+ wait_timeout_ms: z13.coerce.number().int().min(0).max(60000).optional()
6460
6857
  };
6461
- var DESCRIPTION10 = "Search indexed dependency and repository code, docs, and explicit symbols. " + "The query field uses GitHits discovery syntax (AND/OR/parens/qualifiers; see the parameter description). " + "Structured parameters combine with that query using AND semantics. " + "Provide either `target` for one target or `targets` for many. Omit `sources` to use backend AUTO. " + "Results are complete by default; set `allow_partial_results: true` to include available hits while indexing continues. " + "Use `search_status` with that ref to continue.";
6858
+ var DESCRIPTION12 = "Search indexed dependency and repository code, docs, and explicit symbols. " + "Provide either `target` for one target or `targets` for many; omit `sources` to use backend AUTO. " + "The query field uses GitHits discovery syntax (AND/OR/parens/qualifiers; see the parameter description). " + "Structured parameters combine with that query using AND semantics. " + "Results are complete by default if indexing is still running, the response carries a `searchRef` and no hits; pass it to `search_status` to follow up. " + "Set `allow_partial_results: true` to opt into hits from sources that finished while others continue indexing. " + "Each hit's `type` tells you the follow-up tool: `documentation_page` and `repository_doc` → `docs_read` with `locator.pageId`; `repository_code` and `repository_symbol` → `code_read` with `locator.filePath` (and `locator.startLine`/`endLine` when present).";
6462
6859
  function createSearchTool(service) {
6463
6860
  return {
6464
6861
  name: "search",
6465
- description: DESCRIPTION10,
6466
- schema: schema10,
6862
+ description: DESCRIPTION12,
6863
+ schema: schema12,
6467
6864
  annotations: { readOnlyHint: true },
6468
6865
  handler: async (args) => {
6469
6866
  try {
@@ -6480,10 +6877,10 @@ function createSearchTool(service) {
6480
6877
  targets: resolvedTargets?.filter(isResolvedCodeTarget),
6481
6878
  query: args.query,
6482
6879
  sources: args.sources?.map((entry) => entry.toUpperCase()),
6483
- kind: toSearchSymbolsKind(args.kind),
6880
+ kind: toSymbolKind(args.kind),
6484
6881
  category: toSymbolCategory(args.category),
6485
6882
  pathPrefix: args.path_prefix,
6486
- fileIntent: toSearchSymbolsFileIntent(args.file_intent),
6883
+ fileIntent: toFileIntent(args.file_intent),
6487
6884
  publicOnly: args.public_only,
6488
6885
  name: args.name,
6489
6886
  language: args.language,
@@ -6493,7 +6890,7 @@ function createSearchTool(service) {
6493
6890
  waitTimeoutMs: args.wait_timeout_ms
6494
6891
  });
6495
6892
  const outcome = await service.search(built.params);
6496
- const payload = buildUnifiedSearchSuccessPayload(built.params, built.rawQuery, built.compiledQuery, built.defaulted, outcome);
6893
+ const payload = buildUnifiedSearchSuccessPayload(built.params, built.rawQuery, built.compiledQuery, outcome);
6497
6894
  return textResult(JSON.stringify(payload));
6498
6895
  } catch (error2) {
6499
6896
  return errorResult(JSON.stringify(buildUnifiedSearchErrorPayload(error2)));
@@ -6505,45 +6902,36 @@ function isResolvedCodeTarget(target) {
6505
6902
  return !("content" in target);
6506
6903
  }
6507
6904
  // src/tools/search-language.ts
6508
- import { z as z12 } from "zod";
6509
- var schema11 = {
6510
- query: z12.string().min(1).describe('Language name or partial name to search for (e.g., "python", "type", "java")')
6905
+ import { z as z14 } from "zod";
6906
+ var schema13 = {
6907
+ query: z14.string().min(1).describe('Language name or partial name to search for (e.g., "python", "type", "java")')
6511
6908
  };
6512
- var DESCRIPTION11 = `Search for a programming language supported by GitHits.
6513
-
6514
- Use this tool to find the correct language name before calling the search tool.
6515
- Returns up to 5 matching languages.
6516
-
6517
- Args:
6518
- query: Language name or partial name to search for (e.g., "python", "type", "java")
6519
-
6520
- Returns:
6521
- List of matching languages with name and display_name`;
6909
+ var DESCRIPTION13 = `Find the correct language name for \`get_example\` when it is uncertain. Returns up to 5 matching languages by name, display name, or alias.`;
6522
6910
  function createSearchLanguageTool(service) {
6523
6911
  return {
6524
6912
  name: "search_language",
6525
- description: DESCRIPTION11,
6526
- schema: schema11,
6913
+ description: DESCRIPTION13,
6914
+ schema: schema13,
6527
6915
  handler: async (args) => {
6528
6916
  return withErrorHandling("search languages", async () => {
6529
6917
  const allLanguages = await service.getLanguages();
6530
6918
  const result = filterLanguages(allLanguages, args.query);
6531
- return textResult(JSON.stringify(result, null, 2));
6919
+ return textResult(JSON.stringify(result));
6532
6920
  });
6533
6921
  }
6534
6922
  };
6535
6923
  }
6536
6924
  // src/tools/search-status.ts
6537
- import { z as z13 } from "zod";
6538
- var schema12 = {
6539
- search_ref: z13.string().min(1).describe("Search reference returned by search.")
6925
+ import { z as z15 } from "zod";
6926
+ var schema14 = {
6927
+ search_ref: z15.string().min(1).describe("Search reference returned by search.")
6540
6928
  };
6541
- var DESCRIPTION12 = "Check progress, fetch partial hits when the original request used allow_partial_results: true, or fetch final results for a prior unified search. " + "Pass the search_ref returned by `search` when the original request did not complete within the wait window.";
6929
+ var DESCRIPTION14 = "Check progress, fetch partial hits when the original request used allow_partial_results: true, or fetch final results for a prior unified search. " + "Pass the search_ref returned by `search` when the original request did not complete within the wait window.";
6542
6930
  function createSearchStatusTool(service) {
6543
6931
  return {
6544
6932
  name: "search_status",
6545
- description: DESCRIPTION12,
6546
- schema: schema12,
6933
+ description: DESCRIPTION14,
6934
+ schema: schema14,
6547
6935
  annotations: { readOnlyHint: true },
6548
6936
  handler: async (args) => {
6549
6937
  try {
@@ -6556,79 +6944,27 @@ function createSearchStatusTool(service) {
6556
6944
  }
6557
6945
  };
6558
6946
  }
6559
- // src/tools/search-symbols.ts
6560
- import { z as z14 } from "zod";
6561
- var schema13 = {
6562
- target: codeTargetSchema,
6563
- query: z14.string().max(500).optional().describe("Search keywords - exact source tokens, not natural language. Required if keywords not provided."),
6564
- keywords: z14.array(z14.string()).max(20).optional().describe("Search keywords (max 20). Combined using match_mode. Can be used alone or together with query."),
6565
- match_mode: z14.enum(["or", "and"]).optional().describe("How to combine keywords: or (any match) or and (all match)"),
6566
- category: z14.enum(["callable", "type", "module", "data", "documentation"]).optional().describe("Broad symbol category filter — the preferred surface for filtering. callable (function/method/constructor/getter/setter/operator), type (class/interface/trait/struct/enum/record/protocol/extension/etc.), module (module/namespace/package/object), data (field/property/event/constant), documentation (doc_section)."),
6567
- kind: z14.enum([
6568
- "function",
6569
- "method",
6570
- "constructor",
6571
- "getter",
6572
- "setter",
6573
- "operator",
6574
- "class",
6575
- "interface",
6576
- "trait",
6577
- "struct",
6578
- "enum",
6579
- "record",
6580
- "protocol",
6581
- "extension",
6582
- "delegate",
6583
- "mixin",
6584
- "actor",
6585
- "annotation",
6586
- "type",
6587
- "module",
6588
- "namespace",
6589
- "package",
6590
- "object",
6591
- "field",
6592
- "property",
6593
- "event",
6594
- "constant",
6595
- "doc_section"
6596
- ]).optional().describe("Precise symbol kind filter. Prefer `category` for broad filtering; use `kind` only when you need a specific construct (e.g. trait vs interface)."),
6597
- file_path: z14.string().optional().describe("Filter results to files whose path starts with this value (e.g., 'src/' for a directory)"),
6598
- limit: z14.coerce.number().int().min(1).max(50).optional().describe("Max results to return (max 50)"),
6599
- file_intent: z14.enum([
6600
- "production",
6601
- "test",
6602
- "benchmark",
6603
- "example",
6604
- "generated",
6605
- "fixture",
6606
- "build",
6607
- "vendor",
6608
- "all"
6609
- ]).optional().describe("File intent filter. Defaults to 'production' so top results are production source. Pass 'all' to include tests, benchmarks, examples, and other non-production files."),
6610
- wait_timeout_ms: z14.coerce.number().int().min(0).max(60000).optional().describe("Max milliseconds to wait for indexing before returning. Defaults to 20000 (20 seconds) to cover typical indexing (p50 ~11s). On an INDEXING response, retry with wait_timeout_ms up to 60000 to block until ready.")
6611
- };
6612
- var DESCRIPTION13 = "Search a dependency's source code by exact-token matches. Use for symbol lookup, not natural-language questions. " + 'Prefer unified `search` with `sources:["symbol"]` for new symbol-shaped workflows; use `search_symbols` when you specifically need this dedicated legacy contract. ' + "Package scope: pass target.registry + target.package_name. Repo scope: pass target.repo_url + target.git_ref. " + "`file_intent` defaults to 'production' so top results are production source; pass 'all' to include tests, examples, benchmarks. " + "`query` and `keywords` can combine; `match_mode` controls AND vs OR across keywords. " + "Filter by `category` (broad: callable/type/module/data/documentation — preferred) or `kind` (precise construct like trait/record/namespace). " + "Prefer `file_path` to scope to a directory (e.g., 'src/'). " + "Responses include each match's source code, line range, and unified `kind`/`category` classification. " + "If the response is an INDEXING error, the package is being indexed on-demand — retry the same call with `wait_timeout_ms: 60000` to block until ready.";
6613
6947
  // src/commands/mcp-instructions.ts
6614
6948
  var CORE_BLOCK = `GitHits surfaces verified, canonical code examples from global open source. Use it when you're stuck, the user is frustrated by repeated failed attempts, you need up-to-date API usage, or the user mentions GitHits.
6615
6949
 
6616
- Workflow: call \`search_language\` first if the language name is uncertain call \`get_example\` with one focused question send \`feedback\` on the returned solution_id so quality improves. Each search addresses a single issue; reuse context from prior results before re-searching.`;
6950
+ Workflow: call \`get_example\` with one focused question, optionally passing \`language\` when the desired language is known; call \`search_language\` first only if you need to force a language and the exact name is uncertain. Send \`feedback\` on the returned solution_id so quality improves. Each search addresses a single issue; reuse context from prior results before re-searching.`;
6617
6951
  var PACKAGE_TOOLS_PREAMBLE = `Package tools work with third-party dependency source plus registry metadata. Use them when a stack trace points into a dependency, you need to verify how a library actually works, or you're evaluating whether to add or upgrade a package.
6618
6952
 
6619
6953
  Package spec: \`registry:name[@version]\`.`;
6620
- var PACKAGE_SUMMARY_BULLET = "- `package_summary` — instant package overview: latest version, license, downloads, quickstart, and active advisory count.";
6621
- var PACKAGE_VULNERABILITIES_BULLET = "- `package_vulnerabilities` — known CVE / OSV advisories for npm, PyPI, Hex, or Crates packages (optionally pinned to `@version`). Malicious-package advisories surface in a disjoint `malware` bucket; filter with `min_severity` or include retracted advisories with `include_withdrawn`.";
6622
- var PACKAGE_DEPENDENCIES_BULLET = "- `package_dependencies` — direct runtime deps plus, when the backend has them, dev / peer / optional / feature groups. Pass `lifecycle` to filter groups server-side, `include_transitive` for the full graph, and `include_importers` when you also need per-package provenance. Supports npm, PyPI, Hex, Crates, vcpkg, and Zig.";
6623
- var PACKAGE_CHANGELOG_BULLET = "- `package_changelog` — release notes for a package or GitHub repo, newest-first. Default latest mode returns the 10 most recent entries with full markdown bodies; `from_version` switches to range mode between two versions. Addressable via `registry` + `package_name` or `repo_url`. Set `include_bodies: false` for a version / date / URL timeline when bodies aren't needed.";
6624
- var SEARCH_BULLET = "- `search` — unified search across indexed dependency code, docs, and explicit symbols. Structured fields are the primary UX; omit `sources` for AUTO. Production file intent is applied by default where supported, but some sources may ignore that filter and report it in `sourceStatus`. Returns only trustworthy complete results by default; opt into partial hits with `allow_partial_results: true`. If indexing is still in progress, the response carries a `searchRef`.";
6954
+ var PKG_INFO_BULLET = "- `pkg_info` — instant package overview: latest version, license, downloads, quickstart, and active advisory count.";
6955
+ var DOCS_LIST_BULLET = "- `docs_list` — browse mixed package documentation pages from hosted docs and repository-backed docs. Each entry includes a stable pageId, source kind, source URL, and for repo docs exact file follow-up metadata.";
6956
+ var DOCS_READ_BULLET = "- `docs_read` — read a documentation page by pageId. Works for both hosted docs and repo-backed docs.";
6957
+ var PKG_VULNS_BULLET = "- `pkg_vulns` — known CVE / OSV advisories for npm, PyPI, Hex, or Crates packages (optionally pinned to `@version`). Malicious-package advisories surface in a disjoint `malware` bucket; filter with `min_severity` or include retracted advisories with `include_withdrawn`.";
6958
+ var PKG_DEPS_BULLET = "- `pkg_deps` — direct runtime deps plus, when the backend has them, dev / peer / optional / feature groups. Pass `lifecycle` to filter groups server-side, `include_transitive` for the full graph, and `include_importers` when you also need per-package provenance. Supports npm, PyPI, Hex, Crates, vcpkg, and Zig.";
6959
+ var PKG_CHANGELOG_BULLET = "- `pkg_changelog` — release notes for a package or GitHub repo, newest-first. Default latest mode returns the 10 most recent entries with full markdown bodies; `from_version` switches to range mode between two versions. Addressable via `registry` + `package_name` or `repo_url`. Set `include_bodies: false` for a version / date / URL timeline when bodies aren't needed.";
6960
+ var SEARCH_BULLET = "- `search` — unified search across indexed dependency code, docs, and explicit symbols. Structured fields are the primary UX; omit `sources` for AUTO. Returns only trustworthy complete results by default; opt into partial hits with `allow_partial_results: true`. If indexing is still in progress, the response carries a `searchRef`.";
6625
6961
  var SEARCH_STATUS_BULLET = "- `search_status` — follow up a prior unified search by `searchRef`. Use it after `search` returns incomplete state to check progress, fetch partial hits when the original request used `allow_partial_results: true`, or fetch final results.";
6626
- var LIST_FILES_BULLET = "- `list_files` — discover what files a dependency ships. Use `path_prefix` to scope to a subdirectory; the response includes each file's language, type, and byte size. Returned `path` values feed directly into `read_file` and help scope `grep_repo`.";
6627
- var READ_FILE_BULLET = "- `read_file` — fetch a file's contents from a dependency. Pass the same `path` emitted by `list_files`. Default returns the full file; pass `start_line` / `end_line` for a bounded range. Binary files set `isBinary: true` and omit `content` — branch on the flag, not the null. A `FILE_NOT_FOUND` (or `NOT_FOUND`) response is the signal to call `list_files` for the actual path.";
6628
- var GREP_REPO_BULLET = "- `grep_repo` — deterministic text grep over indexed source files. Use it when you know the exact text or regex to match; use `search` for discovery. Whole-target grep is the default; narrow with `path`, `path_prefix`, `globs`, or `extensions`. Returned `matches[].filePath` feeds directly into `read_file`.";
6629
- var SEARCH_VS_SYMBOLS_TIP = 'Prefer `get_example` for canonical example retrieval; prefer unified `search` for indexed dependency and repository discovery; use `sources:["symbol"]` when you want symbol-shaped results. Use `grep_repo` for deterministic text matching and `read_file` for full-file inspection.';
6962
+ var CODE_FILES_BULLET = "- `code_files` — discover what files a dependency ships. Use `path_prefix` to scope to a subdirectory; the response includes each file's language, type, and byte size. Returned `path` values feed directly into `code_read` and help scope `code_grep`.";
6963
+ var CODE_READ_BULLET = "- `code_read` — fetch a file's contents from a dependency. Pass the same `path` emitted by `code_files`. Default returns the full file; pass `start_line` / `end_line` for a bounded range. Binary files set `isBinary: true` and omit `content` — branch on the flag. A `FILE_NOT_FOUND` (or `NOT_FOUND`) response is the signal to call `code_files` for the actual path.";
6964
+ var CODE_GREP_BULLET = "- `code_grep` — deterministic text grep over indexed source files. Use it when you know the exact text or regex to match; use `search` for discovery. Whole-target grep is the default; narrow with `path`, `path_prefix`, `globs`, or `extensions`. Returned `matches[].filePath` feeds directly into `code_read`.";
6965
+ var SEARCH_VS_SYMBOLS_TIP = 'Prefer `get_example` for canonical example retrieval; prefer unified `search` for indexed dependency and repository discovery; use `sources:["symbol"]` when you want symbol-shaped results. Use `code_grep` for deterministic text matching and `code_read` for full-file inspection.';
6630
6966
  function isPackageToolsCapabilityOpen(deps) {
6631
- return deps.codeNavigationCapability === "enabled";
6967
+ return deps.codeNavigationCliOverrideEnabled || deps.codeNavigationCapability === "enabled" || deps.codeNavigationCapability === "unknown" && deps.envApiToken !== undefined;
6632
6968
  }
6633
6969
  function buildMcpInstructions(deps) {
6634
6970
  const sections = [CORE_BLOCK];
@@ -6639,17 +6975,19 @@ function buildMcpInstructions(deps) {
6639
6975
  }
6640
6976
  const bullets = [];
6641
6977
  if (deps.packageIntelligenceService) {
6642
- bullets.push(PACKAGE_SUMMARY_BULLET);
6643
- bullets.push(PACKAGE_VULNERABILITIES_BULLET);
6644
- bullets.push(PACKAGE_DEPENDENCIES_BULLET);
6645
- bullets.push(PACKAGE_CHANGELOG_BULLET);
6978
+ bullets.push(DOCS_LIST_BULLET);
6979
+ bullets.push(DOCS_READ_BULLET);
6980
+ bullets.push(PKG_INFO_BULLET);
6981
+ bullets.push(PKG_VULNS_BULLET);
6982
+ bullets.push(PKG_DEPS_BULLET);
6983
+ bullets.push(PKG_CHANGELOG_BULLET);
6646
6984
  }
6647
6985
  if (deps.codeNavigationService) {
6648
6986
  bullets.push(SEARCH_BULLET);
6649
6987
  bullets.push(SEARCH_STATUS_BULLET);
6650
- bullets.push(LIST_FILES_BULLET);
6651
- bullets.push(READ_FILE_BULLET);
6652
- bullets.push(GREP_REPO_BULLET);
6988
+ bullets.push(CODE_FILES_BULLET);
6989
+ bullets.push(CODE_READ_BULLET);
6990
+ bullets.push(CODE_GREP_BULLET);
6653
6991
  }
6654
6992
  if (bullets.length === 0) {
6655
6993
  return sections.join(`
@@ -6672,26 +7010,34 @@ function buildMcpInstructions(deps) {
6672
7010
  // src/commands/mcp.ts
6673
7011
  function getMcpToolDefinitions(deps) {
6674
7012
  const tools = [
6675
- createGetExampleTool(deps.githitsService),
6676
- createSearchLanguageTool(deps.githitsService),
6677
- createFeedbackTool(deps.githitsService)
7013
+ eraseTool(createGetExampleTool(deps.githitsService)),
7014
+ eraseTool(createSearchLanguageTool(deps.githitsService)),
7015
+ eraseTool(createFeedbackTool(deps.githitsService))
6678
7016
  ];
6679
7017
  const gateOpen = isPackageToolsCapabilityOpen(deps);
6680
7018
  if (gateOpen && deps.codeNavigationService) {
6681
- tools.push(createSearchTool(deps.codeNavigationService));
6682
- tools.push(createSearchStatusTool(deps.codeNavigationService));
6683
- tools.push(createListFilesTool(deps.codeNavigationService));
6684
- tools.push(createReadFileTool(deps.codeNavigationService));
6685
- tools.push(createGrepRepoTool(deps.codeNavigationService));
7019
+ tools.push(eraseTool(createSearchTool(deps.codeNavigationService)));
7020
+ tools.push(eraseTool(createSearchStatusTool(deps.codeNavigationService)));
7021
+ tools.push(eraseTool(createListFilesTool(deps.codeNavigationService)));
7022
+ tools.push(eraseTool(createReadFileTool(deps.codeNavigationService)));
7023
+ tools.push(eraseTool(createGrepRepoTool(deps.codeNavigationService)));
6686
7024
  }
6687
7025
  if (gateOpen && deps.packageIntelligenceService) {
6688
- tools.push(createPackageSummaryTool(deps.packageIntelligenceService));
6689
- tools.push(createPackageVulnerabilitiesTool(deps.packageIntelligenceService));
6690
- tools.push(createPackageDependenciesTool(deps.packageIntelligenceService));
6691
- tools.push(createPackageChangelogTool(deps.packageIntelligenceService));
7026
+ tools.push(eraseTool(createListPackageDocsTool(deps.packageIntelligenceService)));
7027
+ tools.push(eraseTool(createReadPackageDocTool(deps.packageIntelligenceService)));
7028
+ tools.push(eraseTool(createPackageSummaryTool(deps.packageIntelligenceService)));
7029
+ tools.push(eraseTool(createPackageVulnerabilitiesTool(deps.packageIntelligenceService)));
7030
+ tools.push(eraseTool(createPackageDependenciesTool(deps.packageIntelligenceService)));
7031
+ tools.push(eraseTool(createPackageChangelogTool(deps.packageIntelligenceService)));
6692
7032
  }
6693
7033
  return tools;
6694
7034
  }
7035
+ function eraseTool(tool) {
7036
+ return {
7037
+ ...tool,
7038
+ handler: (args, extra) => tool.handler(args, extra)
7039
+ };
7040
+ }
6695
7041
  function createMcpServer(deps) {
6696
7042
  const server = new McpServer({
6697
7043
  name: "githits",
@@ -7169,16 +7515,8 @@ function registerPkgVulnsCommand(pkgCommand) {
7169
7515
 
7170
7516
  // src/commands/pkg/index.ts
7171
7517
  async function registerPkgCommandGroup(program, options = {}) {
7172
- const codeNavigationUrl = options.codeNavigationUrl ?? getCodeNavigationUrl();
7173
- if (!codeNavigationUrl) {
7174
- return;
7175
- }
7176
- const overrideEnabled = options.overrideEnabled ?? isCodeNavigationCliOverrideEnabled();
7177
- const registrationState = options.capability !== undefined || options.expiredStoredAuth !== undefined ? {
7178
- capability: options.capability ?? "unknown",
7179
- expiredStoredAuth: options.expiredStoredAuth ?? false
7180
- } : await resolveStartupCodeNavigationRegistrationState();
7181
- if (!overrideEnabled && registrationState.capability !== "enabled" && !registrationState.expiredStoredAuth) {
7518
+ const registration = await resolveGatedCommandGroupRegistrationState(options);
7519
+ if (!registration.shouldRegister) {
7182
7520
  return;
7183
7521
  }
7184
7522
  const pkgCommand = program.command("pkg").summary("Package metadata: info, vulnerabilities, dependencies, changelog").description("Inspect package metadata from npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, vcpkg, and Zig: overviews, advisories, dependency graphs, and changelogs. For source-level operations inside a dependency, use `githits code`.");
@@ -7187,56 +7525,6 @@ async function registerPkgCommandGroup(program, options = {}) {
7187
7525
  registerPkgDepsCommand(pkgCommand);
7188
7526
  registerPkgChangelogCommand(pkgCommand);
7189
7527
  }
7190
- // src/commands/example.ts
7191
- import { Option as Option2 } from "commander";
7192
- async function exampleAction(query, options, deps) {
7193
- requireAuth(deps);
7194
- try {
7195
- const result = await deps.githitsService.search({
7196
- query,
7197
- language: options.lang,
7198
- licenseMode: options.license,
7199
- includeExplanation: options.explain
7200
- });
7201
- if (options.json) {
7202
- console.log(JSON.stringify({ result }));
7203
- } else {
7204
- console.log(result);
7205
- }
7206
- } catch (error2) {
7207
- console.error(`Failed to get example: ${error2 instanceof Error ? error2.message : error2}`);
7208
- process.exit(1);
7209
- }
7210
- }
7211
- var EXAMPLE_DESCRIPTION = `Get verified, canonical code examples from global open source.
7212
-
7213
- This is the GitHits example-search surface. For dependency/package/repo source search,
7214
- use
7215
- githits search
7216
-
7217
- instead.
7218
-
7219
- Examples:
7220
- githits example "how to use express middleware" --lang javascript
7221
- githits example "async file reading" -l python --license yolo
7222
- githits example "react hooks patterns" -l typescript --explain
7223
- githits example "react hooks patterns" -l typescript --json`;
7224
- function registerExampleCommand(program) {
7225
- program.command("example").summary("Get code examples from global open source").description(EXAMPLE_DESCRIPTION).argument("<query>", "Natural language example-search query").requiredOption("-l, --lang <language>", "Programming language").addOption(new Option2("--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) => {
7226
- try {
7227
- const deps = await loadContainer();
7228
- await exampleAction(query, options, deps);
7229
- } catch (error2) {
7230
- if (error2 instanceof AuthRequiredError)
7231
- process.exit(1);
7232
- throw error2;
7233
- }
7234
- });
7235
- }
7236
- async function loadContainer() {
7237
- const { createContainer: createContainer2 } = await import("./shared/chunk-cdx2gnrs.js");
7238
- return createContainer2();
7239
- }
7240
7528
  // src/commands/search.ts
7241
7529
  import { Option as Option3 } from "commander";
7242
7530
  async function searchAction(query, options, deps) {
@@ -7247,20 +7535,20 @@ async function searchAction(query, options, deps) {
7247
7535
  targets: parseTargetSpecs(options.in),
7248
7536
  query,
7249
7537
  sources: parseSources(options.source),
7250
- kind: toSearchSymbolsKind(options.kind),
7538
+ kind: toSymbolKind(options.kind),
7251
7539
  category: toSymbolCategory(options.category),
7252
7540
  pathPrefix: options.pathPrefix,
7253
- fileIntent: toSearchSymbolsFileIntent(options.intent),
7541
+ fileIntent: toFileIntent(options.intent),
7254
7542
  publicOnly: options.public,
7255
7543
  name: options.name,
7256
7544
  language: options.lang,
7257
7545
  allowPartialResults: options.allowPartial,
7258
- limit: parseOptionalInt2(options.limit, "--limit", 1, 100),
7259
- offset: parseOptionalInt2(options.offset, "--offset", 0),
7546
+ limit: parseOptionalInt(options.limit, "--limit", 1, 100),
7547
+ offset: parseOptionalInt(options.offset, "--offset", 0),
7260
7548
  waitTimeoutMs: parseWaitMs(options.wait)
7261
7549
  });
7262
7550
  const outcome = await service.search(built.params);
7263
- const payload = buildUnifiedSearchSuccessPayload(built.params, built.rawQuery, built.compiledQuery, built.defaulted, outcome);
7551
+ const payload = buildUnifiedSearchSuccessPayload(built.params, built.rawQuery, built.compiledQuery, outcome);
7264
7552
  if (options.json) {
7265
7553
  console.log(JSON.stringify(payload));
7266
7554
  return;
@@ -7298,32 +7586,16 @@ async function searchStatusAction(searchRef, options, deps) {
7298
7586
  }
7299
7587
  var SEARCH_DESCRIPTION = `Search code, docs, and symbols across indexed dependencies and repositories.
7300
7588
 
7301
- Use repeatable --in targets in package form (npm:express[@version]) or repo
7302
- form (https://github.com/org/repo[#ref]). Structured flags are AND-combined with
7303
- the discovery query. Unified search defaults to production file intent where the
7304
- backend supports that filter. Results are complete-by-default: if indexing is
7305
- still in progress, search returns a searchRef instead of partial hits unless
7306
- --allow-partial is passed.
7307
-
7308
- Query syntax:
7309
- implicit AND foo bar
7310
- OR foo OR bar (must be uppercase)
7311
- grouping (foo OR bar) baz
7312
- exclude foo -bar
7313
- phrase "exact phrase"
7314
- qualifiers kind: category: path: lang: name: intent:
7315
- routing registry: package: version: repo:
7589
+ Repeatable --in targets accept package form (npm:express[@version]) or repo
7590
+ form (https://github.com/org/repo[#ref]). Structured flags are AND-combined
7591
+ with the query. Complete by default if indexing is still running, returns
7592
+ a searchRef instead of partial hits unless --allow-partial is passed. Use
7593
+ \`githits example\` for canonical cross-project examples; \`--source symbol\`
7594
+ here returns symbol-shaped hits.
7316
7595
 
7317
- Decision guide:
7318
- githits example ... canonical cross-project examples
7319
- githits search ... indexed dependency/repository search
7320
- githits search --source symbol ... symbol-shaped unified search
7321
-
7322
- Plain output labels:
7323
- docs page hosted documentation page
7324
- repo doc documentation-like block from a repository file
7325
- repo code code block from a repository file
7326
- repo symbol explicit symbol hit from the repository index
7596
+ The query supports implicit AND, uppercase OR, parens, unary -, "phrases",
7597
+ and qualifiers (kind:, category:, path:, lang:, name:, intent:, registry:,
7598
+ package:, version:, repo:).
7327
7599
 
7328
7600
  Examples:
7329
7601
  githits search "router middleware" --in npm:express
@@ -7337,9 +7609,9 @@ Pass the searchRef returned by githits search when the initial request could
7337
7609
  not complete within the wait window. This can return progress, partial hits when
7338
7610
  the original request used --allow-partial, or final results.`;
7339
7611
  function registerSearchCommand(program) {
7340
- 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([
7612
+ 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]", collectRepeatable2, []).addOption(new Option3("--source <source>", "Source to search (repeatable; default: auto)").choices(["docs", "code", "symbol"]).argParser((value, previous = []) => collectRepeatable2(value.toLowerCase(), previous)).default(undefined)).addOption(new Option3("--kind <kind>", "Precise symbol kind filter").choices([
7341
7613
  ...knownSymbolKindList()
7342
- ])).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 (default: production for AUTO/code/symbol, omitted for docs-only)").choices([
7614
+ ])).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([
7343
7615
  "production",
7344
7616
  "test",
7345
7617
  "benchmark",
@@ -7379,11 +7651,11 @@ function requireSearchService(deps) {
7379
7651
  return deps.codeNavigationService;
7380
7652
  }
7381
7653
  async function loadContainer2() {
7382
- const { createContainer: createContainer2 } = await import("./shared/chunk-cdx2gnrs.js");
7654
+ const { createContainer: createContainer2 } = await import("./shared/chunk-4drb8s8v.js");
7383
7655
  return createContainer2();
7384
7656
  }
7385
7657
  async function loadStartupCodeNavigationRegistrationState() {
7386
- const { resolveStartupCodeNavigationRegistrationState: resolveStartupCodeNavigationRegistrationState2 } = await import("./shared/chunk-cdx2gnrs.js");
7658
+ const { resolveStartupCodeNavigationRegistrationState: resolveStartupCodeNavigationRegistrationState2 } = await import("./shared/chunk-4drb8s8v.js");
7387
7659
  return resolveStartupCodeNavigationRegistrationState2();
7388
7660
  }
7389
7661
  function parseTargetSpecs(specs) {
@@ -7421,7 +7693,7 @@ function parseSources(values) {
7421
7693
  }
7422
7694
  });
7423
7695
  }
7424
- function parseOptionalInt2(value, flag, min, max = Number.MAX_SAFE_INTEGER) {
7696
+ function parseOptionalInt(value, flag, min, max = Number.MAX_SAFE_INTEGER) {
7425
7697
  if (value === undefined)
7426
7698
  return;
7427
7699
  const parsed = Number.parseInt(value, 10);
@@ -7440,7 +7712,7 @@ function parseWaitMs(value) {
7440
7712
  }
7441
7713
  return seconds * 1000;
7442
7714
  }
7443
- function collectRepeatable3(value, previous) {
7715
+ function collectRepeatable2(value, previous) {
7444
7716
  return [...previous, value];
7445
7717
  }
7446
7718
  function handleSearchError(error2, json, context = "search") {
@@ -7481,7 +7753,7 @@ function formatUnifiedSearchTerminal(payload) {
7481
7753
  lines.push("");
7482
7754
  lines.push("Partial results:");
7483
7755
  }
7484
- const sourceStatusNotes = formatSourceStatusNotes(payload.sourceStatus, payload.query.defaulted);
7756
+ const sourceStatusNotes = formatSourceStatusNotes(payload.sourceStatus);
7485
7757
  if (payload.results.length === 0) {
7486
7758
  lines.push("No results.");
7487
7759
  if (sourceStatusNotes.length > 0) {
@@ -7504,6 +7776,10 @@ function formatUnifiedSearchTerminal(payload) {
7504
7776
  const location = formatUnifiedSearchLocation(entry.locator);
7505
7777
  const header = formatUnifiedSearchHeader(entry, useColors, location);
7506
7778
  lines.push(header);
7779
+ const metadata = formatUnifiedSearchMetadata(entry, useColors);
7780
+ if (metadata.length > 0) {
7781
+ lines.push(...metadata);
7782
+ }
7507
7783
  if (entry.summary) {
7508
7784
  lines.push(...formatUnifiedSearchSummary(entry.summary, entry.highlights?.summary, useColors));
7509
7785
  }
@@ -7560,45 +7836,40 @@ function formatSearchStatusHeadline(status) {
7560
7836
  function formatSearchStatusCompletedTerminal(payload) {
7561
7837
  return formatUnifiedSearchTerminal({
7562
7838
  completed: true,
7563
- returnedCount: payload.result.returnedCount,
7564
7839
  hasMore: payload.result.hasMore,
7565
7840
  nextOffset: payload.result.nextOffset,
7566
7841
  results: payload.result.results,
7567
7842
  searchRef: payload.searchRef,
7568
7843
  progress: undefined,
7569
- query: { warnings: payload.result.queryWarnings },
7844
+ query: { warnings: payload.result.warnings },
7570
7845
  sourceStatus: payload.result.sourceStatus
7571
7846
  });
7572
7847
  }
7573
7848
  function formatSearchStatusPartialTerminal(payload) {
7574
7849
  return formatUnifiedSearchTerminal({
7575
7850
  completed: false,
7576
- returnedCount: payload.result.returnedCount,
7577
7851
  hasMore: payload.result.hasMore,
7578
7852
  nextOffset: payload.result.nextOffset,
7579
7853
  results: payload.result.results,
7580
7854
  searchRef: payload.searchRef,
7581
7855
  progress: payload.progress,
7582
- query: { warnings: payload.result.queryWarnings },
7856
+ query: { warnings: payload.result.warnings },
7583
7857
  sourceStatus: payload.result.sourceStatus
7584
7858
  });
7585
7859
  }
7586
- function formatSourceStatusNotes(sourceStatus, defaulted) {
7860
+ function formatSourceStatusNotes(sourceStatus) {
7587
7861
  const useColors = shouldUseColors();
7588
7862
  if (!sourceStatus) {
7589
7863
  return [];
7590
7864
  }
7591
- const defaultedSet = new Set(defaulted ?? []);
7592
7865
  const lines = [];
7593
7866
  for (const entry of sourceStatus) {
7594
7867
  const label = `${entry.source.toLowerCase()} on ${entry.targetLabel}`;
7595
- const ignoredFilters = entry.ignoredFilters.filter((name) => !defaultedSet.has(name));
7596
- if (ignoredFilters.length > 0) {
7597
- lines.push(dim(`Note: ${label} ignored filters: ${ignoredFilters.join(", ")}`, useColors));
7868
+ if (entry.ignoredFilters && entry.ignoredFilters.length > 0) {
7869
+ lines.push(dim(`Note: ${label} ignored filters: ${entry.ignoredFilters.join(", ")}`, useColors));
7598
7870
  }
7599
- const incompatibleFilters = entry.incompatibleFilters.filter((name) => !defaultedSet.has(name));
7600
- if (incompatibleFilters.length > 0) {
7601
- lines.push(dim(`Note: ${label} incompatible filters: ${incompatibleFilters.join(", ")}`, useColors));
7871
+ if (entry.incompatibleFilters && entry.incompatibleFilters.length > 0) {
7872
+ lines.push(dim(`Note: ${label} incompatible filters: ${entry.incompatibleFilters.join(", ")}`, useColors));
7602
7873
  }
7603
7874
  if (entry.ignoredQueryFeatures && entry.ignoredQueryFeatures.length > 0) {
7604
7875
  lines.push(dim(`Note: ${label} ignored query features: ${entry.ignoredQueryFeatures.join(", ")}`, useColors));
@@ -7606,6 +7877,9 @@ function formatSourceStatusNotes(sourceStatus, defaulted) {
7606
7877
  if (entry.incompatibleQueryFeatures && entry.incompatibleQueryFeatures.length > 0) {
7607
7878
  lines.push(dim(`Note: ${label} incompatible query features: ${entry.incompatibleQueryFeatures.join(", ")}`, useColors));
7608
7879
  }
7880
+ if (entry.indexingStatus === "INDEXING") {
7881
+ lines.push(dim(`Note: ${label} still indexing — re-run with the searchRef for full results.`, useColors));
7882
+ }
7609
7883
  if (entry.note) {
7610
7884
  lines.push(dim(`Note: ${label}: ${entry.note}`, useColors));
7611
7885
  }
@@ -7623,11 +7897,12 @@ function dedupeSearchResultsForDisplay(results) {
7623
7897
  entry.title ?? "",
7624
7898
  (entry.summary ?? "").slice(0, 120)
7625
7899
  ].join("\x01");
7626
- if (seen.has(key)) {
7900
+ const dedupeKey = `${key}\x01${entry.locator.pageId ?? entry.locator.filePath ?? ""}`;
7901
+ if (seen.has(dedupeKey)) {
7627
7902
  duplicatesFolded += 1;
7628
7903
  continue;
7629
7904
  }
7630
- seen.add(key);
7905
+ seen.add(dedupeKey);
7631
7906
  display.push(entry);
7632
7907
  }
7633
7908
  return { display, duplicatesFolded };
@@ -7682,7 +7957,7 @@ function formatUnifiedSearchSummary(summary, ranges, useColors) {
7682
7957
  }
7683
7958
  function formatUnifiedSearchLocation(locator) {
7684
7959
  if (!locator.filePath) {
7685
- return;
7960
+ return locator.sourceUrl;
7686
7961
  }
7687
7962
  if (!locator.startLine) {
7688
7963
  return locator.filePath;
@@ -7690,11 +7965,29 @@ function formatUnifiedSearchLocation(locator) {
7690
7965
  return `${locator.filePath}:${locator.startLine}${locator.endLine && locator.endLine !== locator.startLine ? `-${locator.endLine}` : ""}`;
7691
7966
  }
7692
7967
  function formatUnifiedSearchHeader(entry, useColors, location) {
7693
- const primary = location ? `${entry.target} ${location}` : entry.target;
7968
+ const primary = entry.type === "documentation_page" ? entry.target : location ? `${entry.target} ${location}` : entry.target;
7694
7969
  const badge = `[${formatUnifiedSearchResultLabel(entry.type)}]`;
7695
7970
  const title = entry.title ? highlightRanges(entry.title, entry.highlights?.title, useColors) : undefined;
7696
7971
  return `${highlight(primary, useColors)} ${dim(badge, useColors)}${title ? ` - ${title}` : ""}`;
7697
7972
  }
7973
+ function formatUnifiedSearchMetadata(entry, useColors) {
7974
+ if (entry.type !== "documentation_page" && entry.type !== "repository_doc") {
7975
+ return [];
7976
+ }
7977
+ const lines = [];
7978
+ if (entry.locator.pageId) {
7979
+ lines.push(` ${dim("pageId:", useColors)} ${entry.locator.pageId}`);
7980
+ }
7981
+ const sourceBadge = entry.locator.sourceKind?.toLowerCase() === "repository" ? "[repo]" : entry.locator.sourceKind?.toLowerCase() === "crawled" ? "[crawled]" : undefined;
7982
+ if (entry.locator.sourceUrl) {
7983
+ lines.push(` ${dim("source:", useColors)} ${sourceBadge ? `${sourceBadge} ` : ""}${entry.locator.sourceUrl}`);
7984
+ }
7985
+ if (entry.type === "repository_doc" && entry.locator.filePath) {
7986
+ const ref = entry.locator.requestedRef ?? entry.locator.gitRef;
7987
+ lines.push(` ${dim("file:", useColors)} ${entry.locator.filePath}${ref ? ` @ ${ref}` : ""}`);
7988
+ }
7989
+ return lines;
7990
+ }
7698
7991
  // src/cli.ts
7699
7992
  var program = new Command;
7700
7993
  var commandSpans = new WeakMap;
@@ -7713,10 +8006,10 @@ program.name("githits").description("Code examples from global open source for y
7713
8006
  endTelemetrySpan(commandSpans.get(actionCommand));
7714
8007
  }).addHelpText("after", `
7715
8008
  Getting started:
7716
- githits init Set up MCP for your coding agents
7717
- githits login Authenticate with your GitHits account
7718
- githits mcp Start MCP server for your AI assistant
7719
- githits example "query" --lang python Get code examples
8009
+ githits init Set up MCP for your coding agents
8010
+ githits login Authenticate with your GitHits account
8011
+ githits mcp Show MCP setup instructions
8012
+ githits example "query" Get code examples
7720
8013
 
7721
8014
  Learn more at https://githits.com
7722
8015
  Docs: https://app.githits.com/docs/
@@ -7729,28 +8022,34 @@ registerExampleCommand(program);
7729
8022
  registerLanguagesCommand(program);
7730
8023
  registerFeedbackCommand(program);
7731
8024
  var argv = process.argv.slice(2);
7732
- var helpInvocation = isHelpInvocation(argv);
7733
- var shouldLoadGatedHelpRegistration = needsGatedHelpRegistration(argv);
7734
- var helpRegistrationOptions = shouldLoadGatedHelpRegistration ? await loadHelpRegistrationOptions() : undefined;
7735
- if (shouldEagerLoadSearchCommands(argv)) {
8025
+ var registrationArgv = stripRootRegistrationOptions(argv);
8026
+ var shouldLoadGatedHelpRegistration = needsGatedHelpRegistration(registrationArgv);
8027
+ var helpRegistrationOptions = shouldLoadGatedHelpRegistration ? await loadHelpRegistrationOptions(registrationArgv) : undefined;
8028
+ if (shouldEagerLoadSearchCommands(registrationArgv)) {
7736
8029
  await withTelemetrySpan("cli.register.search", () => registerUnifiedSearchCommands(program, helpRegistrationOptions));
7737
8030
  }
7738
- if (shouldEagerLoadGatedCommandGroup(argv, "code")) {
8031
+ if (shouldEagerLoadGatedCommandGroup(registrationArgv, "code")) {
7739
8032
  await withTelemetrySpan("cli.register.code-group", () => registerCodeCommandGroup(program, helpRegistrationOptions));
7740
8033
  }
7741
- if (shouldEagerLoadGatedCommandGroup(argv, "pkg")) {
8034
+ if (shouldEagerLoadGatedCommandGroup(registrationArgv, "pkg")) {
7742
8035
  await withTelemetrySpan("cli.register.pkg-group", () => registerPkgCommandGroup(program, helpRegistrationOptions));
7743
8036
  }
8037
+ if (shouldEagerLoadGatedCommandGroup(registrationArgv, "docs")) {
8038
+ await withTelemetrySpan("cli.register.docs-group", () => registerDocsCommandGroup(program, helpRegistrationOptions));
8039
+ }
7744
8040
  var authCommand = program.command("auth").summary("Manage authentication").description("Manage authentication with GitHits.");
7745
8041
  registerAuthStatusCommand(authCommand);
7746
8042
  await withTelemetrySpan("cli.parse", () => program.parseAsync());
8043
+ function stripRootRegistrationOptions(args) {
8044
+ return args.filter((arg) => arg !== "--no-color");
8045
+ }
7747
8046
  function shouldEagerLoadGatedCommandGroup(args, groupName) {
7748
8047
  const [firstArg] = args;
7749
- return firstArg === groupName || firstArg === "help" && args[1] === groupName;
8048
+ return args.length === 0 || firstArg === groupName || firstArg === "help" && (!args[1] || args[1] === groupName) || firstArg === "--help" || firstArg === "-h";
7750
8049
  }
7751
8050
  function shouldEagerLoadSearchCommands(args) {
7752
8051
  const [firstArg] = args;
7753
- return firstArg === "search" || firstArg === "search-status" || firstArg === "help" && isSearchHelpTarget(args[1]);
8052
+ return args.length === 0 || firstArg === "search" || firstArg === "search-status" || firstArg === "--help" || firstArg === "-h" || firstArg === "help" && (!args[1] || isSearchHelpTarget(args[1]));
7754
8053
  }
7755
8054
  function isHelpInvocation(args) {
7756
8055
  return args.length === 0 || args[0] === "help" || args.includes("--help") || args.includes("-h");
@@ -7761,24 +8060,24 @@ function needsGatedHelpRegistration(args) {
7761
8060
  }
7762
8061
  const [firstArg, secondArg] = args;
7763
8062
  if (firstArg === "help") {
7764
- return isSearchHelpTarget(secondArg) || secondArg === "code" || secondArg === "pkg";
8063
+ return isSearchHelpTarget(secondArg) || secondArg === "code" || secondArg === "pkg" || secondArg === "docs";
7765
8064
  }
7766
- return isSearchHelpTarget(firstArg) || firstArg === "code" || firstArg === "pkg";
8065
+ return isSearchHelpTarget(firstArg) || firstArg === "code" || firstArg === "pkg" || firstArg === "docs";
7767
8066
  }
7768
8067
  function isSearchHelpTarget(value) {
7769
8068
  return value === "search" || value === "search-status";
7770
8069
  }
7771
- async function loadHelpRegistrationOptions() {
7772
- const { resolveStartupCodeNavigationRegistrationState: resolveStartupCodeNavigationRegistrationState2 } = await import("./shared/chunk-cdx2gnrs.js");
8070
+ async function loadHelpRegistrationOptions(args) {
8071
+ const { resolveStartupCodeNavigationRegistrationState: resolveStartupCodeNavigationRegistrationState2 } = await import("./shared/chunk-4drb8s8v.js");
7773
8072
  const registrationState = await resolveStartupCodeNavigationRegistrationState2();
7774
8073
  return {
7775
8074
  capability: registrationState.capability,
7776
- expiredStoredAuth: shouldUseExpiredStoredAuthFallbackForHelp(argv) ? registrationState.expiredStoredAuth : false
8075
+ expiredStoredAuth: shouldUseExpiredStoredAuthFallbackForHelp(args) ? registrationState.expiredStoredAuth : false
7777
8076
  };
7778
8077
  }
7779
8078
  function shouldUseExpiredStoredAuthFallbackForHelp(args) {
7780
8079
  const [firstArg, secondArg] = args;
7781
- return firstArg === "search" || firstArg === "search-status" || firstArg === "code" || firstArg === "pkg" || firstArg === "help" && (isSearchHelpTarget(secondArg) || secondArg === "code" || secondArg === "pkg");
8080
+ return firstArg === "search" || firstArg === "search-status" || firstArg === "code" || firstArg === "pkg" || firstArg === "docs" || firstArg === "help" && (isSearchHelpTarget(secondArg) || secondArg === "code" || secondArg === "pkg" || secondArg === "docs");
7782
8081
  }
7783
8082
  function getTelemetryCommandName(command) {
7784
8083
  const names = [];