githits 0.4.0 → 0.4.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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "GitHits plugins for Claude Code - code examples from global open source",
9
- "version": "0.4.0"
9
+ "version": "0.4.2"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githits",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Code examples from global open source for developers and AI assistants",
5
5
  "author": {
6
6
  "name": "GitHits"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githits",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Code examples from global open source for developers and AI assistants",
5
5
  "author": {
6
6
  "name": "GitHits"
package/dist/cli.js CHANGED
@@ -49,11 +49,11 @@ import {
49
49
  shouldRunUpdateCheck,
50
50
  startTelemetrySpan,
51
51
  withTelemetrySpan
52
- } from "./shared/chunk-1cyka46a.js";
52
+ } from "./shared/chunk-v8sths32.js";
53
53
  import {
54
54
  __require,
55
55
  version
56
- } from "./shared/chunk-fqzth5mv.js";
56
+ } from "./shared/chunk-jdygt0ra.js";
57
57
 
58
58
  // src/cli.ts
59
59
  import { Command } from "commander";
@@ -411,6 +411,19 @@ function dim(text, useColors) {
411
411
  }
412
412
 
413
413
  // src/shared/pkgseer-registry.ts
414
+ var PKGSEER_REGISTRY_ARGS = [
415
+ "npm",
416
+ "pypi",
417
+ "hex",
418
+ "crates",
419
+ "nuget",
420
+ "maven",
421
+ "zig",
422
+ "vcpkg",
423
+ "packagist",
424
+ "rubygems",
425
+ "go"
426
+ ];
414
427
  var registryMap = {
415
428
  npm: "NPM",
416
429
  pypi: "PYPI",
@@ -420,8 +433,11 @@ var registryMap = {
420
433
  maven: "MAVEN",
421
434
  zig: "ZIG",
422
435
  vcpkg: "VCPKG",
423
- packagist: "PACKAGIST"
436
+ packagist: "PACKAGIST",
437
+ rubygems: "RUBYGEMS",
438
+ go: "GO"
424
439
  };
440
+ var PKGSEER_REGISTRY_LIST = PKGSEER_REGISTRY_ARGS.join(", ");
425
441
  function toPkgseerRegistry(registry) {
426
442
  return registryMap[registry];
427
443
  }
@@ -688,22 +704,12 @@ function isInvalidArgumentError(error2) {
688
704
  return error2.name.startsWith("Invalid") || error2.name.startsWith("Unsupported");
689
705
  }
690
706
  // src/shared/package-spec.ts
691
- var KNOWN_REGISTRIES = [
692
- "npm",
693
- "pypi",
694
- "hex",
695
- "crates",
696
- "nuget",
697
- "maven",
698
- "zig",
699
- "vcpkg",
700
- "packagist"
701
- ];
707
+ var KNOWN_REGISTRIES = PKGSEER_REGISTRY_ARGS;
702
708
 
703
709
  class UnsupportedRegistryError extends Error {
704
710
  attempted;
705
711
  constructor(attempted) {
706
- super(`Unsupported registry "${attempted}". Supported: ${KNOWN_REGISTRIES.join(", ")}.`);
712
+ super(`Unsupported registry "${attempted}". Supported: ${PKGSEER_REGISTRY_LIST}.`);
707
713
  this.attempted = attempted;
708
714
  this.name = "UnsupportedRegistryError";
709
715
  }
@@ -1764,7 +1770,7 @@ function buildListPackageDocsParams(input) {
1764
1770
  }
1765
1771
  const registry = input.registry?.trim().toLowerCase() ?? "";
1766
1772
  if (!isKnownPkgseerRegistryArg(registry)) {
1767
- throw new UnsupportedRegistryError(`Unsupported registry '${input.registry}'. Supported: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, packagist.`);
1773
+ throw new UnsupportedRegistryError(`Unsupported registry '${input.registry}'. Supported: ${PKGSEER_REGISTRY_LIST}.`);
1768
1774
  }
1769
1775
  const params = {
1770
1776
  registry: toPkgseerRegistry(registry),
@@ -2012,9 +2018,11 @@ var SUPPORTED_DEPS_REGISTRIES = new Set([
2012
2018
  "HEX",
2013
2019
  "CRATES",
2014
2020
  "VCPKG",
2015
- "ZIG"
2021
+ "ZIG",
2022
+ "RUBYGEMS",
2023
+ "GO"
2016
2024
  ]);
2017
- var SUPPORTED_DEPS_REGISTRIES_HUMAN = "npm, pypi, hex, crates, vcpkg, and zig";
2025
+ var SUPPORTED_DEPS_REGISTRIES_HUMAN = "npm, pypi, hex, crates, vcpkg, zig, rubygems, and go";
2018
2026
  function supportsDependenciesRegistry(registry) {
2019
2027
  return SUPPORTED_DEPS_REGISTRIES.has(registry);
2020
2028
  }
@@ -2025,7 +2033,7 @@ function buildPackageDependenciesParams(input) {
2025
2033
  }
2026
2034
  const normalisedRegistryArg = input.registry?.trim().toLowerCase() ?? "";
2027
2035
  if (!isKnownPkgseerRegistryArg(normalisedRegistryArg)) {
2028
- throw new UnsupportedRegistryError(`Unsupported registry '${input.registry}'. Supported: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, packagist.`);
2036
+ throw new UnsupportedRegistryError(`Unsupported registry '${input.registry}'. Supported: ${PKGSEER_REGISTRY_LIST}.`);
2029
2037
  }
2030
2038
  const registry = toPkgseerRegistry(normalisedRegistryArg);
2031
2039
  if (!supportsDependenciesRegistry(registry)) {
@@ -2831,7 +2839,7 @@ function buildPackageSummaryParams(input) {
2831
2839
  }
2832
2840
  const normalisedRegistry = input.registry?.trim().toLowerCase() ?? "";
2833
2841
  if (!isKnownPkgseerRegistryArg(normalisedRegistry)) {
2834
- throw new UnsupportedRegistryError(`Unsupported registry '${input.registry}'. Supported: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, packagist.`);
2842
+ throw new UnsupportedRegistryError(`Unsupported registry '${input.registry}'. Supported: ${PKGSEER_REGISTRY_LIST}.`);
2835
2843
  }
2836
2844
  return {
2837
2845
  params: {
@@ -3233,7 +3241,7 @@ function buildPackageVulnerabilitiesParams(input) {
3233
3241
  }
3234
3242
  const normalisedRegistryArg = input.registry?.trim().toLowerCase() ?? "";
3235
3243
  if (!isKnownPkgseerRegistryArg(normalisedRegistryArg)) {
3236
- throw new UnsupportedRegistryError(`Unsupported registry '${input.registry}'. Supported: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, packagist.`);
3244
+ throw new UnsupportedRegistryError(`Unsupported registry '${input.registry}'. Supported: ${PKGSEER_REGISTRY_LIST}.`);
3237
3245
  }
3238
3246
  const registry = toPkgseerRegistry(normalisedRegistryArg);
3239
3247
  if (!supportsVulnerabilitiesRegistry(registry)) {
@@ -4168,150 +4176,437 @@ function buildRange2(envelope) {
4168
4176
  return `${envelope.totalLines} lines`;
4169
4177
  return;
4170
4178
  }
4171
- // src/shared/auto-login.ts
4172
- var AUTO_LOGIN_ELIGIBLE_COMMANDS = new Set([
4173
- "example",
4174
- "languages",
4175
- "feedback",
4176
- "search",
4177
- "search-status",
4178
- "code files",
4179
- "code read",
4180
- "code grep",
4181
- "docs list",
4182
- "docs read",
4183
- "pkg info",
4184
- "pkg vulns",
4185
- "pkg deps",
4186
- "pkg changelog"
4187
- ]);
4188
- function getCommandPath(command) {
4189
- const names = [];
4190
- let current = command;
4191
- while (current) {
4192
- const name = current.name();
4193
- if (name && name !== "githits") {
4194
- names.unshift(name);
4195
- }
4196
- current = current.parent ?? null;
4179
+ // src/commands/login.ts
4180
+ var stdoutLoginOutput = {
4181
+ write: (message) => {
4182
+ console.log(message);
4197
4183
  }
4198
- return names;
4199
- }
4200
- function isAutoLoginEligibleCommand(command, runtime = {
4201
- stdinIsTTY: Boolean(process.stdin.isTTY),
4202
- stdoutIsTTY: Boolean(process.stdout.isTTY)
4203
- }) {
4204
- const commandPath = getCommandPath(command).join(" ");
4205
- if (!AUTO_LOGIN_ELIGIBLE_COMMANDS.has(commandPath)) {
4206
- return false;
4184
+ };
4185
+ var stderrLoginOutput = {
4186
+ write: (message) => {
4187
+ console.error(message);
4207
4188
  }
4208
- if (!runtime.stdinIsTTY || !runtime.stdoutIsTTY) {
4209
- return false;
4189
+ };
4190
+ var TIMEOUT_MS = 5 * 60 * 1000;
4191
+ var AUTH_TIMEOUT_MESSAGE = "Authentication timed out after 5 minutes. The browser link has expired, so it will not work anymore. Run the same command again to try signing in again.";
4192
+ function randomPort() {
4193
+ return Math.floor(Math.random() * 2000) + 8000;
4194
+ }
4195
+ async function preflightAuthPersistence(authStorage, mcpUrl) {
4196
+ const probeUrl = `${mcpUrl.replace(/\/+$/, "")}/__githits_storage_probe__`;
4197
+ const probeClient = {
4198
+ clientId: "__githits_storage_probe__",
4199
+ clientSecret: "__githits_storage_probe__",
4200
+ redirectUri: "http://127.0.0.1:1/callback",
4201
+ registeredAt: new Date(0).toISOString()
4202
+ };
4203
+ const probeTokens = {
4204
+ accessToken: "__githits_storage_probe__",
4205
+ refreshToken: "__githits_storage_probe__",
4206
+ expiresAt: new Date(0).toISOString(),
4207
+ createdAt: new Date(0).toISOString()
4208
+ };
4209
+ try {
4210
+ await authStorage.saveAuthSession(probeUrl, probeClient, probeTokens);
4211
+ await authStorage.clearAuthSession(probeUrl);
4212
+ return null;
4213
+ } catch (error2) {
4214
+ await authStorage.clearAuthSession(probeUrl).catch(() => {});
4215
+ const message = error2 instanceof Error ? error2.message : String(error2);
4216
+ return {
4217
+ status: "failed",
4218
+ message: `Cannot persist OAuth credentials: ${message}`
4219
+ };
4210
4220
  }
4211
- return true;
4212
4221
  }
4213
- async function maybeAutoLoginBeforeCommand(command, deps) {
4214
- if (!isAutoLoginEligibleCommand(command, {
4215
- stdinIsTTY: deps.stdinIsTTY ?? Boolean(process.stdin.isTTY),
4216
- stdoutIsTTY: deps.stdoutIsTTY ?? Boolean(process.stdout.isTTY)
4217
- })) {
4218
- return { status: "skipped" };
4222
+ async function loginFlow(options, deps, output = stdoutLoginOutput) {
4223
+ const { authService, authStorage, browserService, mcpUrl } = deps;
4224
+ const existing = await authStorage.loadTokens(mcpUrl);
4225
+ if (options.port !== undefined && (Number.isNaN(options.port) || options.port < 1 || options.port > 65535)) {
4226
+ return {
4227
+ status: "failed",
4228
+ message: "Invalid port number. Must be between 1 and 65535."
4229
+ };
4219
4230
  }
4220
- const container = await deps.createContainer();
4221
- if (container.hasValidToken) {
4222
- return { status: "already-authenticated" };
4231
+ if (existing && !options.force) {
4232
+ const isExpired = existing.expiresAt && new Date(existing.expiresAt) < new Date;
4233
+ if (!isExpired) {
4234
+ return { status: "already_authenticated", message: "Already logged in." };
4235
+ }
4236
+ output.write(`Token expired. Starting new login...
4237
+ `);
4238
+ } else if (existing && options.force) {
4239
+ output.write(`Re-authenticating (--force flag)...
4240
+ `);
4223
4241
  }
4224
- const result = await deps.loginFlow({}, container);
4225
- switch (result.status) {
4226
- case "success":
4227
- return { status: "authenticated", message: result.message };
4228
- case "already_authenticated":
4229
- return { status: "already-authenticated", message: result.message };
4230
- case "failed":
4231
- return { status: "failed", message: result.message };
4242
+ if (!existing) {
4243
+ await authStorage.clearClient(mcpUrl);
4232
4244
  }
4233
- }
4234
-
4235
- // src/shared/root-cli-pre-action.ts
4236
- function createRootCliPreAction(deps) {
4237
- return async (thisCommand, actionCommand) => {
4238
- if (thisCommand.opts().color === false) {
4239
- process.env.NO_COLOR = "1";
4240
- }
4241
- const command = actionCommand ?? thisCommand;
4242
- const authResult = await maybeAutoLoginBeforeCommand(command, {
4243
- ...deps,
4244
- stdinIsTTY: deps.stdinIsTTY,
4245
- stdoutIsTTY: deps.stdoutIsTTY
4246
- });
4247
- if (authResult.status === "authenticated") {
4248
- const continuationMessage = getPostLoginContinuationMessage(command);
4249
- if (continuationMessage) {
4250
- console.error(continuationMessage);
4245
+ const persistenceError = await preflightAuthPersistence(authStorage, mcpUrl);
4246
+ if (persistenceError)
4247
+ return persistenceError;
4248
+ output.write("Discovering OAuth endpoints...");
4249
+ const metadata = await authService.discoverEndpoints(mcpUrl);
4250
+ let client = await authStorage.loadClient(mcpUrl);
4251
+ const hadStoredClient = client !== null;
4252
+ let shouldClearClientOnFailedAttempt = false;
4253
+ let port;
4254
+ let redirectUri;
4255
+ if (client) {
4256
+ if (options.port) {
4257
+ redirectUri = `http://127.0.0.1:${options.port}/callback`;
4258
+ if (redirectUri !== client.redirectUri) {
4259
+ output.write("Registering CLI client with new port...");
4260
+ const registration = await authService.registerClient({
4261
+ registrationEndpoint: metadata.registrationEndpoint,
4262
+ redirectUri
4263
+ });
4264
+ client = {
4265
+ clientId: registration.clientId,
4266
+ clientSecret: registration.clientSecret,
4267
+ redirectUri,
4268
+ registeredAt: new Date().toISOString()
4269
+ };
4270
+ shouldClearClientOnFailedAttempt = !hadStoredClient;
4251
4271
  }
4272
+ port = options.port;
4273
+ } else {
4274
+ redirectUri = client.redirectUri;
4275
+ const storedUrl = new URL(redirectUri);
4276
+ port = Number(storedUrl.port) || randomPort();
4252
4277
  }
4253
- if (authResult.status !== "failed") {
4254
- return;
4255
- }
4256
- console.error(`${authResult.message}
4257
- `);
4258
- console.error("Run `githits login` to try again.");
4259
- (deps.exit ?? process.exit)(1);
4260
- };
4261
- }
4262
- function getPostLoginContinuationMessage(command) {
4263
- switch (getCommandPath(command).join(" ")) {
4264
- case "example":
4265
- return "Authentication complete. Running example search...";
4266
- case "languages":
4267
- return "Authentication complete. Loading supported languages...";
4268
- case "feedback":
4269
- return "Authentication complete. Submitting feedback...";
4270
- case "search":
4271
- case "search-status":
4272
- case "code files":
4273
- case "code read":
4274
- case "code grep":
4275
- case "docs list":
4276
- case "docs read":
4277
- case "pkg info":
4278
- case "pkg vulns":
4279
- case "pkg deps":
4280
- case "pkg changelog":
4281
- return "Authentication complete. Running command...";
4282
- default:
4283
- return;
4278
+ } else {
4279
+ port = options.port ?? randomPort();
4280
+ redirectUri = `http://127.0.0.1:${port}/callback`;
4281
+ output.write("Registering CLI client...");
4282
+ const registration = await authService.registerClient({
4283
+ registrationEndpoint: metadata.registrationEndpoint,
4284
+ redirectUri
4285
+ });
4286
+ client = {
4287
+ clientId: registration.clientId,
4288
+ clientSecret: registration.clientSecret,
4289
+ redirectUri,
4290
+ registeredAt: new Date().toISOString()
4291
+ };
4292
+ shouldClearClientOnFailedAttempt = !hadStoredClient;
4284
4293
  }
4285
- }
4286
- // src/shared/unified-search-request.ts
4287
- var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
4288
- function buildUnifiedSearchParams(input) {
4289
- const targets = resolveTargets(input.target, input.targets);
4290
- const rawQuery = normaliseRequiredQuery(input.query);
4291
- const limit = input.limit ?? DEFAULT_UNIFIED_SEARCH_LIMIT;
4292
- const offset = input.offset ?? 0;
4293
- const waitTimeoutMs = input.waitTimeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
4294
- const qualifierClauses = buildQualifierClauses({
4295
- name: input.name,
4296
- language: input.language
4297
- });
4298
- const compiledQuery = compileQuery(rawQuery, qualifierClauses);
4299
- const filters = buildFilters({
4300
- kind: input.kind,
4301
- category: input.category,
4302
- pathPrefix: input.pathPrefix,
4303
- fileIntent: input.fileIntent,
4304
- publicOnly: input.publicOnly
4294
+ const { verifier, challenge, state } = authService.generatePkceParams();
4295
+ const authUrl = authService.buildAuthUrl({
4296
+ authorizationEndpoint: metadata.authorizationEndpoint,
4297
+ clientId: client.clientId,
4298
+ redirectUri,
4299
+ state,
4300
+ codeChallenge: challenge
4305
4301
  });
4306
- return {
4307
- params: {
4308
- targets,
4309
- query: compiledQuery,
4310
- sources: input.sources,
4311
- filters,
4312
- allowPartialResults: input.allowPartialResults,
4313
- limit,
4314
- offset,
4302
+ let callbackServer;
4303
+ try {
4304
+ callbackServer = await authService.startCallbackServer(port, state);
4305
+ } catch (error2) {
4306
+ const msg = error2 instanceof Error ? error2.message : String(error2);
4307
+ return { status: "failed", message: msg };
4308
+ }
4309
+ if (options.browser === false) {
4310
+ output.write(`Open this URL in your browser:
4311
+ `);
4312
+ output.write(` ${authUrl}
4313
+ `);
4314
+ } else {
4315
+ output.write("Opening browser...");
4316
+ try {
4317
+ await browserService.open(authUrl);
4318
+ } catch (error2) {
4319
+ const msg = error2 instanceof Error ? error2.message : String(error2);
4320
+ output.write(`Could not open browser automatically: ${msg}
4321
+ `);
4322
+ output.write(`Open this URL in your browser:
4323
+ `);
4324
+ output.write(` ${authUrl}
4325
+ `);
4326
+ }
4327
+ }
4328
+ output.write(`Waiting for authentication...
4329
+ `);
4330
+ let timeoutId;
4331
+ const timeoutPromise = new Promise((_, reject) => {
4332
+ timeoutId = setTimeout(() => reject(new Error(AUTH_TIMEOUT_MESSAGE)), TIMEOUT_MS);
4333
+ });
4334
+ let callback;
4335
+ try {
4336
+ callback = await Promise.race([callbackServer.result, timeoutPromise]);
4337
+ if (timeoutId)
4338
+ clearTimeout(timeoutId);
4339
+ await callbackServer.close().catch(() => {});
4340
+ } catch (error2) {
4341
+ if (timeoutId)
4342
+ clearTimeout(timeoutId);
4343
+ await callbackServer.close().catch(() => {});
4344
+ if (shouldClearClientOnFailedAttempt) {
4345
+ await authStorage.clearClient(mcpUrl).catch(() => {});
4346
+ }
4347
+ const msg = error2 instanceof Error ? error2.message : "Authentication failed";
4348
+ return { status: "failed", message: ensureTerminalPeriod(msg) };
4349
+ }
4350
+ if (callback.type !== "success") {
4351
+ await new Promise((r) => setTimeout(r, 2000));
4352
+ if (shouldClearClientOnFailedAttempt) {
4353
+ await authStorage.clearClient(mcpUrl).catch(() => {});
4354
+ }
4355
+ return {
4356
+ status: "failed",
4357
+ message: callback.message ?? "Authentication callback failed."
4358
+ };
4359
+ }
4360
+ let tokenResponse;
4361
+ try {
4362
+ tokenResponse = await authService.exchangeCodeForTokens({
4363
+ tokenEndpoint: metadata.tokenEndpoint,
4364
+ clientId: client.clientId,
4365
+ clientSecret: client.clientSecret,
4366
+ code: callback.code,
4367
+ codeVerifier: verifier,
4368
+ redirectUri
4369
+ });
4370
+ } catch (error2) {
4371
+ try {
4372
+ await authStorage.clearClient(mcpUrl);
4373
+ } catch {}
4374
+ const msg = error2 instanceof Error ? error2.message : String(error2);
4375
+ return {
4376
+ status: "failed",
4377
+ message: `Failed to complete authentication: ${msg}`
4378
+ };
4379
+ }
4380
+ const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1000).toISOString();
4381
+ await authStorage.saveAuthSession(mcpUrl, client, {
4382
+ accessToken: tokenResponse.accessToken,
4383
+ refreshToken: tokenResponse.refreshToken,
4384
+ expiresAt,
4385
+ createdAt: new Date().toISOString()
4386
+ });
4387
+ const hours = Math.round(tokenResponse.expiresIn / 3600);
4388
+ return {
4389
+ status: "success",
4390
+ message: `Logged in successfully. Token expires in ${hours} hour${hours !== 1 ? "s" : ""}.`
4391
+ };
4392
+ }
4393
+ async function loginAction(options, deps) {
4394
+ const result = await loginFlow(options, deps, stdoutLoginOutput);
4395
+ if (result.status === "already_authenticated") {
4396
+ console.log(`Already logged in.
4397
+ `);
4398
+ console.log(` Environment: ${deps.mcpUrl}
4399
+ `);
4400
+ console.log("To re-authenticate, use `githits login --force`.");
4401
+ return;
4402
+ }
4403
+ if (result.status === "failed") {
4404
+ console.error(`${result.message}
4405
+ `);
4406
+ printLoginRecoveryHint(result.message);
4407
+ process.exit(1);
4408
+ }
4409
+ console.log(`Logged in successfully.
4410
+ `);
4411
+ console.log(` Environment: ${deps.mcpUrl}`);
4412
+ console.log(result.message.replace("Logged in successfully. ", " "));
4413
+ console.log(`
4414
+ You're ready to use githits with your AI assistant.`);
4415
+ }
4416
+ function printLoginRecoveryHint(message) {
4417
+ console.log("Recovery steps:");
4418
+ if (message.includes("Authentication timed out")) {
4419
+ console.log(" Run the same command again to open a fresh sign-in link.");
4420
+ console.log(" githits login --no-browser # if the browser did not open or you are on SSH");
4421
+ console.log(" githits logout && githits login # if sign-in keeps failing after a retry");
4422
+ return;
4423
+ }
4424
+ console.log(" githits auth status");
4425
+ console.log(" githits login --force");
4426
+ if (message.includes("Cannot persist OAuth credentials")) {
4427
+ console.log("If your system keychain is locked or unavailable, unlock it and retry.");
4428
+ console.log("For CI/automation, set GITHITS_API_TOKEN.");
4429
+ console.log("As a last resort, set GITHITS_AUTH_STORAGE=file to use plaintext file storage.");
4430
+ }
4431
+ }
4432
+ function printAutoLoginRecoveryHint(message) {
4433
+ if (message.includes("Authentication timed out")) {
4434
+ console.error("Run the same command again to open a fresh sign-in link.");
4435
+ console.error("If the browser did not open, run `githits login --no-browser` and follow the printed link.");
4436
+ console.error("If sign-in keeps failing after a retry, run `githits logout` and then run your command again.");
4437
+ return;
4438
+ }
4439
+ console.error("Run the same command again to try signing in again.");
4440
+ console.error("Run `githits auth status` to check whether you are signed in.");
4441
+ if (message.includes("Cannot persist OAuth credentials")) {
4442
+ console.error("If your system keychain is locked or unavailable, unlock it and try again.");
4443
+ console.error("For CI/automation, set GITHITS_API_TOKEN.");
4444
+ }
4445
+ }
4446
+ function ensureTerminalPeriod(message) {
4447
+ return /[.!?]$/.test(message) ? message : `${message}.`;
4448
+ }
4449
+ var LOGIN_DESCRIPTION = `Authenticate with your GitHits account via browser.
4450
+
4451
+ Opens your browser to complete authentication securely using OAuth.
4452
+ OAuth credentials are stored in the system keychain by default. If your
4453
+ machine has no usable keychain, use GITHITS_API_TOKEN or explicitly configure
4454
+ auth.storage = "file". File storage is plaintext on disk.
4455
+
4456
+ Use --no-browser in environments without a display (CI, SSH sessions)
4457
+ to get a URL you can open on another device.`;
4458
+ function registerLoginCommand(program) {
4459
+ program.command("login").summary("Authenticate with your GitHits account").description(LOGIN_DESCRIPTION).option("--no-browser", "Print URL instead of opening browser").option("--port <port>", "Port for local callback server", parseInt).option("--force", "Re-authenticate even if already logged in").action(async (options) => {
4460
+ const deps = await createAuthCommandDependencies();
4461
+ await loginAction(options, deps);
4462
+ });
4463
+ }
4464
+
4465
+ // src/shared/auto-login.ts
4466
+ var AUTO_LOGIN_ELIGIBLE_COMMANDS = new Set([
4467
+ "example",
4468
+ "languages",
4469
+ "feedback",
4470
+ "search",
4471
+ "search-status",
4472
+ "code files",
4473
+ "code read",
4474
+ "code grep",
4475
+ "docs list",
4476
+ "docs read",
4477
+ "pkg info",
4478
+ "pkg vulns",
4479
+ "pkg deps",
4480
+ "pkg changelog"
4481
+ ]);
4482
+ function getCommandPath(command) {
4483
+ const names = [];
4484
+ let current = command;
4485
+ while (current) {
4486
+ const name = current.name();
4487
+ if (name && name !== "githits") {
4488
+ names.unshift(name);
4489
+ }
4490
+ current = current.parent ?? null;
4491
+ }
4492
+ return names;
4493
+ }
4494
+ function isAutoLoginEligibleCommand(command, runtime = {
4495
+ stdinIsTTY: Boolean(process.stdin.isTTY),
4496
+ stdoutIsTTY: Boolean(process.stdout.isTTY)
4497
+ }) {
4498
+ const commandPath = getCommandPath(command).join(" ");
4499
+ if (!AUTO_LOGIN_ELIGIBLE_COMMANDS.has(commandPath)) {
4500
+ return false;
4501
+ }
4502
+ if (!runtime.stdinIsTTY || !runtime.stdoutIsTTY) {
4503
+ return false;
4504
+ }
4505
+ return true;
4506
+ }
4507
+ async function maybeAutoLoginBeforeCommand(command, deps) {
4508
+ if (!isAutoLoginEligibleCommand(command, {
4509
+ stdinIsTTY: deps.stdinIsTTY ?? Boolean(process.stdin.isTTY),
4510
+ stdoutIsTTY: deps.stdoutIsTTY ?? Boolean(process.stdout.isTTY)
4511
+ })) {
4512
+ return { status: "skipped" };
4513
+ }
4514
+ const container = await deps.createContainer();
4515
+ if (container.hasValidToken) {
4516
+ return { status: "already-authenticated" };
4517
+ }
4518
+ const result = await deps.loginFlow({}, container);
4519
+ switch (result.status) {
4520
+ case "success":
4521
+ return { status: "authenticated", message: result.message };
4522
+ case "already_authenticated":
4523
+ return { status: "already-authenticated", message: result.message };
4524
+ case "failed":
4525
+ return { status: "failed", message: result.message };
4526
+ }
4527
+ }
4528
+
4529
+ // src/shared/root-cli-pre-action.ts
4530
+ function createRootCliPreAction(deps) {
4531
+ return async (thisCommand, actionCommand) => {
4532
+ if (thisCommand.opts().color === false) {
4533
+ process.env.NO_COLOR = "1";
4534
+ }
4535
+ const command = actionCommand ?? thisCommand;
4536
+ const authResult = await maybeAutoLoginBeforeCommand(command, {
4537
+ ...deps,
4538
+ stdinIsTTY: deps.stdinIsTTY,
4539
+ stdoutIsTTY: deps.stdoutIsTTY
4540
+ });
4541
+ if (authResult.status === "authenticated") {
4542
+ const continuationMessage = getPostLoginContinuationMessage(command);
4543
+ if (continuationMessage) {
4544
+ console.error(continuationMessage);
4545
+ }
4546
+ }
4547
+ if (authResult.status !== "failed") {
4548
+ return;
4549
+ }
4550
+ const failureMessage = authResult.message ?? "Authentication failed.";
4551
+ console.error(`${failureMessage}
4552
+ `);
4553
+ printAutoLoginRecoveryHint(failureMessage);
4554
+ (deps.exit ?? process.exit)(1);
4555
+ };
4556
+ }
4557
+ function getPostLoginContinuationMessage(command) {
4558
+ switch (getCommandPath(command).join(" ")) {
4559
+ case "example":
4560
+ return "Authentication complete. Running example search...";
4561
+ case "languages":
4562
+ return "Authentication complete. Loading supported languages...";
4563
+ case "feedback":
4564
+ return "Authentication complete. Submitting feedback...";
4565
+ case "search":
4566
+ case "search-status":
4567
+ case "code files":
4568
+ case "code read":
4569
+ case "code grep":
4570
+ case "docs list":
4571
+ case "docs read":
4572
+ case "pkg info":
4573
+ case "pkg vulns":
4574
+ case "pkg deps":
4575
+ case "pkg changelog":
4576
+ return "Authentication complete. Running command...";
4577
+ default:
4578
+ return;
4579
+ }
4580
+ }
4581
+ // src/shared/unified-search-request.ts
4582
+ var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
4583
+ function buildUnifiedSearchParams(input) {
4584
+ const targets = resolveTargets(input.target, input.targets);
4585
+ const rawQuery = normaliseRequiredQuery(input.query);
4586
+ const limit = input.limit ?? DEFAULT_UNIFIED_SEARCH_LIMIT;
4587
+ const offset = input.offset ?? 0;
4588
+ const waitTimeoutMs = input.waitTimeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
4589
+ const qualifierClauses = buildQualifierClauses({
4590
+ name: input.name,
4591
+ language: input.language
4592
+ });
4593
+ const compiledQuery = compileQuery(rawQuery, qualifierClauses);
4594
+ const filters = buildFilters({
4595
+ kind: input.kind,
4596
+ category: input.category,
4597
+ pathPrefix: input.pathPrefix,
4598
+ fileIntent: input.fileIntent,
4599
+ publicOnly: input.publicOnly
4600
+ });
4601
+ return {
4602
+ params: {
4603
+ targets,
4604
+ query: compiledQuery,
4605
+ sources: input.sources,
4606
+ filters,
4607
+ allowPartialResults: input.allowPartialResults,
4608
+ limit,
4609
+ offset,
4315
4610
  waitTimeoutMs
4316
4611
  },
4317
4612
  rawQuery,
@@ -5798,7 +6093,7 @@ function buildCliListFilesParams(input) {
5798
6093
  throw new InvalidPackageSpecError(rewritten);
5799
6094
  }
5800
6095
  }
5801
- var REGISTRY_SPEC_HINT = /^(npm|pypi|hex|crates|nuget|maven|zig|vcpkg|packagist):/i;
6096
+ var REGISTRY_SPEC_HINT = new RegExp(`^(${PKGSEER_REGISTRY_ARGS.join("|")}):`, "i");
5802
6097
  function resolvePositionals(firstArg, secondArg, hasRepoUrl) {
5803
6098
  if (hasRepoUrl) {
5804
6099
  if (secondArg !== undefined) {
@@ -5824,8 +6119,7 @@ and \`githits code grep\`.
5824
6119
  filters intersect on top.
5825
6120
 
5826
6121
  Addressing: <spec> (registry:name[@version]) OR --repo-url <url>
5827
- --git-ref <ref>. Supported registries: npm, pypi, hex, crates,
5828
- vcpkg, zig, nuget, maven, packagist.
6122
+ --git-ref <ref>. Supported registries: ${PKGSEER_REGISTRY_LIST}.
5829
6123
 
5830
6124
  By default each result is a bare path for easy piping; pass
5831
6125
  --verbose to include language / file-type / size annotations.
@@ -5977,7 +6271,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
5977
6271
  match in --verbose output; full payload in --json).`;
5978
6272
  function registerCodeGrepCommand(pkgCommand) {
5979
6273
  return pkgCommand.command("grep").summary("Deterministic text grep over indexed dependency source").description(PKG_GREP_DESCRIPTION).argument("[spec-or-pattern]", "Spec mode: package spec (e.g. npm:express). Repo mode (with --repo-url): the pattern.").argument("[pattern-or-prefix]", "Spec mode: the pattern. Repo mode: optional path-prefix.").argument("[path-prefix]", "Spec mode only: optional path-prefix. Ignored with --repo-url.").option("--repo-url <url>", "Repository URL addressing (requires --git-ref)").option("--git-ref <ref>", "Tag, commit, branch, or HEAD. Required with --repo-url.").option("--path <path>", "Exact file path to grep").option("--glob <glob>", "Glob scope (repeatable)", collectRepeatable2, []).option("--ext <ext>", "Extension filter without leading dot (repeatable)", collectRepeatable2, []).option("--regex", "Interpret the pattern as RE2 regex").option("--case-sensitive", "Enable ASCII case-sensitive matching").option("-C, --context <n>", "Context lines before and after each match (0-10)").option("-B, --before-context <n>", "Context lines before each match (0-10)").option("-A, --after-context <n>", "Context lines after each match (0-10)").option("--exclude-docs", "Skip files classified as documentation").option("--exclude-tests", "Skip files classified as tests").option("--limit <n>", "Max matches to return on this page (1-1000, default 50)").option("--per-file-limit <n>", "Cap matches per file within this page (0-1000, 0 = unlimited)").option("--cursor <cursor>", "Opaque nextCursor from a previous grep result").option("--symbol-field <field>", `Repeatable; surfaces in --json and under each --verbose match. ${GREP_REPO_SYMBOL_FIELDS_NOTE}`, collectRepeatable2, []).option("--wait <ms>", `Indexing wait timeout (0-${MAX_WAIT_TIMEOUT_MS}, default ${DEFAULT_WAIT_TIMEOUT_MS})`).option("-v, --verbose", "Render grouped output with file headers").option("--json", "Emit the JSON envelope").action(async (arg1, arg2, arg3, options) => {
5980
- const { createContainer: createContainer2 } = await import("./shared/chunk-1h7sanjw.js");
6274
+ const { createContainer: createContainer2 } = await import("./shared/chunk-q2mre780.js");
5981
6275
  const deps = await createContainer2();
5982
6276
  await pkgGrepAction(arg1, arg2, arg3, options, {
5983
6277
  codeNavigationService: deps.codeNavigationService,
@@ -6511,7 +6805,7 @@ function registerExampleCommand(program) {
6511
6805
  });
6512
6806
  }
6513
6807
  async function loadContainer() {
6514
- const { createContainer: createContainer2 } = await import("./shared/chunk-1h7sanjw.js");
6808
+ const { createContainer: createContainer2 } = await import("./shared/chunk-q2mre780.js");
6515
6809
  return createContainer2();
6516
6810
  }
6517
6811
  // src/commands/feedback.ts
@@ -7280,257 +7574,6 @@ async function scanAgents(definitions, fs, execService) {
7280
7574
  class ExitPromptError extends Error {
7281
7575
  name = "ExitPromptError";
7282
7576
  }
7283
- // src/commands/login.ts
7284
- var stdoutLoginOutput = {
7285
- write: (message) => {
7286
- console.log(message);
7287
- }
7288
- };
7289
- var stderrLoginOutput = {
7290
- write: (message) => {
7291
- console.error(message);
7292
- }
7293
- };
7294
- var TIMEOUT_MS = 5 * 60 * 1000;
7295
- function randomPort() {
7296
- return Math.floor(Math.random() * 2000) + 8000;
7297
- }
7298
- async function preflightAuthPersistence(authStorage, mcpUrl) {
7299
- const probeUrl = `${mcpUrl.replace(/\/+$/, "")}/__githits_storage_probe__`;
7300
- const probeClient = {
7301
- clientId: "__githits_storage_probe__",
7302
- clientSecret: "__githits_storage_probe__",
7303
- redirectUri: "http://127.0.0.1:1/callback",
7304
- registeredAt: new Date(0).toISOString()
7305
- };
7306
- const probeTokens = {
7307
- accessToken: "__githits_storage_probe__",
7308
- refreshToken: "__githits_storage_probe__",
7309
- expiresAt: new Date(0).toISOString(),
7310
- createdAt: new Date(0).toISOString()
7311
- };
7312
- try {
7313
- await authStorage.saveAuthSession(probeUrl, probeClient, probeTokens);
7314
- await authStorage.clearAuthSession(probeUrl);
7315
- return null;
7316
- } catch (error2) {
7317
- await authStorage.clearAuthSession(probeUrl).catch(() => {});
7318
- const message = error2 instanceof Error ? error2.message : String(error2);
7319
- return {
7320
- status: "failed",
7321
- message: `Cannot persist OAuth credentials: ${message}`
7322
- };
7323
- }
7324
- }
7325
- async function loginFlow(options, deps, output = stdoutLoginOutput) {
7326
- const { authService, authStorage, browserService, mcpUrl } = deps;
7327
- if (options.port !== undefined && (Number.isNaN(options.port) || options.port < 1 || options.port > 65535)) {
7328
- return {
7329
- status: "failed",
7330
- message: "Invalid port number. Must be between 1 and 65535."
7331
- };
7332
- }
7333
- const existing = await authStorage.loadTokens(mcpUrl);
7334
- if (existing && !options.force) {
7335
- const isExpired = existing.expiresAt && new Date(existing.expiresAt) < new Date;
7336
- if (!isExpired) {
7337
- return { status: "already_authenticated", message: "Already logged in." };
7338
- }
7339
- output.write(`Token expired. Starting new login...
7340
- `);
7341
- } else if (existing && options.force) {
7342
- output.write(`Re-authenticating (--force flag)...
7343
- `);
7344
- }
7345
- if (!existing) {
7346
- await authStorage.clearClient(mcpUrl);
7347
- }
7348
- const persistenceError = await preflightAuthPersistence(authStorage, mcpUrl);
7349
- if (persistenceError)
7350
- return persistenceError;
7351
- output.write("Discovering OAuth endpoints...");
7352
- const metadata = await authService.discoverEndpoints(mcpUrl);
7353
- let client = await authStorage.loadClient(mcpUrl);
7354
- let port;
7355
- let redirectUri;
7356
- if (client) {
7357
- if (options.port) {
7358
- redirectUri = `http://127.0.0.1:${options.port}/callback`;
7359
- if (redirectUri !== client.redirectUri) {
7360
- output.write("Registering CLI client with new port...");
7361
- const registration = await authService.registerClient({
7362
- registrationEndpoint: metadata.registrationEndpoint,
7363
- redirectUri
7364
- });
7365
- client = {
7366
- clientId: registration.clientId,
7367
- clientSecret: registration.clientSecret,
7368
- redirectUri,
7369
- registeredAt: new Date().toISOString()
7370
- };
7371
- }
7372
- port = options.port;
7373
- } else {
7374
- redirectUri = client.redirectUri;
7375
- const storedUrl = new URL(redirectUri);
7376
- port = Number(storedUrl.port) || randomPort();
7377
- }
7378
- } else {
7379
- port = options.port ?? randomPort();
7380
- redirectUri = `http://127.0.0.1:${port}/callback`;
7381
- output.write("Registering CLI client...");
7382
- const registration = await authService.registerClient({
7383
- registrationEndpoint: metadata.registrationEndpoint,
7384
- redirectUri
7385
- });
7386
- client = {
7387
- clientId: registration.clientId,
7388
- clientSecret: registration.clientSecret,
7389
- redirectUri,
7390
- registeredAt: new Date().toISOString()
7391
- };
7392
- }
7393
- const { verifier, challenge, state } = authService.generatePkceParams();
7394
- const authUrl = authService.buildAuthUrl({
7395
- authorizationEndpoint: metadata.authorizationEndpoint,
7396
- clientId: client.clientId,
7397
- redirectUri,
7398
- state,
7399
- codeChallenge: challenge
7400
- });
7401
- let callbackServer;
7402
- try {
7403
- callbackServer = await authService.startCallbackServer(port, state);
7404
- } catch (error2) {
7405
- const msg = error2 instanceof Error ? error2.message : String(error2);
7406
- return { status: "failed", message: msg };
7407
- }
7408
- if (options.browser === false) {
7409
- output.write(`Open this URL in your browser:
7410
- `);
7411
- output.write(` ${authUrl}
7412
- `);
7413
- } else {
7414
- output.write("Opening browser...");
7415
- try {
7416
- await browserService.open(authUrl);
7417
- } catch (error2) {
7418
- const msg = error2 instanceof Error ? error2.message : String(error2);
7419
- output.write(`Could not open browser automatically: ${msg}
7420
- `);
7421
- output.write(`Open this URL in your browser:
7422
- `);
7423
- output.write(` ${authUrl}
7424
- `);
7425
- }
7426
- }
7427
- output.write(`Waiting for authentication...
7428
- `);
7429
- let timeoutId;
7430
- const timeoutPromise = new Promise((_, reject) => {
7431
- timeoutId = setTimeout(() => reject(new Error("Authentication timed out")), TIMEOUT_MS);
7432
- });
7433
- let callback;
7434
- try {
7435
- callback = await Promise.race([callbackServer.result, timeoutPromise]);
7436
- if (timeoutId)
7437
- clearTimeout(timeoutId);
7438
- } catch (error2) {
7439
- if (timeoutId)
7440
- clearTimeout(timeoutId);
7441
- await callbackServer.close().catch(() => {});
7442
- const msg = error2 instanceof Error ? error2.message : "Authentication failed";
7443
- return { status: "failed", message: `${msg}.` };
7444
- }
7445
- if (callback.type !== "success") {
7446
- await new Promise((r) => setTimeout(r, 2000));
7447
- return {
7448
- status: "failed",
7449
- message: callback.message ?? "Authentication callback failed."
7450
- };
7451
- }
7452
- let tokenResponse;
7453
- try {
7454
- tokenResponse = await authService.exchangeCodeForTokens({
7455
- tokenEndpoint: metadata.tokenEndpoint,
7456
- clientId: client.clientId,
7457
- clientSecret: client.clientSecret,
7458
- code: callback.code,
7459
- codeVerifier: verifier,
7460
- redirectUri
7461
- });
7462
- } catch (error2) {
7463
- try {
7464
- await authStorage.clearClient(mcpUrl);
7465
- } catch {}
7466
- const msg = error2 instanceof Error ? error2.message : String(error2);
7467
- return {
7468
- status: "failed",
7469
- message: `Failed to complete authentication: ${msg}`
7470
- };
7471
- }
7472
- const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1000).toISOString();
7473
- await authStorage.saveAuthSession(mcpUrl, client, {
7474
- accessToken: tokenResponse.accessToken,
7475
- refreshToken: tokenResponse.refreshToken,
7476
- expiresAt,
7477
- createdAt: new Date().toISOString()
7478
- });
7479
- const hours = Math.round(tokenResponse.expiresIn / 3600);
7480
- return {
7481
- status: "success",
7482
- message: `Logged in successfully. Token expires in ${hours} hour${hours !== 1 ? "s" : ""}.`
7483
- };
7484
- }
7485
- async function loginAction(options, deps) {
7486
- const result = await loginFlow(options, deps, stdoutLoginOutput);
7487
- if (result.status === "already_authenticated") {
7488
- console.log(`Already logged in.
7489
- `);
7490
- console.log(` Environment: ${deps.mcpUrl}
7491
- `);
7492
- console.log("To re-authenticate, use `githits login --force`.");
7493
- return;
7494
- }
7495
- if (result.status === "failed") {
7496
- console.error(`${result.message}
7497
- `);
7498
- printLoginRecoveryHint(result.message);
7499
- process.exit(1);
7500
- }
7501
- console.log(`Logged in successfully.
7502
- `);
7503
- console.log(` Environment: ${deps.mcpUrl}`);
7504
- console.log(result.message.replace("Logged in successfully. ", " "));
7505
- console.log(`
7506
- You're ready to use githits with your AI assistant.`);
7507
- }
7508
- function printLoginRecoveryHint(message) {
7509
- console.log("Recovery steps:");
7510
- console.log(" githits auth status");
7511
- console.log(" githits login --force");
7512
- if (message.includes("Cannot persist OAuth credentials")) {
7513
- console.log("If your system keychain is locked or unavailable, unlock it and retry.");
7514
- console.log("For CI/automation, set GITHITS_API_TOKEN.");
7515
- console.log("As a last resort, set GITHITS_AUTH_STORAGE=file to use plaintext file storage.");
7516
- }
7517
- }
7518
- var LOGIN_DESCRIPTION = `Authenticate with your GitHits account via browser.
7519
-
7520
- Opens your browser to complete authentication securely using OAuth.
7521
- OAuth credentials are stored in the system keychain by default. If your
7522
- machine has no usable keychain, use GITHITS_API_TOKEN or explicitly configure
7523
- auth.storage = "file". File storage is plaintext on disk.
7524
-
7525
- Use --no-browser in environments without a display (CI, SSH sessions)
7526
- to get a URL you can open on another device.`;
7527
- function registerLoginCommand(program) {
7528
- program.command("login").summary("Authenticate with your GitHits account").description(LOGIN_DESCRIPTION).option("--no-browser", "Print URL instead of opening browser").option("--port <port>", "Port for local callback server", parseInt).option("--force", "Re-authenticate even if already logged in").action(async (options) => {
7529
- const deps = await createAuthCommandDependencies();
7530
- await loginAction(options, deps);
7531
- });
7532
- }
7533
-
7534
7577
  // src/commands/init/init.ts
7535
7578
  async function verifyAgentConfigured(agent, fileSystemService, execService) {
7536
7579
  const postCheck = await scanAgents([agent], fileSystemService, execService);
@@ -7934,17 +7977,7 @@ import { z as z4 } from "zod";
7934
7977
  // src/tools/code-navigation-shared.ts
7935
7978
  import { z as z3 } from "zod";
7936
7979
  var structuredCodeTargetSchema = z3.object({
7937
- registry: z3.enum([
7938
- "npm",
7939
- "pypi",
7940
- "hex",
7941
- "crates",
7942
- "nuget",
7943
- "maven",
7944
- "zig",
7945
- "vcpkg",
7946
- "packagist"
7947
- ]).optional().describe("Package registry (npm, pypi, hex, etc.). Required for package scope."),
7980
+ registry: z3.enum(PKGSEER_REGISTRY_ARGS).optional().describe(`Package registry (${PKGSEER_REGISTRY_LIST}). Required for package scope.`),
7948
7981
  package_name: z3.string().max(255).optional().describe("Package name. Required for package scope."),
7949
7982
  version: z3.string().max(100).optional().describe("Package version, e.g. '4.18.2' (defaults to latest). For package scope only."),
7950
7983
  repo_url: z3.string().optional().describe("Repository URL (GitHub). Required for repo scope. Example: https://github.com/expressjs/express"),
@@ -8197,7 +8230,7 @@ function isTextFormat3(format) {
8197
8230
  // src/tools/list-package-docs.ts
8198
8231
  import { z as z6 } from "zod";
8199
8232
  var schema5 = {
8200
- registry: z6.string().describe("Package registry. One of: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, packagist."),
8233
+ registry: z6.string().describe(`Package registry. One of: ${PKGSEER_REGISTRY_LIST}.`),
8201
8234
  package_name: z6.string().describe("Package name (scoped names ok: @types/node)."),
8202
8235
  version: z6.string().optional().describe("Optional package version."),
8203
8236
  limit: z6.number().optional().describe("Max pages to return (1-500, default 100)."),
@@ -8304,7 +8337,7 @@ function resolveAddressing(input) {
8304
8337
  }
8305
8338
  const normalisedRegistryArg = input.registry?.trim().toLowerCase() ?? "";
8306
8339
  if (!isKnownPkgseerRegistryArg(normalisedRegistryArg)) {
8307
- throw new UnsupportedRegistryError(`Unsupported registry '${input.registry}'. Supported: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, packagist.`);
8340
+ throw new UnsupportedRegistryError(`Unsupported registry '${input.registry}'. Supported: ${PKGSEER_REGISTRY_LIST}.`);
8308
8341
  }
8309
8342
  const registry = toPkgseerRegistry(normalisedRegistryArg);
8310
8343
  return { registry, packageName };
@@ -8511,7 +8544,7 @@ function stripAnsi(text) {
8511
8544
 
8512
8545
  // src/tools/package-changelog.ts
8513
8546
  var schema6 = {
8514
- 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."),
8547
+ registry: z7.string().optional().describe(`Package registry (with \`package_name\`). Mutually exclusive with \`repo_url\`. Supported: ${PKGSEER_REGISTRY_LIST}.`),
8515
8548
  package_name: z7.string().optional().describe("Package name (with `registry`). Scoped names ok (`@types/node`). Mutually exclusive with `repo_url`."),
8516
8549
  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."),
8517
8550
  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."),
@@ -8521,7 +8554,7 @@ var schema6 = {
8521
8554
  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."),
8522
8555
  format: z7.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` is compact for agents. Pass `json` for the structured envelope.")
8523
8556
  };
8524
- var DESCRIPTION6 = "Release notes for a package or GitHub repo, newest-first. Default " + "latest mode returns the ten most recent entries (`limit` 1–50). " + "With `from_version`, returns every entry in the " + "`[from_version, to_version]` range (range mode, no count cap). " + "Address via `registry` + `package_name` or `repo_url` (mutually " + 'exclusive). Response includes optional `source` (`"releases"` / ' + '`"changelog_file"` / `"hexdocs"`) when a concrete changelog source ' + 'exists, `mode` (`"latest"` or `"range"`), ' + 'and entries with markdown body previews. Pass `format: "json"` ' + "for the structured envelope with full markdown bodies. Set " + "`include_bodies: false` for a version / date / URL timeline only. " + "Package-version entries without changelog text succeed with `source` " + "omitted; no-source plus no entries returns `NOT_FOUND`. Supports npm, " + "PyPI, Hex, Crates, vcpkg, Zig, NuGet, Maven, Packagist.";
8557
+ var DESCRIPTION6 = "Release notes for a package or GitHub repo, newest-first. Default " + "latest mode returns the ten most recent entries (`limit` 1–50). " + "With `from_version`, returns every entry in the " + "`[from_version, to_version]` range (range mode, no count cap). " + "Address via `registry` + `package_name` or `repo_url` (mutually " + 'exclusive). Response includes optional `source` (`"releases"` / ' + '`"changelog_file"` / `"hexdocs"`) when a concrete changelog source ' + 'exists, `mode` (`"latest"` or `"range"`), ' + 'and entries with markdown body previews. Pass `format: "json"` ' + "for the structured envelope with full markdown bodies. Set " + "`include_bodies: false` for a version / date / URL timeline only. " + "Package-version entries without changelog text succeed with `source` " + "omitted; no-source plus no entries returns `NOT_FOUND`. Supports npm, " + "PyPI, Hex, Crates, vcpkg, Zig, NuGet, Maven, Packagist, RubyGems, Go.";
8525
8558
  function createPackageChangelogTool(service) {
8526
8559
  return {
8527
8560
  name: "pkg_changelog",
@@ -8586,7 +8619,7 @@ function isTextFormat5(format) {
8586
8619
  // src/tools/package-dependencies.ts
8587
8620
  import { z as z8 } from "zod";
8588
8621
  var schema7 = {
8589
- registry: z8.string().describe("Package registry. Dependency data is available on npm, pypi, hex, crates, vcpkg, and zig."),
8622
+ registry: z8.string().describe(`Package registry. Dependency data is available on ${SUPPORTED_DEPS_REGISTRIES_HUMAN}.`),
8590
8623
  package_name: z8.string().describe("Package name (scoped names ok: @types/node)."),
8591
8624
  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`)."),
8592
8625
  lifecycle: z8.union([z8.string(), z8.array(z8.string())]).optional().describe("Lifecycle breadth. Omit for runtime-only. Use `runtime` for explicit runtime-only, a concrete non-runtime lifecycle (`development`, `build`, `peer`, `optional`) for runtime plus matching groups, or `all` for runtime plus all available groups. Accepts a single value, a comma-separated string, or an array; `all` cannot be combined with other values. Uppercase is tolerated."),
@@ -8595,7 +8628,7 @@ var schema7 = {
8595
8628
  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`."),
8596
8629
  format: z8.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` is compact for agents. Pass `json` for the structured envelope.")
8597
8630
  };
8598
- var DESCRIPTION7 = "Analyze a package's dependency graph. Default output is compact " + "text listing direct runtime dependencies with resolved versions; " + 'pass `format: "json"` for the structured envelope. Non-runtime ' + "groups are omitted by default for token efficiency. Use `lifecycle` " + "with a concrete value for runtime plus matching groups, or `all` " + "for runtime plus all available groups. Set " + "`include_transitive: true` to add a `transitive` block with the " + "full install footprint, conflict detection, and circular-" + "dependency flags; layer `include_importers: true` on top when you " + "also need per-package provenance. Supports npm, PyPI, Hex, Crates, " + "vcpkg, and Zig.";
8631
+ var DESCRIPTION7 = "Analyze a package's dependency graph. Default output is compact " + "text listing direct runtime dependencies with resolved versions; " + 'pass `format: "json"` for the structured envelope. Non-runtime ' + "groups are omitted by default for token efficiency. Use `lifecycle` " + "with a concrete value for runtime plus matching groups, or `all` " + "for runtime plus all available groups. Set " + "`include_transitive: true` to add a `transitive` block with the " + "full install footprint, conflict detection, and circular-" + "dependency flags; layer `include_importers: true` on top when you " + "also need per-package provenance. Supports npm, PyPI, Hex, Crates, " + "RubyGems, Go, vcpkg, and Zig.";
8599
8632
  function createPackageDependenciesTool(service) {
8600
8633
  return {
8601
8634
  name: "pkg_deps",
@@ -8666,11 +8699,11 @@ function isTextFormat6(format) {
8666
8699
  // src/tools/package-summary.ts
8667
8700
  import { z as z9 } from "zod";
8668
8701
  var schema8 = {
8669
- registry: z9.string().describe("Package registry. One of: npm, pypi, hex, crates, nuget, maven, zig, vcpkg, packagist."),
8702
+ registry: z9.string().describe(`Package registry. One of: ${PKGSEER_REGISTRY_LIST}.`),
8670
8703
  package_name: z9.string().describe("Package name (scoped names ok: @types/node)."),
8671
8704
  format: z9.enum(["json", "text", "text-v1"]).optional().describe("Response format. Default `text-v1` is compact for agents. Pass `json` for the structured envelope.")
8672
8705
  };
8673
- var DESCRIPTION8 = "Get a package overview — latest version, license, description, " + "repository, downloads, GitHub stars, install command, recent " + "changes, and a count of known vulnerabilities. Use before " + "recommending a package or to orient on what a dependency is. " + 'Default output is compact text; pass `format: "json"` for the ' + "structured envelope. " + "Works across npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, " + "vcpkg, and Zig. Always returns data for the latest published " + "version.";
8706
+ var DESCRIPTION8 = "Get a package overview — latest version, license, description, " + "repository, downloads, GitHub stars, install command, recent " + "changes, and a count of known vulnerabilities. Use before " + "recommending a package or to orient on what a dependency is. " + 'Default output is compact text; pass `format: "json"` for the ' + "structured envelope. " + "Works across npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, " + "RubyGems, Go, vcpkg, and Zig. Always returns data for the latest published " + "version.";
8674
8707
  function createPackageSummaryTool(service) {
8675
8708
  return {
8676
8709
  name: "pkg_info",
@@ -9426,8 +9459,7 @@ Addressing: <spec> (registry:name) OR --repo-url <url>. Source
9426
9459
  (GitHub Releases, CHANGELOG.md, or HexDocs) is shown on the summary
9427
9460
  line.
9428
9461
 
9429
- Package spec: <registry>:<name>. Supported registries: npm, pypi,
9430
- hex, crates, vcpkg, zig, nuget, maven, packagist. \`<spec>@<version>\`
9462
+ Package spec: <registry>:<name>. Supported registries: ${PKGSEER_REGISTRY_LIST}. \`<spec>@<version>\`
9431
9463
  is NOT accepted here — use --to <version> for "entries up to this
9432
9464
  version".`;
9433
9465
  function registerPkgChangelogCommand(pkgCommand) {
@@ -9550,7 +9582,7 @@ groups). Concrete --lifecycle values include runtime plus matching groups.
9550
9582
  conflict detection, and circular-dependency flags.
9551
9583
 
9552
9584
  Package spec: <registry>:<name>[@<version>]. Supported registries:
9553
- npm, pypi, hex, crates, vcpkg, zig. Omit @<version> for the latest
9585
+ npm, pypi, hex, crates, vcpkg, zig, rubygems, go. Omit @<version> for the latest
9554
9586
  release.`;
9555
9587
  function registerPkgDepsCommand(pkgCommand) {
9556
9588
  return pkgCommand.command("deps").summary("Analyze dependencies for a package").description(PKG_DEPS_DESCRIPTION).argument("<spec>", "Package spec, e.g. npm:express or npm:express@4.18.0").option("-l, --lifecycle <phases>", "Dependency lifecycle breadth (runtime, development, build, peer, optional, all; comma-separated for multi-select except all).").option("-t, --transitive", "Include aggregate transitive counts, conflicts, and circular dependencies").option("--depth <n>", "Cap transitive traversal depth (1-10). Omit for the full graph.").option("-v, --verbose", "Show conditionType / selectionMode / environmentConstraints metadata in the groups view").option("--json", "Emit the lean JSON envelope").action(async (spec, options) => {
@@ -9613,8 +9645,7 @@ repository, downloads, GitHub stars, install command, and known
9613
9645
  vulnerabilities. Use before picking a dependency or to orient on what
9614
9646
  a package is.
9615
9647
 
9616
- Package spec: <registry>:<name>. Supported registries: npm, pypi, hex,
9617
- crates, nuget, maven, zig, vcpkg, packagist.
9648
+ Package spec: <registry>:<name>. Supported registries: ${PKGSEER_REGISTRY_LIST}.
9618
9649
 
9619
9650
  Always returns data for the latest published version.`;
9620
9651
  function registerPkgInfoCommand(pkgCommand) {
@@ -9732,7 +9763,7 @@ async function registerPkgCommandGroup(program, options = {}) {
9732
9763
  if (!registration.shouldRegister) {
9733
9764
  return;
9734
9765
  }
9735
- 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`.");
9766
+ const pkgCommand = program.command("pkg").summary("Package metadata: info, vulnerabilities, dependencies, changelog").description("Inspect package metadata from npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, RubyGems, Go, vcpkg, and Zig: overviews, advisories, dependency graphs, and changelogs. For source-level operations inside a dependency, use `githits code`.");
9736
9767
  registerPkgInfoCommand(pkgCommand);
9737
9768
  registerPkgVulnsCommand(pkgCommand);
9738
9769
  registerPkgDepsCommand(pkgCommand);
@@ -9856,7 +9887,7 @@ function requireSearchService(deps) {
9856
9887
  return deps.codeNavigationService;
9857
9888
  }
9858
9889
  async function loadContainer2() {
9859
- const { createContainer: createContainer2 } = await import("./shared/chunk-1h7sanjw.js");
9890
+ const { createContainer: createContainer2 } = await import("./shared/chunk-q2mre780.js");
9860
9891
  return createContainer2();
9861
9892
  }
9862
9893
  function parseTargetSpecs(specs) {
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  version
3
- } from "./shared/chunk-fqzth5mv.js";
3
+ } from "./shared/chunk-jdygt0ra.js";
4
4
  export {
5
5
  version
6
6
  };
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
  // package.json
4
- var version = "0.4.0";
4
+ var version = "0.4.2";
5
5
 
6
6
  export { __require, version };
@@ -2,8 +2,8 @@ import {
2
2
  createAuthCommandDependencies,
3
3
  createAuthStatusDependencies,
4
4
  createContainer
5
- } from "./chunk-1cyka46a.js";
6
- import"./chunk-fqzth5mv.js";
5
+ } from "./chunk-v8sths32.js";
6
+ import"./chunk-jdygt0ra.js";
7
7
  export {
8
8
  createContainer,
9
9
  createAuthStatusDependencies,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  __require,
3
3
  version
4
- } from "./chunk-fqzth5mv.js";
4
+ } from "./chunk-jdygt0ra.js";
5
5
 
6
6
  // src/services/app-config-paths.ts
7
7
  var APP_DIR = "githits";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githits",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Code examples from global open source for developers and AI assistants.",
5
5
  "mcpServers": {
6
6
  "githits": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "githits",
3
3
  "description": "CLI companion for GitHits - code examples from global open source for developers and AI assistants",
4
- "version": "0.4.0",
4
+ "version": "0.4.2",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githits",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Code examples from global open source for developers and AI assistants",
5
5
  "author": {
6
6
  "name": "GitHits"