githits 0.1.5 → 0.1.8
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 +26 -0
- package/.claude-plugin/plugin.json +12 -0
- package/.mcp.json +8 -0
- package/.plugin/plugin.json +12 -0
- package/GEMINI.md +75 -0
- package/README.md +102 -17
- package/commands/help.md +46 -0
- package/commands/login.md +36 -0
- package/commands/logout.md +16 -0
- package/commands/search.md +23 -0
- package/commands/status.md +22 -0
- package/dist/cli.js +836 -88
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-0fnprry7.js → chunk-6w8an8m7.js} +1 -1
- package/gemini-extension.json +12 -0
- package/package.json +12 -3
- package/plugins/claude/.claude-plugin/plugin.json +12 -0
- package/plugins/claude/.mcp.json +8 -0
- package/plugins/claude/commands/help.md +46 -0
- package/plugins/claude/commands/login.md +36 -0
- package/plugins/claude/commands/logout.md +16 -0
- package/plugins/claude/commands/search.md +23 -0
- package/plugins/claude/commands/status.md +22 -0
- package/plugins/claude/skills/search/SKILL.md +36 -0
- package/skills/search/SKILL.md +36 -0
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
version
|
|
4
|
-
} from "./shared/chunk-
|
|
4
|
+
} from "./shared/chunk-6w8an8m7.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -547,11 +547,40 @@ function getApiUrl() {
|
|
|
547
547
|
function getEnvApiToken() {
|
|
548
548
|
return process.env.GITHITS_API_TOKEN;
|
|
549
549
|
}
|
|
550
|
+
// src/services/exec-service.ts
|
|
551
|
+
import { spawn } from "node:child_process";
|
|
552
|
+
|
|
553
|
+
class ExecServiceImpl {
|
|
554
|
+
async exec(command, args) {
|
|
555
|
+
return new Promise((resolve, reject) => {
|
|
556
|
+
const child = spawn(command, args, {
|
|
557
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
558
|
+
env: { ...process.env },
|
|
559
|
+
...process.platform === "win32" && { shell: true }
|
|
560
|
+
});
|
|
561
|
+
const stdoutChunks = [];
|
|
562
|
+
const stderrChunks = [];
|
|
563
|
+
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
564
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
565
|
+
child.on("error", (error) => {
|
|
566
|
+
reject(error);
|
|
567
|
+
});
|
|
568
|
+
child.on("close", (code) => {
|
|
569
|
+
resolve({
|
|
570
|
+
exitCode: code ?? 1,
|
|
571
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
572
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8")
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
550
578
|
// src/services/filesystem-service.ts
|
|
551
579
|
import {
|
|
552
580
|
mkdir,
|
|
553
581
|
readdir,
|
|
554
582
|
readFile,
|
|
583
|
+
rename,
|
|
555
584
|
stat,
|
|
556
585
|
unlink,
|
|
557
586
|
writeFile
|
|
@@ -609,6 +638,23 @@ class FileSystemServiceImpl {
|
|
|
609
638
|
return false;
|
|
610
639
|
}
|
|
611
640
|
}
|
|
641
|
+
async atomicWriteFile(path, contents) {
|
|
642
|
+
const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
643
|
+
let mode = 384;
|
|
644
|
+
try {
|
|
645
|
+
const existing = await stat(path);
|
|
646
|
+
mode = existing.mode & 511;
|
|
647
|
+
} catch {}
|
|
648
|
+
try {
|
|
649
|
+
await writeFile(tmpPath, contents, { mode });
|
|
650
|
+
await rename(tmpPath, path);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
try {
|
|
653
|
+
await unlink(tmpPath);
|
|
654
|
+
} catch {}
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
612
658
|
}
|
|
613
659
|
// src/services/githits-service.ts
|
|
614
660
|
class AuthenticationError extends Error {
|
|
@@ -891,6 +937,28 @@ class MigratingAuthStorage {
|
|
|
891
937
|
return this.primary.getStorageLocation();
|
|
892
938
|
}
|
|
893
939
|
}
|
|
940
|
+
// src/services/prompt-service.ts
|
|
941
|
+
import { checkbox, select } from "@inquirer/prompts";
|
|
942
|
+
|
|
943
|
+
class PromptServiceImpl {
|
|
944
|
+
async checkbox(message, choices) {
|
|
945
|
+
return checkbox({ message, choices });
|
|
946
|
+
}
|
|
947
|
+
async confirm3(message) {
|
|
948
|
+
return select({
|
|
949
|
+
message,
|
|
950
|
+
choices: [
|
|
951
|
+
{ value: "yes", name: "Yes" },
|
|
952
|
+
{ value: "no", name: "No" },
|
|
953
|
+
{
|
|
954
|
+
value: "always",
|
|
955
|
+
name: "Yes to all",
|
|
956
|
+
description: "Skip confirmation for remaining agents"
|
|
957
|
+
}
|
|
958
|
+
]
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
894
962
|
// src/services/refreshing-githits-service.ts
|
|
895
963
|
class RefreshingGitHitsService {
|
|
896
964
|
apiUrl;
|
|
@@ -1239,6 +1307,495 @@ function registerFeedbackCommand(program) {
|
|
|
1239
1307
|
}
|
|
1240
1308
|
});
|
|
1241
1309
|
}
|
|
1310
|
+
// src/commands/init/setup-handlers.ts
|
|
1311
|
+
function mergeServerConfig(existingContent, serversKey, serverName, serverConfig) {
|
|
1312
|
+
let content = existingContent;
|
|
1313
|
+
if (content.charCodeAt(0) === 65279) {
|
|
1314
|
+
content = content.slice(1);
|
|
1315
|
+
}
|
|
1316
|
+
const trimmed = content.trim();
|
|
1317
|
+
if (trimmed === "") {
|
|
1318
|
+
content = "{}";
|
|
1319
|
+
}
|
|
1320
|
+
let config;
|
|
1321
|
+
try {
|
|
1322
|
+
config = JSON.parse(content);
|
|
1323
|
+
} catch (err) {
|
|
1324
|
+
return {
|
|
1325
|
+
status: "parse_error",
|
|
1326
|
+
error: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
1330
|
+
return {
|
|
1331
|
+
status: "parse_error",
|
|
1332
|
+
error: "Config file root is not a JSON object"
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
if (!(serversKey in config)) {
|
|
1336
|
+
config[serversKey] = {};
|
|
1337
|
+
}
|
|
1338
|
+
const servers = config[serversKey];
|
|
1339
|
+
if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
|
|
1340
|
+
return {
|
|
1341
|
+
status: "parse_error",
|
|
1342
|
+
error: `"${serversKey}" is not a JSON object`
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
const serversObj = servers;
|
|
1346
|
+
if (serverName in serversObj) {
|
|
1347
|
+
return { status: "already_configured" };
|
|
1348
|
+
}
|
|
1349
|
+
serversObj[serverName] = serverConfig;
|
|
1350
|
+
return {
|
|
1351
|
+
status: "added",
|
|
1352
|
+
content: `${JSON.stringify(config, null, 2)}
|
|
1353
|
+
`
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
function formatSetupPreview(config) {
|
|
1357
|
+
if (config.method === "cli") {
|
|
1358
|
+
return config.commands.map((cmd) => `Will run: ${cmd.command} ${cmd.args.join(" ")}`).join(`
|
|
1359
|
+
`);
|
|
1360
|
+
}
|
|
1361
|
+
const snippet = JSON.stringify({ [config.serverName]: config.serverConfig }, null, 2);
|
|
1362
|
+
return `Will add to ${config.configPath}:
|
|
1363
|
+
|
|
1364
|
+
${snippet}`;
|
|
1365
|
+
}
|
|
1366
|
+
async function isAlreadyConfigured(config, fs) {
|
|
1367
|
+
try {
|
|
1368
|
+
let content;
|
|
1369
|
+
try {
|
|
1370
|
+
content = await fs.readFile(config.configPath);
|
|
1371
|
+
} catch {
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
if (content.charCodeAt(0) === 65279) {
|
|
1375
|
+
content = content.slice(1);
|
|
1376
|
+
}
|
|
1377
|
+
const trimmed = content.trim();
|
|
1378
|
+
if (trimmed === "") {
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1381
|
+
const parsed = JSON.parse(trimmed);
|
|
1382
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1383
|
+
return false;
|
|
1384
|
+
}
|
|
1385
|
+
const servers = parsed[config.serversKey];
|
|
1386
|
+
if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
return config.serverName in servers;
|
|
1390
|
+
} catch {
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
async function isCliAlreadyConfigured(check, execService) {
|
|
1395
|
+
try {
|
|
1396
|
+
const result = await execService.exec(check.command, check.args);
|
|
1397
|
+
const combined = `${result.stdout} ${result.stderr}`;
|
|
1398
|
+
return check.configuredPattern.test(combined);
|
|
1399
|
+
} catch {
|
|
1400
|
+
return false;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
var ALREADY_EXISTS_PATTERNS = [
|
|
1404
|
+
/already exists/i,
|
|
1405
|
+
/already configured/i,
|
|
1406
|
+
/already added/i
|
|
1407
|
+
];
|
|
1408
|
+
function isAlreadyConfiguredOutput(output) {
|
|
1409
|
+
return ALREADY_EXISTS_PATTERNS.some((pattern) => pattern.test(output));
|
|
1410
|
+
}
|
|
1411
|
+
async function executeCliCommand(cmd, execService) {
|
|
1412
|
+
try {
|
|
1413
|
+
const result = await execService.exec(cmd.command, cmd.args);
|
|
1414
|
+
const combined = `${result.stdout} ${result.stderr}`;
|
|
1415
|
+
if (isAlreadyConfiguredOutput(combined)) {
|
|
1416
|
+
return {
|
|
1417
|
+
status: "already_configured",
|
|
1418
|
+
message: `GitHits already configured via ${cmd.command}`
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
if (result.exitCode === 0) {
|
|
1422
|
+
return { status: "success", message: "Configured successfully" };
|
|
1423
|
+
}
|
|
1424
|
+
const detail = result.stderr.trim() || result.stdout.trim();
|
|
1425
|
+
return {
|
|
1426
|
+
status: "failed",
|
|
1427
|
+
message: `Command exited with code ${result.exitCode}${detail ? `: ${detail}` : ""}`
|
|
1428
|
+
};
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
1431
|
+
return {
|
|
1432
|
+
status: "failed",
|
|
1433
|
+
message: `"${cmd.command}" not found on PATH. Install it or configure manually.`
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
return {
|
|
1437
|
+
status: "failed",
|
|
1438
|
+
message: `Failed to run command: ${err instanceof Error ? err.message : String(err)}`
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
async function executeCliSetup(setup, execService) {
|
|
1443
|
+
let anyAlreadyConfigured = false;
|
|
1444
|
+
for (const cmd of setup.commands) {
|
|
1445
|
+
const result = await executeCliCommand(cmd, execService);
|
|
1446
|
+
if (result.status === "failed") {
|
|
1447
|
+
return result;
|
|
1448
|
+
}
|
|
1449
|
+
if (result.status === "already_configured") {
|
|
1450
|
+
anyAlreadyConfigured = true;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
if (anyAlreadyConfigured) {
|
|
1454
|
+
return {
|
|
1455
|
+
status: "already_configured",
|
|
1456
|
+
message: `GitHits already configured via ${setup.commands[0]?.command}`
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
return { status: "success", message: "Configured successfully" };
|
|
1460
|
+
}
|
|
1461
|
+
async function executeConfigFileSetup(setup, fs) {
|
|
1462
|
+
try {
|
|
1463
|
+
const parentDir = fs.getDirname(setup.configPath);
|
|
1464
|
+
await fs.ensureDir(parentDir);
|
|
1465
|
+
let existingContent = "";
|
|
1466
|
+
try {
|
|
1467
|
+
existingContent = await fs.readFile(setup.configPath);
|
|
1468
|
+
} catch (err) {
|
|
1469
|
+
if (!(err instanceof Error) || !("code" in err) || err.code !== "ENOENT") {
|
|
1470
|
+
return {
|
|
1471
|
+
status: "failed",
|
|
1472
|
+
message: `Cannot read ${setup.configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
const result = mergeServerConfig(existingContent, setup.serversKey, setup.serverName, setup.serverConfig);
|
|
1477
|
+
if (result.status === "already_configured") {
|
|
1478
|
+
return {
|
|
1479
|
+
status: "already_configured",
|
|
1480
|
+
message: `GitHits already configured in ${setup.configPath}`
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
if (result.status === "parse_error") {
|
|
1484
|
+
return {
|
|
1485
|
+
status: "failed",
|
|
1486
|
+
message: `Cannot parse ${setup.configPath}: ${result.error}. File left unchanged.`
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
await fs.atomicWriteFile(setup.configPath, result.content);
|
|
1490
|
+
return { status: "success", message: "Configured successfully" };
|
|
1491
|
+
} catch (err) {
|
|
1492
|
+
if (err instanceof Error && "code" in err && err.code === "EACCES") {
|
|
1493
|
+
return {
|
|
1494
|
+
status: "failed",
|
|
1495
|
+
message: `Permission denied writing to ${setup.configPath}. Check file permissions.`
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
return {
|
|
1499
|
+
status: "failed",
|
|
1500
|
+
message: `Failed to configure: ${err instanceof Error ? err.message : String(err)}`
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// src/commands/init/agent-definitions.ts
|
|
1506
|
+
function getAppDataPath(fs, appName) {
|
|
1507
|
+
const home = fs.getHomeDir();
|
|
1508
|
+
switch (process.platform) {
|
|
1509
|
+
case "win32":
|
|
1510
|
+
return fs.joinPath(process.env.APPDATA ?? fs.joinPath(home, "AppData", "Roaming"), appName);
|
|
1511
|
+
case "darwin":
|
|
1512
|
+
return fs.joinPath(home, "Library", "Application Support", appName);
|
|
1513
|
+
default:
|
|
1514
|
+
return fs.joinPath(home, ".config", appName);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
async function isExecutableAvailable(exec, executable) {
|
|
1518
|
+
try {
|
|
1519
|
+
const lookupCommand = process.platform === "win32" ? "where" : "which";
|
|
1520
|
+
const result = await exec.exec(lookupCommand, [executable]);
|
|
1521
|
+
return result.exitCode === 0;
|
|
1522
|
+
} catch {
|
|
1523
|
+
return false;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
var claudeCode = {
|
|
1527
|
+
name: "Claude Code",
|
|
1528
|
+
id: "claude-code",
|
|
1529
|
+
detectionMethod: "binary",
|
|
1530
|
+
setupMethod: "cli",
|
|
1531
|
+
detectBinary: async (exec) => isExecutableAvailable(exec, "claude"),
|
|
1532
|
+
getSetupConfig: () => ({
|
|
1533
|
+
method: "cli",
|
|
1534
|
+
commands: [
|
|
1535
|
+
{
|
|
1536
|
+
command: "claude",
|
|
1537
|
+
args: ["plugin", "marketplace", "add", "githits-com/githits-cli"]
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
command: "claude",
|
|
1541
|
+
args: ["plugin", "install", "githits@githits-plugins"]
|
|
1542
|
+
}
|
|
1543
|
+
],
|
|
1544
|
+
checkCommand: {
|
|
1545
|
+
command: "claude",
|
|
1546
|
+
args: ["plugin", "list"],
|
|
1547
|
+
configuredPattern: /githits/i
|
|
1548
|
+
}
|
|
1549
|
+
})
|
|
1550
|
+
};
|
|
1551
|
+
var cursor = {
|
|
1552
|
+
name: "Cursor",
|
|
1553
|
+
id: "cursor",
|
|
1554
|
+
detectionMethod: "path",
|
|
1555
|
+
setupMethod: "config-file",
|
|
1556
|
+
detectPaths: (fs) => [fs.joinPath(fs.getHomeDir(), ".cursor")],
|
|
1557
|
+
getSetupConfig: (fs) => ({
|
|
1558
|
+
method: "config-file",
|
|
1559
|
+
configPath: fs.joinPath(fs.getHomeDir(), ".cursor", "mcp.json"),
|
|
1560
|
+
serversKey: "mcpServers",
|
|
1561
|
+
serverName: "GitHits",
|
|
1562
|
+
serverConfig: { url: getMcpUrl() }
|
|
1563
|
+
})
|
|
1564
|
+
};
|
|
1565
|
+
var windsurf = {
|
|
1566
|
+
name: "Windsurf",
|
|
1567
|
+
id: "windsurf",
|
|
1568
|
+
detectionMethod: "path",
|
|
1569
|
+
setupMethod: "config-file",
|
|
1570
|
+
detectPaths: (fs) => [fs.joinPath(fs.getHomeDir(), ".codeium", "windsurf")],
|
|
1571
|
+
getSetupConfig: (fs) => ({
|
|
1572
|
+
method: "config-file",
|
|
1573
|
+
configPath: fs.joinPath(fs.getHomeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
1574
|
+
serversKey: "mcpServers",
|
|
1575
|
+
serverName: "GitHits",
|
|
1576
|
+
serverConfig: { serverUrl: getMcpUrl() }
|
|
1577
|
+
})
|
|
1578
|
+
};
|
|
1579
|
+
var claudeDesktop = {
|
|
1580
|
+
name: "Claude Desktop",
|
|
1581
|
+
id: "claude-desktop",
|
|
1582
|
+
detectionMethod: "path",
|
|
1583
|
+
setupMethod: "config-file",
|
|
1584
|
+
detectPaths: (fs) => {
|
|
1585
|
+
const appData = getAppDataPath(fs, "Claude");
|
|
1586
|
+
if (process.platform === "win32") {
|
|
1587
|
+
const home = fs.getHomeDir();
|
|
1588
|
+
const localAppData = process.env.LOCALAPPDATA ?? fs.joinPath(home, "AppData", "Local");
|
|
1589
|
+
return [
|
|
1590
|
+
appData,
|
|
1591
|
+
fs.joinPath(localAppData, "Claude"),
|
|
1592
|
+
fs.joinPath(localAppData, "Programs", "Claude")
|
|
1593
|
+
];
|
|
1594
|
+
}
|
|
1595
|
+
return [appData];
|
|
1596
|
+
},
|
|
1597
|
+
getSetupConfig: (fs) => {
|
|
1598
|
+
const appData = getAppDataPath(fs, "Claude");
|
|
1599
|
+
return {
|
|
1600
|
+
method: "config-file",
|
|
1601
|
+
configPath: fs.joinPath(appData, "claude_desktop_config.json"),
|
|
1602
|
+
serversKey: "mcpServers",
|
|
1603
|
+
serverName: "GitHits",
|
|
1604
|
+
serverConfig: {
|
|
1605
|
+
command: "npx",
|
|
1606
|
+
args: ["-y", "mcp-remote", getMcpUrl()]
|
|
1607
|
+
}
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
var codexCli = {
|
|
1612
|
+
name: "Codex CLI",
|
|
1613
|
+
id: "codex-cli",
|
|
1614
|
+
detectionMethod: "binary",
|
|
1615
|
+
setupMethod: "cli",
|
|
1616
|
+
detectBinary: async (exec) => isExecutableAvailable(exec, "codex"),
|
|
1617
|
+
getSetupConfig: () => ({
|
|
1618
|
+
method: "cli",
|
|
1619
|
+
commands: [
|
|
1620
|
+
{
|
|
1621
|
+
command: "codex",
|
|
1622
|
+
args: [
|
|
1623
|
+
"mcp",
|
|
1624
|
+
"add",
|
|
1625
|
+
"githits",
|
|
1626
|
+
"--",
|
|
1627
|
+
"npx",
|
|
1628
|
+
"-y",
|
|
1629
|
+
"githits@latest",
|
|
1630
|
+
"mcp",
|
|
1631
|
+
"start"
|
|
1632
|
+
]
|
|
1633
|
+
}
|
|
1634
|
+
],
|
|
1635
|
+
checkCommand: {
|
|
1636
|
+
command: "codex",
|
|
1637
|
+
args: ["mcp", "list"],
|
|
1638
|
+
configuredPattern: /githits/i
|
|
1639
|
+
}
|
|
1640
|
+
})
|
|
1641
|
+
};
|
|
1642
|
+
var vscode = {
|
|
1643
|
+
name: "VS Code / Copilot",
|
|
1644
|
+
id: "vscode",
|
|
1645
|
+
detectionMethod: "path",
|
|
1646
|
+
setupMethod: "config-file",
|
|
1647
|
+
detectPaths: (fs) => {
|
|
1648
|
+
const appData = getAppDataPath(fs, "Code");
|
|
1649
|
+
return [appData];
|
|
1650
|
+
},
|
|
1651
|
+
getSetupConfig: (fs) => {
|
|
1652
|
+
const appData = getAppDataPath(fs, "Code");
|
|
1653
|
+
return {
|
|
1654
|
+
method: "config-file",
|
|
1655
|
+
configPath: fs.joinPath(appData, "User", "mcp.json"),
|
|
1656
|
+
serversKey: "servers",
|
|
1657
|
+
serverName: "GitHits",
|
|
1658
|
+
serverConfig: { url: getMcpUrl(), type: "http" }
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
var cline = {
|
|
1663
|
+
name: "Cline",
|
|
1664
|
+
id: "cline",
|
|
1665
|
+
detectionMethod: "path",
|
|
1666
|
+
setupMethod: "config-file",
|
|
1667
|
+
detectPaths: (fs) => [fs.joinPath(fs.getHomeDir(), ".cline")],
|
|
1668
|
+
getSetupConfig: (fs) => ({
|
|
1669
|
+
method: "config-file",
|
|
1670
|
+
configPath: fs.joinPath(fs.getHomeDir(), ".cline", "data", "settings", "cline_mcp_settings.json"),
|
|
1671
|
+
serversKey: "mcpServers",
|
|
1672
|
+
serverName: "GitHits",
|
|
1673
|
+
serverConfig: { url: getMcpUrl(), type: "streamableHttp" }
|
|
1674
|
+
})
|
|
1675
|
+
};
|
|
1676
|
+
var geminiCli = {
|
|
1677
|
+
name: "Gemini CLI",
|
|
1678
|
+
id: "gemini-cli",
|
|
1679
|
+
detectionMethod: "binary",
|
|
1680
|
+
setupMethod: "cli",
|
|
1681
|
+
detectBinary: async (exec) => isExecutableAvailable(exec, "gemini"),
|
|
1682
|
+
getSetupConfig: () => ({
|
|
1683
|
+
method: "cli",
|
|
1684
|
+
commands: [
|
|
1685
|
+
{
|
|
1686
|
+
command: "gemini",
|
|
1687
|
+
args: [
|
|
1688
|
+
"extensions",
|
|
1689
|
+
"install",
|
|
1690
|
+
"https://github.com/githits-com/githits-cli"
|
|
1691
|
+
]
|
|
1692
|
+
}
|
|
1693
|
+
],
|
|
1694
|
+
checkCommand: {
|
|
1695
|
+
command: "gemini",
|
|
1696
|
+
args: ["extensions", "list"],
|
|
1697
|
+
configuredPattern: /githits/i
|
|
1698
|
+
}
|
|
1699
|
+
})
|
|
1700
|
+
};
|
|
1701
|
+
var googleAntigravity = {
|
|
1702
|
+
name: "Google Antigravity",
|
|
1703
|
+
id: "google-antigravity",
|
|
1704
|
+
detectionMethod: "path",
|
|
1705
|
+
setupMethod: "config-file",
|
|
1706
|
+
detectPaths: (fs) => [fs.joinPath(fs.getHomeDir(), ".gemini", "antigravity")],
|
|
1707
|
+
getSetupConfig: (fs) => ({
|
|
1708
|
+
method: "config-file",
|
|
1709
|
+
configPath: fs.joinPath(fs.getHomeDir(), ".gemini", "antigravity", "mcp_config.json"),
|
|
1710
|
+
serversKey: "mcpServers",
|
|
1711
|
+
serverName: "GitHits",
|
|
1712
|
+
serverConfig: { serverUrl: getMcpUrl() }
|
|
1713
|
+
})
|
|
1714
|
+
};
|
|
1715
|
+
var openCode = {
|
|
1716
|
+
name: "OpenCode",
|
|
1717
|
+
id: "opencode",
|
|
1718
|
+
detectionMethod: "binary",
|
|
1719
|
+
setupMethod: "config-file",
|
|
1720
|
+
detectBinary: async (exec) => isExecutableAvailable(exec, "opencode"),
|
|
1721
|
+
getSetupConfig: (fs) => ({
|
|
1722
|
+
method: "config-file",
|
|
1723
|
+
configPath: process.platform === "win32" ? fs.joinPath(process.env.APPDATA ?? fs.joinPath(fs.getHomeDir(), "AppData", "Roaming"), "opencode", "opencode.json") : fs.joinPath(fs.getHomeDir(), ".config", "opencode", "opencode.json"),
|
|
1724
|
+
serversKey: "mcp",
|
|
1725
|
+
serverName: "GitHits",
|
|
1726
|
+
serverConfig: {
|
|
1727
|
+
type: "local",
|
|
1728
|
+
command: ["npx", "-y", "githits@latest", "mcp", "start"],
|
|
1729
|
+
enabled: true
|
|
1730
|
+
}
|
|
1731
|
+
})
|
|
1732
|
+
};
|
|
1733
|
+
var agentDefinitions = [
|
|
1734
|
+
claudeCode,
|
|
1735
|
+
cursor,
|
|
1736
|
+
windsurf,
|
|
1737
|
+
vscode,
|
|
1738
|
+
cline,
|
|
1739
|
+
claudeDesktop,
|
|
1740
|
+
codexCli,
|
|
1741
|
+
geminiCli,
|
|
1742
|
+
googleAntigravity,
|
|
1743
|
+
openCode
|
|
1744
|
+
];
|
|
1745
|
+
async function scanAgents(definitions, fs, execService) {
|
|
1746
|
+
const result = {
|
|
1747
|
+
needsSetup: [],
|
|
1748
|
+
alreadyConfigured: [],
|
|
1749
|
+
notDetected: []
|
|
1750
|
+
};
|
|
1751
|
+
for (const agent of definitions) {
|
|
1752
|
+
let detected = false;
|
|
1753
|
+
if (agent.detectionMethod === "binary" && agent.detectBinary) {
|
|
1754
|
+
try {
|
|
1755
|
+
detected = await agent.detectBinary(execService);
|
|
1756
|
+
} catch {
|
|
1757
|
+
detected = false;
|
|
1758
|
+
}
|
|
1759
|
+
} else if (agent.detectionMethod === "path" && agent.detectPaths) {
|
|
1760
|
+
const paths = agent.detectPaths(fs);
|
|
1761
|
+
for (const path of paths) {
|
|
1762
|
+
if (await fs.isDirectory(path)) {
|
|
1763
|
+
detected = true;
|
|
1764
|
+
break;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
if (!detected) {
|
|
1769
|
+
result.notDetected.push(agent);
|
|
1770
|
+
continue;
|
|
1771
|
+
}
|
|
1772
|
+
if (agent.setupMethod === "config-file") {
|
|
1773
|
+
const config = agent.getSetupConfig(fs);
|
|
1774
|
+
if (config.method === "config-file" && await isAlreadyConfigured(config, fs)) {
|
|
1775
|
+
result.alreadyConfigured.push(agent);
|
|
1776
|
+
} else {
|
|
1777
|
+
result.needsSetup.push(agent);
|
|
1778
|
+
}
|
|
1779
|
+
} else {
|
|
1780
|
+
const config = agent.getSetupConfig(fs);
|
|
1781
|
+
if (config.method === "cli" && config.checkCommand) {
|
|
1782
|
+
const configured = await isCliAlreadyConfigured(config.checkCommand, execService);
|
|
1783
|
+
if (configured) {
|
|
1784
|
+
result.alreadyConfigured.push(agent);
|
|
1785
|
+
} else {
|
|
1786
|
+
result.needsSetup.push(agent);
|
|
1787
|
+
}
|
|
1788
|
+
} else {
|
|
1789
|
+
result.needsSetup.push(agent);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
return result;
|
|
1794
|
+
}
|
|
1795
|
+
// node_modules/@inquirer/core/dist/lib/errors.js
|
|
1796
|
+
class ExitPromptError extends Error {
|
|
1797
|
+
name = "ExitPromptError";
|
|
1798
|
+
}
|
|
1242
1799
|
// src/shared/colors.ts
|
|
1243
1800
|
var colors = {
|
|
1244
1801
|
reset: "\x1B[0m",
|
|
@@ -1263,6 +1820,18 @@ function colorize(text, color, useColors) {
|
|
|
1263
1820
|
return text;
|
|
1264
1821
|
return `${colors[color]}${text}${colors.reset}`;
|
|
1265
1822
|
}
|
|
1823
|
+
function success(text, useColors) {
|
|
1824
|
+
const checkmark = useColors ? `${colors.green}✓${colors.reset}` : "✓";
|
|
1825
|
+
return `${checkmark} ${text}`;
|
|
1826
|
+
}
|
|
1827
|
+
function error(text, useColors) {
|
|
1828
|
+
const cross = useColors ? `${colors.red}✗${colors.reset}` : "✗";
|
|
1829
|
+
return `${cross} ${text}`;
|
|
1830
|
+
}
|
|
1831
|
+
function warning(text, useColors) {
|
|
1832
|
+
const warn = useColors ? `${colors.yellow}⚠${colors.reset}` : "⚠";
|
|
1833
|
+
return `${warn} ${text}`;
|
|
1834
|
+
}
|
|
1266
1835
|
function highlight(text, useColors) {
|
|
1267
1836
|
if (!useColors)
|
|
1268
1837
|
return text;
|
|
@@ -1274,76 +1843,24 @@ function dim(text, useColors) {
|
|
|
1274
1843
|
return `${colors.dim}${text}${colors.reset}`;
|
|
1275
1844
|
}
|
|
1276
1845
|
|
|
1277
|
-
// src/shared/language-filter.ts
|
|
1278
|
-
var DEFAULT_LIMIT = 5;
|
|
1279
|
-
function filterLanguages(languages, query, limit = DEFAULT_LIMIT) {
|
|
1280
|
-
const lowerQuery = query.toLowerCase();
|
|
1281
|
-
return languages.filter((lang) => lang.name.toLowerCase().includes(lowerQuery) || lang.display_name.toLowerCase().includes(lowerQuery) || lang.aliases.some((a) => a.toLowerCase().includes(lowerQuery))).slice(0, limit).map(({ name, display_name }) => ({ name, display_name }));
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
// src/commands/languages.ts
|
|
1285
|
-
async function languagesAction(query, options, deps) {
|
|
1286
|
-
requireAuth(deps);
|
|
1287
|
-
try {
|
|
1288
|
-
const allLanguages = await deps.githitsService.getLanguages();
|
|
1289
|
-
const displayList = query ? filterLanguages(allLanguages, query) : allLanguages.map(({ name, display_name }) => ({ name, display_name }));
|
|
1290
|
-
if (options.json) {
|
|
1291
|
-
console.log(JSON.stringify(displayList));
|
|
1292
|
-
} else if (query && displayList.length === 0) {
|
|
1293
|
-
console.log(`No languages matching "${query}".`);
|
|
1294
|
-
} else {
|
|
1295
|
-
const useColors = shouldUseColors();
|
|
1296
|
-
for (const lang of displayList) {
|
|
1297
|
-
console.log(` ${colorize(lang.name, "cyan", useColors)} ${dim(lang.display_name, useColors)}`);
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
} catch (error) {
|
|
1301
|
-
console.error(`Failed to list languages: ${error instanceof Error ? error.message : error}`);
|
|
1302
|
-
process.exit(1);
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
var LANGUAGES_DESCRIPTION = `List supported programming languages.
|
|
1306
|
-
|
|
1307
|
-
Without a query, lists all supported languages.
|
|
1308
|
-
With a query, filters to the top 5 matches by name, display name, or alias.
|
|
1309
|
-
|
|
1310
|
-
Examples:
|
|
1311
|
-
githits languages List all languages
|
|
1312
|
-
githits languages python Filter by name
|
|
1313
|
-
githits languages type --json JSON output for piping`;
|
|
1314
|
-
function registerLanguagesCommand(program) {
|
|
1315
|
-
program.command("languages").summary("List supported programming languages").description(LANGUAGES_DESCRIPTION).argument("[query]", "Filter by name, display name, or alias").option("--json", "Output as JSON for piping").action(async (query, options) => {
|
|
1316
|
-
try {
|
|
1317
|
-
const deps = await createContainer();
|
|
1318
|
-
await languagesAction(query, options, deps);
|
|
1319
|
-
} catch (error) {
|
|
1320
|
-
if (error instanceof AuthRequiredError)
|
|
1321
|
-
process.exit(1);
|
|
1322
|
-
throw error;
|
|
1323
|
-
}
|
|
1324
|
-
});
|
|
1325
|
-
}
|
|
1326
1846
|
// src/commands/login.ts
|
|
1327
1847
|
var TIMEOUT_MS = 5 * 60 * 1000;
|
|
1328
1848
|
function randomPort() {
|
|
1329
1849
|
return Math.floor(Math.random() * 2000) + 8000;
|
|
1330
1850
|
}
|
|
1331
|
-
async function
|
|
1851
|
+
async function loginFlow(options, deps) {
|
|
1332
1852
|
const { authService, authStorage, browserService, mcpUrl } = deps;
|
|
1333
1853
|
if (options.port !== undefined && (Number.isNaN(options.port) || options.port < 1 || options.port > 65535)) {
|
|
1334
|
-
|
|
1335
|
-
|
|
1854
|
+
return {
|
|
1855
|
+
status: "failed",
|
|
1856
|
+
message: "Invalid port number. Must be between 1 and 65535."
|
|
1857
|
+
};
|
|
1336
1858
|
}
|
|
1337
1859
|
const existing = await authStorage.loadTokens(mcpUrl);
|
|
1338
1860
|
if (existing && !options.force) {
|
|
1339
1861
|
const isExpired = existing.expiresAt && new Date(existing.expiresAt) < new Date;
|
|
1340
1862
|
if (!isExpired) {
|
|
1341
|
-
|
|
1342
|
-
`);
|
|
1343
|
-
console.log(` Environment: ${mcpUrl}
|
|
1344
|
-
`);
|
|
1345
|
-
console.log("To re-authenticate, use `githits login --force`.");
|
|
1346
|
-
return;
|
|
1863
|
+
return { status: "already_authenticated", message: "Already logged in." };
|
|
1347
1864
|
}
|
|
1348
1865
|
console.log(`Token expired. Starting new login...
|
|
1349
1866
|
`);
|
|
@@ -1427,22 +1944,18 @@ async function loginAction(options, deps) {
|
|
|
1427
1944
|
callback = await Promise.race([serverPromise, timeoutPromise]);
|
|
1428
1945
|
if (timeoutId)
|
|
1429
1946
|
clearTimeout(timeoutId);
|
|
1430
|
-
} catch (
|
|
1947
|
+
} catch (error2) {
|
|
1431
1948
|
if (timeoutId)
|
|
1432
1949
|
clearTimeout(timeoutId);
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
`);
|
|
1436
|
-
console.log("Run `githits login` to try again.");
|
|
1437
|
-
}
|
|
1438
|
-
process.exit(1);
|
|
1950
|
+
const msg = error2 instanceof Error ? error2.message : "Authentication failed";
|
|
1951
|
+
return { status: "failed", message: `${msg}.` };
|
|
1439
1952
|
}
|
|
1440
1953
|
if (callback.type !== "success") {
|
|
1441
|
-
console.log(`${callback.message}
|
|
1442
|
-
`);
|
|
1443
|
-
console.log("Run `githits login` to try again.");
|
|
1444
1954
|
await new Promise((r) => setTimeout(r, 2000));
|
|
1445
|
-
|
|
1955
|
+
return {
|
|
1956
|
+
status: "failed",
|
|
1957
|
+
message: callback.message ?? "Authentication callback failed."
|
|
1958
|
+
};
|
|
1446
1959
|
}
|
|
1447
1960
|
let tokenResponse;
|
|
1448
1961
|
try {
|
|
@@ -1454,14 +1967,15 @@ async function loginAction(options, deps) {
|
|
|
1454
1967
|
codeVerifier: verifier,
|
|
1455
1968
|
redirectUri
|
|
1456
1969
|
});
|
|
1457
|
-
} catch (
|
|
1970
|
+
} catch (error2) {
|
|
1458
1971
|
try {
|
|
1459
1972
|
await authStorage.clearClient(mcpUrl);
|
|
1460
1973
|
} catch {}
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1974
|
+
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
1975
|
+
return {
|
|
1976
|
+
status: "failed",
|
|
1977
|
+
message: `Failed to complete authentication: ${msg}`
|
|
1978
|
+
};
|
|
1465
1979
|
}
|
|
1466
1980
|
const expiresAt = new Date(Date.now() + tokenResponse.expiresIn * 1000).toISOString();
|
|
1467
1981
|
await authStorage.saveTokens(mcpUrl, {
|
|
@@ -1471,10 +1985,31 @@ async function loginAction(options, deps) {
|
|
|
1471
1985
|
createdAt: new Date().toISOString()
|
|
1472
1986
|
});
|
|
1473
1987
|
const hours = Math.round(tokenResponse.expiresIn / 3600);
|
|
1988
|
+
return {
|
|
1989
|
+
status: "success",
|
|
1990
|
+
message: `Logged in successfully. Token expires in ${hours} hour${hours !== 1 ? "s" : ""}.`
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
async function loginAction(options, deps) {
|
|
1994
|
+
const result = await loginFlow(options, deps);
|
|
1995
|
+
if (result.status === "already_authenticated") {
|
|
1996
|
+
console.log(`Already logged in.
|
|
1997
|
+
`);
|
|
1998
|
+
console.log(` Environment: ${deps.mcpUrl}
|
|
1999
|
+
`);
|
|
2000
|
+
console.log("To re-authenticate, use `githits login --force`.");
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
if (result.status === "failed") {
|
|
2004
|
+
console.error(`${result.message}
|
|
2005
|
+
`);
|
|
2006
|
+
console.log("Run `githits login` to try again.");
|
|
2007
|
+
process.exit(1);
|
|
2008
|
+
}
|
|
1474
2009
|
console.log(`Logged in successfully.
|
|
1475
2010
|
`);
|
|
1476
|
-
console.log(` Environment: ${mcpUrl}`);
|
|
1477
|
-
console.log(
|
|
2011
|
+
console.log(` Environment: ${deps.mcpUrl}`);
|
|
2012
|
+
console.log(result.message.replace("Logged in successfully. ", " "));
|
|
1478
2013
|
console.log(`
|
|
1479
2014
|
You're ready to use githits with your AI assistant.`);
|
|
1480
2015
|
}
|
|
@@ -1491,6 +2026,217 @@ function registerLoginCommand(program) {
|
|
|
1491
2026
|
await loginAction(options, deps);
|
|
1492
2027
|
});
|
|
1493
2028
|
}
|
|
2029
|
+
|
|
2030
|
+
// src/commands/init/init.ts
|
|
2031
|
+
async function initAction(options, deps) {
|
|
2032
|
+
const useColors = shouldUseColors();
|
|
2033
|
+
const { fileSystemService, promptService, execService, createLoginDeps } = deps;
|
|
2034
|
+
console.log(`
|
|
2035
|
+
${colorize("GitHits", "bold", useColors)} — Set up MCP server for your coding agents
|
|
2036
|
+
`);
|
|
2037
|
+
if (!options.skipLogin && createLoginDeps) {
|
|
2038
|
+
console.log(` Checking authentication...
|
|
2039
|
+
`);
|
|
2040
|
+
let loginResult;
|
|
2041
|
+
try {
|
|
2042
|
+
const loginDeps = await createLoginDeps();
|
|
2043
|
+
loginResult = await loginFlow({}, loginDeps);
|
|
2044
|
+
} catch (error2) {
|
|
2045
|
+
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
2046
|
+
loginResult = { status: "failed", message: msg };
|
|
2047
|
+
}
|
|
2048
|
+
if (loginResult.status === "already_authenticated") {
|
|
2049
|
+
console.log(` ${success("Already authenticated", useColors)}
|
|
2050
|
+
`);
|
|
2051
|
+
} else if (loginResult.status === "success") {
|
|
2052
|
+
console.log(` ${success("Logged in successfully", useColors)}
|
|
2053
|
+
`);
|
|
2054
|
+
} else {
|
|
2055
|
+
console.log(` ${warning(`Login failed: ${loginResult.message}`, useColors)}
|
|
2056
|
+
`);
|
|
2057
|
+
if (!options.yes) {
|
|
2058
|
+
try {
|
|
2059
|
+
const choice = await promptService.confirm3("Continue without authentication?");
|
|
2060
|
+
if (choice === "no") {
|
|
2061
|
+
console.log("\n Setup cancelled. Run `githits login` to authenticate.\n");
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
} catch (err) {
|
|
2065
|
+
if (err instanceof ExitPromptError) {
|
|
2066
|
+
console.log(`
|
|
2067
|
+
Setup cancelled.
|
|
2068
|
+
`);
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
throw err;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
console.log(` Continuing without authentication...
|
|
2075
|
+
`);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
console.log(` Scanning for available agents...
|
|
2079
|
+
`);
|
|
2080
|
+
const scan = await scanAgents(agentDefinitions, fileSystemService, execService);
|
|
2081
|
+
for (const agent of scan.alreadyConfigured) {
|
|
2082
|
+
console.log(` ${success(`${agent.name} — already configured`, useColors)}`);
|
|
2083
|
+
}
|
|
2084
|
+
for (const agent of scan.needsSetup) {
|
|
2085
|
+
console.log(` ${colorize(`● ${agent.name} — needs setup`, "cyan", useColors)}`);
|
|
2086
|
+
}
|
|
2087
|
+
for (const agent of scan.notDetected) {
|
|
2088
|
+
console.log(` ${colorize(`${agent.name} — not detected`, "dim", useColors)}`);
|
|
2089
|
+
}
|
|
2090
|
+
console.log();
|
|
2091
|
+
if (scan.needsSetup.length === 0 && scan.alreadyConfigured.length === 0) {
|
|
2092
|
+
console.log(` No coding agents detected. Install an agent and try again.
|
|
2093
|
+
`);
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
if (scan.needsSetup.length === 0) {
|
|
2097
|
+
console.log(` All detected agents are already configured. Nothing to do.
|
|
2098
|
+
`);
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
const toSetup = scan.needsSetup;
|
|
2102
|
+
const outcomes = [];
|
|
2103
|
+
let alwaysMode = options.yes ?? false;
|
|
2104
|
+
for (const agent of toSetup) {
|
|
2105
|
+
console.log(` Setting up ${colorize(agent.name, "bold", useColors)}...
|
|
2106
|
+
`);
|
|
2107
|
+
const config = agent.getSetupConfig(fileSystemService);
|
|
2108
|
+
const preview = formatSetupPreview(config);
|
|
2109
|
+
for (const line of preview.split(`
|
|
2110
|
+
`)) {
|
|
2111
|
+
console.log(` ${line}`);
|
|
2112
|
+
}
|
|
2113
|
+
console.log();
|
|
2114
|
+
if (!alwaysMode) {
|
|
2115
|
+
let choice;
|
|
2116
|
+
try {
|
|
2117
|
+
choice = await promptService.confirm3("Proceed?");
|
|
2118
|
+
} catch (err) {
|
|
2119
|
+
if (err instanceof ExitPromptError) {
|
|
2120
|
+
console.log(`
|
|
2121
|
+
Setup cancelled.
|
|
2122
|
+
`);
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
throw err;
|
|
2126
|
+
}
|
|
2127
|
+
if (choice === "no") {
|
|
2128
|
+
outcomes.push({ name: agent.name, status: "skipped" });
|
|
2129
|
+
console.log();
|
|
2130
|
+
continue;
|
|
2131
|
+
}
|
|
2132
|
+
if (choice === "always") {
|
|
2133
|
+
alwaysMode = true;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
const result = config.method === "cli" ? await executeCliSetup(config, execService) : await executeConfigFileSetup(config, fileSystemService);
|
|
2137
|
+
outcomes.push({ name: agent.name, status: result.status });
|
|
2138
|
+
if (result.status === "success") {
|
|
2139
|
+
console.log(` ${success(`${agent.name} configured`, useColors)}
|
|
2140
|
+
`);
|
|
2141
|
+
} else if (result.status === "already_configured") {
|
|
2142
|
+
console.log(` ${warning(`${agent.name} already configured`, useColors)}
|
|
2143
|
+
`);
|
|
2144
|
+
} else {
|
|
2145
|
+
console.log(` ${error(result.message, useColors)}
|
|
2146
|
+
`);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
const configured = outcomes.filter((o) => o.status === "success").length;
|
|
2150
|
+
const alreadyDone = outcomes.filter((o) => o.status === "already_configured").length + scan.alreadyConfigured.length;
|
|
2151
|
+
const failed = outcomes.filter((o) => o.status === "failed").length;
|
|
2152
|
+
const skipped = outcomes.filter((o) => o.status === "skipped").length;
|
|
2153
|
+
if (configured > 0 || alreadyDone > 0) {
|
|
2154
|
+
console.log(" Done! GitHits is ready.");
|
|
2155
|
+
} else if (failed > 0) {
|
|
2156
|
+
console.log(" Setup completed with errors.");
|
|
2157
|
+
} else if (skipped > 0) {
|
|
2158
|
+
console.log(" Setup skipped.");
|
|
2159
|
+
}
|
|
2160
|
+
if (failed > 0) {
|
|
2161
|
+
console.log(` ${failed} agent${failed !== 1 ? "s" : ""} failed to configure.`);
|
|
2162
|
+
}
|
|
2163
|
+
if (skipped > 0) {
|
|
2164
|
+
console.log(` ${skipped} agent${skipped !== 1 ? "s" : ""} skipped.`);
|
|
2165
|
+
}
|
|
2166
|
+
console.log();
|
|
2167
|
+
}
|
|
2168
|
+
var INIT_DESCRIPTION = `Set up GitHits MCP server for your coding agents.
|
|
2169
|
+
|
|
2170
|
+
Authenticates with your GitHits account, then scans for available agents
|
|
2171
|
+
(Claude Code, Cursor, Windsurf, VS Code, Cline, Claude Desktop, Codex CLI,
|
|
2172
|
+
Gemini CLI, Google Antigravity), checks which are already configured,
|
|
2173
|
+
and sets up unconfigured ones with your confirmation.
|
|
2174
|
+
|
|
2175
|
+
Supports CLI-based setup (Claude Code, Codex, Gemini CLI) and config
|
|
2176
|
+
file editing (Cursor, Windsurf, VS Code, Cline, Claude Desktop,
|
|
2177
|
+
Google Antigravity) with atomic writes.`;
|
|
2178
|
+
function registerInitCommand(program) {
|
|
2179
|
+
program.command("init").summary("Set up MCP server for your coding agents").description(INIT_DESCRIPTION).option("-y, --yes", "Skip prompts, configure all detected agents").option("--skip-login", "Skip authentication step").action(async (options) => {
|
|
2180
|
+
const fileSystemService = new FileSystemServiceImpl;
|
|
2181
|
+
const promptService = new PromptServiceImpl;
|
|
2182
|
+
const execService = new ExecServiceImpl;
|
|
2183
|
+
await initAction(options, {
|
|
2184
|
+
fileSystemService,
|
|
2185
|
+
promptService,
|
|
2186
|
+
execService,
|
|
2187
|
+
createLoginDeps: () => createContainer()
|
|
2188
|
+
});
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
// src/shared/language-filter.ts
|
|
2192
|
+
var DEFAULT_LIMIT = 5;
|
|
2193
|
+
function filterLanguages(languages, query, limit = DEFAULT_LIMIT) {
|
|
2194
|
+
const lowerQuery = query.toLowerCase();
|
|
2195
|
+
return languages.filter((lang) => lang.name.toLowerCase().includes(lowerQuery) || lang.display_name.toLowerCase().includes(lowerQuery) || lang.aliases.some((a) => a.toLowerCase().includes(lowerQuery))).slice(0, limit).map(({ name, display_name }) => ({ name, display_name }));
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
// src/commands/languages.ts
|
|
2199
|
+
async function languagesAction(query, options, deps) {
|
|
2200
|
+
requireAuth(deps);
|
|
2201
|
+
try {
|
|
2202
|
+
const allLanguages = await deps.githitsService.getLanguages();
|
|
2203
|
+
const displayList = query ? filterLanguages(allLanguages, query) : allLanguages.map(({ name, display_name }) => ({ name, display_name }));
|
|
2204
|
+
if (options.json) {
|
|
2205
|
+
console.log(JSON.stringify(displayList));
|
|
2206
|
+
} else if (query && displayList.length === 0) {
|
|
2207
|
+
console.log(`No languages matching "${query}".`);
|
|
2208
|
+
} else {
|
|
2209
|
+
const useColors = shouldUseColors();
|
|
2210
|
+
for (const lang of displayList) {
|
|
2211
|
+
console.log(` ${colorize(lang.name, "cyan", useColors)} ${dim(lang.display_name, useColors)}`);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
} catch (error2) {
|
|
2215
|
+
console.error(`Failed to list languages: ${error2 instanceof Error ? error2.message : error2}`);
|
|
2216
|
+
process.exit(1);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
var LANGUAGES_DESCRIPTION = `List supported programming languages.
|
|
2220
|
+
|
|
2221
|
+
Without a query, lists all supported languages.
|
|
2222
|
+
With a query, filters to the top 5 matches by name, display name, or alias.
|
|
2223
|
+
|
|
2224
|
+
Examples:
|
|
2225
|
+
githits languages List all languages
|
|
2226
|
+
githits languages python Filter by name
|
|
2227
|
+
githits languages type --json JSON output for piping`;
|
|
2228
|
+
function registerLanguagesCommand(program) {
|
|
2229
|
+
program.command("languages").summary("List supported programming languages").description(LANGUAGES_DESCRIPTION).argument("[query]", "Filter by name, display name, or alias").option("--json", "Output as JSON for piping").action(async (query, options) => {
|
|
2230
|
+
try {
|
|
2231
|
+
const deps = await createContainer();
|
|
2232
|
+
await languagesAction(query, options, deps);
|
|
2233
|
+
} catch (error2) {
|
|
2234
|
+
if (error2 instanceof AuthRequiredError)
|
|
2235
|
+
process.exit(1);
|
|
2236
|
+
throw error2;
|
|
2237
|
+
}
|
|
2238
|
+
});
|
|
2239
|
+
}
|
|
1494
2240
|
// src/commands/logout.ts
|
|
1495
2241
|
async function logoutAction(deps) {
|
|
1496
2242
|
const { authStorage, mcpUrl } = deps;
|
|
@@ -1498,13 +2244,13 @@ async function logoutAction(deps) {
|
|
|
1498
2244
|
let firstError;
|
|
1499
2245
|
try {
|
|
1500
2246
|
await authStorage.clearTokens(mcpUrl);
|
|
1501
|
-
} catch (
|
|
1502
|
-
firstError =
|
|
2247
|
+
} catch (error2) {
|
|
2248
|
+
firstError = error2;
|
|
1503
2249
|
}
|
|
1504
2250
|
try {
|
|
1505
2251
|
await authStorage.clearClient(mcpUrl);
|
|
1506
|
-
} catch (
|
|
1507
|
-
firstError ??=
|
|
2252
|
+
} catch (error2) {
|
|
2253
|
+
firstError ??= error2;
|
|
1508
2254
|
}
|
|
1509
2255
|
if (!auth) {
|
|
1510
2256
|
console.log(`Not currently logged in.
|
|
@@ -1553,8 +2299,8 @@ function errorResult(message) {
|
|
|
1553
2299
|
async function withErrorHandling(operation, fn) {
|
|
1554
2300
|
try {
|
|
1555
2301
|
return await fn();
|
|
1556
|
-
} catch (
|
|
1557
|
-
const message =
|
|
2302
|
+
} catch (error2) {
|
|
2303
|
+
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
1558
2304
|
return errorResult(`Failed to ${operation}: ${message}`);
|
|
1559
2305
|
}
|
|
1560
2306
|
}
|
|
@@ -1775,8 +2521,8 @@ async function searchAction(query, options, deps) {
|
|
|
1775
2521
|
} else {
|
|
1776
2522
|
console.log(result);
|
|
1777
2523
|
}
|
|
1778
|
-
} catch (
|
|
1779
|
-
console.error(`Failed to search: ${
|
|
2524
|
+
} catch (error2) {
|
|
2525
|
+
console.error(`Failed to search: ${error2 instanceof Error ? error2.message : error2}`);
|
|
1780
2526
|
process.exit(1);
|
|
1781
2527
|
}
|
|
1782
2528
|
}
|
|
@@ -1796,10 +2542,10 @@ function registerSearchCommand(program) {
|
|
|
1796
2542
|
try {
|
|
1797
2543
|
const deps = await createContainer();
|
|
1798
2544
|
await searchAction(query, options, deps);
|
|
1799
|
-
} catch (
|
|
1800
|
-
if (
|
|
2545
|
+
} catch (error2) {
|
|
2546
|
+
if (error2 instanceof AuthRequiredError)
|
|
1801
2547
|
process.exit(1);
|
|
1802
|
-
throw
|
|
2548
|
+
throw error2;
|
|
1803
2549
|
}
|
|
1804
2550
|
});
|
|
1805
2551
|
}
|
|
@@ -1811,6 +2557,7 @@ program.name("githits").description("Code examples from global open source for y
|
|
|
1811
2557
|
}
|
|
1812
2558
|
}).addHelpText("after", `
|
|
1813
2559
|
Getting started:
|
|
2560
|
+
githits init Set up MCP for your coding agents
|
|
1814
2561
|
githits login Authenticate with your GitHits account
|
|
1815
2562
|
githits mcp Start MCP server for your AI assistant
|
|
1816
2563
|
githits search "query" --lang python Search for code examples
|
|
@@ -1818,6 +2565,7 @@ Getting started:
|
|
|
1818
2565
|
Learn more at https://githits.com
|
|
1819
2566
|
Docs: https://app.githits.com/docs/
|
|
1820
2567
|
Support: support@githits.com`);
|
|
2568
|
+
registerInitCommand(program);
|
|
1821
2569
|
registerLoginCommand(program);
|
|
1822
2570
|
registerLogoutCommand(program);
|
|
1823
2571
|
registerMcpCommand(program);
|