githits 0.4.0 → 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 +293 -258
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-fqzth5mv.js → chunk-f2ah8xnh.js} +1 -1
- package/dist/shared/{chunk-1cyka46a.js → chunk-rgvmtzjt.js} +1 -1
- package/dist/shared/{chunk-1h7sanjw.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/.plugin/plugin.json
CHANGED
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";
|
|
@@ -4168,6 +4168,291 @@ function buildRange2(envelope) {
|
|
|
4168
4168
|
return `${envelope.totalLines} lines`;
|
|
4169
4169
|
return;
|
|
4170
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
|
+
|
|
4171
4456
|
// src/shared/auto-login.ts
|
|
4172
4457
|
var AUTO_LOGIN_ELIGIBLE_COMMANDS = new Set([
|
|
4173
4458
|
"example",
|
|
@@ -4253,9 +4538,10 @@ function createRootCliPreAction(deps) {
|
|
|
4253
4538
|
if (authResult.status !== "failed") {
|
|
4254
4539
|
return;
|
|
4255
4540
|
}
|
|
4256
|
-
|
|
4541
|
+
const failureMessage = authResult.message ?? "Authentication failed.";
|
|
4542
|
+
console.error(`${failureMessage}
|
|
4257
4543
|
`);
|
|
4258
|
-
|
|
4544
|
+
printAutoLoginRecoveryHint(failureMessage);
|
|
4259
4545
|
(deps.exit ?? process.exit)(1);
|
|
4260
4546
|
};
|
|
4261
4547
|
}
|
|
@@ -5977,7 +6263,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
|
|
|
5977
6263
|
match in --verbose output; full payload in --json).`;
|
|
5978
6264
|
function registerCodeGrepCommand(pkgCommand) {
|
|
5979
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) => {
|
|
5980
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
6266
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-xzq0ecpa.js");
|
|
5981
6267
|
const deps = await createContainer2();
|
|
5982
6268
|
await pkgGrepAction(arg1, arg2, arg3, options, {
|
|
5983
6269
|
codeNavigationService: deps.codeNavigationService,
|
|
@@ -6511,7 +6797,7 @@ function registerExampleCommand(program) {
|
|
|
6511
6797
|
});
|
|
6512
6798
|
}
|
|
6513
6799
|
async function loadContainer() {
|
|
6514
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
6800
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-xzq0ecpa.js");
|
|
6515
6801
|
return createContainer2();
|
|
6516
6802
|
}
|
|
6517
6803
|
// src/commands/feedback.ts
|
|
@@ -7280,257 +7566,6 @@ async function scanAgents(definitions, fs, execService) {
|
|
|
7280
7566
|
class ExitPromptError extends Error {
|
|
7281
7567
|
name = "ExitPromptError";
|
|
7282
7568
|
}
|
|
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
7569
|
// src/commands/init/init.ts
|
|
7535
7570
|
async function verifyAgentConfigured(agent, fileSystemService, execService) {
|
|
7536
7571
|
const postCheck = await scanAgents([agent], fileSystemService, execService);
|
|
@@ -9856,7 +9891,7 @@ function requireSearchService(deps) {
|
|
|
9856
9891
|
return deps.codeNavigationService;
|
|
9857
9892
|
}
|
|
9858
9893
|
async function loadContainer2() {
|
|
9859
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
9894
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-xzq0ecpa.js");
|
|
9860
9895
|
return createContainer2();
|
|
9861
9896
|
}
|
|
9862
9897
|
function parseTargetSpecs(specs) {
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
createAuthCommandDependencies,
|
|
3
3
|
createAuthStatusDependencies,
|
|
4
4
|
createContainer
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import"./chunk-
|
|
5
|
+
} from "./chunk-rgvmtzjt.js";
|
|
6
|
+
import"./chunk-f2ah8xnh.js";
|
|
7
7
|
export {
|
|
8
8
|
createContainer,
|
|
9
9
|
createAuthStatusDependencies,
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED