githits 0.3.3 → 0.4.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.plugin/plugin.json +1 -1
- package/dist/cli.js +781 -287
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-tze0rsay.js → chunk-f2ah8xnh.js} +1 -1
- package/dist/shared/{chunk-2d202fpq.js → chunk-rgvmtzjt.js} +227 -17
- package/dist/shared/{chunk-b6avnx6d.js → chunk-xzq0ecpa.js} +2 -2
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -49,11 +49,11 @@ import {
|
|
|
49
49
|
shouldRunUpdateCheck,
|
|
50
50
|
startTelemetrySpan,
|
|
51
51
|
withTelemetrySpan
|
|
52
|
-
} from "./shared/chunk-
|
|
52
|
+
} from "./shared/chunk-rgvmtzjt.js";
|
|
53
53
|
import {
|
|
54
54
|
__require,
|
|
55
55
|
version
|
|
56
|
-
} from "./shared/chunk-
|
|
56
|
+
} from "./shared/chunk-f2ah8xnh.js";
|
|
57
57
|
|
|
58
58
|
// src/cli.ts
|
|
59
59
|
import { Command } from "commander";
|
|
@@ -254,9 +254,9 @@ async function authStatusAction(deps) {
|
|
|
254
254
|
`);
|
|
255
255
|
console.log(` Environment: ${mcpUrl}
|
|
256
256
|
`);
|
|
257
|
-
console.log(
|
|
258
|
-
|
|
259
|
-
|
|
257
|
+
console.log(` Storage: ${authStorage.getStorageLocation()}
|
|
258
|
+
`);
|
|
259
|
+
printAuthTroubleshooting();
|
|
260
260
|
return;
|
|
261
261
|
}
|
|
262
262
|
if (auth.expiresAt && new Date(auth.expiresAt) < new Date) {
|
|
@@ -276,8 +276,9 @@ async function authStatusAction(deps) {
|
|
|
276
276
|
console.log(` Environment: ${mcpUrl}`);
|
|
277
277
|
console.log(` Expired: ${new Date(auth.expiresAt).toLocaleDateString()}
|
|
278
278
|
`);
|
|
279
|
-
console.log(
|
|
280
|
-
|
|
279
|
+
console.log(` Storage: ${authStorage.getStorageLocation()}
|
|
280
|
+
`);
|
|
281
|
+
printAuthTroubleshooting("expired");
|
|
281
282
|
return;
|
|
282
283
|
}
|
|
283
284
|
console.log(`Authenticated.
|
|
@@ -287,6 +288,17 @@ async function authStatusAction(deps) {
|
|
|
287
288
|
console.log(`
|
|
288
289
|
Storage: ${authStorage.getStorageLocation()}`);
|
|
289
290
|
}
|
|
291
|
+
function printAuthTroubleshooting(reason = "missing") {
|
|
292
|
+
const loginCommand = reason === "expired" ? "githits login --force" : "githits login";
|
|
293
|
+
console.log("Recovery steps:");
|
|
294
|
+
console.log(` ${loginCommand}`);
|
|
295
|
+
if (reason === "missing") {
|
|
296
|
+
console.log(" githits login --force # if a previous login is stale");
|
|
297
|
+
}
|
|
298
|
+
console.log("For CI/automation, set GITHITS_API_TOKEN.");
|
|
299
|
+
console.log("If your system keychain is locked or unavailable, unlock it and retry.");
|
|
300
|
+
console.log("As a last resort, set GITHITS_AUTH_STORAGE=file to use plaintext file storage.");
|
|
301
|
+
}
|
|
290
302
|
var STATUS_DESCRIPTION = `Show current authentication status.
|
|
291
303
|
|
|
292
304
|
Displays details about the stored token including environment
|
|
@@ -353,6 +365,11 @@ function highlight(text, useColors) {
|
|
|
353
365
|
return text;
|
|
354
366
|
return `${colors.bold}${colors.cyan}${text}${colors.reset}`;
|
|
355
367
|
}
|
|
368
|
+
function highlightMatch(text, useColors) {
|
|
369
|
+
if (!useColors)
|
|
370
|
+
return text;
|
|
371
|
+
return `${colors.bold}${colors.yellow}${text}${colors.reset}`;
|
|
372
|
+
}
|
|
356
373
|
function highlightRanges(text, ranges, useColors) {
|
|
357
374
|
if (!useColors || !text || !ranges || ranges.length === 0)
|
|
358
375
|
return text;
|
|
@@ -380,7 +397,7 @@ function highlightRanges(text, ranges, useColors) {
|
|
|
380
397
|
for (const [start, end] of merged) {
|
|
381
398
|
if (cursor < start)
|
|
382
399
|
result += text.slice(cursor, start);
|
|
383
|
-
result +=
|
|
400
|
+
result += highlightMatch(text.slice(start, end), useColors);
|
|
384
401
|
cursor = end;
|
|
385
402
|
}
|
|
386
403
|
if (cursor < text.length)
|
|
@@ -763,7 +780,7 @@ function parseCodeNavigationTargetSpec(spec) {
|
|
|
763
780
|
function parseRepoTarget(spec) {
|
|
764
781
|
const hashIndex = spec.lastIndexOf("#");
|
|
765
782
|
if (hashIndex === -1) {
|
|
766
|
-
|
|
783
|
+
throw new InvalidArgumentError("Repository target must include #gitRef for exact code navigation.");
|
|
767
784
|
}
|
|
768
785
|
const repoUrl = spec.slice(0, hashIndex);
|
|
769
786
|
const gitRef = spec.slice(hashIndex + 1);
|
|
@@ -804,7 +821,8 @@ function buildSearchHitFollowUpCommand(hit) {
|
|
|
804
821
|
gitRef: loc.gitRef,
|
|
805
822
|
filePath: loc.filePath,
|
|
806
823
|
startLine: loc.startLine,
|
|
807
|
-
endLine: loc.endLine
|
|
824
|
+
endLine: loc.endLine,
|
|
825
|
+
preferPackageTarget: isPackageTarget(hit)
|
|
808
826
|
});
|
|
809
827
|
}
|
|
810
828
|
if (hit.type === "repository_code" || hit.type === "repository_symbol") {
|
|
@@ -833,14 +851,24 @@ function buildCodeReadCommand(input) {
|
|
|
833
851
|
return parts.join(" ");
|
|
834
852
|
}
|
|
835
853
|
function buildTargetSpec(input) {
|
|
854
|
+
if (input.preferPackageTarget && input.registry && input.packageName) {
|
|
855
|
+
return `${input.registry}:${input.packageName}${input.version ? `@${input.version}` : ""}`;
|
|
856
|
+
}
|
|
836
857
|
if (input.repoUrl) {
|
|
837
|
-
|
|
858
|
+
if (!input.gitRef)
|
|
859
|
+
return;
|
|
860
|
+
return `${input.repoUrl}#${input.gitRef}`;
|
|
838
861
|
}
|
|
839
862
|
if (input.registry && input.packageName) {
|
|
840
863
|
return `${input.registry}:${input.packageName}${input.version ? `@${input.version}` : ""}`;
|
|
841
864
|
}
|
|
842
865
|
return;
|
|
843
866
|
}
|
|
867
|
+
function isPackageTarget(hit) {
|
|
868
|
+
const registry = hit.locator.registry;
|
|
869
|
+
const packageName = hit.locator.packageName;
|
|
870
|
+
return Boolean(registry && packageName && hit.target.startsWith(`${registry}:${packageName}`));
|
|
871
|
+
}
|
|
844
872
|
function appendRange(parts, startLine, endLine) {
|
|
845
873
|
if (typeof startLine === "number")
|
|
846
874
|
parts.push(`start_line=${startLine}`);
|
|
@@ -1934,7 +1962,7 @@ function renderListPackageDocsText(envelope) {
|
|
|
1934
1962
|
if (page.sourceKind === "repo" && page.repoUrl && page.filePath) {
|
|
1935
1963
|
lines.push(` ${buildCodeReadCommand({
|
|
1936
1964
|
repoUrl: page.repoUrl,
|
|
1937
|
-
gitRef: page.
|
|
1965
|
+
gitRef: page.gitRef,
|
|
1938
1966
|
filePath: page.filePath,
|
|
1939
1967
|
startLine: 1,
|
|
1940
1968
|
endLine: 150
|
|
@@ -4110,7 +4138,7 @@ function renderReadPackageDocText(envelope) {
|
|
|
4110
4138
|
if (envelope.sourceUrl)
|
|
4111
4139
|
lines.push(`source: ${envelope.sourceUrl}`);
|
|
4112
4140
|
if (envelope.filePath) {
|
|
4113
|
-
const ref = envelope.
|
|
4141
|
+
const ref = envelope.gitRef;
|
|
4114
4142
|
lines.push(`file: ${envelope.filePath}${ref ? ` @ ${ref}` : ""}`);
|
|
4115
4143
|
}
|
|
4116
4144
|
lines.push("");
|
|
@@ -4140,9 +4168,293 @@ function buildRange2(envelope) {
|
|
|
4140
4168
|
return `${envelope.totalLines} lines`;
|
|
4141
4169
|
return;
|
|
4142
4170
|
}
|
|
4171
|
+
// src/commands/login.ts
|
|
4172
|
+
var stdoutLoginOutput = {
|
|
4173
|
+
write: (message) => {
|
|
4174
|
+
console.log(message);
|
|
4175
|
+
}
|
|
4176
|
+
};
|
|
4177
|
+
var stderrLoginOutput = {
|
|
4178
|
+
write: (message) => {
|
|
4179
|
+
console.error(message);
|
|
4180
|
+
}
|
|
4181
|
+
};
|
|
4182
|
+
var TIMEOUT_MS = 5 * 60 * 1000;
|
|
4183
|
+
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.";
|
|
4184
|
+
function randomPort() {
|
|
4185
|
+
return Math.floor(Math.random() * 2000) + 8000;
|
|
4186
|
+
}
|
|
4187
|
+
async function preflightAuthPersistence(authStorage, mcpUrl) {
|
|
4188
|
+
const probeUrl = `${mcpUrl.replace(/\/+$/, "")}/__githits_storage_probe__`;
|
|
4189
|
+
const probeClient = {
|
|
4190
|
+
clientId: "__githits_storage_probe__",
|
|
4191
|
+
clientSecret: "__githits_storage_probe__",
|
|
4192
|
+
redirectUri: "http://127.0.0.1:1/callback",
|
|
4193
|
+
registeredAt: new Date(0).toISOString()
|
|
4194
|
+
};
|
|
4195
|
+
const probeTokens = {
|
|
4196
|
+
accessToken: "__githits_storage_probe__",
|
|
4197
|
+
refreshToken: "__githits_storage_probe__",
|
|
4198
|
+
expiresAt: new Date(0).toISOString(),
|
|
4199
|
+
createdAt: new Date(0).toISOString()
|
|
4200
|
+
};
|
|
4201
|
+
try {
|
|
4202
|
+
await authStorage.saveAuthSession(probeUrl, probeClient, probeTokens);
|
|
4203
|
+
await authStorage.clearAuthSession(probeUrl);
|
|
4204
|
+
return null;
|
|
4205
|
+
} catch (error2) {
|
|
4206
|
+
await authStorage.clearAuthSession(probeUrl).catch(() => {});
|
|
4207
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
4208
|
+
return {
|
|
4209
|
+
status: "failed",
|
|
4210
|
+
message: `Cannot persist OAuth credentials: ${message}`
|
|
4211
|
+
};
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
async function loginFlow(options, deps, output = stdoutLoginOutput) {
|
|
4215
|
+
const { authService, authStorage, browserService, mcpUrl } = deps;
|
|
4216
|
+
const existing = await authStorage.loadTokens(mcpUrl);
|
|
4217
|
+
if (options.port !== undefined && (Number.isNaN(options.port) || options.port < 1 || options.port > 65535)) {
|
|
4218
|
+
return {
|
|
4219
|
+
status: "failed",
|
|
4220
|
+
message: "Invalid port number. Must be between 1 and 65535."
|
|
4221
|
+
};
|
|
4222
|
+
}
|
|
4223
|
+
if (existing && !options.force) {
|
|
4224
|
+
const isExpired = existing.expiresAt && new Date(existing.expiresAt) < new Date;
|
|
4225
|
+
if (!isExpired) {
|
|
4226
|
+
return { status: "already_authenticated", message: "Already logged in." };
|
|
4227
|
+
}
|
|
4228
|
+
output.write(`Token expired. Starting new login...
|
|
4229
|
+
`);
|
|
4230
|
+
} else if (existing && options.force) {
|
|
4231
|
+
output.write(`Re-authenticating (--force flag)...
|
|
4232
|
+
`);
|
|
4233
|
+
}
|
|
4234
|
+
if (!existing) {
|
|
4235
|
+
await authStorage.clearClient(mcpUrl);
|
|
4236
|
+
}
|
|
4237
|
+
const persistenceError = await preflightAuthPersistence(authStorage, mcpUrl);
|
|
4238
|
+
if (persistenceError)
|
|
4239
|
+
return persistenceError;
|
|
4240
|
+
output.write("Discovering OAuth endpoints...");
|
|
4241
|
+
const metadata = await authService.discoverEndpoints(mcpUrl);
|
|
4242
|
+
let client = await authStorage.loadClient(mcpUrl);
|
|
4243
|
+
const hadStoredClient = client !== null;
|
|
4244
|
+
let shouldClearClientOnFailedAttempt = false;
|
|
4245
|
+
let port;
|
|
4246
|
+
let redirectUri;
|
|
4247
|
+
if (client) {
|
|
4248
|
+
if (options.port) {
|
|
4249
|
+
redirectUri = `http://127.0.0.1:${options.port}/callback`;
|
|
4250
|
+
if (redirectUri !== client.redirectUri) {
|
|
4251
|
+
output.write("Registering CLI client with new port...");
|
|
4252
|
+
const registration = await authService.registerClient({
|
|
4253
|
+
registrationEndpoint: metadata.registrationEndpoint,
|
|
4254
|
+
redirectUri
|
|
4255
|
+
});
|
|
4256
|
+
client = {
|
|
4257
|
+
clientId: registration.clientId,
|
|
4258
|
+
clientSecret: registration.clientSecret,
|
|
4259
|
+
redirectUri,
|
|
4260
|
+
registeredAt: new Date().toISOString()
|
|
4261
|
+
};
|
|
4262
|
+
shouldClearClientOnFailedAttempt = !hadStoredClient;
|
|
4263
|
+
}
|
|
4264
|
+
port = options.port;
|
|
4265
|
+
} else {
|
|
4266
|
+
redirectUri = client.redirectUri;
|
|
4267
|
+
const storedUrl = new URL(redirectUri);
|
|
4268
|
+
port = Number(storedUrl.port) || randomPort();
|
|
4269
|
+
}
|
|
4270
|
+
} else {
|
|
4271
|
+
port = options.port ?? randomPort();
|
|
4272
|
+
redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
4273
|
+
output.write("Registering CLI client...");
|
|
4274
|
+
const registration = await authService.registerClient({
|
|
4275
|
+
registrationEndpoint: metadata.registrationEndpoint,
|
|
4276
|
+
redirectUri
|
|
4277
|
+
});
|
|
4278
|
+
client = {
|
|
4279
|
+
clientId: registration.clientId,
|
|
4280
|
+
clientSecret: registration.clientSecret,
|
|
4281
|
+
redirectUri,
|
|
4282
|
+
registeredAt: new Date().toISOString()
|
|
4283
|
+
};
|
|
4284
|
+
shouldClearClientOnFailedAttempt = !hadStoredClient;
|
|
4285
|
+
}
|
|
4286
|
+
const { verifier, challenge, state } = authService.generatePkceParams();
|
|
4287
|
+
const authUrl = authService.buildAuthUrl({
|
|
4288
|
+
authorizationEndpoint: metadata.authorizationEndpoint,
|
|
4289
|
+
clientId: client.clientId,
|
|
4290
|
+
redirectUri,
|
|
4291
|
+
state,
|
|
4292
|
+
codeChallenge: challenge
|
|
4293
|
+
});
|
|
4294
|
+
let callbackServer;
|
|
4295
|
+
try {
|
|
4296
|
+
callbackServer = await authService.startCallbackServer(port, state);
|
|
4297
|
+
} catch (error2) {
|
|
4298
|
+
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
4299
|
+
return { status: "failed", message: msg };
|
|
4300
|
+
}
|
|
4301
|
+
if (options.browser === false) {
|
|
4302
|
+
output.write(`Open this URL in your browser:
|
|
4303
|
+
`);
|
|
4304
|
+
output.write(` ${authUrl}
|
|
4305
|
+
`);
|
|
4306
|
+
} else {
|
|
4307
|
+
output.write("Opening browser...");
|
|
4308
|
+
try {
|
|
4309
|
+
await browserService.open(authUrl);
|
|
4310
|
+
} catch (error2) {
|
|
4311
|
+
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
4312
|
+
output.write(`Could not open browser automatically: ${msg}
|
|
4313
|
+
`);
|
|
4314
|
+
output.write(`Open this URL in your browser:
|
|
4315
|
+
`);
|
|
4316
|
+
output.write(` ${authUrl}
|
|
4317
|
+
`);
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
output.write(`Waiting for authentication...
|
|
4321
|
+
`);
|
|
4322
|
+
let timeoutId;
|
|
4323
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
4324
|
+
timeoutId = setTimeout(() => reject(new Error(AUTH_TIMEOUT_MESSAGE)), TIMEOUT_MS);
|
|
4325
|
+
});
|
|
4326
|
+
let callback;
|
|
4327
|
+
try {
|
|
4328
|
+
callback = await Promise.race([callbackServer.result, timeoutPromise]);
|
|
4329
|
+
if (timeoutId)
|
|
4330
|
+
clearTimeout(timeoutId);
|
|
4331
|
+
} catch (error2) {
|
|
4332
|
+
if (timeoutId)
|
|
4333
|
+
clearTimeout(timeoutId);
|
|
4334
|
+
await callbackServer.close().catch(() => {});
|
|
4335
|
+
if (shouldClearClientOnFailedAttempt) {
|
|
4336
|
+
await authStorage.clearClient(mcpUrl).catch(() => {});
|
|
4337
|
+
}
|
|
4338
|
+
const msg = error2 instanceof Error ? error2.message : "Authentication failed";
|
|
4339
|
+
return { status: "failed", message: ensureTerminalPeriod(msg) };
|
|
4340
|
+
}
|
|
4341
|
+
if (callback.type !== "success") {
|
|
4342
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
4343
|
+
if (shouldClearClientOnFailedAttempt) {
|
|
4344
|
+
await authStorage.clearClient(mcpUrl).catch(() => {});
|
|
4345
|
+
}
|
|
4346
|
+
return {
|
|
4347
|
+
status: "failed",
|
|
4348
|
+
message: callback.message ?? "Authentication callback failed."
|
|
4349
|
+
};
|
|
4350
|
+
}
|
|
4351
|
+
let tokenResponse;
|
|
4352
|
+
try {
|
|
4353
|
+
tokenResponse = await authService.exchangeCodeForTokens({
|
|
4354
|
+
tokenEndpoint: metadata.tokenEndpoint,
|
|
4355
|
+
clientId: client.clientId,
|
|
4356
|
+
clientSecret: client.clientSecret,
|
|
4357
|
+
code: callback.code,
|
|
4358
|
+
codeVerifier: verifier,
|
|
4359
|
+
redirectUri
|
|
4360
|
+
});
|
|
4361
|
+
} catch (error2) {
|
|
4362
|
+
try {
|
|
4363
|
+
await authStorage.clearClient(mcpUrl);
|
|
4364
|
+
} catch {}
|
|
4365
|
+
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
4366
|
+
return {
|
|
4367
|
+
status: "failed",
|
|
4368
|
+
message: `Failed to complete authentication: ${msg}`
|
|
4369
|
+
};
|
|
4370
|
+
}
|
|
4371
|
+
const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1000).toISOString();
|
|
4372
|
+
await authStorage.saveAuthSession(mcpUrl, client, {
|
|
4373
|
+
accessToken: tokenResponse.accessToken,
|
|
4374
|
+
refreshToken: tokenResponse.refreshToken,
|
|
4375
|
+
expiresAt,
|
|
4376
|
+
createdAt: new Date().toISOString()
|
|
4377
|
+
});
|
|
4378
|
+
const hours = Math.round(tokenResponse.expiresIn / 3600);
|
|
4379
|
+
return {
|
|
4380
|
+
status: "success",
|
|
4381
|
+
message: `Logged in successfully. Token expires in ${hours} hour${hours !== 1 ? "s" : ""}.`
|
|
4382
|
+
};
|
|
4383
|
+
}
|
|
4384
|
+
async function loginAction(options, deps) {
|
|
4385
|
+
const result = await loginFlow(options, deps, stdoutLoginOutput);
|
|
4386
|
+
if (result.status === "already_authenticated") {
|
|
4387
|
+
console.log(`Already logged in.
|
|
4388
|
+
`);
|
|
4389
|
+
console.log(` Environment: ${deps.mcpUrl}
|
|
4390
|
+
`);
|
|
4391
|
+
console.log("To re-authenticate, use `githits login --force`.");
|
|
4392
|
+
return;
|
|
4393
|
+
}
|
|
4394
|
+
if (result.status === "failed") {
|
|
4395
|
+
console.error(`${result.message}
|
|
4396
|
+
`);
|
|
4397
|
+
printLoginRecoveryHint(result.message);
|
|
4398
|
+
process.exit(1);
|
|
4399
|
+
}
|
|
4400
|
+
console.log(`Logged in successfully.
|
|
4401
|
+
`);
|
|
4402
|
+
console.log(` Environment: ${deps.mcpUrl}`);
|
|
4403
|
+
console.log(result.message.replace("Logged in successfully. ", " "));
|
|
4404
|
+
console.log(`
|
|
4405
|
+
You're ready to use githits with your AI assistant.`);
|
|
4406
|
+
}
|
|
4407
|
+
function printLoginRecoveryHint(message) {
|
|
4408
|
+
console.log("Recovery steps:");
|
|
4409
|
+
if (message.includes("Authentication timed out")) {
|
|
4410
|
+
console.log(" Run the same command again to open a fresh sign-in link.");
|
|
4411
|
+
console.log(" githits login --no-browser # if the browser did not open or you are on SSH");
|
|
4412
|
+
console.log(" githits logout && githits login # if sign-in keeps failing after a retry");
|
|
4413
|
+
return;
|
|
4414
|
+
}
|
|
4415
|
+
console.log(" githits auth status");
|
|
4416
|
+
console.log(" githits login --force");
|
|
4417
|
+
if (message.includes("Cannot persist OAuth credentials")) {
|
|
4418
|
+
console.log("If your system keychain is locked or unavailable, unlock it and retry.");
|
|
4419
|
+
console.log("For CI/automation, set GITHITS_API_TOKEN.");
|
|
4420
|
+
console.log("As a last resort, set GITHITS_AUTH_STORAGE=file to use plaintext file storage.");
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
function printAutoLoginRecoveryHint(message) {
|
|
4424
|
+
if (message.includes("Authentication timed out")) {
|
|
4425
|
+
console.error("Run the same command again to open a fresh sign-in link.");
|
|
4426
|
+
console.error("If the browser did not open, run `githits login --no-browser` and follow the printed link.");
|
|
4427
|
+
console.error("If sign-in keeps failing after a retry, run `githits logout` and then run your command again.");
|
|
4428
|
+
return;
|
|
4429
|
+
}
|
|
4430
|
+
console.error("Run the same command again to try signing in again.");
|
|
4431
|
+
console.error("Run `githits auth status` to check whether you are signed in.");
|
|
4432
|
+
if (message.includes("Cannot persist OAuth credentials")) {
|
|
4433
|
+
console.error("If your system keychain is locked or unavailable, unlock it and try again.");
|
|
4434
|
+
console.error("For CI/automation, set GITHITS_API_TOKEN.");
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
function ensureTerminalPeriod(message) {
|
|
4438
|
+
return /[.!?]$/.test(message) ? message : `${message}.`;
|
|
4439
|
+
}
|
|
4440
|
+
var LOGIN_DESCRIPTION = `Authenticate with your GitHits account via browser.
|
|
4441
|
+
|
|
4442
|
+
Opens your browser to complete authentication securely using OAuth.
|
|
4443
|
+
OAuth credentials are stored in the system keychain by default. If your
|
|
4444
|
+
machine has no usable keychain, use GITHITS_API_TOKEN or explicitly configure
|
|
4445
|
+
auth.storage = "file". File storage is plaintext on disk.
|
|
4446
|
+
|
|
4447
|
+
Use --no-browser in environments without a display (CI, SSH sessions)
|
|
4448
|
+
to get a URL you can open on another device.`;
|
|
4449
|
+
function registerLoginCommand(program) {
|
|
4450
|
+
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) => {
|
|
4451
|
+
const deps = await createAuthCommandDependencies();
|
|
4452
|
+
await loginAction(options, deps);
|
|
4453
|
+
});
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4143
4456
|
// src/shared/auto-login.ts
|
|
4144
4457
|
var AUTO_LOGIN_ELIGIBLE_COMMANDS = new Set([
|
|
4145
|
-
"init",
|
|
4146
4458
|
"example",
|
|
4147
4459
|
"languages",
|
|
4148
4460
|
"feedback",
|
|
@@ -4175,9 +4487,6 @@ function isAutoLoginEligibleCommand(command, runtime = {
|
|
|
4175
4487
|
stdoutIsTTY: Boolean(process.stdout.isTTY)
|
|
4176
4488
|
}) {
|
|
4177
4489
|
const commandPath = getCommandPath(command).join(" ");
|
|
4178
|
-
if (commandPath === "init" && command.opts().skipLogin === true) {
|
|
4179
|
-
return false;
|
|
4180
|
-
}
|
|
4181
4490
|
if (!AUTO_LOGIN_ELIGIBLE_COMMANDS.has(commandPath)) {
|
|
4182
4491
|
return false;
|
|
4183
4492
|
}
|
|
@@ -4229,16 +4538,15 @@ function createRootCliPreAction(deps) {
|
|
|
4229
4538
|
if (authResult.status !== "failed") {
|
|
4230
4539
|
return;
|
|
4231
4540
|
}
|
|
4232
|
-
|
|
4541
|
+
const failureMessage = authResult.message ?? "Authentication failed.";
|
|
4542
|
+
console.error(`${failureMessage}
|
|
4233
4543
|
`);
|
|
4234
|
-
|
|
4544
|
+
printAutoLoginRecoveryHint(failureMessage);
|
|
4235
4545
|
(deps.exit ?? process.exit)(1);
|
|
4236
4546
|
};
|
|
4237
4547
|
}
|
|
4238
4548
|
function getPostLoginContinuationMessage(command) {
|
|
4239
4549
|
switch (getCommandPath(command).join(" ")) {
|
|
4240
|
-
case "init":
|
|
4241
|
-
return "Authentication complete. Continuing setup...";
|
|
4242
4550
|
case "example":
|
|
4243
4551
|
return "Authentication complete. Running example search...";
|
|
4244
4552
|
case "languages":
|
|
@@ -4313,11 +4621,6 @@ function resolveTargets(target, targets) {
|
|
|
4313
4621
|
seen.add(key);
|
|
4314
4622
|
deduped.push(entry);
|
|
4315
4623
|
}
|
|
4316
|
-
const hasPackageTarget = deduped.some((entry) => entry.packageName);
|
|
4317
|
-
const hasRepoTarget = deduped.some((entry) => entry.repoUrl);
|
|
4318
|
-
if (hasPackageTarget && hasRepoTarget) {
|
|
4319
|
-
throw new InvalidArgumentError("Do not mix package-scoped and repo-scoped targets in one search.");
|
|
4320
|
-
}
|
|
4321
4624
|
return deduped;
|
|
4322
4625
|
}
|
|
4323
4626
|
function normaliseRequiredQuery(query) {
|
|
@@ -4376,6 +4679,7 @@ var DEFAULT_LIMIT2 = DEFAULT_UNIFIED_SEARCH_LIMIT;
|
|
|
4376
4679
|
var DEFAULT_OFFSET = 0;
|
|
4377
4680
|
function buildUnifiedSearchSuccessPayload(params, rawQuery, compiledQuery, outcome) {
|
|
4378
4681
|
const warnings = outcome.state === "completed" ? outcome.result.queryWarnings : outcome.result?.queryWarnings ?? outcome.progress?.queryWarnings ?? [];
|
|
4682
|
+
const progress = compactProgress(outcome.progress);
|
|
4379
4683
|
const query = buildQueryEcho(params, rawQuery, compiledQuery, warnings);
|
|
4380
4684
|
if (outcome.state === "incomplete") {
|
|
4381
4685
|
const result = outcome.result;
|
|
@@ -4389,13 +4693,12 @@ function buildUnifiedSearchSuccessPayload(params, rawQuery, compiledQuery, outco
|
|
|
4389
4693
|
if (result?.page.hasMore === true) {
|
|
4390
4694
|
payload.nextOffset = result.page.offset + result.page.returned;
|
|
4391
4695
|
}
|
|
4392
|
-
const progress = compactProgress(outcome.progress);
|
|
4393
4696
|
if (progress)
|
|
4394
4697
|
payload.progress = progress;
|
|
4395
4698
|
const sourceStatus2 = compactSourceStatus(result?.sourceStatus);
|
|
4396
4699
|
if (sourceStatus2)
|
|
4397
4700
|
payload.sourceStatus = sourceStatus2;
|
|
4398
|
-
const combinedWarnings2 = combineWarnings(warnings, sourceStatus2);
|
|
4701
|
+
const combinedWarnings2 = combineWarnings(warnings, sourceStatus2, payload.results, progress);
|
|
4399
4702
|
if (combinedWarnings2.length > 0)
|
|
4400
4703
|
payload.warnings = combinedWarnings2;
|
|
4401
4704
|
return payload;
|
|
@@ -4414,15 +4717,17 @@ function buildUnifiedSearchSuccessPayload(params, rawQuery, compiledQuery, outco
|
|
|
4414
4717
|
const sourceStatus = compactSourceStatus(outcome.result.sourceStatus);
|
|
4415
4718
|
if (sourceStatus)
|
|
4416
4719
|
completed.sourceStatus = sourceStatus;
|
|
4417
|
-
const combinedWarnings = combineWarnings(warnings, sourceStatus);
|
|
4720
|
+
const combinedWarnings = combineWarnings(warnings, sourceStatus, completed.results, progress);
|
|
4418
4721
|
if (combinedWarnings.length > 0)
|
|
4419
4722
|
completed.warnings = combinedWarnings;
|
|
4420
4723
|
return completed;
|
|
4421
4724
|
}
|
|
4422
|
-
function combineWarnings(parserWarnings, sourceStatus) {
|
|
4725
|
+
function combineWarnings(parserWarnings, sourceStatus, hits = [], progress) {
|
|
4423
4726
|
const out = [];
|
|
4424
4727
|
if (parserWarnings.length > 0)
|
|
4425
4728
|
out.push(...parserWarnings);
|
|
4729
|
+
out.push(...buildHitFreshnessWarnings(hits));
|
|
4730
|
+
out.push(...buildProgressFreshnessWarnings(progress));
|
|
4426
4731
|
out.push(...buildSourceStatusWarnings(sourceStatus));
|
|
4427
4732
|
return out;
|
|
4428
4733
|
}
|
|
@@ -4449,6 +4754,9 @@ function buildUnifiedSearchStatusPayload(outcome) {
|
|
|
4449
4754
|
const progress = compactProgress(outcome.progress);
|
|
4450
4755
|
if (progress)
|
|
4451
4756
|
payload2.progress = progress;
|
|
4757
|
+
const progressWarnings = buildProgressFreshnessWarnings(progress);
|
|
4758
|
+
if (progressWarnings.length > 0)
|
|
4759
|
+
payload2.warnings = progressWarnings;
|
|
4452
4760
|
if (outcome.result) {
|
|
4453
4761
|
payload2.result = buildUnifiedSearchStatusResultPayload(outcome.result);
|
|
4454
4762
|
}
|
|
@@ -4464,6 +4772,7 @@ function buildUnifiedSearchStatusPayload(outcome) {
|
|
|
4464
4772
|
}
|
|
4465
4773
|
function buildUnifiedSearchStatusResultPayload(result) {
|
|
4466
4774
|
const payload = {
|
|
4775
|
+
query: buildStatusQueryEcho(result),
|
|
4467
4776
|
hasMore: result.page.hasMore,
|
|
4468
4777
|
results: result.results.map(buildHitPayload)
|
|
4469
4778
|
};
|
|
@@ -4482,6 +4791,18 @@ function buildUnifiedSearchStatusResultPayload(result) {
|
|
|
4482
4791
|
}
|
|
4483
4792
|
return payload;
|
|
4484
4793
|
}
|
|
4794
|
+
function buildStatusQueryEcho(result) {
|
|
4795
|
+
const query = {
|
|
4796
|
+
raw: result.query
|
|
4797
|
+
};
|
|
4798
|
+
if (result.queryWarnings.length > 0) {
|
|
4799
|
+
query.warnings = result.queryWarnings;
|
|
4800
|
+
}
|
|
4801
|
+
if (result.sources.length > 0) {
|
|
4802
|
+
query.sources = result.sources.map((entry) => entry.toLowerCase());
|
|
4803
|
+
}
|
|
4804
|
+
return query;
|
|
4805
|
+
}
|
|
4485
4806
|
function buildQueryEcho(params, rawQuery, compiledQuery, warnings) {
|
|
4486
4807
|
const echo = {
|
|
4487
4808
|
raw: rawQuery
|
|
@@ -4531,6 +4852,12 @@ function buildHitPayload(hit) {
|
|
|
4531
4852
|
target: hit.targetLabel,
|
|
4532
4853
|
locator: buildLocatorPayload(hit)
|
|
4533
4854
|
};
|
|
4855
|
+
appendFreshness(payload, {
|
|
4856
|
+
requestedTargetLabel: hit.requestedTargetLabel,
|
|
4857
|
+
freshTargetLabel: hit.freshTargetLabel,
|
|
4858
|
+
servedTargetLabel: hit.servedTargetLabel,
|
|
4859
|
+
freshness: hit.freshness
|
|
4860
|
+
});
|
|
4534
4861
|
if (hit.title)
|
|
4535
4862
|
payload.title = hit.title;
|
|
4536
4863
|
if (hit.summary)
|
|
@@ -4538,6 +4865,9 @@ function buildHitPayload(hit) {
|
|
|
4538
4865
|
const highlights = buildHighlights(hit.highlights);
|
|
4539
4866
|
if (highlights)
|
|
4540
4867
|
payload.highlights = highlights;
|
|
4868
|
+
const followUp = buildSearchHitFollowUpCommand(payload);
|
|
4869
|
+
if (followUp)
|
|
4870
|
+
payload.followUp = followUp;
|
|
4541
4871
|
return payload;
|
|
4542
4872
|
}
|
|
4543
4873
|
function buildLocatorPayload(hit) {
|
|
@@ -4599,10 +4929,79 @@ function compactProgress(progress) {
|
|
|
4599
4929
|
targetsTotal: progress.targetsTotal,
|
|
4600
4930
|
elapsedMs: progress.elapsedMs
|
|
4601
4931
|
};
|
|
4932
|
+
if (progress.query)
|
|
4933
|
+
payload.query = progress.query;
|
|
4934
|
+
if (progress.requestedSources?.length) {
|
|
4935
|
+
payload.requestedSources = progress.requestedSources.map((entry) => entry.toLowerCase());
|
|
4936
|
+
}
|
|
4937
|
+
if (progress.targetMode)
|
|
4938
|
+
payload.targetMode = progress.targetMode;
|
|
4939
|
+
if (progress.requestedTargets?.length) {
|
|
4940
|
+
payload.requestedTargets = progress.requestedTargets;
|
|
4941
|
+
}
|
|
4942
|
+
if (progress.filters)
|
|
4943
|
+
payload.filters = buildFilterEcho3(progress.filters);
|
|
4944
|
+
if (typeof progress.limit === "number")
|
|
4945
|
+
payload.limit = progress.limit;
|
|
4946
|
+
if (typeof progress.offset === "number")
|
|
4947
|
+
payload.offset = progress.offset;
|
|
4948
|
+
const targets = progress.targets?.map(compactProgressTarget).filter(Boolean);
|
|
4949
|
+
if (targets?.length) {
|
|
4950
|
+
payload.targets = targets;
|
|
4951
|
+
}
|
|
4602
4952
|
if (progress.expiresAt)
|
|
4603
4953
|
payload.expiresAt = progress.expiresAt;
|
|
4954
|
+
payload.next = `search_status search_ref=${JSON.stringify(progress.searchRef)}`;
|
|
4604
4955
|
return payload;
|
|
4605
4956
|
}
|
|
4957
|
+
function appendFreshness(payload, source) {
|
|
4958
|
+
if (!isTrustRelevantFreshness(source.freshness) || !labelsDiverge({
|
|
4959
|
+
requestedTarget: source.requestedTargetLabel,
|
|
4960
|
+
freshTarget: source.freshTargetLabel,
|
|
4961
|
+
servedTarget: source.servedTargetLabel
|
|
4962
|
+
})) {
|
|
4963
|
+
return;
|
|
4964
|
+
}
|
|
4965
|
+
if (source.requestedTargetLabel)
|
|
4966
|
+
payload.requestedTarget = source.requestedTargetLabel;
|
|
4967
|
+
if (source.freshTargetLabel)
|
|
4968
|
+
payload.freshTarget = source.freshTargetLabel;
|
|
4969
|
+
if (source.servedTargetLabel)
|
|
4970
|
+
payload.servedTarget = source.servedTargetLabel;
|
|
4971
|
+
if (source.freshness)
|
|
4972
|
+
payload.freshness = source.freshness;
|
|
4973
|
+
}
|
|
4974
|
+
function compactProgressTarget(target) {
|
|
4975
|
+
const payload = {};
|
|
4976
|
+
if (target.requested)
|
|
4977
|
+
payload.requested = target.requested;
|
|
4978
|
+
if (target.resolvedRequested)
|
|
4979
|
+
payload.resolvedRequested = target.resolvedRequested;
|
|
4980
|
+
if (target.served)
|
|
4981
|
+
payload.served = target.served;
|
|
4982
|
+
if (target.freshness)
|
|
4983
|
+
payload.freshness = target.freshness;
|
|
4984
|
+
if (target.indexingRef)
|
|
4985
|
+
payload.indexingRef = target.indexingRef;
|
|
4986
|
+
if (target.requestedRefKind)
|
|
4987
|
+
payload.requestedRefKind = target.requestedRefKind;
|
|
4988
|
+
return Object.keys(payload).length > 0 ? payload : undefined;
|
|
4989
|
+
}
|
|
4990
|
+
function buildFilterEcho3(filters) {
|
|
4991
|
+
const echo = {};
|
|
4992
|
+
if (filters.kind)
|
|
4993
|
+
echo.kind = filters.kind.toLowerCase();
|
|
4994
|
+
if (filters.category)
|
|
4995
|
+
echo.category = filters.category.toLowerCase();
|
|
4996
|
+
if (filters.pathPrefix)
|
|
4997
|
+
echo.pathPrefix = filters.pathPrefix;
|
|
4998
|
+
if (filters.fileIntent)
|
|
4999
|
+
echo.fileIntent = filters.fileIntent.toLowerCase();
|
|
5000
|
+
if (typeof filters.publicOnly === "boolean") {
|
|
5001
|
+
echo.publicOnly = filters.publicOnly;
|
|
5002
|
+
}
|
|
5003
|
+
return Object.keys(echo).length > 0 ? echo : undefined;
|
|
5004
|
+
}
|
|
4606
5005
|
function buildSourceStatusWarnings(sourceStatus) {
|
|
4607
5006
|
if (!sourceStatus || sourceStatus.length === 0)
|
|
4608
5007
|
return [];
|
|
@@ -4614,8 +5013,51 @@ function buildSourceStatusWarnings(sourceStatus) {
|
|
|
4614
5013
|
}
|
|
4615
5014
|
return warnings;
|
|
4616
5015
|
}
|
|
5016
|
+
function buildHitFreshnessWarnings(hits) {
|
|
5017
|
+
return hits.map((hit) => freshnessWarning({
|
|
5018
|
+
freshness: hit.freshness,
|
|
5019
|
+
requestedTarget: hit.requestedTarget,
|
|
5020
|
+
freshTarget: hit.freshTarget,
|
|
5021
|
+
servedTarget: hit.servedTarget
|
|
5022
|
+
})).filter((entry) => Boolean(entry));
|
|
5023
|
+
}
|
|
5024
|
+
function buildProgressFreshnessWarnings(progress) {
|
|
5025
|
+
return (progress?.targets ?? []).map((target) => freshnessWarning({
|
|
5026
|
+
freshness: target.freshness,
|
|
5027
|
+
requestedTarget: target.requested,
|
|
5028
|
+
freshTarget: target.resolvedRequested,
|
|
5029
|
+
servedTarget: target.served
|
|
5030
|
+
})).filter((entry) => Boolean(entry));
|
|
5031
|
+
}
|
|
5032
|
+
function freshnessWarning(input) {
|
|
5033
|
+
if (!isTrustRelevantFreshness(input.freshness))
|
|
5034
|
+
return;
|
|
5035
|
+
if (!labelsDiverge(input))
|
|
5036
|
+
return;
|
|
5037
|
+
const requested = input.requestedTarget ?? "requested target";
|
|
5038
|
+
const served = input.servedTarget ?? "served target";
|
|
5039
|
+
const fresh = input.freshTarget;
|
|
5040
|
+
return fresh ? `requested ${requested}; served stale ${served} while ${fresh} indexes.` : `requested ${requested}; served stale ${served}.`;
|
|
5041
|
+
}
|
|
5042
|
+
function isTrustRelevantFreshness(value) {
|
|
5043
|
+
return value === "STALE" || value === "INDEXING";
|
|
5044
|
+
}
|
|
5045
|
+
function labelsDiverge(input) {
|
|
5046
|
+
const served = input.servedTarget;
|
|
5047
|
+
if (!served)
|
|
5048
|
+
return false;
|
|
5049
|
+
return Boolean(input.freshTarget && input.freshTarget !== served || input.requestedTarget && input.requestedTarget !== served);
|
|
5050
|
+
}
|
|
4617
5051
|
function warningForEntry(entry) {
|
|
4618
5052
|
const reasons = [];
|
|
5053
|
+
const freshness = freshnessWarning({
|
|
5054
|
+
freshness: entry.codeIndexState,
|
|
5055
|
+
requestedTarget: entry.requestedTarget,
|
|
5056
|
+
freshTarget: entry.freshTarget,
|
|
5057
|
+
servedTarget: entry.servedTarget
|
|
5058
|
+
});
|
|
5059
|
+
if (freshness)
|
|
5060
|
+
return freshness;
|
|
4619
5061
|
if (entry.incompatibleQueryFeatures?.length) {
|
|
4620
5062
|
reasons.push(`incompatible query features [${entry.incompatibleQueryFeatures.join(", ")}]`);
|
|
4621
5063
|
}
|
|
@@ -4632,7 +5074,9 @@ function warningForEntry(entry) {
|
|
|
4632
5074
|
reasons.push(`indexing status ${entry.indexingStatus}`);
|
|
4633
5075
|
}
|
|
4634
5076
|
if (entry.codeIndexState) {
|
|
4635
|
-
|
|
5077
|
+
if (entry.codeIndexState !== "STALE") {
|
|
5078
|
+
reasons.push(`code index state ${entry.codeIndexState}`);
|
|
5079
|
+
}
|
|
4636
5080
|
}
|
|
4637
5081
|
const prefix = `Source '${entry.source}' for ${entry.targetLabel}`;
|
|
4638
5082
|
if (reasons.length > 0) {
|
|
@@ -4660,11 +5104,23 @@ function compactSourceStatusEntry(entry) {
|
|
|
4660
5104
|
targetLabel: entry.targetLabel
|
|
4661
5105
|
};
|
|
4662
5106
|
let interesting = false;
|
|
5107
|
+
const staleDiverges = entry.codeIndexState === "STALE" && labelsDiverge({
|
|
5108
|
+
requestedTarget: entry.requestedTargetLabel,
|
|
5109
|
+
freshTarget: entry.freshTargetLabel,
|
|
5110
|
+
servedTarget: entry.servedTargetLabel
|
|
5111
|
+
});
|
|
5112
|
+
if (staleDiverges) {
|
|
5113
|
+
payload.requestedTarget = entry.requestedTargetLabel;
|
|
5114
|
+
payload.freshTarget = entry.freshTargetLabel;
|
|
5115
|
+
payload.servedTarget = entry.servedTargetLabel;
|
|
5116
|
+
payload.codeIndexState = entry.codeIndexState;
|
|
5117
|
+
interesting = true;
|
|
5118
|
+
}
|
|
4663
5119
|
if (entry.indexingStatus && entry.indexingStatus !== "INDEXED") {
|
|
4664
5120
|
payload.indexingStatus = entry.indexingStatus;
|
|
4665
5121
|
interesting = true;
|
|
4666
5122
|
}
|
|
4667
|
-
if (entry.codeIndexState && entry.codeIndexState !== "CURRENT" && entry.codeIndexState !== "STALE") {
|
|
5123
|
+
if (entry.codeIndexState && entry.codeIndexState !== "CURRENT" && (entry.codeIndexState !== "STALE" || staleDiverges)) {
|
|
4668
5124
|
payload.codeIndexState = entry.codeIndexState;
|
|
4669
5125
|
interesting = true;
|
|
4670
5126
|
}
|
|
@@ -4839,8 +5295,45 @@ function buildTrailer2(payload) {
|
|
|
4839
5295
|
lines.push(` - ${formatSourceStatus(entry)}`);
|
|
4840
5296
|
}
|
|
4841
5297
|
}
|
|
5298
|
+
const progress = "progress" in payload ? payload.progress : undefined;
|
|
5299
|
+
if (progress?.targets?.length) {
|
|
5300
|
+
lines.push("progress targets:");
|
|
5301
|
+
for (const target of progress.targets) {
|
|
5302
|
+
lines.push(` - ${formatProgressTarget(target)}`);
|
|
5303
|
+
}
|
|
5304
|
+
}
|
|
4842
5305
|
return lines;
|
|
4843
5306
|
}
|
|
5307
|
+
function formatProgressTarget(target) {
|
|
5308
|
+
const parts = [];
|
|
5309
|
+
if (target.requested)
|
|
5310
|
+
parts.push(`requested=${target.requested}`);
|
|
5311
|
+
if (target.resolvedRequested)
|
|
5312
|
+
parts.push(`fresh=${target.resolvedRequested}`);
|
|
5313
|
+
if (target.served)
|
|
5314
|
+
parts.push(`served=${target.served}`);
|
|
5315
|
+
if (target.freshness)
|
|
5316
|
+
parts.push(`state=${describeFreshness(target.freshness)}`);
|
|
5317
|
+
if (target.requestedRefKind)
|
|
5318
|
+
parts.push(`intent=${target.requestedRefKind}`);
|
|
5319
|
+
if (target.indexingRef)
|
|
5320
|
+
parts.push(`indexingRef=${target.indexingRef}`);
|
|
5321
|
+
return parts.length > 0 ? parts.join(SEP6) : "target progress unavailable";
|
|
5322
|
+
}
|
|
5323
|
+
function describeFreshness(value) {
|
|
5324
|
+
switch (value) {
|
|
5325
|
+
case "PENDING":
|
|
5326
|
+
case "INDEXING":
|
|
5327
|
+
return "indexing fresh target";
|
|
5328
|
+
case "STALE":
|
|
5329
|
+
return "served stale evidence";
|
|
5330
|
+
case "CURRENT":
|
|
5331
|
+
case "INDEXED":
|
|
5332
|
+
return "current";
|
|
5333
|
+
default:
|
|
5334
|
+
return value.toLowerCase();
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
4844
5337
|
function formatSourceStatus(entry) {
|
|
4845
5338
|
const parts = [`${entry.source} (${entry.targetLabel})`];
|
|
4846
5339
|
if (entry.indexingStatus)
|
|
@@ -4903,6 +5396,17 @@ function renderUnifiedSearchStatusText(payload) {
|
|
|
4903
5396
|
lines.push(buildHeader8(payload));
|
|
4904
5397
|
if (!payload.completed && payload.progress) {
|
|
4905
5398
|
lines.push(formatProgress(payload.progress));
|
|
5399
|
+
if (payload.progress.targets?.length) {
|
|
5400
|
+
lines.push("progress targets:");
|
|
5401
|
+
for (const target of payload.progress.targets) {
|
|
5402
|
+
lines.push(` - ${formatProgressTarget(target)}`);
|
|
5403
|
+
}
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
if (!payload.completed && payload.warnings && payload.warnings.length > 0) {
|
|
5407
|
+
lines.push("warnings:");
|
|
5408
|
+
for (const warning2 of payload.warnings)
|
|
5409
|
+
lines.push(` - ${warning2}`);
|
|
4906
5410
|
}
|
|
4907
5411
|
const result = payload.result;
|
|
4908
5412
|
if (result)
|
|
@@ -4969,14 +5473,39 @@ function formatSourceStatus2(entry) {
|
|
|
4969
5473
|
return parts.join(SEP7);
|
|
4970
5474
|
}
|
|
4971
5475
|
function formatProgress(progress) {
|
|
4972
|
-
|
|
5476
|
+
const next = progress.next ? `; next: ${progress.next}` : "";
|
|
5477
|
+
return `progress: ${progress.status}, ${progress.targetsReady}/${progress.targetsTotal} targets ready, ${progress.elapsedMs}ms elapsed${next}`;
|
|
4973
5478
|
}
|
|
4974
5479
|
function quote5(value) {
|
|
4975
5480
|
return JSON.stringify(value);
|
|
4976
5481
|
}
|
|
4977
5482
|
// src/shared/unified-search-target.ts
|
|
4978
5483
|
function parseUnifiedSearchTargetSpec(spec) {
|
|
4979
|
-
|
|
5484
|
+
const trimmed = spec.trim();
|
|
5485
|
+
if (trimmed.length === 0) {
|
|
5486
|
+
throw new InvalidArgumentError("Target spec cannot be empty.");
|
|
5487
|
+
}
|
|
5488
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
|
5489
|
+
return parseDiscoveryRepoTarget(trimmed);
|
|
5490
|
+
}
|
|
5491
|
+
const parsed = parsePackageSpec(trimmed);
|
|
5492
|
+
return {
|
|
5493
|
+
registry: toCodeNavigationRegistry(parsed.registry),
|
|
5494
|
+
packageName: parsed.name,
|
|
5495
|
+
version: parsed.version
|
|
5496
|
+
};
|
|
5497
|
+
}
|
|
5498
|
+
function parseDiscoveryRepoTarget(spec) {
|
|
5499
|
+
const hashIndex = spec.lastIndexOf("#");
|
|
5500
|
+
if (hashIndex === -1) {
|
|
5501
|
+
return { repoUrl: spec };
|
|
5502
|
+
}
|
|
5503
|
+
const repoUrl = spec.slice(0, hashIndex);
|
|
5504
|
+
const gitRef = spec.slice(hashIndex + 1);
|
|
5505
|
+
if (!repoUrl || !gitRef) {
|
|
5506
|
+
throw new InvalidArgumentError("Repository target must be a full URL with optional #gitRef suffix.");
|
|
5507
|
+
}
|
|
5508
|
+
return { repoUrl, gitRef };
|
|
4980
5509
|
}
|
|
4981
5510
|
// src/shared/list-files-request.ts
|
|
4982
5511
|
var LIMIT_MIN2 = 1;
|
|
@@ -5734,7 +6263,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
|
|
|
5734
6263
|
match in --verbose output; full payload in --json).`;
|
|
5735
6264
|
function registerCodeGrepCommand(pkgCommand) {
|
|
5736
6265
|
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) => {
|
|
5737
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
6266
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-xzq0ecpa.js");
|
|
5738
6267
|
const deps = await createContainer2();
|
|
5739
6268
|
await pkgGrepAction(arg1, arg2, arg3, options, {
|
|
5740
6269
|
codeNavigationService: deps.codeNavigationService,
|
|
@@ -6210,6 +6739,10 @@ async function registerDocsCommandGroup(program, options = {}) {
|
|
|
6210
6739
|
// src/commands/example.ts
|
|
6211
6740
|
import { Option } from "commander";
|
|
6212
6741
|
async function exampleAction(query, options, deps) {
|
|
6742
|
+
if (!deps.hasValidToken && options.json) {
|
|
6743
|
+
printExampleError("Authentication required. Run `githits login`, then retry this command.", "AUTH_REQUIRED", true);
|
|
6744
|
+
process.exit(1);
|
|
6745
|
+
}
|
|
6213
6746
|
requireAuth(deps);
|
|
6214
6747
|
try {
|
|
6215
6748
|
const result = await deps.githitsService.search({
|
|
@@ -6226,10 +6759,21 @@ async function exampleAction(query, options, deps) {
|
|
|
6226
6759
|
console.log(result);
|
|
6227
6760
|
}
|
|
6228
6761
|
} catch (error2) {
|
|
6229
|
-
|
|
6762
|
+
if (error2 instanceof AuthenticationError) {
|
|
6763
|
+
printExampleError("Authentication required. Run `githits login`, then retry this command.", "AUTH_REQUIRED", options.json ?? false);
|
|
6764
|
+
process.exit(1);
|
|
6765
|
+
}
|
|
6766
|
+
printExampleError(`Failed to get example: ${error2 instanceof Error ? error2.message : error2}`, "UNKNOWN", options.json ?? false);
|
|
6230
6767
|
process.exit(1);
|
|
6231
6768
|
}
|
|
6232
6769
|
}
|
|
6770
|
+
function printExampleError(error2, code, json) {
|
|
6771
|
+
if (json) {
|
|
6772
|
+
console.error(JSON.stringify({ error: error2, code, retryable: false }));
|
|
6773
|
+
return;
|
|
6774
|
+
}
|
|
6775
|
+
console.error(error2);
|
|
6776
|
+
}
|
|
6233
6777
|
var EXAMPLE_DESCRIPTION = `Get verified, canonical code examples from global open source.
|
|
6234
6778
|
|
|
6235
6779
|
For dependency, package, or repository source search, use \`githits search\` instead.
|
|
@@ -6253,7 +6797,7 @@ function registerExampleCommand(program) {
|
|
|
6253
6797
|
});
|
|
6254
6798
|
}
|
|
6255
6799
|
async function loadContainer() {
|
|
6256
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
6800
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-xzq0ecpa.js");
|
|
6257
6801
|
return createContainer2();
|
|
6258
6802
|
}
|
|
6259
6803
|
// src/commands/feedback.ts
|
|
@@ -7022,230 +7566,6 @@ async function scanAgents(definitions, fs, execService) {
|
|
|
7022
7566
|
class ExitPromptError extends Error {
|
|
7023
7567
|
name = "ExitPromptError";
|
|
7024
7568
|
}
|
|
7025
|
-
// src/commands/login.ts
|
|
7026
|
-
var stdoutLoginOutput = {
|
|
7027
|
-
write: (message) => {
|
|
7028
|
-
console.log(message);
|
|
7029
|
-
}
|
|
7030
|
-
};
|
|
7031
|
-
var stderrLoginOutput = {
|
|
7032
|
-
write: (message) => {
|
|
7033
|
-
console.error(message);
|
|
7034
|
-
}
|
|
7035
|
-
};
|
|
7036
|
-
var TIMEOUT_MS = 5 * 60 * 1000;
|
|
7037
|
-
function randomPort() {
|
|
7038
|
-
return Math.floor(Math.random() * 2000) + 8000;
|
|
7039
|
-
}
|
|
7040
|
-
async function preflightAuthPersistence(authStorage, mcpUrl) {
|
|
7041
|
-
const probeUrl = `${mcpUrl.replace(/\/+$/, "")}/__githits_storage_probe__`;
|
|
7042
|
-
const probeClient = {
|
|
7043
|
-
clientId: "__githits_storage_probe__",
|
|
7044
|
-
clientSecret: "__githits_storage_probe__",
|
|
7045
|
-
redirectUri: "http://127.0.0.1:1/callback",
|
|
7046
|
-
registeredAt: new Date(0).toISOString()
|
|
7047
|
-
};
|
|
7048
|
-
const probeTokens = {
|
|
7049
|
-
accessToken: "__githits_storage_probe__",
|
|
7050
|
-
refreshToken: "__githits_storage_probe__",
|
|
7051
|
-
expiresAt: new Date(0).toISOString(),
|
|
7052
|
-
createdAt: new Date(0).toISOString()
|
|
7053
|
-
};
|
|
7054
|
-
try {
|
|
7055
|
-
await authStorage.saveAuthSession(probeUrl, probeClient, probeTokens);
|
|
7056
|
-
await authStorage.clearAuthSession(probeUrl);
|
|
7057
|
-
return null;
|
|
7058
|
-
} catch (error2) {
|
|
7059
|
-
await authStorage.clearAuthSession(probeUrl).catch(() => {});
|
|
7060
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
7061
|
-
return {
|
|
7062
|
-
status: "failed",
|
|
7063
|
-
message: `Cannot persist OAuth credentials: ${message}`
|
|
7064
|
-
};
|
|
7065
|
-
}
|
|
7066
|
-
}
|
|
7067
|
-
async function loginFlow(options, deps, output = stdoutLoginOutput) {
|
|
7068
|
-
const { authService, authStorage, browserService, mcpUrl } = deps;
|
|
7069
|
-
if (options.port !== undefined && (Number.isNaN(options.port) || options.port < 1 || options.port > 65535)) {
|
|
7070
|
-
return {
|
|
7071
|
-
status: "failed",
|
|
7072
|
-
message: "Invalid port number. Must be between 1 and 65535."
|
|
7073
|
-
};
|
|
7074
|
-
}
|
|
7075
|
-
const existing = await authStorage.loadTokens(mcpUrl);
|
|
7076
|
-
if (existing && !options.force) {
|
|
7077
|
-
const isExpired = existing.expiresAt && new Date(existing.expiresAt) < new Date;
|
|
7078
|
-
if (!isExpired) {
|
|
7079
|
-
return { status: "already_authenticated", message: "Already logged in." };
|
|
7080
|
-
}
|
|
7081
|
-
output.write(`Token expired. Starting new login...
|
|
7082
|
-
`);
|
|
7083
|
-
} else if (existing && options.force) {
|
|
7084
|
-
output.write(`Re-authenticating (--force flag)...
|
|
7085
|
-
`);
|
|
7086
|
-
}
|
|
7087
|
-
if (!existing) {
|
|
7088
|
-
await authStorage.clearClient(mcpUrl);
|
|
7089
|
-
}
|
|
7090
|
-
const persistenceError = await preflightAuthPersistence(authStorage, mcpUrl);
|
|
7091
|
-
if (persistenceError)
|
|
7092
|
-
return persistenceError;
|
|
7093
|
-
output.write("Discovering OAuth endpoints...");
|
|
7094
|
-
const metadata = await authService.discoverEndpoints(mcpUrl);
|
|
7095
|
-
let client = await authStorage.loadClient(mcpUrl);
|
|
7096
|
-
let port;
|
|
7097
|
-
let redirectUri;
|
|
7098
|
-
if (client) {
|
|
7099
|
-
if (options.port) {
|
|
7100
|
-
redirectUri = `http://127.0.0.1:${options.port}/callback`;
|
|
7101
|
-
if (redirectUri !== client.redirectUri) {
|
|
7102
|
-
output.write("Registering CLI client with new port...");
|
|
7103
|
-
const registration = await authService.registerClient({
|
|
7104
|
-
registrationEndpoint: metadata.registrationEndpoint,
|
|
7105
|
-
redirectUri
|
|
7106
|
-
});
|
|
7107
|
-
client = {
|
|
7108
|
-
clientId: registration.clientId,
|
|
7109
|
-
clientSecret: registration.clientSecret,
|
|
7110
|
-
redirectUri,
|
|
7111
|
-
registeredAt: new Date().toISOString()
|
|
7112
|
-
};
|
|
7113
|
-
}
|
|
7114
|
-
port = options.port;
|
|
7115
|
-
} else {
|
|
7116
|
-
redirectUri = client.redirectUri;
|
|
7117
|
-
const storedUrl = new URL(redirectUri);
|
|
7118
|
-
port = Number(storedUrl.port) || randomPort();
|
|
7119
|
-
}
|
|
7120
|
-
} else {
|
|
7121
|
-
port = options.port ?? randomPort();
|
|
7122
|
-
redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
7123
|
-
output.write("Registering CLI client...");
|
|
7124
|
-
const registration = await authService.registerClient({
|
|
7125
|
-
registrationEndpoint: metadata.registrationEndpoint,
|
|
7126
|
-
redirectUri
|
|
7127
|
-
});
|
|
7128
|
-
client = {
|
|
7129
|
-
clientId: registration.clientId,
|
|
7130
|
-
clientSecret: registration.clientSecret,
|
|
7131
|
-
redirectUri,
|
|
7132
|
-
registeredAt: new Date().toISOString()
|
|
7133
|
-
};
|
|
7134
|
-
}
|
|
7135
|
-
const { verifier, challenge, state } = authService.generatePkceParams();
|
|
7136
|
-
const authUrl = authService.buildAuthUrl({
|
|
7137
|
-
authorizationEndpoint: metadata.authorizationEndpoint,
|
|
7138
|
-
clientId: client.clientId,
|
|
7139
|
-
redirectUri,
|
|
7140
|
-
state,
|
|
7141
|
-
codeChallenge: challenge
|
|
7142
|
-
});
|
|
7143
|
-
const serverPromise = authService.startCallbackServer(port, state);
|
|
7144
|
-
if (options.browser === false) {
|
|
7145
|
-
output.write(`Open this URL in your browser:
|
|
7146
|
-
`);
|
|
7147
|
-
output.write(` ${authUrl}
|
|
7148
|
-
`);
|
|
7149
|
-
} else {
|
|
7150
|
-
output.write("Opening browser...");
|
|
7151
|
-
await browserService.open(authUrl);
|
|
7152
|
-
}
|
|
7153
|
-
output.write(`Waiting for authentication...
|
|
7154
|
-
`);
|
|
7155
|
-
let timeoutId;
|
|
7156
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
7157
|
-
timeoutId = setTimeout(() => reject(new Error("Authentication timed out")), TIMEOUT_MS);
|
|
7158
|
-
});
|
|
7159
|
-
let callback;
|
|
7160
|
-
try {
|
|
7161
|
-
callback = await Promise.race([serverPromise, timeoutPromise]);
|
|
7162
|
-
if (timeoutId)
|
|
7163
|
-
clearTimeout(timeoutId);
|
|
7164
|
-
} catch (error2) {
|
|
7165
|
-
if (timeoutId)
|
|
7166
|
-
clearTimeout(timeoutId);
|
|
7167
|
-
const msg = error2 instanceof Error ? error2.message : "Authentication failed";
|
|
7168
|
-
return { status: "failed", message: `${msg}.` };
|
|
7169
|
-
}
|
|
7170
|
-
if (callback.type !== "success") {
|
|
7171
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
7172
|
-
return {
|
|
7173
|
-
status: "failed",
|
|
7174
|
-
message: callback.message ?? "Authentication callback failed."
|
|
7175
|
-
};
|
|
7176
|
-
}
|
|
7177
|
-
let tokenResponse;
|
|
7178
|
-
try {
|
|
7179
|
-
tokenResponse = await authService.exchangeCodeForTokens({
|
|
7180
|
-
tokenEndpoint: metadata.tokenEndpoint,
|
|
7181
|
-
clientId: client.clientId,
|
|
7182
|
-
clientSecret: client.clientSecret,
|
|
7183
|
-
code: callback.code,
|
|
7184
|
-
codeVerifier: verifier,
|
|
7185
|
-
redirectUri
|
|
7186
|
-
});
|
|
7187
|
-
} catch (error2) {
|
|
7188
|
-
try {
|
|
7189
|
-
await authStorage.clearClient(mcpUrl);
|
|
7190
|
-
} catch {}
|
|
7191
|
-
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
7192
|
-
return {
|
|
7193
|
-
status: "failed",
|
|
7194
|
-
message: `Failed to complete authentication: ${msg}`
|
|
7195
|
-
};
|
|
7196
|
-
}
|
|
7197
|
-
const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1000).toISOString();
|
|
7198
|
-
await authStorage.saveAuthSession(mcpUrl, client, {
|
|
7199
|
-
accessToken: tokenResponse.accessToken,
|
|
7200
|
-
refreshToken: tokenResponse.refreshToken,
|
|
7201
|
-
expiresAt,
|
|
7202
|
-
createdAt: new Date().toISOString()
|
|
7203
|
-
});
|
|
7204
|
-
const hours = Math.round(tokenResponse.expiresIn / 3600);
|
|
7205
|
-
return {
|
|
7206
|
-
status: "success",
|
|
7207
|
-
message: `Logged in successfully. Token expires in ${hours} hour${hours !== 1 ? "s" : ""}.`
|
|
7208
|
-
};
|
|
7209
|
-
}
|
|
7210
|
-
async function loginAction(options, deps) {
|
|
7211
|
-
const result = await loginFlow(options, deps, stdoutLoginOutput);
|
|
7212
|
-
if (result.status === "already_authenticated") {
|
|
7213
|
-
console.log(`Already logged in.
|
|
7214
|
-
`);
|
|
7215
|
-
console.log(` Environment: ${deps.mcpUrl}
|
|
7216
|
-
`);
|
|
7217
|
-
console.log("To re-authenticate, use `githits login --force`.");
|
|
7218
|
-
return;
|
|
7219
|
-
}
|
|
7220
|
-
if (result.status === "failed") {
|
|
7221
|
-
console.error(`${result.message}
|
|
7222
|
-
`);
|
|
7223
|
-
console.log("Run `githits login` to try again.");
|
|
7224
|
-
process.exit(1);
|
|
7225
|
-
}
|
|
7226
|
-
console.log(`Logged in successfully.
|
|
7227
|
-
`);
|
|
7228
|
-
console.log(` Environment: ${deps.mcpUrl}`);
|
|
7229
|
-
console.log(result.message.replace("Logged in successfully. ", " "));
|
|
7230
|
-
console.log(`
|
|
7231
|
-
You're ready to use githits with your AI assistant.`);
|
|
7232
|
-
}
|
|
7233
|
-
var LOGIN_DESCRIPTION = `Authenticate with your GitHits account via browser.
|
|
7234
|
-
|
|
7235
|
-
Opens your browser to complete authentication securely using OAuth.
|
|
7236
|
-
OAuth credentials are stored in the system keychain by default. If your
|
|
7237
|
-
machine has no usable keychain, use GITHITS_API_TOKEN or explicitly configure
|
|
7238
|
-
auth.storage = "file". File storage is plaintext on disk.
|
|
7239
|
-
|
|
7240
|
-
Use --no-browser in environments without a display (CI, SSH sessions)
|
|
7241
|
-
to get a URL you can open on another device.`;
|
|
7242
|
-
function registerLoginCommand(program) {
|
|
7243
|
-
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) => {
|
|
7244
|
-
const deps = await createAuthCommandDependencies();
|
|
7245
|
-
await loginAction(options, deps);
|
|
7246
|
-
});
|
|
7247
|
-
}
|
|
7248
|
-
|
|
7249
7569
|
// src/commands/init/init.ts
|
|
7250
7570
|
async function verifyAgentConfigured(agent, fileSystemService, execService) {
|
|
7251
7571
|
const postCheck = await scanAgents([agent], fileSystemService, execService);
|
|
@@ -7266,6 +7586,7 @@ async function verifyAgentConfigured(agent, fileSystemService, execService) {
|
|
|
7266
7586
|
async function initAction(options, deps) {
|
|
7267
7587
|
const useColors = shouldUseColors();
|
|
7268
7588
|
const { fileSystemService, promptService, execService, createLoginDeps } = deps;
|
|
7589
|
+
let continuedWithoutAuth = false;
|
|
7269
7590
|
console.log(`
|
|
7270
7591
|
${colorize("GitHits", "bold", useColors)} — Set up MCP server for your coding agents
|
|
7271
7592
|
`);
|
|
@@ -7289,6 +7610,7 @@ async function initAction(options, deps) {
|
|
|
7289
7610
|
} else {
|
|
7290
7611
|
console.log(` ${warning(`Login failed: ${loginResult.message}`, useColors)}
|
|
7291
7612
|
`);
|
|
7613
|
+
printAuthRecoveryHint();
|
|
7292
7614
|
if (!options.yes) {
|
|
7293
7615
|
try {
|
|
7294
7616
|
const choice = await promptService.confirm3("Continue without authentication?");
|
|
@@ -7306,6 +7628,7 @@ async function initAction(options, deps) {
|
|
|
7306
7628
|
throw err;
|
|
7307
7629
|
}
|
|
7308
7630
|
}
|
|
7631
|
+
continuedWithoutAuth = true;
|
|
7309
7632
|
console.log(` Continuing without authentication...
|
|
7310
7633
|
`);
|
|
7311
7634
|
}
|
|
@@ -7329,6 +7652,11 @@ async function initAction(options, deps) {
|
|
|
7329
7652
|
return;
|
|
7330
7653
|
}
|
|
7331
7654
|
if (scan.needsSetup.length === 0) {
|
|
7655
|
+
if (continuedWithoutAuth) {
|
|
7656
|
+
console.log(" MCP is already configured, but authentication is still required.");
|
|
7657
|
+
console.log(" Run `githits login` before using GitHits tools.\n");
|
|
7658
|
+
return;
|
|
7659
|
+
}
|
|
7332
7660
|
console.log(` All detected agents are already configured. Nothing to do.
|
|
7333
7661
|
`);
|
|
7334
7662
|
return;
|
|
@@ -7401,6 +7729,9 @@ async function initAction(options, deps) {
|
|
|
7401
7729
|
const skipped = outcomes.filter((o) => o.status === "skipped").length;
|
|
7402
7730
|
if (failed > 0) {
|
|
7403
7731
|
console.log(" Setup completed with errors.");
|
|
7732
|
+
} else if (continuedWithoutAuth && (configured > 0 || alreadyDone > 0)) {
|
|
7733
|
+
console.log(" MCP is configured, but authentication is still required.");
|
|
7734
|
+
console.log(" Run `githits login` before using GitHits tools.");
|
|
7404
7735
|
} else if (configured > 0 || alreadyDone > 0) {
|
|
7405
7736
|
console.log(" Done! GitHits is ready.");
|
|
7406
7737
|
} else if (skipped > 0) {
|
|
@@ -7417,6 +7748,15 @@ async function initAction(options, deps) {
|
|
|
7417
7748
|
}
|
|
7418
7749
|
console.log();
|
|
7419
7750
|
}
|
|
7751
|
+
function printAuthRecoveryHint() {
|
|
7752
|
+
console.log(" You can still configure MCP, but GitHits tools will require auth.");
|
|
7753
|
+
console.log(" Recovery steps:");
|
|
7754
|
+
console.log(" githits auth status");
|
|
7755
|
+
console.log(" githits login --force");
|
|
7756
|
+
console.log(" For CI or locked-down machines, set GITHITS_API_TOKEN.");
|
|
7757
|
+
console.log(` If your system keychain is unavailable, set GITHITS_AUTH_STORAGE=file after accepting plaintext storage.
|
|
7758
|
+
`);
|
|
7759
|
+
}
|
|
7420
7760
|
var INIT_DESCRIPTION = `Set up GitHits MCP server for your coding agents.
|
|
7421
7761
|
|
|
7422
7762
|
Authenticates with your GitHits account, then scans for available agents
|
|
@@ -7647,7 +7987,7 @@ var structuredCodeTargetSchema = z3.object({
|
|
|
7647
7987
|
}).describe("Target: provide registry + package_name (package scope) or repo_url + git_ref (repo scope).");
|
|
7648
7988
|
var codeTargetSchema = z3.union([
|
|
7649
7989
|
structuredCodeTargetSchema,
|
|
7650
|
-
z3.string().min(1).describe("Compact target string. Package: `npm:react@18.2.0`. Repository: `https://github.com/facebook/react#HEAD` (git ref suffix
|
|
7990
|
+
z3.string().min(1).describe("Compact target string. Package: `npm:react@18.2.0`. Repository: `https://github.com/facebook/react#HEAD` (git ref suffix required for exact code navigation).")
|
|
7651
7991
|
]);
|
|
7652
7992
|
function resolveCodeTarget(target) {
|
|
7653
7993
|
if (typeof target === "string") {
|
|
@@ -7679,10 +8019,7 @@ function resolveCodeTarget(target) {
|
|
|
7679
8019
|
if (!target.repo_url) {
|
|
7680
8020
|
return invalidTargetResult("Incomplete repository target: repo_url is required.");
|
|
7681
8021
|
}
|
|
7682
|
-
return
|
|
7683
|
-
repoUrl: target.repo_url,
|
|
7684
|
-
gitRef: target.git_ref ?? "HEAD"
|
|
7685
|
-
};
|
|
8022
|
+
return invalidTargetResult("Incomplete repository target: git_ref is required for exact code navigation.");
|
|
7686
8023
|
}
|
|
7687
8024
|
return {
|
|
7688
8025
|
repoUrl: target.repo_url,
|
|
@@ -8638,10 +8975,14 @@ function buildRange3(args, textMode) {
|
|
|
8638
8975
|
}
|
|
8639
8976
|
// src/tools/search.ts
|
|
8640
8977
|
import { z as z13 } from "zod";
|
|
8978
|
+
var searchTargetSchema = z13.union([
|
|
8979
|
+
structuredCodeTargetSchema,
|
|
8980
|
+
z13.string().min(1).describe("Compact discovery target string. Package: `npm:react@18.2.0`. Repository: `https://github.com/facebook/react` for backend default branch or `https://github.com/facebook/react#HEAD` for an explicit ref.")
|
|
8981
|
+
]);
|
|
8641
8982
|
var schema12 = {
|
|
8642
8983
|
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."),
|
|
8643
|
-
target:
|
|
8644
|
-
targets: z13.array(
|
|
8984
|
+
target: searchTargetSchema.optional(),
|
|
8985
|
+
targets: z13.array(searchTargetSchema).max(20).optional(),
|
|
8645
8986
|
sources: z13.array(z13.enum(["docs", "code", "symbol"])).optional().describe("Optional source selection. Omit for backend AUTO."),
|
|
8646
8987
|
category: z13.enum(["callable", "type", "module", "data", "documentation"]).optional(),
|
|
8647
8988
|
kind: z13.enum([
|
|
@@ -8703,17 +9044,17 @@ function createSearchTool(service) {
|
|
|
8703
9044
|
annotations: { readOnlyHint: true },
|
|
8704
9045
|
handler: async (args) => {
|
|
8705
9046
|
try {
|
|
8706
|
-
const resolvedTarget = args.target ?
|
|
9047
|
+
const resolvedTarget = args.target ? resolveSearchTarget(args.target) : undefined;
|
|
8707
9048
|
if (resolvedTarget && "content" in resolvedTarget)
|
|
8708
9049
|
return resolvedTarget;
|
|
8709
|
-
const resolvedTargets = args.targets?.map((entry) =>
|
|
9050
|
+
const resolvedTargets = args.targets?.map((entry) => resolveSearchTarget(entry));
|
|
8710
9051
|
const resolvedTargetsError = resolvedTargets?.find((entry) => ("content" in entry));
|
|
8711
9052
|
if (resolvedTargetsError) {
|
|
8712
9053
|
return resolvedTargetsError;
|
|
8713
9054
|
}
|
|
8714
9055
|
const built = buildUnifiedSearchParams({
|
|
8715
9056
|
target: resolvedTarget && !("content" in resolvedTarget) ? resolvedTarget : undefined,
|
|
8716
|
-
targets: resolvedTargets?.filter(
|
|
9057
|
+
targets: resolvedTargets?.filter(isResolvedSearchTarget),
|
|
8717
9058
|
query: args.query,
|
|
8718
9059
|
sources: args.sources?.map((entry) => entry.toUpperCase()),
|
|
8719
9060
|
kind: toSymbolKind(args.kind),
|
|
@@ -8744,9 +9085,53 @@ function createSearchTool(service) {
|
|
|
8744
9085
|
}
|
|
8745
9086
|
};
|
|
8746
9087
|
}
|
|
8747
|
-
function
|
|
9088
|
+
function isResolvedSearchTarget(target) {
|
|
8748
9089
|
return !("content" in target);
|
|
8749
9090
|
}
|
|
9091
|
+
function resolveSearchTarget(target) {
|
|
9092
|
+
if (typeof target === "string") {
|
|
9093
|
+
try {
|
|
9094
|
+
return parseUnifiedSearchTargetSpec(target);
|
|
9095
|
+
} catch (error2) {
|
|
9096
|
+
const mapped = mapCodeNavigationError(error2);
|
|
9097
|
+
return errorResult(JSON.stringify({
|
|
9098
|
+
error: mapped.message,
|
|
9099
|
+
code: mapped.code,
|
|
9100
|
+
retryable: mapped.retryable ?? false,
|
|
9101
|
+
...mapped.details ? { details: mapped.details } : {}
|
|
9102
|
+
}));
|
|
9103
|
+
}
|
|
9104
|
+
}
|
|
9105
|
+
const hasPackageTarget = Boolean(target.registry || target.package_name);
|
|
9106
|
+
const hasRepoTarget = Boolean(target.repo_url || target.git_ref);
|
|
9107
|
+
if (hasPackageTarget && hasRepoTarget) {
|
|
9108
|
+
return invalidSearchTargetResult("Invalid target: provide either registry + package_name or repo_url + git_ref, not both.");
|
|
9109
|
+
}
|
|
9110
|
+
if (!hasPackageTarget && !hasRepoTarget) {
|
|
9111
|
+
return invalidSearchTargetResult("Missing target: provide registry + package_name or repo_url.");
|
|
9112
|
+
}
|
|
9113
|
+
if (hasPackageTarget) {
|
|
9114
|
+
if (!target.registry || !target.package_name) {
|
|
9115
|
+
return invalidSearchTargetResult("Incomplete package target: both registry and package_name are required.");
|
|
9116
|
+
}
|
|
9117
|
+
return {
|
|
9118
|
+
registry: toCodeNavigationRegistry(target.registry),
|
|
9119
|
+
packageName: target.package_name,
|
|
9120
|
+
version: target.version
|
|
9121
|
+
};
|
|
9122
|
+
}
|
|
9123
|
+
if (!target.repo_url) {
|
|
9124
|
+
return invalidSearchTargetResult("Incomplete repository target: repo_url is required.");
|
|
9125
|
+
}
|
|
9126
|
+
return { repoUrl: target.repo_url, gitRef: target.git_ref };
|
|
9127
|
+
}
|
|
9128
|
+
function invalidSearchTargetResult(message) {
|
|
9129
|
+
return errorResult(JSON.stringify({
|
|
9130
|
+
error: message,
|
|
9131
|
+
code: "INVALID_ARGUMENT",
|
|
9132
|
+
retryable: false
|
|
9133
|
+
}));
|
|
9134
|
+
}
|
|
8750
9135
|
function isTextFormat11(format) {
|
|
8751
9136
|
return format === undefined || format === "text" || format === "text-v1";
|
|
8752
9137
|
}
|
|
@@ -9506,7 +9891,7 @@ function requireSearchService(deps) {
|
|
|
9506
9891
|
return deps.codeNavigationService;
|
|
9507
9892
|
}
|
|
9508
9893
|
async function loadContainer2() {
|
|
9509
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
9894
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-xzq0ecpa.js");
|
|
9510
9895
|
return createContainer2();
|
|
9511
9896
|
}
|
|
9512
9897
|
function parseTargetSpecs(specs) {
|
|
@@ -9585,8 +9970,9 @@ function formatSearchErrorTerminal(payload, context) {
|
|
|
9585
9970
|
function formatUnifiedSearchTerminal(payload) {
|
|
9586
9971
|
const lines = [];
|
|
9587
9972
|
const useColors = shouldUseColors();
|
|
9588
|
-
|
|
9589
|
-
|
|
9973
|
+
const warnings = payload.warnings ?? payload.query.warnings;
|
|
9974
|
+
if (warnings && warnings.length > 0) {
|
|
9975
|
+
for (const warning2 of warnings) {
|
|
9590
9976
|
lines.push(`Warning: ${warning2}`);
|
|
9591
9977
|
}
|
|
9592
9978
|
lines.push("");
|
|
@@ -9625,7 +10011,7 @@ function formatUnifiedSearchTerminal(payload) {
|
|
|
9625
10011
|
lines.push("");
|
|
9626
10012
|
for (const entry of display) {
|
|
9627
10013
|
const location = formatUnifiedSearchLocation(entry.locator);
|
|
9628
|
-
const header = formatUnifiedSearchHeader(entry, useColors, location);
|
|
10014
|
+
const header = formatUnifiedSearchHeader(entry, useColors, location, payload.query.raw);
|
|
9629
10015
|
lines.push(header);
|
|
9630
10016
|
const metadata = formatUnifiedSearchMetadata(entry, useColors);
|
|
9631
10017
|
if (metadata.length > 0) {
|
|
@@ -9692,7 +10078,11 @@ function formatSearchStatusCompletedTerminal(payload) {
|
|
|
9692
10078
|
results: payload.result.results,
|
|
9693
10079
|
searchRef: payload.searchRef,
|
|
9694
10080
|
progress: undefined,
|
|
9695
|
-
query: {
|
|
10081
|
+
query: {
|
|
10082
|
+
raw: payload.result.query?.raw,
|
|
10083
|
+
warnings: payload.result.warnings
|
|
10084
|
+
},
|
|
10085
|
+
warnings: payload.result.warnings,
|
|
9696
10086
|
sourceStatus: payload.result.sourceStatus
|
|
9697
10087
|
});
|
|
9698
10088
|
}
|
|
@@ -9704,7 +10094,11 @@ function formatSearchStatusPartialTerminal(payload) {
|
|
|
9704
10094
|
results: payload.result.results,
|
|
9705
10095
|
searchRef: payload.searchRef,
|
|
9706
10096
|
progress: payload.progress,
|
|
9707
|
-
query: {
|
|
10097
|
+
query: {
|
|
10098
|
+
raw: payload.result.query?.raw,
|
|
10099
|
+
warnings: payload.result.warnings
|
|
10100
|
+
},
|
|
10101
|
+
warnings: payload.result.warnings,
|
|
9708
10102
|
sourceStatus: payload.result.sourceStatus
|
|
9709
10103
|
});
|
|
9710
10104
|
}
|
|
@@ -9815,11 +10209,113 @@ function formatUnifiedSearchLocation(locator) {
|
|
|
9815
10209
|
}
|
|
9816
10210
|
return `${locator.filePath}:${locator.startLine}${locator.endLine && locator.endLine !== locator.startLine ? `-${locator.endLine}` : ""}`;
|
|
9817
10211
|
}
|
|
9818
|
-
function formatUnifiedSearchHeader(entry, useColors, location) {
|
|
9819
|
-
const primary = entry.type
|
|
10212
|
+
function formatUnifiedSearchHeader(entry, useColors, location, rawQuery) {
|
|
10213
|
+
const primary = formatUnifiedSearchPrimary(entry.type, entry.target, location, rawQuery, useColors);
|
|
9820
10214
|
const badge = `[${formatUnifiedSearchResultLabel(entry.type)}]`;
|
|
9821
10215
|
const title = entry.title ? highlightRanges(entry.title, entry.highlights?.title, useColors) : undefined;
|
|
9822
|
-
return `${
|
|
10216
|
+
return `${primary} ${dim(badge, useColors)}${title ? ` - ${title}` : ""}`;
|
|
10217
|
+
}
|
|
10218
|
+
function formatUnifiedSearchPrimary(type, target, location, rawQuery, useColors) {
|
|
10219
|
+
const formattedTarget = highlight(target, useColors);
|
|
10220
|
+
if (type === "documentation_page" || !location) {
|
|
10221
|
+
return formattedTarget;
|
|
10222
|
+
}
|
|
10223
|
+
return `${formattedTarget} ${formatLocationWithQueryHighlights(location, rawQuery, useColors)}`;
|
|
10224
|
+
}
|
|
10225
|
+
function formatLocationWithQueryHighlights(location, rawQuery, useColors) {
|
|
10226
|
+
const ranges = buildQueryTermRanges(location, rawQuery);
|
|
10227
|
+
if (ranges.length === 0)
|
|
10228
|
+
return highlight(location, useColors);
|
|
10229
|
+
if (!useColors)
|
|
10230
|
+
return location;
|
|
10231
|
+
let result = "";
|
|
10232
|
+
let cursor2 = 0;
|
|
10233
|
+
for (const [start, end] of ranges) {
|
|
10234
|
+
if (cursor2 < start)
|
|
10235
|
+
result += highlight(location.slice(cursor2, start), true);
|
|
10236
|
+
result += highlightMatch(location.slice(start, end), true);
|
|
10237
|
+
cursor2 = end;
|
|
10238
|
+
}
|
|
10239
|
+
if (cursor2 < location.length)
|
|
10240
|
+
result += highlight(location.slice(cursor2), true);
|
|
10241
|
+
return result;
|
|
10242
|
+
}
|
|
10243
|
+
function buildQueryTermRanges(text, rawQuery) {
|
|
10244
|
+
const terms = extractQueryHighlightTerms(rawQuery);
|
|
10245
|
+
if (terms.length === 0)
|
|
10246
|
+
return [];
|
|
10247
|
+
const lowerText = text.toLowerCase();
|
|
10248
|
+
const ranges = [];
|
|
10249
|
+
const orderedTerms = [...terms].sort((left, right) => right.length - left.length);
|
|
10250
|
+
for (const term of orderedTerms) {
|
|
10251
|
+
const lowerTerm = term.toLowerCase();
|
|
10252
|
+
let cursor2 = 0;
|
|
10253
|
+
while (cursor2 < lowerText.length) {
|
|
10254
|
+
const start = lowerText.indexOf(lowerTerm, cursor2);
|
|
10255
|
+
if (start === -1)
|
|
10256
|
+
break;
|
|
10257
|
+
const end = start + lowerTerm.length;
|
|
10258
|
+
if (!ranges.some((range) => rangesOverlap(range, [start, end]))) {
|
|
10259
|
+
ranges.push([start, end]);
|
|
10260
|
+
}
|
|
10261
|
+
cursor2 = end;
|
|
10262
|
+
}
|
|
10263
|
+
}
|
|
10264
|
+
return mergeRanges2(ranges);
|
|
10265
|
+
}
|
|
10266
|
+
function extractQueryHighlightTerms(rawQuery) {
|
|
10267
|
+
if (!rawQuery)
|
|
10268
|
+
return [];
|
|
10269
|
+
const booleanOperators = new Set(["AND", "OR", "NOT"]);
|
|
10270
|
+
const terms = new Set;
|
|
10271
|
+
const quotedRanges = [];
|
|
10272
|
+
for (const match of rawQuery.matchAll(/"([^"]+)"/g)) {
|
|
10273
|
+
const phrase = match[1];
|
|
10274
|
+
if (phrase) {
|
|
10275
|
+
addQueryHighlightTerm(phrase, terms, booleanOperators, {
|
|
10276
|
+
stripQualifier: false
|
|
10277
|
+
});
|
|
10278
|
+
}
|
|
10279
|
+
if (typeof match.index === "number") {
|
|
10280
|
+
quotedRanges.push([match.index, match.index + match[0].length]);
|
|
10281
|
+
}
|
|
10282
|
+
}
|
|
10283
|
+
for (const match of rawQuery.matchAll(/[A-Za-z0-9_./@:-]+/g)) {
|
|
10284
|
+
const index = match.index ?? 0;
|
|
10285
|
+
if (quotedRanges.some(([start, end]) => index >= start && index < end)) {
|
|
10286
|
+
continue;
|
|
10287
|
+
}
|
|
10288
|
+
addQueryHighlightTerm(match[0], terms, booleanOperators);
|
|
10289
|
+
}
|
|
10290
|
+
return Array.from(terms);
|
|
10291
|
+
}
|
|
10292
|
+
function addQueryHighlightTerm(candidate, terms, booleanOperators, options = { stripQualifier: true }) {
|
|
10293
|
+
const normalised = options.stripQualifier && /^[A-Za-z]+:.+/.test(candidate) ? candidate.split(":").slice(1).join(":") : candidate;
|
|
10294
|
+
const term = normalised.replace(/^[-+]+/, "").replace(/[-+]+$/, "");
|
|
10295
|
+
if (term.length < 2)
|
|
10296
|
+
return;
|
|
10297
|
+
if (booleanOperators.has(term.toUpperCase()))
|
|
10298
|
+
return;
|
|
10299
|
+
terms.add(term);
|
|
10300
|
+
}
|
|
10301
|
+
function rangesOverlap(left, right) {
|
|
10302
|
+
return left[0] < right[1] && right[0] < left[1];
|
|
10303
|
+
}
|
|
10304
|
+
function mergeRanges2(ranges) {
|
|
10305
|
+
const sorted = ranges.filter(([start, end]) => end > start).sort((left, right) => left[0] - right[0] || left[1] - right[1]);
|
|
10306
|
+
const merged = [];
|
|
10307
|
+
for (const current of sorted) {
|
|
10308
|
+
const previous = merged[merged.length - 1];
|
|
10309
|
+
if (!previous || current[0] > previous[1]) {
|
|
10310
|
+
merged.push(current);
|
|
10311
|
+
continue;
|
|
10312
|
+
}
|
|
10313
|
+
merged[merged.length - 1] = [
|
|
10314
|
+
previous[0],
|
|
10315
|
+
Math.max(previous[1], current[1])
|
|
10316
|
+
];
|
|
10317
|
+
}
|
|
10318
|
+
return merged;
|
|
9823
10319
|
}
|
|
9824
10320
|
function formatUnifiedSearchMetadata(entry, useColors) {
|
|
9825
10321
|
if (entry.type !== "documentation_page" && entry.type !== "repository_doc") {
|
|
@@ -9827,16 +10323,14 @@ function formatUnifiedSearchMetadata(entry, useColors) {
|
|
|
9827
10323
|
}
|
|
9828
10324
|
const lines = [];
|
|
9829
10325
|
if (entry.locator.pageId) {
|
|
9830
|
-
|
|
10326
|
+
if (entry.type === "documentation_page") {
|
|
10327
|
+
lines.push(` ${dim("pageId:", useColors)} ${entry.locator.pageId}`);
|
|
10328
|
+
}
|
|
9831
10329
|
}
|
|
9832
10330
|
const sourceBadge = entry.locator.sourceKind?.toLowerCase() === "repository" ? "[repo]" : entry.locator.sourceKind?.toLowerCase() === "crawled" ? "[crawled]" : undefined;
|
|
9833
|
-
if (entry.locator.sourceUrl) {
|
|
10331
|
+
if (entry.locator.sourceUrl && entry.type === "documentation_page") {
|
|
9834
10332
|
lines.push(` ${dim("source:", useColors)} ${sourceBadge ? `${sourceBadge} ` : ""}${entry.locator.sourceUrl}`);
|
|
9835
10333
|
}
|
|
9836
|
-
if (entry.type === "repository_doc" && entry.locator.filePath) {
|
|
9837
|
-
const ref = entry.locator.requestedRef ?? entry.locator.gitRef;
|
|
9838
|
-
lines.push(` ${dim("file:", useColors)} ${entry.locator.filePath}${ref ? ` @ ${ref}` : ""}`);
|
|
9839
|
-
}
|
|
9840
10334
|
return lines;
|
|
9841
10335
|
}
|
|
9842
10336
|
// src/cli.ts
|