githits 0.4.6 → 0.4.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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.plugin/plugin.json +1 -1
- package/README.md +3 -3
- package/dist/cli.js +1485 -341
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-2fdxv7pe.js → chunk-681avsyw.js} +2 -2
- package/dist/shared/{chunk-f16s86ze.js → chunk-6hq1gf0g.js} +4 -1
- package/dist/shared/{chunk-a1hzwt2m.js → chunk-wyphcypv.js} +1 -1
- package/gemini-extension.json +1 -1
- package/package.json +3 -2
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -51,11 +51,11 @@ import {
|
|
|
51
51
|
shouldRunUpdateCheck,
|
|
52
52
|
startTelemetrySpan,
|
|
53
53
|
withTelemetrySpan
|
|
54
|
-
} from "./shared/chunk-
|
|
54
|
+
} from "./shared/chunk-6hq1gf0g.js";
|
|
55
55
|
import {
|
|
56
56
|
__require,
|
|
57
57
|
version
|
|
58
|
-
} from "./shared/chunk-
|
|
58
|
+
} from "./shared/chunk-wyphcypv.js";
|
|
59
59
|
|
|
60
60
|
// src/cli.ts
|
|
61
61
|
import { Command } from "commander";
|
|
@@ -331,6 +331,7 @@ var colors = {
|
|
|
331
331
|
reset: "\x1B[0m",
|
|
332
332
|
bold: "\x1B[1m",
|
|
333
333
|
dim: "\x1B[2m",
|
|
334
|
+
italic: "\x1B[3m",
|
|
334
335
|
green: "\x1B[32m",
|
|
335
336
|
yellow: "\x1B[33m",
|
|
336
337
|
blue: "\x1B[34m",
|
|
@@ -338,6 +339,20 @@ var colors = {
|
|
|
338
339
|
cyan: "\x1B[36m",
|
|
339
340
|
red: "\x1B[31m"
|
|
340
341
|
};
|
|
342
|
+
var brandColors = {
|
|
343
|
+
primary: {
|
|
344
|
+
hex: "#FF72BE",
|
|
345
|
+
rgb: [255, 114, 190],
|
|
346
|
+
ansi256: 205,
|
|
347
|
+
ansi16: "magenta"
|
|
348
|
+
},
|
|
349
|
+
secondary: {
|
|
350
|
+
hex: "#FF872F",
|
|
351
|
+
rgb: [255, 135, 47],
|
|
352
|
+
ansi256: 208,
|
|
353
|
+
ansi16: "yellow"
|
|
354
|
+
}
|
|
355
|
+
};
|
|
341
356
|
function shouldUseColors(noColor) {
|
|
342
357
|
if (noColor)
|
|
343
358
|
return false;
|
|
@@ -350,6 +365,32 @@ function colorize(text, color, useColors) {
|
|
|
350
365
|
return text;
|
|
351
366
|
return `${colors[color]}${text}${colors.reset}`;
|
|
352
367
|
}
|
|
368
|
+
function getColorDepth() {
|
|
369
|
+
const stream = process.stdout;
|
|
370
|
+
return stream.getColorDepth?.() ?? 1;
|
|
371
|
+
}
|
|
372
|
+
function foregroundColorCode(color, colorDepth) {
|
|
373
|
+
if (colorDepth >= 24) {
|
|
374
|
+
const [red, green, blue] = color.rgb;
|
|
375
|
+
return `\x1B[38;2;${red};${green};${blue}m`;
|
|
376
|
+
}
|
|
377
|
+
if (colorDepth >= 8) {
|
|
378
|
+
return `\x1B[38;5;${color.ansi256}m`;
|
|
379
|
+
}
|
|
380
|
+
return colors[color.ansi16];
|
|
381
|
+
}
|
|
382
|
+
function colorizeTerminal(text, color, useColors, options = {}) {
|
|
383
|
+
if (!useColors)
|
|
384
|
+
return text;
|
|
385
|
+
const colorDepth = options.colorDepth ?? getColorDepth();
|
|
386
|
+
if (colorDepth <= 1)
|
|
387
|
+
return text;
|
|
388
|
+
const prefix = `${options.bold ? colors.bold : ""}${options.dim ? colors.dim : ""}${foregroundColorCode(color, colorDepth)}`;
|
|
389
|
+
return `${prefix}${text}${colors.reset}`;
|
|
390
|
+
}
|
|
391
|
+
function colorizeBrand(text, colorName, useColors, options) {
|
|
392
|
+
return colorizeTerminal(text, brandColors[colorName], useColors, options);
|
|
393
|
+
}
|
|
353
394
|
function success(text, useColors) {
|
|
354
395
|
const checkmark = useColors ? `${colors.green}✓${colors.reset}` : "✓";
|
|
355
396
|
return `${checkmark} ${text}`;
|
|
@@ -5963,7 +6004,7 @@ auth.storage = "file". File storage is plaintext on disk.
|
|
|
5963
6004
|
Use --no-browser in environments without a display (CI, SSH sessions)
|
|
5964
6005
|
to get a URL you can open on another device.`;
|
|
5965
6006
|
function registerLoginCommand(program) {
|
|
5966
|
-
program.command("login").summary("
|
|
6007
|
+
program.command("login").summary("Sign in to 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) => {
|
|
5967
6008
|
const deps = await createAuthCommandDependencies();
|
|
5968
6009
|
await loginAction(options, deps);
|
|
5969
6010
|
});
|
|
@@ -6105,6 +6146,61 @@ function getPostLoginContinuationMessage(command) {
|
|
|
6105
6146
|
return;
|
|
6106
6147
|
}
|
|
6107
6148
|
}
|
|
6149
|
+
// src/shared/spinner.ts
|
|
6150
|
+
var SPINNER_FRAMES = ["|", "/", "-", "\\"];
|
|
6151
|
+
var FRAME_INTERVAL_MS = 80;
|
|
6152
|
+
var MESSAGE_INTERVAL_MS = 2000;
|
|
6153
|
+
function startSpinner(message, enabled = true, runtime = {}) {
|
|
6154
|
+
const stdoutIsTTY = runtime.stdoutIsTTY ?? process.stdout.isTTY;
|
|
6155
|
+
const stderrIsTTY = runtime.stderrIsTTY ?? process.stderr.isTTY;
|
|
6156
|
+
const writeStderr = runtime.writeStderr ?? ((chunk) => process.stderr.write(chunk));
|
|
6157
|
+
if (!enabled || !stdoutIsTTY || !stderrIsTTY) {
|
|
6158
|
+
return { stop: () => {} };
|
|
6159
|
+
}
|
|
6160
|
+
const messages = typeof message === "string" ? [message] : message;
|
|
6161
|
+
const framesPerMessage = Math.round(MESSAGE_INTERVAL_MS / FRAME_INTERVAL_MS);
|
|
6162
|
+
const useColors = runtime.useColors ?? process.env.NO_COLOR === undefined;
|
|
6163
|
+
let frame = 0;
|
|
6164
|
+
const render = () => {
|
|
6165
|
+
const glyph = SPINNER_FRAMES[frame % SPINNER_FRAMES.length] ?? "|";
|
|
6166
|
+
const label = messages[Math.floor(frame / framesPerMessage) % messages.length] ?? "";
|
|
6167
|
+
frame += 1;
|
|
6168
|
+
writeStderr(`\r\x1B[2K${colorizeBrand(glyph, "primary", useColors)} ${label}`);
|
|
6169
|
+
};
|
|
6170
|
+
render();
|
|
6171
|
+
const interval = setInterval(render, FRAME_INTERVAL_MS);
|
|
6172
|
+
return {
|
|
6173
|
+
stop: () => {
|
|
6174
|
+
clearInterval(interval);
|
|
6175
|
+
writeStderr("\r\x1B[2K");
|
|
6176
|
+
}
|
|
6177
|
+
};
|
|
6178
|
+
}
|
|
6179
|
+
// src/shared/spinner-messages.ts
|
|
6180
|
+
var SPINNER_MESSAGES = {
|
|
6181
|
+
example: [
|
|
6182
|
+
"Searching real implementations...",
|
|
6183
|
+
"Exploring open-source code...",
|
|
6184
|
+
"Finding production patterns...",
|
|
6185
|
+
"Grounding results..."
|
|
6186
|
+
],
|
|
6187
|
+
search: [
|
|
6188
|
+
"Exploring repositories...",
|
|
6189
|
+
"Tracing symbols...",
|
|
6190
|
+
"Inspecting dependencies...",
|
|
6191
|
+
"Scanning source code..."
|
|
6192
|
+
],
|
|
6193
|
+
code: [
|
|
6194
|
+
"Inspecting source code...",
|
|
6195
|
+
"Resolving symbols...",
|
|
6196
|
+
"Reading dependency internals..."
|
|
6197
|
+
],
|
|
6198
|
+
docs: [
|
|
6199
|
+
"Reading documentation...",
|
|
6200
|
+
"Resolving references...",
|
|
6201
|
+
"Collecting package docs..."
|
|
6202
|
+
]
|
|
6203
|
+
};
|
|
6108
6204
|
// src/shared/unified-search-request.ts
|
|
6109
6205
|
var DEFAULT_UNIFIED_SEARCH_LIMIT = 10;
|
|
6110
6206
|
function buildUnifiedSearchParams(input) {
|
|
@@ -6595,7 +6691,30 @@ function labelsDiverge(input) {
|
|
|
6595
6691
|
const served = input.servedTarget;
|
|
6596
6692
|
if (!served)
|
|
6597
6693
|
return false;
|
|
6598
|
-
return Boolean(input.freshTarget && input.freshTarget !== served);
|
|
6694
|
+
return Boolean(input.freshTarget && canonicalTargetLabel(input.freshTarget) !== canonicalTargetLabel(served));
|
|
6695
|
+
}
|
|
6696
|
+
function canonicalTargetLabel(label) {
|
|
6697
|
+
const parsed = parsePackageVersionLabel(label);
|
|
6698
|
+
if (!parsed)
|
|
6699
|
+
return label;
|
|
6700
|
+
const version2 = parsed.version.replace(/^v(?=\d)/i, "");
|
|
6701
|
+
return `${parsed.registry.toLowerCase()}:${parsed.packageName}@${version2}`;
|
|
6702
|
+
}
|
|
6703
|
+
function parsePackageVersionLabel(label) {
|
|
6704
|
+
const registryEnd = label.indexOf(":");
|
|
6705
|
+
if (registryEnd <= 0)
|
|
6706
|
+
return;
|
|
6707
|
+
const versionStart = label.lastIndexOf("@");
|
|
6708
|
+
if (versionStart <= registryEnd + 1)
|
|
6709
|
+
return;
|
|
6710
|
+
const version2 = label.slice(versionStart + 1);
|
|
6711
|
+
if (!version2)
|
|
6712
|
+
return;
|
|
6713
|
+
return {
|
|
6714
|
+
registry: label.slice(0, registryEnd),
|
|
6715
|
+
packageName: label.slice(registryEnd + 1, versionStart),
|
|
6716
|
+
version: version2
|
|
6717
|
+
};
|
|
6599
6718
|
}
|
|
6600
6719
|
function warningForEntry(entry) {
|
|
6601
6720
|
const reasons = [];
|
|
@@ -7650,7 +7769,8 @@ async function pkgFilesAction(firstArg, secondArg, options, deps) {
|
|
|
7650
7769
|
limit,
|
|
7651
7770
|
waitTimeoutMs: wait
|
|
7652
7771
|
});
|
|
7653
|
-
const
|
|
7772
|
+
const spinner = startSpinner(SPINNER_MESSAGES.code, !options.json);
|
|
7773
|
+
const result = await deps.codeNavigationService.listFiles(build.params).finally(() => spinner.stop());
|
|
7654
7774
|
const payload = buildListFilesSuccessPayload(result, {
|
|
7655
7775
|
registry: target.registry ? toPkgseerRegistryLowercase(target.registry) : undefined,
|
|
7656
7776
|
name: target.packageName,
|
|
@@ -7786,7 +7906,8 @@ async function pkgGrepAction(first, second, third, options, deps) {
|
|
|
7786
7906
|
symbolFields: options.symbolField,
|
|
7787
7907
|
waitTimeoutMs: wait
|
|
7788
7908
|
});
|
|
7789
|
-
const
|
|
7909
|
+
const spinner = startSpinner(SPINNER_MESSAGES.code, !options.json);
|
|
7910
|
+
const result = await deps.codeNavigationService.grepRepo(build.params).finally(() => spinner.stop());
|
|
7790
7911
|
const payload = buildGrepRepoSuccessPayload(result, {
|
|
7791
7912
|
registry: target.registry ? toPkgseerRegistryLowercase(target.registry) : undefined,
|
|
7792
7913
|
name: target.packageName,
|
|
@@ -7880,7 +8001,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
|
|
|
7880
8001
|
match in --verbose output; full payload in --json).`;
|
|
7881
8002
|
function registerCodeGrepCommand(pkgCommand) {
|
|
7882
8003
|
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 (defaults to the repo default branch)").option("--git-ref <ref>", "Optional tag, commit, branch, or HEAD for --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) => {
|
|
7883
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
8004
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-681avsyw.js");
|
|
7884
8005
|
const deps = await createContainer2();
|
|
7885
8006
|
await pkgGrepAction(arg1, arg2, arg3, options, {
|
|
7886
8007
|
codeNavigationService: deps.codeNavigationService,
|
|
@@ -7990,7 +8111,8 @@ async function pkgReadAction(firstArg, secondArg, options, deps) {
|
|
|
7990
8111
|
endLine: range.endLine,
|
|
7991
8112
|
waitTimeoutMs: wait
|
|
7992
8113
|
});
|
|
7993
|
-
const
|
|
8114
|
+
const spinner = startSpinner(SPINNER_MESSAGES.code, !options.json);
|
|
8115
|
+
const result = await deps.codeNavigationService.readFile(build.params).finally(() => spinner.stop());
|
|
7994
8116
|
const payload = buildReadFileSuccessPayload(result, {
|
|
7995
8117
|
registry: target.registry ? toPkgseerRegistryLowercase(target.registry) : undefined,
|
|
7996
8118
|
name: target.packageName,
|
|
@@ -8131,7 +8253,7 @@ async function registerCodeCommandGroup(program, options = {}) {
|
|
|
8131
8253
|
if (!registration.shouldRegister) {
|
|
8132
8254
|
return;
|
|
8133
8255
|
}
|
|
8134
|
-
const codeCommand = program.command("code").summary("
|
|
8256
|
+
const codeCommand = program.command("code").summary("Inspect dependency source code and symbols").description("List files, read files, and grep substrings inside indexed dependency source. Every command accepts either `<spec>` (registry:name[@version]) or `--repo-url <url> [--git-ref <ref>]`. Omitted package versions use the latest release; omitted repo refs use the default-branch intent. For package-level metadata use `githits pkg`.");
|
|
8135
8257
|
registerCodeFilesCommand(codeCommand);
|
|
8136
8258
|
registerCodeReadCommand(codeCommand);
|
|
8137
8259
|
registerCodeGrepCommand(codeCommand);
|
|
@@ -8152,7 +8274,8 @@ async function docsListAction(spec, options, deps) {
|
|
|
8152
8274
|
limit,
|
|
8153
8275
|
after: options.after
|
|
8154
8276
|
});
|
|
8155
|
-
const
|
|
8277
|
+
const spinner = startSpinner(SPINNER_MESSAGES.docs, !options.json);
|
|
8278
|
+
const result = await deps.packageIntelligenceService.listPackageDocs(build.params).finally(() => spinner.stop());
|
|
8156
8279
|
const payload = buildListPackageDocsSuccessPayload(result, {
|
|
8157
8280
|
limitExplicit: build.limitExplicit,
|
|
8158
8281
|
afterExplicit: build.afterExplicit,
|
|
@@ -8222,7 +8345,8 @@ async function docsReadAction(pageId, options, deps) {
|
|
|
8222
8345
|
}
|
|
8223
8346
|
const range = options.lines ? parseLinesOption(options.lines) : undefined;
|
|
8224
8347
|
const build = buildReadPackageDocParams({ pageId });
|
|
8225
|
-
const
|
|
8348
|
+
const spinner = startSpinner(SPINNER_MESSAGES.docs, !options.json);
|
|
8349
|
+
const result = await deps.packageIntelligenceService.readPackageDoc(build.params).finally(() => spinner.stop());
|
|
8226
8350
|
const payload = buildReadPackageDocSuccessPayload(result, build.params.pageId, range);
|
|
8227
8351
|
if (options.json) {
|
|
8228
8352
|
console.log(JSON.stringify(payload));
|
|
@@ -8275,7 +8399,7 @@ async function registerDocsCommandGroup(program, options = {}) {
|
|
|
8275
8399
|
if (!registration.shouldRegister) {
|
|
8276
8400
|
return;
|
|
8277
8401
|
}
|
|
8278
|
-
const docsCommand = program.command("docs").summary("Browse and read
|
|
8402
|
+
const docsCommand = program.command("docs").summary("Browse and read package documentation").description("Browse and read package documentation across hosted docs and repository-backed docs. Docs are mixed by default; entries are source-badged and repo-backed pages also expose exact file follow-up metadata.");
|
|
8279
8403
|
registerDocsListCommand(docsCommand);
|
|
8280
8404
|
registerDocsReadCommand(docsCommand);
|
|
8281
8405
|
}
|
|
@@ -8288,12 +8412,13 @@ async function exampleAction(query, options, deps) {
|
|
|
8288
8412
|
}
|
|
8289
8413
|
requireAuth(deps);
|
|
8290
8414
|
try {
|
|
8415
|
+
const spinner = startSpinner(SPINNER_MESSAGES.example, !options.json);
|
|
8291
8416
|
const result = await deps.githitsService.search({
|
|
8292
8417
|
query,
|
|
8293
8418
|
language: options.lang,
|
|
8294
8419
|
licenseMode: options.license,
|
|
8295
8420
|
includeExplanation: options.explain
|
|
8296
|
-
});
|
|
8421
|
+
}).finally(() => spinner.stop());
|
|
8297
8422
|
if (options.json) {
|
|
8298
8423
|
const solutionId = extractSolutionId(result);
|
|
8299
8424
|
const payload = solutionId ? { result, solution_id: solutionId } : { result };
|
|
@@ -8328,7 +8453,7 @@ Examples:
|
|
|
8328
8453
|
githits example "react hooks patterns" -l typescript --explain
|
|
8329
8454
|
githits example "react hooks patterns" -l typescript --json`;
|
|
8330
8455
|
function registerExampleCommand(program) {
|
|
8331
|
-
program.command("example").summary("
|
|
8456
|
+
program.command("example").summary("Find real-world implementations from open-source code").description(EXAMPLE_DESCRIPTION).argument("<query>", "Natural language example-search query").option("-l, --lang <language>", "Optional programming language; omitted values are inferred by GitHits").addOption(new Option("--license <mode>", "License filter mode").choices(["strict", "yolo", "custom"]).default(undefined)).option("--explain", "Include AI-generated explanation").option("--json", "Output as JSON for piping").action(async (query, options) => {
|
|
8332
8457
|
try {
|
|
8333
8458
|
const deps = await loadContainer();
|
|
8334
8459
|
await exampleAction(query, options, deps);
|
|
@@ -8340,7 +8465,7 @@ function registerExampleCommand(program) {
|
|
|
8340
8465
|
});
|
|
8341
8466
|
}
|
|
8342
8467
|
async function loadContainer() {
|
|
8343
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
8468
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-681avsyw.js");
|
|
8344
8469
|
return createContainer2();
|
|
8345
8470
|
}
|
|
8346
8471
|
// src/commands/feedback.ts
|
|
@@ -8388,7 +8513,7 @@ Examples:
|
|
|
8388
8513
|
githits feedback --accept --tool code_grep -m "regex is fast on npm:lodash"
|
|
8389
8514
|
githits feedback --reject --tool search -m "missing kotlin support"`;
|
|
8390
8515
|
function registerFeedbackCommand(program) {
|
|
8391
|
-
program.command("feedback").summary("Submit feedback
|
|
8516
|
+
program.command("feedback").summary("Submit feedback about GitHits results or experience").description(FEEDBACK_DESCRIPTION).argument("[solution_id]", "Solution ID from a prior 'githits example' result (omit for generic feedback)").addOption(new Option2("--accept", "Mark as helpful").conflicts("reject")).addOption(new Option2("--reject", "Mark as unhelpful").conflicts("accept")).option("-m, --message <text>", "Feedback explanation").option("--tool <name>", "Command or MCP tool name being rated").option("--json", "Output as JSON for piping").action(async (solutionId, options) => {
|
|
8392
8517
|
try {
|
|
8393
8518
|
const deps = await createContainer();
|
|
8394
8519
|
await feedbackAction(solutionId, options, deps);
|
|
@@ -8404,11 +8529,21 @@ import {
|
|
|
8404
8529
|
parse as parseJsonc,
|
|
8405
8530
|
printParseErrorCode
|
|
8406
8531
|
} from "jsonc-parser";
|
|
8532
|
+
import {
|
|
8533
|
+
isMap,
|
|
8534
|
+
isScalar,
|
|
8535
|
+
parseDocument,
|
|
8536
|
+
parse as parseYaml,
|
|
8537
|
+
stringify as stringifyYaml
|
|
8538
|
+
} from "yaml";
|
|
8539
|
+
function normalizeConfigContent(content) {
|
|
8540
|
+
if (content.charCodeAt(0) === 65279) {
|
|
8541
|
+
return content.slice(1);
|
|
8542
|
+
}
|
|
8543
|
+
return content;
|
|
8544
|
+
}
|
|
8407
8545
|
function parseConfigObject(content) {
|
|
8408
|
-
|
|
8409
|
-
if (normalizedContent.charCodeAt(0) === 65279) {
|
|
8410
|
-
normalizedContent = normalizedContent.slice(1);
|
|
8411
|
-
}
|
|
8546
|
+
const normalizedContent = normalizeConfigContent(content);
|
|
8412
8547
|
const trimmed = normalizedContent.trim();
|
|
8413
8548
|
if (trimmed === "") {
|
|
8414
8549
|
return {
|
|
@@ -8456,6 +8591,221 @@ function parseConfigObject(content) {
|
|
|
8456
8591
|
};
|
|
8457
8592
|
}
|
|
8458
8593
|
}
|
|
8594
|
+
function parseYamlConfigObject(content) {
|
|
8595
|
+
const normalizedContent = normalizeConfigContent(content);
|
|
8596
|
+
if (normalizedContent.trim() === "") {
|
|
8597
|
+
return { value: {} };
|
|
8598
|
+
}
|
|
8599
|
+
try {
|
|
8600
|
+
const parsed = parseYaml(normalizedContent);
|
|
8601
|
+
if (parsed === null || parsed === undefined) {
|
|
8602
|
+
return { value: {} };
|
|
8603
|
+
}
|
|
8604
|
+
if (!isPlainObject(parsed)) {
|
|
8605
|
+
return { error: "Config file root is not a YAML object" };
|
|
8606
|
+
}
|
|
8607
|
+
return { value: parsed };
|
|
8608
|
+
} catch (err) {
|
|
8609
|
+
return {
|
|
8610
|
+
error: `Invalid YAML: ${err instanceof Error ? err.message : String(err)}`
|
|
8611
|
+
};
|
|
8612
|
+
}
|
|
8613
|
+
}
|
|
8614
|
+
function formatYamlError(error2) {
|
|
8615
|
+
if (error2 instanceof Error) {
|
|
8616
|
+
return error2.message;
|
|
8617
|
+
}
|
|
8618
|
+
return String(error2);
|
|
8619
|
+
}
|
|
8620
|
+
function parseYamlConfigDocument(content) {
|
|
8621
|
+
const normalizedContent = normalizeConfigContent(content);
|
|
8622
|
+
const source = normalizedContent.trim() === "" ? `{}
|
|
8623
|
+
` : normalizedContent;
|
|
8624
|
+
let doc;
|
|
8625
|
+
try {
|
|
8626
|
+
doc = parseDocument(source);
|
|
8627
|
+
} catch (err) {
|
|
8628
|
+
return {
|
|
8629
|
+
status: "error",
|
|
8630
|
+
error: `Invalid YAML: ${formatYamlError(err)}`
|
|
8631
|
+
};
|
|
8632
|
+
}
|
|
8633
|
+
if (doc.errors.length > 0) {
|
|
8634
|
+
const firstError = doc.errors[0];
|
|
8635
|
+
return {
|
|
8636
|
+
status: "error",
|
|
8637
|
+
error: `Invalid YAML: ${formatYamlError(firstError)}`
|
|
8638
|
+
};
|
|
8639
|
+
}
|
|
8640
|
+
const contents = doc.contents;
|
|
8641
|
+
if (!contents || !isMap(contents)) {
|
|
8642
|
+
return {
|
|
8643
|
+
status: "error",
|
|
8644
|
+
error: "Config file root is not a YAML object"
|
|
8645
|
+
};
|
|
8646
|
+
}
|
|
8647
|
+
return {
|
|
8648
|
+
status: "ok",
|
|
8649
|
+
doc,
|
|
8650
|
+
root: contents
|
|
8651
|
+
};
|
|
8652
|
+
}
|
|
8653
|
+
function toYamlConfigShapeError(serversKey) {
|
|
8654
|
+
return `"${serversKey}" is not a YAML object`;
|
|
8655
|
+
}
|
|
8656
|
+
function toYamlKeyString(key) {
|
|
8657
|
+
if (typeof key === "string") {
|
|
8658
|
+
return key;
|
|
8659
|
+
}
|
|
8660
|
+
if (!isScalar(key) || typeof key.value !== "string") {
|
|
8661
|
+
return null;
|
|
8662
|
+
}
|
|
8663
|
+
return key.value;
|
|
8664
|
+
}
|
|
8665
|
+
function getYamlMatchingServerKeys(serversMap, serverName) {
|
|
8666
|
+
const normalizedTarget = serverName.toLowerCase();
|
|
8667
|
+
return serversMap.items.map((pair) => toYamlKeyString(pair.key)).filter((key) => typeof key === "string" && key.toLowerCase() === normalizedTarget);
|
|
8668
|
+
}
|
|
8669
|
+
function yamlNodeToJsValue(value) {
|
|
8670
|
+
if (typeof value === "object" && value !== null && "toJSON" in value && typeof value.toJSON === "function") {
|
|
8671
|
+
return value.toJSON();
|
|
8672
|
+
}
|
|
8673
|
+
return value;
|
|
8674
|
+
}
|
|
8675
|
+
function createEmptyYamlMapNode() {
|
|
8676
|
+
const map = parseDocument(`{}
|
|
8677
|
+
`).contents;
|
|
8678
|
+
if (!map || !isMap(map)) {
|
|
8679
|
+
throw new Error("Failed to initialize YAML object node");
|
|
8680
|
+
}
|
|
8681
|
+
return map;
|
|
8682
|
+
}
|
|
8683
|
+
function ensureYamlServersMapForSetup(root, serversKey) {
|
|
8684
|
+
const existingServers = root.get(serversKey, true);
|
|
8685
|
+
if (existingServers === undefined || existingServers === null || isScalar(existingServers) && existingServers.value === null) {
|
|
8686
|
+
root.set(serversKey, createEmptyYamlMapNode());
|
|
8687
|
+
const initializedServers = root.get(serversKey, true);
|
|
8688
|
+
if (!initializedServers || !isMap(initializedServers)) {
|
|
8689
|
+
return {
|
|
8690
|
+
status: "error",
|
|
8691
|
+
error: toYamlConfigShapeError(serversKey)
|
|
8692
|
+
};
|
|
8693
|
+
}
|
|
8694
|
+
return {
|
|
8695
|
+
status: "ok",
|
|
8696
|
+
serversMap: initializedServers
|
|
8697
|
+
};
|
|
8698
|
+
}
|
|
8699
|
+
if (!isMap(existingServers)) {
|
|
8700
|
+
return {
|
|
8701
|
+
status: "error",
|
|
8702
|
+
error: toYamlConfigShapeError(serversKey)
|
|
8703
|
+
};
|
|
8704
|
+
}
|
|
8705
|
+
return {
|
|
8706
|
+
status: "ok",
|
|
8707
|
+
serversMap: existingServers
|
|
8708
|
+
};
|
|
8709
|
+
}
|
|
8710
|
+
function getYamlServersMapForUninstall(root, serversKey) {
|
|
8711
|
+
const existingServers = root.get(serversKey, true);
|
|
8712
|
+
if (existingServers === undefined || existingServers === null || isScalar(existingServers) && existingServers.value === null) {
|
|
8713
|
+
return { status: "not_configured" };
|
|
8714
|
+
}
|
|
8715
|
+
if (!isMap(existingServers)) {
|
|
8716
|
+
return {
|
|
8717
|
+
status: "error",
|
|
8718
|
+
error: toYamlConfigShapeError(serversKey)
|
|
8719
|
+
};
|
|
8720
|
+
}
|
|
8721
|
+
return {
|
|
8722
|
+
status: "ok",
|
|
8723
|
+
serversMap: existingServers
|
|
8724
|
+
};
|
|
8725
|
+
}
|
|
8726
|
+
function renderYamlDocument(doc) {
|
|
8727
|
+
const rendered = doc.toString();
|
|
8728
|
+
return rendered.endsWith(`
|
|
8729
|
+
`) ? rendered : `${rendered}
|
|
8730
|
+
`;
|
|
8731
|
+
}
|
|
8732
|
+
function mergeYamlServerConfig(existingContent, serversKey, serverName, serverConfig) {
|
|
8733
|
+
const parsed = parseYamlConfigDocument(existingContent);
|
|
8734
|
+
if (parsed.status === "error") {
|
|
8735
|
+
return { status: "parse_error", error: parsed.error };
|
|
8736
|
+
}
|
|
8737
|
+
const serversResult = ensureYamlServersMapForSetup(parsed.root, serversKey);
|
|
8738
|
+
if (serversResult.status !== "ok") {
|
|
8739
|
+
return {
|
|
8740
|
+
status: "parse_error",
|
|
8741
|
+
error: serversResult.status === "error" ? serversResult.error : toYamlConfigShapeError(serversKey)
|
|
8742
|
+
};
|
|
8743
|
+
}
|
|
8744
|
+
const { serversMap } = serversResult;
|
|
8745
|
+
const matchingKeys = getYamlMatchingServerKeys(serversMap, serverName);
|
|
8746
|
+
if (matchingKeys.length === 1 && matchingKeys[0] === serverName) {
|
|
8747
|
+
const existingValue = yamlNodeToJsValue(serversMap.get(serverName, true));
|
|
8748
|
+
if (isEquivalentConfiguredValue(existingValue, serverConfig)) {
|
|
8749
|
+
return { status: "already_configured" };
|
|
8750
|
+
}
|
|
8751
|
+
}
|
|
8752
|
+
for (const key of matchingKeys) {
|
|
8753
|
+
serversMap.delete(key);
|
|
8754
|
+
}
|
|
8755
|
+
const hadExisting = matchingKeys.length > 0;
|
|
8756
|
+
serversMap.set(serverName, serverConfig);
|
|
8757
|
+
return {
|
|
8758
|
+
status: hadExisting ? "updated" : "added",
|
|
8759
|
+
content: renderYamlDocument(parsed.doc)
|
|
8760
|
+
};
|
|
8761
|
+
}
|
|
8762
|
+
function removeYamlServerConfig(existingContent, serversKey, serverName) {
|
|
8763
|
+
const parsed = parseYamlConfigDocument(existingContent);
|
|
8764
|
+
if (parsed.status === "error") {
|
|
8765
|
+
return { status: "parse_error", error: parsed.error };
|
|
8766
|
+
}
|
|
8767
|
+
const serversResult = getYamlServersMapForUninstall(parsed.root, serversKey);
|
|
8768
|
+
if (serversResult.status === "not_configured") {
|
|
8769
|
+
return { status: "not_configured" };
|
|
8770
|
+
}
|
|
8771
|
+
if (serversResult.status === "error") {
|
|
8772
|
+
return {
|
|
8773
|
+
status: "parse_error",
|
|
8774
|
+
error: serversResult.error
|
|
8775
|
+
};
|
|
8776
|
+
}
|
|
8777
|
+
const matchingKeys = getYamlMatchingServerKeys(serversResult.serversMap, serverName);
|
|
8778
|
+
if (matchingKeys.length === 0) {
|
|
8779
|
+
return { status: "not_configured" };
|
|
8780
|
+
}
|
|
8781
|
+
for (const key of matchingKeys) {
|
|
8782
|
+
serversResult.serversMap.delete(key);
|
|
8783
|
+
}
|
|
8784
|
+
return {
|
|
8785
|
+
status: "removed",
|
|
8786
|
+
content: renderYamlDocument(parsed.doc)
|
|
8787
|
+
};
|
|
8788
|
+
}
|
|
8789
|
+
function parseConfigObjectForFormat(content, format = "json") {
|
|
8790
|
+
if (format === "yaml") {
|
|
8791
|
+
return parseYamlConfigObject(content);
|
|
8792
|
+
}
|
|
8793
|
+
const parsed = parseConfigObject(content);
|
|
8794
|
+
if (parsed.format === "invalid") {
|
|
8795
|
+
return { error: parsed.error };
|
|
8796
|
+
}
|
|
8797
|
+
return { value: parsed.value };
|
|
8798
|
+
}
|
|
8799
|
+
function renderConfigObjectForFormat(config, format = "json") {
|
|
8800
|
+
if (format === "yaml") {
|
|
8801
|
+
const rendered = stringifyYaml(config);
|
|
8802
|
+
return rendered.endsWith(`
|
|
8803
|
+
`) ? rendered : `${rendered}
|
|
8804
|
+
`;
|
|
8805
|
+
}
|
|
8806
|
+
return `${JSON.stringify(config, null, 2)}
|
|
8807
|
+
`;
|
|
8808
|
+
}
|
|
8459
8809
|
function isPlainObject(value) {
|
|
8460
8810
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8461
8811
|
}
|
|
@@ -8563,9 +8913,12 @@ function getMatchingServerKeys(servers, serverName) {
|
|
|
8563
8913
|
const normalizedTarget = serverName.toLowerCase();
|
|
8564
8914
|
return Object.keys(servers).filter((key) => key.toLowerCase() === normalizedTarget);
|
|
8565
8915
|
}
|
|
8566
|
-
function mergeServerConfig(existingContent, serversKey, serverName, serverConfig) {
|
|
8567
|
-
|
|
8568
|
-
|
|
8916
|
+
function mergeServerConfig(existingContent, serversKey, serverName, serverConfig, format = "json") {
|
|
8917
|
+
if (format === "yaml") {
|
|
8918
|
+
return mergeYamlServerConfig(existingContent, serversKey, serverName, serverConfig);
|
|
8919
|
+
}
|
|
8920
|
+
const parsedConfig = parseConfigObjectForFormat(existingContent, format);
|
|
8921
|
+
if ("error" in parsedConfig) {
|
|
8569
8922
|
return {
|
|
8570
8923
|
status: "parse_error",
|
|
8571
8924
|
error: parsedConfig.error
|
|
@@ -8594,13 +8947,15 @@ function mergeServerConfig(existingContent, serversKey, serverName, serverConfig
|
|
|
8594
8947
|
serversObj[serverName] = serverConfig;
|
|
8595
8948
|
return {
|
|
8596
8949
|
status: hadExisting ? "updated" : "added",
|
|
8597
|
-
content:
|
|
8598
|
-
`
|
|
8950
|
+
content: renderConfigObjectForFormat(config, format)
|
|
8599
8951
|
};
|
|
8600
8952
|
}
|
|
8601
|
-
function removeServerConfig(existingContent, serversKey, serverName) {
|
|
8602
|
-
|
|
8603
|
-
|
|
8953
|
+
function removeServerConfig(existingContent, serversKey, serverName, format = "json") {
|
|
8954
|
+
if (format === "yaml") {
|
|
8955
|
+
return removeYamlServerConfig(existingContent, serversKey, serverName);
|
|
8956
|
+
}
|
|
8957
|
+
const parsedConfig = parseConfigObjectForFormat(existingContent, format);
|
|
8958
|
+
if ("error" in parsedConfig) {
|
|
8604
8959
|
return {
|
|
8605
8960
|
status: "parse_error",
|
|
8606
8961
|
error: parsedConfig.error
|
|
@@ -8627,24 +8982,9 @@ function removeServerConfig(existingContent, serversKey, serverName) {
|
|
|
8627
8982
|
}
|
|
8628
8983
|
return {
|
|
8629
8984
|
status: "removed",
|
|
8630
|
-
content:
|
|
8631
|
-
`
|
|
8985
|
+
content: renderConfigObjectForFormat(config, format)
|
|
8632
8986
|
};
|
|
8633
8987
|
}
|
|
8634
|
-
function formatSetupPreview(config) {
|
|
8635
|
-
if (config.method === "composite") {
|
|
8636
|
-
return config.steps.map((step) => formatSetupPreview(step)).join(`
|
|
8637
|
-
`);
|
|
8638
|
-
}
|
|
8639
|
-
if (config.method === "cli") {
|
|
8640
|
-
return config.commands.map((cmd) => `Will run: ${cmd.command} ${cmd.args.join(" ")}`).join(`
|
|
8641
|
-
`);
|
|
8642
|
-
}
|
|
8643
|
-
const snippet = JSON.stringify({ [config.serverName]: config.serverConfig }, null, 2);
|
|
8644
|
-
return `Will add to ${config.configPath}:
|
|
8645
|
-
|
|
8646
|
-
${snippet}`;
|
|
8647
|
-
}
|
|
8648
8988
|
function formatUninstallPreview(config) {
|
|
8649
8989
|
if (config.method === "composite") {
|
|
8650
8990
|
return config.steps.map(({ step }) => formatUninstallPreview(step)).join(`
|
|
@@ -8659,8 +8999,8 @@ function formatUninstallPreview(config) {
|
|
|
8659
8999
|
async function isAlreadyConfigured(config, fs) {
|
|
8660
9000
|
try {
|
|
8661
9001
|
const content = await fs.readFile(config.configPath);
|
|
8662
|
-
const parsedConfig =
|
|
8663
|
-
if (
|
|
9002
|
+
const parsedConfig = parseConfigObjectForFormat(content, config.format);
|
|
9003
|
+
if ("error" in parsedConfig) {
|
|
8664
9004
|
return false;
|
|
8665
9005
|
}
|
|
8666
9006
|
const parsed = parsedConfig.value;
|
|
@@ -8681,21 +9021,21 @@ async function isAlreadyConfigured(config, fs) {
|
|
|
8681
9021
|
async function getConfigUninstallCheckStatus(config, fs) {
|
|
8682
9022
|
try {
|
|
8683
9023
|
const content = await fs.readFile(config.configPath);
|
|
8684
|
-
const parsedConfig =
|
|
8685
|
-
if (
|
|
9024
|
+
const parsedConfig = parseConfigObjectForFormat(content, config.format);
|
|
9025
|
+
if ("error" in parsedConfig) {
|
|
8686
9026
|
return {
|
|
8687
9027
|
status: "failed",
|
|
8688
9028
|
message: `Cannot parse ${config.configPath}: ${parsedConfig.error}. File left unchanged.`
|
|
8689
9029
|
};
|
|
8690
9030
|
}
|
|
8691
9031
|
const servers = parsedConfig.value[config.serversKey];
|
|
8692
|
-
if (servers === undefined) {
|
|
9032
|
+
if (servers === undefined || servers === null) {
|
|
8693
9033
|
return { status: "not_configured" };
|
|
8694
9034
|
}
|
|
8695
9035
|
if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
|
|
8696
9036
|
return {
|
|
8697
9037
|
status: "failed",
|
|
8698
|
-
message: `Cannot parse ${config.configPath}: "${config.serversKey}" is not a JSON object. File left unchanged.`
|
|
9038
|
+
message: `Cannot parse ${config.configPath}: "${config.serversKey}" is not a ${config.format === "yaml" ? "YAML" : "JSON"} object. File left unchanged.`
|
|
8699
9039
|
};
|
|
8700
9040
|
}
|
|
8701
9041
|
const hasEntry = getMatchingServerKeys(servers, config.serverName).length > 0;
|
|
@@ -8954,7 +9294,7 @@ async function executeConfigFileSetup(setup, fs) {
|
|
|
8954
9294
|
};
|
|
8955
9295
|
}
|
|
8956
9296
|
}
|
|
8957
|
-
const result = mergeServerConfig(existingContent, setup.serversKey, setup.serverName, setup.serverConfig);
|
|
9297
|
+
const result = mergeServerConfig(existingContent, setup.serversKey, setup.serverName, setup.serverConfig, setup.format);
|
|
8958
9298
|
if (result.status === "already_configured") {
|
|
8959
9299
|
return {
|
|
8960
9300
|
status: "already_configured",
|
|
@@ -9019,7 +9359,7 @@ async function executeConfigFileUninstall(setup, fs) {
|
|
|
9019
9359
|
message: `Cannot read ${setup.configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
9020
9360
|
};
|
|
9021
9361
|
}
|
|
9022
|
-
const result = removeServerConfig(existingContent, setup.serversKey, setup.serverName);
|
|
9362
|
+
const result = removeServerConfig(existingContent, setup.serversKey, setup.serverName, setup.format);
|
|
9023
9363
|
if (result.status === "not_configured") {
|
|
9024
9364
|
return {
|
|
9025
9365
|
status: "not_configured",
|
|
@@ -9107,6 +9447,16 @@ function getPiAgentDir(fs) {
|
|
|
9107
9447
|
function getPiMcpConfigPath(fs) {
|
|
9108
9448
|
return fs.joinPath(getPiAgentDir(fs), "mcp.json");
|
|
9109
9449
|
}
|
|
9450
|
+
function getHermesHomeDir(fs) {
|
|
9451
|
+
const configuredDir = process.env.HERMES_HOME?.trim();
|
|
9452
|
+
if (configuredDir) {
|
|
9453
|
+
return expandHomePath(fs, configuredDir);
|
|
9454
|
+
}
|
|
9455
|
+
return fs.joinPath(fs.getHomeDir(), ".hermes");
|
|
9456
|
+
}
|
|
9457
|
+
function getHermesConfigPath(fs) {
|
|
9458
|
+
return fs.joinPath(getHermesHomeDir(fs), "config.yaml");
|
|
9459
|
+
}
|
|
9110
9460
|
function getOpenCodeDesktopDetectPaths(fs) {
|
|
9111
9461
|
const userDataRoot = getUserDataRoot(fs);
|
|
9112
9462
|
return [
|
|
@@ -9501,6 +9851,25 @@ var openCode = {
|
|
|
9501
9851
|
}
|
|
9502
9852
|
})
|
|
9503
9853
|
};
|
|
9854
|
+
var hermesAgent = {
|
|
9855
|
+
name: "Hermes Agent",
|
|
9856
|
+
id: "hermes-agent",
|
|
9857
|
+
detectionMethod: "hybrid",
|
|
9858
|
+
setupMethod: "config-file",
|
|
9859
|
+
detectPaths: (fs) => [getHermesHomeDir(fs)],
|
|
9860
|
+
detectBinary: async (exec) => isExecutableAvailable(exec, "hermes-agent"),
|
|
9861
|
+
getSetupConfig: (fs) => ({
|
|
9862
|
+
method: "config-file",
|
|
9863
|
+
format: "yaml",
|
|
9864
|
+
configPath: getHermesConfigPath(fs),
|
|
9865
|
+
serversKey: "mcp_servers",
|
|
9866
|
+
serverName: GITHITS_SERVER_NAME,
|
|
9867
|
+
serverConfig: {
|
|
9868
|
+
command: GITHITS_MCP_COMMAND,
|
|
9869
|
+
args: [...GITHITS_MCP_ARGS]
|
|
9870
|
+
}
|
|
9871
|
+
})
|
|
9872
|
+
};
|
|
9504
9873
|
var agentDefinitions = [
|
|
9505
9874
|
claudeCode,
|
|
9506
9875
|
cursor,
|
|
@@ -9512,91 +9881,108 @@ var agentDefinitions = [
|
|
|
9512
9881
|
pi,
|
|
9513
9882
|
geminiCli,
|
|
9514
9883
|
googleAntigravity,
|
|
9515
|
-
openCode
|
|
9884
|
+
openCode,
|
|
9885
|
+
hermesAgent
|
|
9516
9886
|
];
|
|
9517
|
-
async function
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9887
|
+
async function scanSingleAgent(agent, fs, execService) {
|
|
9888
|
+
let detected = false;
|
|
9889
|
+
let setupContext;
|
|
9890
|
+
if (agent.detectCommand) {
|
|
9891
|
+
try {
|
|
9892
|
+
const resolvedCommand = await agent.detectCommand(execService, fs);
|
|
9893
|
+
if (resolvedCommand) {
|
|
9894
|
+
detected = true;
|
|
9895
|
+
setupContext = { command: resolvedCommand.command };
|
|
9896
|
+
}
|
|
9897
|
+
} catch {
|
|
9898
|
+
detected = false;
|
|
9899
|
+
}
|
|
9900
|
+
} else if (agent.detectionMethod === "binary" && agent.detectBinary) {
|
|
9901
|
+
try {
|
|
9902
|
+
detected = await agent.detectBinary(execService);
|
|
9903
|
+
} catch {
|
|
9904
|
+
detected = false;
|
|
9905
|
+
}
|
|
9906
|
+
} else if (agent.detectionMethod === "path" && agent.detectPaths) {
|
|
9907
|
+
const paths = agent.detectPaths(fs);
|
|
9908
|
+
for (const path of paths) {
|
|
9909
|
+
if (await fs.isDirectory(path)) {
|
|
9910
|
+
detected = true;
|
|
9911
|
+
break;
|
|
9535
9912
|
}
|
|
9536
|
-
}
|
|
9913
|
+
}
|
|
9914
|
+
} else if (agent.detectionMethod === "hybrid") {
|
|
9915
|
+
let binaryDetected = false;
|
|
9916
|
+
let pathDetected = false;
|
|
9917
|
+
if (agent.detectBinary) {
|
|
9537
9918
|
try {
|
|
9538
|
-
|
|
9919
|
+
binaryDetected = await agent.detectBinary(execService);
|
|
9539
9920
|
} catch {
|
|
9540
|
-
|
|
9921
|
+
binaryDetected = false;
|
|
9541
9922
|
}
|
|
9542
|
-
}
|
|
9923
|
+
}
|
|
9924
|
+
if (!binaryDetected && agent.detectPaths) {
|
|
9543
9925
|
const paths = agent.detectPaths(fs);
|
|
9544
9926
|
for (const path of paths) {
|
|
9545
9927
|
if (await fs.isDirectory(path)) {
|
|
9546
|
-
|
|
9928
|
+
pathDetected = true;
|
|
9547
9929
|
break;
|
|
9548
9930
|
}
|
|
9549
9931
|
}
|
|
9550
|
-
} else if (agent.detectionMethod === "hybrid") {
|
|
9551
|
-
let binaryDetected = false;
|
|
9552
|
-
let pathDetected = false;
|
|
9553
|
-
if (agent.detectBinary) {
|
|
9554
|
-
try {
|
|
9555
|
-
binaryDetected = await agent.detectBinary(execService);
|
|
9556
|
-
} catch {
|
|
9557
|
-
binaryDetected = false;
|
|
9558
|
-
}
|
|
9559
|
-
}
|
|
9560
|
-
if (!binaryDetected && agent.detectPaths) {
|
|
9561
|
-
const paths = agent.detectPaths(fs);
|
|
9562
|
-
for (const path of paths) {
|
|
9563
|
-
if (await fs.isDirectory(path)) {
|
|
9564
|
-
pathDetected = true;
|
|
9565
|
-
break;
|
|
9566
|
-
}
|
|
9567
|
-
}
|
|
9568
|
-
}
|
|
9569
|
-
detected = binaryDetected || pathDetected;
|
|
9570
9932
|
}
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9933
|
+
detected = binaryDetected || pathDetected;
|
|
9934
|
+
}
|
|
9935
|
+
if (!detected) {
|
|
9936
|
+
return { status: "not_detected", agent };
|
|
9937
|
+
}
|
|
9938
|
+
const config = agent.getSetupConfig(fs, setupContext);
|
|
9939
|
+
const scannedAgent = {
|
|
9940
|
+
...agent,
|
|
9941
|
+
resolvedSetupConfig: config,
|
|
9942
|
+
resolvedSetupContext: setupContext
|
|
9943
|
+
};
|
|
9944
|
+
if (agent.id === "gemini-cli" && config.method === "cli") {
|
|
9945
|
+
if (!config.checkCommand) {
|
|
9946
|
+
return { status: "needs_setup", agent: scannedAgent };
|
|
9947
|
+
}
|
|
9948
|
+
const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
|
|
9949
|
+
let configured = checkStatus === "configured";
|
|
9950
|
+
if (!configured && checkStatus === "probe_failed") {
|
|
9951
|
+
configured = await isGeminiExtensionInstalledFromFilesystem(fs);
|
|
9574
9952
|
}
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
resolvedSetupConfig: config,
|
|
9579
|
-
resolvedSetupContext: setupContext
|
|
9953
|
+
return {
|
|
9954
|
+
status: configured ? "already_configured" : "needs_setup",
|
|
9955
|
+
agent: scannedAgent
|
|
9580
9956
|
};
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9957
|
+
}
|
|
9958
|
+
if (await isSetupAlreadyConfigured(config, fs, execService)) {
|
|
9959
|
+
return { status: "already_configured", agent: scannedAgent };
|
|
9960
|
+
}
|
|
9961
|
+
return { status: "needs_setup", agent: scannedAgent };
|
|
9962
|
+
}
|
|
9963
|
+
async function scanAgents(definitions, fs, execService, options = {}) {
|
|
9964
|
+
const result = {
|
|
9965
|
+
needsSetup: [],
|
|
9966
|
+
alreadyConfigured: [],
|
|
9967
|
+
notDetected: []
|
|
9968
|
+
};
|
|
9969
|
+
let completed = 0;
|
|
9970
|
+
const outcomes = await Promise.all(definitions.map((agent) => scanSingleAgent(agent, fs, execService).then((outcome) => {
|
|
9971
|
+
completed += 1;
|
|
9972
|
+
options.onProgress?.({
|
|
9973
|
+
completed,
|
|
9974
|
+
total: definitions.length,
|
|
9975
|
+
agent: outcome.agent
|
|
9976
|
+
});
|
|
9977
|
+
return outcome;
|
|
9978
|
+
})));
|
|
9979
|
+
for (const outcome of outcomes) {
|
|
9980
|
+
if (outcome.status === "already_configured") {
|
|
9981
|
+
result.alreadyConfigured.push(outcome.agent);
|
|
9982
|
+
} else if (outcome.status === "needs_setup") {
|
|
9983
|
+
result.needsSetup.push(outcome.agent);
|
|
9598
9984
|
} else {
|
|
9599
|
-
result.
|
|
9985
|
+
result.notDetected.push(outcome.agent);
|
|
9600
9986
|
}
|
|
9601
9987
|
}
|
|
9602
9988
|
return result;
|
|
@@ -9606,6 +9992,17 @@ class ExitPromptError extends Error {
|
|
|
9606
9992
|
name = "ExitPromptError";
|
|
9607
9993
|
}
|
|
9608
9994
|
// src/commands/init/init.ts
|
|
9995
|
+
function createInitLoginOutput() {
|
|
9996
|
+
return {
|
|
9997
|
+
write: (message) => {
|
|
9998
|
+
const lines = message.replace(/\n$/, "").split(`
|
|
9999
|
+
`);
|
|
10000
|
+
for (const line of lines) {
|
|
10001
|
+
console.log(line.length > 0 ? ` ${line}` : "");
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
10004
|
+
};
|
|
10005
|
+
}
|
|
9609
10006
|
function getResolvedSetupConfig(agent, fileSystemService) {
|
|
9610
10007
|
return agent.resolvedSetupConfig ?? agent.getSetupConfig(fileSystemService, agent.resolvedSetupContext);
|
|
9611
10008
|
}
|
|
@@ -9620,62 +10017,859 @@ function getPiConfigFileUninstall(agent, fileSystemService) {
|
|
|
9620
10017
|
const configStep = uninstallConfig.steps.map(({ step }) => step).find((step) => step.method === "config-file");
|
|
9621
10018
|
return configStep ?? null;
|
|
9622
10019
|
}
|
|
10020
|
+
function formatCommand(command, useColors) {
|
|
10021
|
+
return colorizeBrand(command, "secondary", useColors, { bold: true });
|
|
10022
|
+
}
|
|
10023
|
+
var AGENT_SAFE_CLI = "npx -y githits@latest";
|
|
10024
|
+
var AGENT_DETECT_COMMAND = `${AGENT_SAFE_CLI} init --detect-agents`;
|
|
10025
|
+
var AGENT_INSTALL_COMMAND = `${AGENT_SAFE_CLI} init --install-agents`;
|
|
10026
|
+
var AGENT_LOGIN_COMMAND = `${AGENT_SAFE_CLI} login`;
|
|
10027
|
+
var AGENT_LOGIN_NO_BROWSER_COMMAND = `${AGENT_SAFE_CLI} login --no-browser`;
|
|
10028
|
+
var AGENTIC_INIT_YES_WARNING = "Do not run `githits init -y` or `githits init --yes` unless the user explicitly asks to configure every detected tool.";
|
|
10029
|
+
var AGENTIC_INIT_VERIFY_INSTRUCTION = "After a successful --install-agents run, verify with --detect-agents --json instead of running init again.";
|
|
10030
|
+
var AGENTIC_INIT_JSON_VERIFY_INSTRUCTION = "Do not run init again after a successful --install-agents run; verify with --detect-agents --json instead.";
|
|
9623
10031
|
function printReadyNextSteps() {
|
|
9624
|
-
console.log("
|
|
10032
|
+
console.log(" GitHits is now connected to your coding agents.");
|
|
10033
|
+
console.log();
|
|
10034
|
+
console.log(" Here are some examples of the new abilities that your agent just got:");
|
|
10035
|
+
console.log();
|
|
10036
|
+
console.log(" • Find usage examples");
|
|
10037
|
+
console.log(" -> “Find a real example of using Azure Speech SDK TranscribeDefinition”");
|
|
10038
|
+
console.log();
|
|
10039
|
+
console.log(" • Search, grep, list files, and read exact lines in any repo or package to gather information");
|
|
10040
|
+
console.log(" -> “How does Next.js implement route prefetching internally?”");
|
|
10041
|
+
console.log();
|
|
10042
|
+
console.log(" • Inspect dependency versions, changelogs, and upgrade changes");
|
|
10043
|
+
console.log(" -> “What changed between pydantic-ai 1.95 and 1.99?”");
|
|
9625
10044
|
console.log();
|
|
9626
|
-
console.log("
|
|
9627
|
-
console.log(' npx githits@latest example "How do I use useEffect cleanup?"');
|
|
10045
|
+
console.log(" Open a new coding agent session and try out one of the above.");
|
|
9628
10046
|
console.log();
|
|
9629
|
-
console.log(
|
|
9630
|
-
console.log(
|
|
10047
|
+
console.log(' In your normal workflow, your agent will call GitHits automatically depending on the task, but you can prompt it to use GitHits explicitly by adding "use GitHits".');
|
|
10048
|
+
console.log();
|
|
10049
|
+
console.log(" See docs for more use cases and trigger guides: https://docs.githits.com");
|
|
9631
10050
|
}
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
}
|
|
9637
|
-
if (postCheck.needsSetup.some((a) => a.id === agent.id)) {
|
|
9638
|
-
return {
|
|
9639
|
-
ok: false,
|
|
9640
|
-
message: `${agent.name} verification failed: not configured after setup.`
|
|
9641
|
-
};
|
|
9642
|
-
}
|
|
9643
|
-
return {
|
|
9644
|
-
ok: false,
|
|
9645
|
-
message: `${agent.name} verification failed: agent not detected after setup.`
|
|
9646
|
-
};
|
|
10051
|
+
function printAuthRequiredNextSteps(useColors) {
|
|
10052
|
+
console.log(" GitHits MCP is configured, but sign-in is still needed.");
|
|
10053
|
+
console.log();
|
|
10054
|
+
console.log(" Sign in when you're ready:");
|
|
10055
|
+
console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
|
|
9647
10056
|
}
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
}
|
|
9653
|
-
if (postCheck.notDetected.some((a) => a.id === agent.id)) {
|
|
9654
|
-
return {
|
|
9655
|
-
ok: false,
|
|
9656
|
-
message: `${agent.name} verification failed: agent was not detected after uninstall, so removal could not be confirmed.`
|
|
9657
|
-
};
|
|
9658
|
-
}
|
|
9659
|
-
return {
|
|
9660
|
-
ok: false,
|
|
9661
|
-
message: `${agent.name} verification failed: still configured after uninstall.`
|
|
9662
|
-
};
|
|
10057
|
+
function printAuthNotCheckedNextSteps(useColors) {
|
|
10058
|
+
console.log(" GitHits MCP is configured. Sign-in was not checked.");
|
|
10059
|
+
console.log();
|
|
10060
|
+
console.log(" If your agent asks you to sign in, run:");
|
|
10061
|
+
console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
|
|
9663
10062
|
}
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
10063
|
+
function printAgenticLoginInstructions(useColors) {
|
|
10064
|
+
console.log(" Authentication is still required before agents can use GitHits.");
|
|
10065
|
+
console.log();
|
|
10066
|
+
console.log(" Agent instruction:");
|
|
10067
|
+
console.log();
|
|
10068
|
+
console.log(" Ask the user:");
|
|
10069
|
+
console.log(` "GitHits needs to open a browser sign-in page. The sign-in happens in your browser, not in this chat or terminal. I will not see or handle your credentials. Should I run \`${AGENT_LOGIN_COMMAND}\` now?"`);
|
|
10070
|
+
console.log();
|
|
10071
|
+
console.log(" If the user approves, run:");
|
|
10072
|
+
console.log(` ${formatCommand(AGENT_LOGIN_COMMAND, useColors)}`);
|
|
10073
|
+
console.log();
|
|
10074
|
+
console.log(" If the browser cannot open, run:");
|
|
10075
|
+
console.log(` ${formatCommand(AGENT_LOGIN_NO_BROWSER_COMMAND, useColors)}`);
|
|
10076
|
+
}
|
|
10077
|
+
function printAgenticAlreadyAuthenticated() {
|
|
10078
|
+
console.log(" GitHits MCP is installed and you are already signed in.");
|
|
10079
|
+
console.log();
|
|
10080
|
+
console.log(" Open a new coding agent session so it reloads MCP config.");
|
|
10081
|
+
}
|
|
10082
|
+
function printAgenticAuthNotChecked(useColors) {
|
|
10083
|
+
console.log(" GitHits MCP is installed. Sign-in status was not checked.");
|
|
10084
|
+
console.log();
|
|
10085
|
+
console.log(" If the user is not already signed in, ask before running:");
|
|
10086
|
+
console.log(` ${formatCommand(AGENT_LOGIN_COMMAND, useColors)}`);
|
|
10087
|
+
}
|
|
10088
|
+
function printNonInteractiveInitGuidance(useColors) {
|
|
10089
|
+
console.log(" This setup is interactive. Because this session is non-interactive, no changes were made.");
|
|
10090
|
+
console.log();
|
|
10091
|
+
console.log(" If you are an AI coding agent helping a user install GitHits:");
|
|
10092
|
+
console.log();
|
|
10093
|
+
console.log(" 1. Run:");
|
|
10094
|
+
console.log(` ${formatCommand(AGENT_DETECT_COMMAND, useColors)}`);
|
|
10095
|
+
console.log();
|
|
10096
|
+
console.log(" 2. Show the detected tools to the user.");
|
|
10097
|
+
console.log();
|
|
10098
|
+
console.log(" 3. Ask which tools should receive the GitHits MCP server.");
|
|
10099
|
+
console.log();
|
|
10100
|
+
console.log(" 4. Only after approval, run:");
|
|
10101
|
+
console.log(` ${formatCommand(`${AGENT_INSTALL_COMMAND} <ids>`, useColors)}`);
|
|
10102
|
+
console.log();
|
|
10103
|
+
console.log(` ${AGENTIC_INIT_YES_WARNING}`);
|
|
10104
|
+
console.log(` ${AGENTIC_INIT_VERIFY_INSTRUCTION}`);
|
|
10105
|
+
}
|
|
10106
|
+
function printNonInteractiveYesRejected(useColors) {
|
|
10107
|
+
console.error("Non-interactive `githits init --yes` is not supported because it can configure tools without explicit per-tool approval.");
|
|
10108
|
+
console.error();
|
|
10109
|
+
console.error("Use the agent-safe staged flow instead:");
|
|
10110
|
+
console.error(` ${formatCommand(AGENT_DETECT_COMMAND, useColors)}`);
|
|
10111
|
+
console.error(` ${formatCommand(`${AGENT_INSTALL_COMMAND} <ids>`, useColors)}`);
|
|
10112
|
+
process.exitCode = 1;
|
|
10113
|
+
}
|
|
10114
|
+
var GITHITS_ASCII_LOGO = String.raw`
|
|
10115
|
+
____ _ _ _ _ _ _
|
|
10116
|
+
/ ___(_) |_| | | (_) |_ ___
|
|
10117
|
+
| | _| | __| |_| | | __/ __|
|
|
10118
|
+
| |_| | | |_| _ | | |_\__ \
|
|
10119
|
+
\____|_|\__|_| |_|_|\__|___/
|
|
10120
|
+
`;
|
|
10121
|
+
var LOGO_GRADIENT = [
|
|
10122
|
+
{
|
|
10123
|
+
until: 13,
|
|
10124
|
+
color: {
|
|
10125
|
+
hex: "#FF4FAE",
|
|
10126
|
+
rgb: [255, 79, 174],
|
|
10127
|
+
ansi256: 205,
|
|
10128
|
+
ansi16: "magenta"
|
|
10129
|
+
}
|
|
10130
|
+
},
|
|
10131
|
+
{
|
|
10132
|
+
until: 19,
|
|
10133
|
+
color: {
|
|
10134
|
+
hex: "#FF5D8E",
|
|
10135
|
+
rgb: [255, 93, 142],
|
|
10136
|
+
ansi256: 204,
|
|
10137
|
+
ansi16: "magenta"
|
|
10138
|
+
}
|
|
10139
|
+
},
|
|
10140
|
+
{
|
|
10141
|
+
until: 21,
|
|
10142
|
+
color: {
|
|
10143
|
+
hex: "#FF6B6F",
|
|
10144
|
+
rgb: [255, 107, 111],
|
|
10145
|
+
ansi256: 203,
|
|
10146
|
+
ansi16: "red"
|
|
10147
|
+
}
|
|
10148
|
+
},
|
|
10149
|
+
{
|
|
10150
|
+
until: 25,
|
|
10151
|
+
color: { hex: "#FF794F", rgb: [255, 121, 79], ansi256: 209, ansi16: "red" }
|
|
10152
|
+
},
|
|
10153
|
+
{
|
|
10154
|
+
until: 30,
|
|
10155
|
+
color: {
|
|
10156
|
+
hex: "#FF872F",
|
|
10157
|
+
rgb: [255, 135, 47],
|
|
10158
|
+
ansi256: 208,
|
|
10159
|
+
ansi16: "yellow"
|
|
10160
|
+
}
|
|
10161
|
+
}
|
|
10162
|
+
];
|
|
10163
|
+
function colorizeLogo(logo, useColors) {
|
|
10164
|
+
if (!useColors) {
|
|
10165
|
+
return logo;
|
|
10166
|
+
}
|
|
10167
|
+
return logo.split(`
|
|
10168
|
+
`).map((line) => {
|
|
10169
|
+
if (line.length === 0) {
|
|
10170
|
+
return line;
|
|
10171
|
+
}
|
|
10172
|
+
let cursor2 = 0;
|
|
10173
|
+
let colored = "";
|
|
10174
|
+
for (const { until, color } of LOGO_GRADIENT) {
|
|
10175
|
+
if (cursor2 >= line.length) {
|
|
10176
|
+
break;
|
|
10177
|
+
}
|
|
10178
|
+
colored += colorizeTerminal(line.slice(cursor2, until), color, useColors);
|
|
10179
|
+
cursor2 = until;
|
|
10180
|
+
}
|
|
10181
|
+
return cursor2 < line.length ? colored + line.slice(cursor2) : colored;
|
|
10182
|
+
}).join(`
|
|
10183
|
+
`);
|
|
10184
|
+
}
|
|
10185
|
+
var INIT_INTENT_CHOICES = [
|
|
10186
|
+
{
|
|
10187
|
+
name: "Connect GitHits to my agent (Recommended)",
|
|
10188
|
+
value: "mcp",
|
|
10189
|
+
description: "Install the local GitHits MCP server for your coding agents. Allows agents to seamlessly use GitHits."
|
|
10190
|
+
},
|
|
10191
|
+
{
|
|
10192
|
+
name: "Use Agent Skills instead",
|
|
10193
|
+
value: "skills",
|
|
10194
|
+
description: "Use Skills instead of the local MCP server."
|
|
10195
|
+
},
|
|
10196
|
+
{
|
|
10197
|
+
name: "Exit",
|
|
10198
|
+
value: "later",
|
|
10199
|
+
description: "Leave setup without making changes."
|
|
10200
|
+
}
|
|
10201
|
+
];
|
|
10202
|
+
var AUTH_RECOVERY_CHOICES = [
|
|
10203
|
+
{ name: "Retry sign in", value: "retry" },
|
|
10204
|
+
{
|
|
10205
|
+
name: "Configure MCP without signing in",
|
|
10206
|
+
value: "continue_without_auth",
|
|
10207
|
+
description: "Your agent will ask you to sign in before using GitHits."
|
|
10208
|
+
},
|
|
10209
|
+
{ name: "Cancel setup", value: "cancel" }
|
|
10210
|
+
];
|
|
10211
|
+
var AUTH_START_CHOICES = [
|
|
10212
|
+
{
|
|
10213
|
+
name: "Sign in now",
|
|
10214
|
+
value: "sign_in",
|
|
10215
|
+
description: "Open your browser and connect this CLI to GitHits."
|
|
10216
|
+
},
|
|
10217
|
+
{
|
|
10218
|
+
name: "Skip for now",
|
|
10219
|
+
value: "skip",
|
|
10220
|
+
description: "Finish MCP setup and sign in later with `githits login`."
|
|
10221
|
+
},
|
|
10222
|
+
{ name: "Cancel setup", value: "cancel" }
|
|
10223
|
+
];
|
|
10224
|
+
function printSection(index, title, useColors) {
|
|
10225
|
+
console.log();
|
|
10226
|
+
console.log(` ${colorizeBrand(`${index}. ${title}`, "primary", useColors, { bold: true })}`);
|
|
10227
|
+
console.log(` ${colorize("-".repeat(title.length + 3), "dim", useColors)}`);
|
|
10228
|
+
}
|
|
10229
|
+
function printTask(status, label, detail, useColors) {
|
|
10230
|
+
const suffix = detail ? ` ${colorize(detail, "dim", useColors)}` : "";
|
|
10231
|
+
if (status === "success") {
|
|
10232
|
+
console.log(` ${success(label, useColors)}${suffix}`);
|
|
10233
|
+
} else if (status === "failed") {
|
|
10234
|
+
console.log(` ${error(label, useColors)}${suffix}`);
|
|
10235
|
+
} else if (status === "warning") {
|
|
10236
|
+
console.log(` ${warning(label, useColors)}${suffix}`);
|
|
10237
|
+
} else {
|
|
10238
|
+
console.log(` ${colorize("○", "dim", useColors)} ${label}${suffix}`);
|
|
10239
|
+
}
|
|
10240
|
+
}
|
|
10241
|
+
function printInitIntro(useColors) {
|
|
10242
|
+
console.log(colorizeLogo(GITHITS_ASCII_LOGO, useColors));
|
|
10243
|
+
console.log(" Your agent can only read your local codebase.");
|
|
10244
|
+
console.log();
|
|
10245
|
+
console.log(" GitHits lets it navigate the open-source code your app depends on.");
|
|
10246
|
+
console.log();
|
|
10247
|
+
console.log(` ${colorizeBrand("With GitHits, your agent can:", "primary", useColors)}`);
|
|
10248
|
+
console.log(" • Find implementation examples from open-source code, issues, discussions, and pull requests");
|
|
10249
|
+
console.log(" • Search, grep, list files, and read exact lines in any repo or package");
|
|
10250
|
+
console.log(" • Inspect dependency internals, versions, changelogs, and upgrade changes");
|
|
10251
|
+
console.log(" • Access package documentation");
|
|
10252
|
+
console.log();
|
|
10253
|
+
console.log(" No cloning or local indexing required. GitHits handles everything automatically.");
|
|
10254
|
+
console.log();
|
|
10255
|
+
console.log(" Works with Cursor, Claude Code, Codex, OpenCode, Pi, VS Code, Windsurf, and more.");
|
|
10256
|
+
console.log();
|
|
10257
|
+
console.log(" More info: https://docs.githits.com");
|
|
10258
|
+
console.log();
|
|
10259
|
+
}
|
|
10260
|
+
function printSkillsInstructions(useColors) {
|
|
10261
|
+
console.log(`
|
|
10262
|
+
Install GitHits Agent Skills:`);
|
|
10263
|
+
console.log();
|
|
10264
|
+
console.log(` ${formatCommand("npx skills add githits-com/githits-cli", useColors)}`);
|
|
10265
|
+
console.log();
|
|
10266
|
+
console.log(" During setup, choose where you want to enable GitHits.");
|
|
10267
|
+
console.log();
|
|
10268
|
+
console.log(" IMPORTANT: Use either Agent Skills or the local MCP server in the same");
|
|
10269
|
+
console.log(" coding tool, not both.");
|
|
10270
|
+
console.log();
|
|
10271
|
+
console.log(" Then sign in so your agent can use GitHits:");
|
|
10272
|
+
console.log();
|
|
10273
|
+
console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
|
|
10274
|
+
console.log();
|
|
10275
|
+
}
|
|
10276
|
+
function startSafeInitScan(fileSystemService, execService, onProgress) {
|
|
10277
|
+
return scanAgents(agentDefinitions, fileSystemService, execService, {
|
|
10278
|
+
onProgress
|
|
10279
|
+
}).then((scan) => ({ ok: true, scan })).catch((error2) => ({
|
|
10280
|
+
ok: false,
|
|
10281
|
+
error: error2 instanceof Error ? error2 : new Error(String(error2))
|
|
10282
|
+
}));
|
|
10283
|
+
}
|
|
10284
|
+
function createScanProgressReporter(useColors) {
|
|
10285
|
+
if (!process.stdout.isTTY) {
|
|
10286
|
+
return { onProgress: () => {}, finish: () => {} };
|
|
10287
|
+
}
|
|
10288
|
+
let wrote = false;
|
|
10289
|
+
return {
|
|
10290
|
+
onProgress: (progress) => {
|
|
10291
|
+
const width = 20;
|
|
10292
|
+
const filled = Math.round(progress.completed / progress.total * width);
|
|
10293
|
+
const bar = `${colorizeBrand("#".repeat(filled), "primary", useColors)}${"-".repeat(width - filled)}`;
|
|
10294
|
+
const line = ` Scanning tools [${bar}] ${progress.completed}/${progress.total} ${progress.agent.name}`;
|
|
10295
|
+
process.stdout.write(`\r\x1B[2K${line}`);
|
|
10296
|
+
wrote = true;
|
|
10297
|
+
},
|
|
10298
|
+
finish: () => {
|
|
10299
|
+
if (wrote) {
|
|
10300
|
+
process.stdout.write("\r\x1B[2K");
|
|
10301
|
+
}
|
|
10302
|
+
}
|
|
10303
|
+
};
|
|
10304
|
+
}
|
|
10305
|
+
function createInstallTaskReporter(useColors) {
|
|
10306
|
+
if (!process.stdout.isTTY) {
|
|
10307
|
+
return {
|
|
10308
|
+
start: (label) => {
|
|
10309
|
+
printTask("skipped", label, "installing...", useColors);
|
|
10310
|
+
return () => {};
|
|
10311
|
+
}
|
|
10312
|
+
};
|
|
10313
|
+
}
|
|
10314
|
+
const frames = ["-", "\\", "|", "/"];
|
|
10315
|
+
return {
|
|
10316
|
+
start: (label) => {
|
|
10317
|
+
let frame = 0;
|
|
10318
|
+
const render = () => {
|
|
10319
|
+
const spinner = colorizeBrand(frames[frame % frames.length] ?? "-", "primary", useColors);
|
|
10320
|
+
frame += 1;
|
|
10321
|
+
process.stdout.write(`\r\x1B[2K ${spinner} ${label} installing...`);
|
|
10322
|
+
};
|
|
10323
|
+
render();
|
|
10324
|
+
const interval = setInterval(render, 80);
|
|
10325
|
+
return () => {
|
|
10326
|
+
clearInterval(interval);
|
|
10327
|
+
process.stdout.write("\r\x1B[2K");
|
|
10328
|
+
};
|
|
10329
|
+
}
|
|
10330
|
+
};
|
|
10331
|
+
}
|
|
10332
|
+
async function unwrapSafeScan(scanPromise) {
|
|
10333
|
+
const result = await scanPromise;
|
|
10334
|
+
if (!result.ok) {
|
|
10335
|
+
throw result.error;
|
|
10336
|
+
}
|
|
10337
|
+
return result.scan;
|
|
10338
|
+
}
|
|
10339
|
+
function formatAgentNames(agents) {
|
|
10340
|
+
if (agents.length === 0)
|
|
10341
|
+
return "none";
|
|
10342
|
+
if (agents.length === 1)
|
|
10343
|
+
return agents[0]?.name ?? "unknown";
|
|
10344
|
+
if (agents.length === 2) {
|
|
10345
|
+
return `${agents[0]?.name ?? "unknown"} and ${agents[1]?.name ?? "unknown"}`;
|
|
10346
|
+
}
|
|
10347
|
+
const names = agents.map((agent) => agent.name);
|
|
10348
|
+
return `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`;
|
|
10349
|
+
}
|
|
10350
|
+
function buildInitAgentChoices(scan) {
|
|
10351
|
+
return [
|
|
10352
|
+
...scan.needsSetup.map((agent) => ({
|
|
10353
|
+
name: `${agent.name} (detected)`,
|
|
10354
|
+
value: agent,
|
|
10355
|
+
checked: true
|
|
10356
|
+
})),
|
|
10357
|
+
...scan.alreadyConfigured.map((agent) => ({
|
|
10358
|
+
name: `${agent.name} (already configured)`,
|
|
10359
|
+
value: agent,
|
|
10360
|
+
disabled: "already configured"
|
|
10361
|
+
}))
|
|
10362
|
+
];
|
|
10363
|
+
}
|
|
10364
|
+
function printScanSummary(scan, useColors) {
|
|
10365
|
+
const detected = scan.needsSetup.length + scan.alreadyConfigured.length;
|
|
10366
|
+
for (const agent of scan.alreadyConfigured) {
|
|
10367
|
+
printTask("success", agent.name, "already configured", useColors);
|
|
10368
|
+
}
|
|
10369
|
+
for (const agent of scan.needsSetup) {
|
|
10370
|
+
printTask("warning", agent.name, "needs setup", useColors);
|
|
10371
|
+
}
|
|
10372
|
+
if (scan.notDetected.length > 0) {
|
|
10373
|
+
printTask("skipped", `${scan.notDetected.length} supported tool${scan.notDetected.length !== 1 ? "s" : ""} not found`, formatAgentNames(scan.notDetected), useColors);
|
|
10374
|
+
}
|
|
10375
|
+
if (detected > 0) {
|
|
10376
|
+
console.log();
|
|
10377
|
+
console.log(` Found ${detected} supported tool${detected !== 1 ? "s" : ""}.`);
|
|
10378
|
+
}
|
|
10379
|
+
}
|
|
10380
|
+
function buildStagedAgentEntries(scan) {
|
|
10381
|
+
const statuses = new Map;
|
|
10382
|
+
for (const agent of scan.needsSetup)
|
|
10383
|
+
statuses.set(agent.id, "needs_setup");
|
|
10384
|
+
for (const agent of scan.alreadyConfigured) {
|
|
10385
|
+
statuses.set(agent.id, "already_configured");
|
|
10386
|
+
}
|
|
10387
|
+
for (const agent of scan.notDetected)
|
|
10388
|
+
statuses.set(agent.id, "not_detected");
|
|
10389
|
+
return agentDefinitions.map((agent) => ({
|
|
10390
|
+
id: agent.id,
|
|
10391
|
+
name: agent.name,
|
|
10392
|
+
status: statuses.get(agent.id) ?? "not_detected"
|
|
10393
|
+
}));
|
|
10394
|
+
}
|
|
10395
|
+
function printAgenticDetectSummary(scan, useColors) {
|
|
10396
|
+
const entries = buildStagedAgentEntries(scan);
|
|
10397
|
+
const detected = entries.filter((entry) => entry.status !== "not_detected");
|
|
10398
|
+
const installable = entries.filter((entry) => entry.status === "needs_setup");
|
|
10399
|
+
const notDetected = entries.filter((entry) => entry.status === "not_detected");
|
|
10400
|
+
console.log("Detected supported tools:");
|
|
10401
|
+
console.log();
|
|
10402
|
+
if (detected.length === 0) {
|
|
10403
|
+
console.log(" None detected.");
|
|
10404
|
+
} else {
|
|
10405
|
+
console.log(" ID Tool Status");
|
|
10406
|
+
for (const entry of detected) {
|
|
10407
|
+
console.log(` ${entry.id.padEnd(18)} ${entry.name.padEnd(21)} ${entry.status.replace("_", " ")}`);
|
|
10408
|
+
}
|
|
10409
|
+
}
|
|
10410
|
+
console.log();
|
|
10411
|
+
console.log("Not detected:");
|
|
10412
|
+
console.log(` ${notDetected.length > 0 ? notDetected.map((entry) => entry.id).join(", ") : "none"}`);
|
|
10413
|
+
console.log();
|
|
10414
|
+
if (detected.length === 0) {
|
|
10415
|
+
console.log("No supported AI coding tools detected.");
|
|
10416
|
+
console.log();
|
|
10417
|
+
console.log("Next step for agents:");
|
|
10418
|
+
console.log(" Tell the user to install a supported coding tool, then run detection again.");
|
|
10419
|
+
return;
|
|
10420
|
+
}
|
|
10421
|
+
if (installable.length === 0) {
|
|
10422
|
+
console.log("No detected tools need setup.");
|
|
10423
|
+
console.log();
|
|
10424
|
+
console.log("Next step for agents:");
|
|
10425
|
+
console.log(" Tell the user that GitHits is already configured for detected tools.");
|
|
10426
|
+
console.log(` ${AGENTIC_INIT_YES_WARNING}`);
|
|
10427
|
+
console.log(" Do not run init again as a verification step.");
|
|
10428
|
+
return;
|
|
10429
|
+
}
|
|
10430
|
+
const installableIds = installable.map((entry) => entry.id);
|
|
10431
|
+
console.log("Next step for agents:");
|
|
10432
|
+
console.log();
|
|
10433
|
+
console.log(" Ask the user:");
|
|
10434
|
+
console.log(` "GitHits can be installed for ${installable.map((entry) => entry.name).join(", ")}. Which should I configure?"`);
|
|
10435
|
+
console.log();
|
|
10436
|
+
console.log(" If the user approves all detected tools needing setup, run:");
|
|
10437
|
+
console.log(` ${formatCommand(`${AGENT_INSTALL_COMMAND} ${installableIds.join(",")}`, useColors)}`);
|
|
10438
|
+
console.log();
|
|
10439
|
+
console.log(` ${AGENTIC_INIT_YES_WARNING}`);
|
|
10440
|
+
console.log(` ${AGENTIC_INIT_VERIFY_INSTRUCTION}`);
|
|
10441
|
+
}
|
|
10442
|
+
function printAgenticDetectJson(scan) {
|
|
10443
|
+
const entries = buildStagedAgentEntries(scan);
|
|
10444
|
+
const installableIds = entries.filter((entry) => entry.status === "needs_setup").map((entry) => entry.id);
|
|
10445
|
+
console.log(JSON.stringify({
|
|
10446
|
+
mode: "detect-agents",
|
|
10447
|
+
agents: entries,
|
|
10448
|
+
installableIds,
|
|
10449
|
+
suggestedCommand: installableIds.length > 0 ? `${AGENT_INSTALL_COMMAND} ${installableIds.join(",")}` : null,
|
|
10450
|
+
instructions: [
|
|
10451
|
+
"Show detected tools to the user.",
|
|
10452
|
+
"Ask which tools should receive the GitHits MCP server.",
|
|
10453
|
+
"Only run --install-agents with user-approved IDs.",
|
|
10454
|
+
AGENTIC_INIT_YES_WARNING,
|
|
10455
|
+
AGENTIC_INIT_JSON_VERIFY_INSTRUCTION
|
|
10456
|
+
]
|
|
10457
|
+
}, null, 2));
|
|
10458
|
+
}
|
|
10459
|
+
function getStagedModeCount(options) {
|
|
10460
|
+
return [
|
|
10461
|
+
options.detectAgents === true,
|
|
10462
|
+
options.installAgents !== undefined
|
|
10463
|
+
].filter(Boolean).length;
|
|
10464
|
+
}
|
|
10465
|
+
function failInitArgument(message, json) {
|
|
10466
|
+
if (json) {
|
|
10467
|
+
console.error(JSON.stringify({ error: message, code: "INVALID_ARGUMENT" }));
|
|
10468
|
+
} else {
|
|
10469
|
+
console.error(message);
|
|
10470
|
+
}
|
|
10471
|
+
process.exitCode = 1;
|
|
10472
|
+
}
|
|
10473
|
+
function validateInitModeOptions(options) {
|
|
10474
|
+
const stagedModeCount = getStagedModeCount(options);
|
|
10475
|
+
if (stagedModeCount > 1) {
|
|
10476
|
+
failInitArgument("Use only one staged init mode: --detect-agents or --install-agents.", options.json);
|
|
10477
|
+
return false;
|
|
10478
|
+
}
|
|
10479
|
+
if (options.yes && stagedModeCount > 0) {
|
|
10480
|
+
failInitArgument("--yes cannot be combined with --detect-agents or --install-agents.", options.json);
|
|
10481
|
+
return false;
|
|
10482
|
+
}
|
|
10483
|
+
if (options.json && stagedModeCount === 0) {
|
|
10484
|
+
failInitArgument("--json is only supported with --detect-agents or --install-agents.", options.json);
|
|
10485
|
+
return false;
|
|
10486
|
+
}
|
|
10487
|
+
return true;
|
|
10488
|
+
}
|
|
10489
|
+
function parseAgentIdList(value) {
|
|
10490
|
+
if (!value)
|
|
10491
|
+
return [];
|
|
10492
|
+
const ids = value.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
|
|
10493
|
+
return [...new Set(ids)];
|
|
10494
|
+
}
|
|
10495
|
+
function findAgentsByIds(scan, ids) {
|
|
10496
|
+
const detected = [...scan.needsSetup, ...scan.alreadyConfigured];
|
|
10497
|
+
return ids.map((id) => detected.find((agent) => agent.id === id)).filter((agent) => Boolean(agent));
|
|
10498
|
+
}
|
|
10499
|
+
function validateInstallAgentIds(scan, ids) {
|
|
10500
|
+
const supportedIds = new Set(agentDefinitions.map((agent) => agent.id));
|
|
10501
|
+
const detectedIds = [...scan.needsSetup, ...scan.alreadyConfigured].map((agent) => agent.id);
|
|
10502
|
+
const detectedIdSet = new Set(detectedIds);
|
|
10503
|
+
if (ids.length === 0) {
|
|
10504
|
+
return {
|
|
10505
|
+
ok: false,
|
|
10506
|
+
message: detectedIds.length > 0 ? `Provide at least one agent ID. Detected IDs: ${detectedIds.join(", ")}.` : "Provide at least one agent ID. No supported agents are currently detected.",
|
|
10507
|
+
detectedIds
|
|
10508
|
+
};
|
|
10509
|
+
}
|
|
10510
|
+
const unknown = ids.filter((id) => !supportedIds.has(id));
|
|
10511
|
+
if (unknown.length > 0) {
|
|
10512
|
+
return {
|
|
10513
|
+
ok: false,
|
|
10514
|
+
message: `Unsupported agent ID${unknown.length !== 1 ? "s" : ""}: ${unknown.join(", ")}.`,
|
|
10515
|
+
detectedIds
|
|
10516
|
+
};
|
|
10517
|
+
}
|
|
10518
|
+
const undetected = ids.filter((id) => !detectedIdSet.has(id));
|
|
10519
|
+
if (undetected.length > 0) {
|
|
10520
|
+
return {
|
|
10521
|
+
ok: false,
|
|
10522
|
+
message: `Agent ID${undetected.length !== 1 ? "s" : ""} not detected: ${undetected.join(", ")}. Detected IDs: ${detectedIds.length > 0 ? detectedIds.join(", ") : "none"}.`,
|
|
10523
|
+
detectedIds
|
|
10524
|
+
};
|
|
10525
|
+
}
|
|
10526
|
+
return { ok: true };
|
|
10527
|
+
}
|
|
10528
|
+
function printInstallValidationFailure(failure, json) {
|
|
10529
|
+
if (json) {
|
|
10530
|
+
console.error(JSON.stringify({
|
|
10531
|
+
error: failure.message,
|
|
10532
|
+
code: "INVALID_ARGUMENT",
|
|
10533
|
+
detectedIds: failure.detectedIds
|
|
10534
|
+
}));
|
|
10535
|
+
} else {
|
|
10536
|
+
console.error(failure.message);
|
|
10537
|
+
}
|
|
10538
|
+
process.exitCode = 1;
|
|
10539
|
+
}
|
|
10540
|
+
function hasUsableInstallOutcome(outcomes) {
|
|
10541
|
+
return outcomes.some((outcome) => outcome.status === "success" || outcome.status === "already_configured");
|
|
10542
|
+
}
|
|
10543
|
+
async function getStagedInstallAuthStatus(createLoginDeps) {
|
|
10544
|
+
if (!createLoginDeps)
|
|
10545
|
+
return "not_checked";
|
|
10546
|
+
try {
|
|
10547
|
+
const loginDeps = await createLoginDeps();
|
|
10548
|
+
if (typeof loginDeps.hasValidToken === "boolean") {
|
|
10549
|
+
return loginDeps.hasValidToken ? "authenticated" : "required";
|
|
10550
|
+
}
|
|
10551
|
+
const tokens = await loginDeps.authStorage.loadTokens(loginDeps.mcpUrl);
|
|
10552
|
+
const expired = tokens?.expiresAt ? new Date(tokens.expiresAt) < new Date : false;
|
|
10553
|
+
return tokens && !expired ? "authenticated" : "required";
|
|
10554
|
+
} catch {
|
|
10555
|
+
return "not_checked";
|
|
10556
|
+
}
|
|
10557
|
+
}
|
|
10558
|
+
function buildAgenticInstallAuthPayload(authStatus) {
|
|
10559
|
+
if (authStatus === "authenticated") {
|
|
10560
|
+
return { required: false, status: "authenticated" };
|
|
10561
|
+
}
|
|
10562
|
+
if (authStatus === "required") {
|
|
10563
|
+
return {
|
|
10564
|
+
required: true,
|
|
10565
|
+
status: "required",
|
|
10566
|
+
command: AGENT_LOGIN_COMMAND,
|
|
10567
|
+
noBrowserCommand: AGENT_LOGIN_NO_BROWSER_COMMAND
|
|
10568
|
+
};
|
|
10569
|
+
}
|
|
10570
|
+
return {
|
|
10571
|
+
required: null,
|
|
10572
|
+
status: "not_checked",
|
|
10573
|
+
command: AGENT_LOGIN_COMMAND,
|
|
10574
|
+
noBrowserCommand: AGENT_LOGIN_NO_BROWSER_COMMAND
|
|
10575
|
+
};
|
|
10576
|
+
}
|
|
10577
|
+
function buildAgenticInstallInstructions(authStatus) {
|
|
10578
|
+
if (authStatus === "authenticated") {
|
|
10579
|
+
return ["Open a new coding agent session so it reloads MCP config."];
|
|
10580
|
+
}
|
|
10581
|
+
if (authStatus === "required") {
|
|
10582
|
+
return [
|
|
10583
|
+
`Ask the user before running ${AGENT_LOGIN_COMMAND}.`,
|
|
10584
|
+
"Browser sign-in happens outside chat and terminal input.",
|
|
10585
|
+
"Do not ask the user to paste passwords, tokens, cookies, or OAuth codes into chat."
|
|
10586
|
+
];
|
|
10587
|
+
}
|
|
10588
|
+
return [
|
|
10589
|
+
"Sign-in status was not checked.",
|
|
10590
|
+
`If the user is not already signed in, ask before running ${AGENT_LOGIN_COMMAND}.`
|
|
10591
|
+
];
|
|
10592
|
+
}
|
|
10593
|
+
function printAgenticInstallJson(outcomes, authStatus) {
|
|
10594
|
+
const canAuthenticate = hasUsableInstallOutcome(outcomes);
|
|
10595
|
+
console.log(JSON.stringify({
|
|
10596
|
+
mode: "install-agents",
|
|
10597
|
+
outcomes,
|
|
10598
|
+
auth: canAuthenticate ? buildAgenticInstallAuthPayload(authStatus) : {
|
|
10599
|
+
required: false,
|
|
10600
|
+
status: "not_applicable",
|
|
10601
|
+
reason: "Fix installation errors before starting sign-in."
|
|
10602
|
+
},
|
|
10603
|
+
instructions: canAuthenticate ? buildAgenticInstallInstructions(authStatus) : ["Fix installation errors before asking the user to sign in."]
|
|
10604
|
+
}, null, 2));
|
|
10605
|
+
}
|
|
10606
|
+
async function runDetectAgentsMode(options, fileSystemService, execService, useColors) {
|
|
10607
|
+
const scan = await scanAgents(agentDefinitions, fileSystemService, execService);
|
|
10608
|
+
if (options.json) {
|
|
10609
|
+
printAgenticDetectJson(scan);
|
|
10610
|
+
return;
|
|
10611
|
+
}
|
|
10612
|
+
printAgenticDetectSummary(scan, useColors);
|
|
10613
|
+
}
|
|
10614
|
+
async function runInstallAgentsMode(options, fileSystemService, execService, createLoginDeps, useColors) {
|
|
10615
|
+
const requestedIds = parseAgentIdList(options.installAgents);
|
|
10616
|
+
const scan = await scanAgents(agentDefinitions, fileSystemService, execService);
|
|
10617
|
+
const validation = validateInstallAgentIds(scan, requestedIds);
|
|
10618
|
+
if (!validation.ok) {
|
|
10619
|
+
printInstallValidationFailure(validation, options.json);
|
|
10620
|
+
return;
|
|
10621
|
+
}
|
|
10622
|
+
const agents = findAgentsByIds(scan, requestedIds);
|
|
10623
|
+
if (!options.json) {
|
|
10624
|
+
console.log("Installing GitHits MCP:");
|
|
10625
|
+
console.log();
|
|
10626
|
+
}
|
|
10627
|
+
const outcomes = await installSelectedAgents(agents, scan, fileSystemService, execService, useColors, !options.json);
|
|
10628
|
+
const failed = outcomes.filter((outcome) => outcome.status === "failed");
|
|
10629
|
+
const canAuthenticate = hasUsableInstallOutcome(outcomes);
|
|
10630
|
+
const authStatus = canAuthenticate ? await getStagedInstallAuthStatus(createLoginDeps) : "not_checked";
|
|
10631
|
+
if (failed.length > 0) {
|
|
10632
|
+
process.exitCode = 1;
|
|
10633
|
+
}
|
|
10634
|
+
if (options.json) {
|
|
10635
|
+
printAgenticInstallJson(outcomes, authStatus);
|
|
10636
|
+
return;
|
|
10637
|
+
}
|
|
10638
|
+
console.log();
|
|
10639
|
+
if (failed.length === 0) {
|
|
10640
|
+
console.log("GitHits MCP installation complete.");
|
|
10641
|
+
} else {
|
|
10642
|
+
console.log("GitHits MCP installation completed with errors.");
|
|
10643
|
+
for (const outcome of failed) {
|
|
10644
|
+
console.log(` ${outcome.name}: ${outcome.message ?? "Unknown error"}`);
|
|
10645
|
+
}
|
|
10646
|
+
}
|
|
10647
|
+
console.log();
|
|
10648
|
+
if (canAuthenticate) {
|
|
10649
|
+
if (authStatus === "authenticated") {
|
|
10650
|
+
printAgenticAlreadyAuthenticated();
|
|
10651
|
+
} else if (authStatus === "required") {
|
|
10652
|
+
printAgenticLoginInstructions(useColors);
|
|
10653
|
+
} else {
|
|
10654
|
+
printAgenticAuthNotChecked(useColors);
|
|
10655
|
+
}
|
|
10656
|
+
} else {
|
|
10657
|
+
console.log("Fix installation errors before starting sign-in.");
|
|
10658
|
+
}
|
|
10659
|
+
console.log();
|
|
10660
|
+
}
|
|
10661
|
+
function printAuthExplanation() {
|
|
10662
|
+
console.log(" GitHits authentication is required before your agent can use GitHits tools.");
|
|
10663
|
+
console.log();
|
|
10664
|
+
console.log(" We'll open your browser to connect your account.");
|
|
10665
|
+
console.log(" Credentials are stored securely in your OS keychain.");
|
|
10666
|
+
console.log();
|
|
10667
|
+
console.log(" No API keys or secrets are written into your MCP config.");
|
|
10668
|
+
console.log();
|
|
10669
|
+
}
|
|
10670
|
+
async function runInitAuthentication(options, promptService, createLoginDeps, useColors) {
|
|
10671
|
+
if (options.skipLogin) {
|
|
10672
|
+
console.log(` Skipping authentication (--skip-login).
|
|
10673
|
+
`);
|
|
10674
|
+
return "skipped";
|
|
10675
|
+
}
|
|
10676
|
+
if (!createLoginDeps) {
|
|
10677
|
+
printTask("warning", "Sign-in unavailable", "sign in later with `githits login`", useColors);
|
|
10678
|
+
return "unavailable";
|
|
10679
|
+
}
|
|
10680
|
+
while (true) {
|
|
10681
|
+
let loginResult;
|
|
10682
|
+
try {
|
|
10683
|
+
const loginDeps = await createLoginDeps();
|
|
10684
|
+
if (loginDeps.hasValidToken) {
|
|
10685
|
+
printTask("success", "Already signed in", undefined, useColors);
|
|
10686
|
+
return "authenticated";
|
|
10687
|
+
}
|
|
10688
|
+
printAuthExplanation();
|
|
10689
|
+
if (!options.yes) {
|
|
10690
|
+
let authChoice;
|
|
10691
|
+
try {
|
|
10692
|
+
authChoice = await promptService.select(" Continue with browser sign-in?", AUTH_START_CHOICES, "sign_in");
|
|
10693
|
+
} catch (err) {
|
|
10694
|
+
if (err instanceof ExitPromptError) {
|
|
10695
|
+
console.log(`
|
|
10696
|
+
Setup cancelled.
|
|
10697
|
+
`);
|
|
10698
|
+
return "cancelled";
|
|
10699
|
+
}
|
|
10700
|
+
throw err;
|
|
10701
|
+
}
|
|
10702
|
+
if (authChoice === "skip") {
|
|
10703
|
+
printTask("warning", "Sign-in skipped", "your agent will ask you to sign in later", useColors);
|
|
10704
|
+
return "skipped";
|
|
10705
|
+
}
|
|
10706
|
+
if (authChoice === "cancel") {
|
|
10707
|
+
console.log("\n Setup cancelled. Run `githits login` to authenticate.\n");
|
|
10708
|
+
return "cancelled";
|
|
10709
|
+
}
|
|
10710
|
+
}
|
|
10711
|
+
loginResult = await loginFlow({}, loginDeps, createInitLoginOutput());
|
|
10712
|
+
} catch (error2) {
|
|
10713
|
+
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
10714
|
+
loginResult = { status: "failed", message: msg };
|
|
10715
|
+
}
|
|
10716
|
+
if (loginResult.status === "already_authenticated") {
|
|
10717
|
+
printTask("success", "Already signed in", undefined, useColors);
|
|
10718
|
+
return "authenticated";
|
|
10719
|
+
}
|
|
10720
|
+
if (loginResult.status === "success") {
|
|
10721
|
+
printTask("success", "Signed in successfully", undefined, useColors);
|
|
10722
|
+
return "authenticated";
|
|
10723
|
+
}
|
|
10724
|
+
console.log(` ${warning(`Login failed: ${loginResult.message}`, useColors)}
|
|
10725
|
+
`);
|
|
10726
|
+
printAuthRecoveryHint(useColors);
|
|
10727
|
+
if (options.yes) {
|
|
10728
|
+
console.log(` Continuing without authentication...
|
|
10729
|
+
`);
|
|
10730
|
+
return "failed_continue";
|
|
10731
|
+
}
|
|
10732
|
+
let choice;
|
|
10733
|
+
try {
|
|
10734
|
+
choice = await promptService.select(" Authentication failed. What would you like to do?", AUTH_RECOVERY_CHOICES, "retry");
|
|
10735
|
+
} catch (err) {
|
|
10736
|
+
if (err instanceof ExitPromptError) {
|
|
10737
|
+
console.log(`
|
|
10738
|
+
Setup cancelled.
|
|
10739
|
+
`);
|
|
10740
|
+
return "cancelled";
|
|
10741
|
+
}
|
|
10742
|
+
throw err;
|
|
10743
|
+
}
|
|
10744
|
+
if (choice === "retry") {
|
|
10745
|
+
console.log();
|
|
10746
|
+
continue;
|
|
10747
|
+
}
|
|
10748
|
+
if (choice === "cancel") {
|
|
10749
|
+
console.log("\n Setup cancelled. Run `githits login` to authenticate.\n");
|
|
10750
|
+
return "cancelled";
|
|
10751
|
+
}
|
|
10752
|
+
console.log(` Continuing without authentication...
|
|
10753
|
+
`);
|
|
10754
|
+
return "failed_continue";
|
|
10755
|
+
}
|
|
10756
|
+
}
|
|
10757
|
+
function shouldPrintReady(authStatus) {
|
|
10758
|
+
return authStatus === "authenticated";
|
|
10759
|
+
}
|
|
10760
|
+
function printPostSetupNextSteps(authStatus, useColors) {
|
|
10761
|
+
printSection(5, shouldPrintReady(authStatus) ? "Ready" : "Next Steps", useColors);
|
|
10762
|
+
if (shouldPrintReady(authStatus)) {
|
|
10763
|
+
printReadyNextSteps();
|
|
10764
|
+
} else if (authStatus === "failed_continue") {
|
|
10765
|
+
printAuthRequiredNextSteps(useColors);
|
|
10766
|
+
} else {
|
|
10767
|
+
printAuthNotCheckedNextSteps(useColors);
|
|
10768
|
+
}
|
|
10769
|
+
}
|
|
10770
|
+
async function verifyAgentConfigured(agent, fileSystemService, execService) {
|
|
10771
|
+
const postCheck = await scanAgents([agent], fileSystemService, execService);
|
|
10772
|
+
if (postCheck.alreadyConfigured.some((a) => a.id === agent.id)) {
|
|
10773
|
+
return { ok: true };
|
|
10774
|
+
}
|
|
10775
|
+
if (postCheck.needsSetup.some((a) => a.id === agent.id)) {
|
|
10776
|
+
return {
|
|
10777
|
+
ok: false,
|
|
10778
|
+
message: `${agent.name} verification failed: not configured after setup.`
|
|
10779
|
+
};
|
|
10780
|
+
}
|
|
10781
|
+
return {
|
|
10782
|
+
ok: false,
|
|
10783
|
+
message: `${agent.name} verification failed: agent not detected after setup.`
|
|
10784
|
+
};
|
|
10785
|
+
}
|
|
10786
|
+
async function executeAgentSetupWithVerification(agent, fileSystemService, execService) {
|
|
10787
|
+
const config = getResolvedSetupConfig(agent, fileSystemService);
|
|
10788
|
+
let result = config.method === "cli" ? await executeCliSetup(config, execService) : config.method === "config-file" ? await executeConfigFileSetup(config, fileSystemService) : await executeCompositeSetup(config, fileSystemService, execService);
|
|
10789
|
+
if (result.status === "success" || result.status === "already_configured") {
|
|
10790
|
+
const verification = await verifyAgentConfigured(agent, fileSystemService, execService);
|
|
10791
|
+
if (!verification.ok) {
|
|
10792
|
+
result = {
|
|
10793
|
+
status: "failed",
|
|
10794
|
+
message: agent.id === "gemini-cli" ? "Gemini installation did not complete. Retry, or run: gemini extensions install --consent https://github.com/githits-com/githits-cli" : verification.message ?? `${agent.name} verification failed after setup.`
|
|
10795
|
+
};
|
|
10796
|
+
}
|
|
10797
|
+
}
|
|
10798
|
+
return result;
|
|
10799
|
+
}
|
|
10800
|
+
async function installSelectedAgents(agents, scan, fileSystemService, execService, useColors, printResults) {
|
|
10801
|
+
const alreadyConfiguredIds = new Set(scan.alreadyConfigured.map((agent) => agent.id));
|
|
10802
|
+
const outcomes = [];
|
|
10803
|
+
const installTasks = createInstallTaskReporter(useColors);
|
|
10804
|
+
for (const agent of agents) {
|
|
10805
|
+
if (alreadyConfiguredIds.has(agent.id)) {
|
|
10806
|
+
outcomes.push({
|
|
10807
|
+
id: agent.id,
|
|
10808
|
+
name: agent.name,
|
|
10809
|
+
status: "already_configured"
|
|
10810
|
+
});
|
|
10811
|
+
if (printResults) {
|
|
10812
|
+
printTask("warning", agent.name, "already configured", useColors);
|
|
10813
|
+
}
|
|
10814
|
+
continue;
|
|
10815
|
+
}
|
|
10816
|
+
const finishTask = printResults ? installTasks.start(agent.name) : () => {};
|
|
10817
|
+
let result;
|
|
10818
|
+
try {
|
|
10819
|
+
result = await executeAgentSetupWithVerification(agent, fileSystemService, execService);
|
|
10820
|
+
} finally {
|
|
10821
|
+
finishTask();
|
|
10822
|
+
}
|
|
10823
|
+
outcomes.push({
|
|
10824
|
+
id: agent.id,
|
|
10825
|
+
name: agent.name,
|
|
10826
|
+
status: result.status,
|
|
10827
|
+
message: result.status === "failed" ? result.message : undefined
|
|
10828
|
+
});
|
|
10829
|
+
if (!printResults) {
|
|
10830
|
+
continue;
|
|
10831
|
+
}
|
|
10832
|
+
if (result.status === "success") {
|
|
10833
|
+
printTask("success", agent.name, "configured and verified", useColors);
|
|
10834
|
+
} else if (result.status === "already_configured") {
|
|
10835
|
+
printTask("warning", agent.name, "already configured", useColors);
|
|
10836
|
+
} else {
|
|
10837
|
+
printTask("failed", agent.name, result.message, useColors);
|
|
10838
|
+
}
|
|
10839
|
+
}
|
|
10840
|
+
return outcomes;
|
|
10841
|
+
}
|
|
10842
|
+
async function verifyAgentUnconfigured(agent, fileSystemService, execService) {
|
|
10843
|
+
const postCheck = await scanAgents([agent], fileSystemService, execService);
|
|
10844
|
+
if (postCheck.needsSetup.some((a) => a.id === agent.id)) {
|
|
10845
|
+
return { ok: true };
|
|
10846
|
+
}
|
|
10847
|
+
if (postCheck.notDetected.some((a) => a.id === agent.id)) {
|
|
10848
|
+
return {
|
|
10849
|
+
ok: false,
|
|
10850
|
+
message: `${agent.name} verification failed: agent was not detected after uninstall, so removal could not be confirmed.`
|
|
10851
|
+
};
|
|
10852
|
+
}
|
|
10853
|
+
return {
|
|
10854
|
+
ok: false,
|
|
10855
|
+
message: `${agent.name} verification failed: still configured after uninstall.`
|
|
10856
|
+
};
|
|
10857
|
+
}
|
|
10858
|
+
async function inspectSetupForUninstall(agent, config, fileSystemService, execService) {
|
|
10859
|
+
if (config.method === "config-file") {
|
|
10860
|
+
const check = await getConfigUninstallCheckStatus(config, fileSystemService);
|
|
10861
|
+
if (check.status === "configured")
|
|
10862
|
+
return "configured";
|
|
10863
|
+
if (check.status === "not_configured")
|
|
10864
|
+
return "not_configured";
|
|
10865
|
+
return { status: "failed", message: check.message };
|
|
10866
|
+
}
|
|
10867
|
+
if (config.method === "cli") {
|
|
10868
|
+
if (!config.checkCommand) {
|
|
10869
|
+
return {
|
|
10870
|
+
status: "failed",
|
|
10871
|
+
message: `${agent.name} does not have a verified uninstall check command.`
|
|
10872
|
+
};
|
|
9679
10873
|
}
|
|
9680
10874
|
const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
|
|
9681
10875
|
if (checkStatus === "configured")
|
|
@@ -9764,166 +10958,120 @@ async function scanAgentsForUninstall(fileSystemService, execService) {
|
|
|
9764
10958
|
async function initAction(options, deps) {
|
|
9765
10959
|
const useColors = shouldUseColors();
|
|
9766
10960
|
const { fileSystemService, promptService, execService, createLoginDeps } = deps;
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
`);
|
|
9771
|
-
if (!options.skipLogin && createLoginDeps) {
|
|
9772
|
-
console.log(` Checking authentication...
|
|
9773
|
-
`);
|
|
9774
|
-
let loginResult;
|
|
9775
|
-
try {
|
|
9776
|
-
const loginDeps = await createLoginDeps();
|
|
9777
|
-
loginResult = loginDeps.hasValidToken ? { status: "already_authenticated", message: "Already logged in." } : await loginFlow({}, loginDeps);
|
|
9778
|
-
} catch (error2) {
|
|
9779
|
-
const msg = error2 instanceof Error ? error2.message : String(error2);
|
|
9780
|
-
loginResult = { status: "failed", message: msg };
|
|
9781
|
-
}
|
|
9782
|
-
if (loginResult.status === "already_authenticated") {
|
|
9783
|
-
console.log(` ${success("Already authenticated", useColors)}
|
|
9784
|
-
`);
|
|
9785
|
-
} else if (loginResult.status === "success") {
|
|
9786
|
-
console.log(` ${success("Logged in successfully", useColors)}
|
|
9787
|
-
`);
|
|
9788
|
-
} else {
|
|
9789
|
-
console.log(` ${warning(`Login failed: ${loginResult.message}`, useColors)}
|
|
9790
|
-
`);
|
|
9791
|
-
printAuthRecoveryHint();
|
|
9792
|
-
if (!options.yes) {
|
|
9793
|
-
try {
|
|
9794
|
-
const choice = await promptService.confirm3("Continue without authentication?");
|
|
9795
|
-
if (choice === "no") {
|
|
9796
|
-
console.log("\n Setup cancelled. Run `githits login` to authenticate.\n");
|
|
9797
|
-
return;
|
|
9798
|
-
}
|
|
9799
|
-
} catch (err) {
|
|
9800
|
-
if (err instanceof ExitPromptError) {
|
|
9801
|
-
console.log(`
|
|
9802
|
-
Setup cancelled.
|
|
9803
|
-
`);
|
|
9804
|
-
return;
|
|
9805
|
-
}
|
|
9806
|
-
throw err;
|
|
9807
|
-
}
|
|
9808
|
-
}
|
|
9809
|
-
continuedWithoutAuth = true;
|
|
9810
|
-
console.log(` Continuing without authentication...
|
|
9811
|
-
`);
|
|
9812
|
-
}
|
|
9813
|
-
}
|
|
9814
|
-
console.log(` Scanning for available agents...
|
|
9815
|
-
`);
|
|
9816
|
-
const scan = await scanAgents(agentDefinitions, fileSystemService, execService);
|
|
9817
|
-
for (const agent of scan.alreadyConfigured) {
|
|
9818
|
-
console.log(` ${success(`${agent.name} — already configured`, useColors)}`);
|
|
9819
|
-
}
|
|
9820
|
-
for (const agent of scan.needsSetup) {
|
|
9821
|
-
console.log(` ${colorize(`● ${agent.name} — needs setup`, "cyan", useColors)}`);
|
|
10961
|
+
const isInteractive = deps.isInteractive ?? true;
|
|
10962
|
+
if (!validateInitModeOptions(options)) {
|
|
10963
|
+
return;
|
|
9822
10964
|
}
|
|
9823
|
-
|
|
9824
|
-
|
|
10965
|
+
if (options.detectAgents) {
|
|
10966
|
+
await runDetectAgentsMode(options, fileSystemService, execService, useColors);
|
|
10967
|
+
return;
|
|
9825
10968
|
}
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
console.log(` No coding agents detected. Install an agent and try again.
|
|
9829
|
-
`);
|
|
10969
|
+
if (options.installAgents !== undefined) {
|
|
10970
|
+
await runInstallAgentsMode(options, fileSystemService, execService, createLoginDeps, useColors);
|
|
9830
10971
|
return;
|
|
9831
10972
|
}
|
|
9832
|
-
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
10973
|
+
printInitIntro(useColors);
|
|
10974
|
+
if (!isInteractive) {
|
|
10975
|
+
if (options.yes) {
|
|
10976
|
+
printNonInteractiveYesRejected(useColors);
|
|
9836
10977
|
return;
|
|
9837
10978
|
}
|
|
9838
|
-
|
|
9839
|
-
printReadyNextSteps();
|
|
10979
|
+
printNonInteractiveInitGuidance(useColors);
|
|
9840
10980
|
console.log();
|
|
9841
10981
|
return;
|
|
9842
10982
|
}
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
for (const line of preview2.split(`
|
|
9852
|
-
`)) {
|
|
9853
|
-
console.log(` ${line}`);
|
|
9854
|
-
}
|
|
9855
|
-
console.log();
|
|
9856
|
-
if (!alwaysMode) {
|
|
9857
|
-
let choice;
|
|
9858
|
-
try {
|
|
9859
|
-
choice = await promptService.confirm3("Proceed?");
|
|
9860
|
-
} catch (err) {
|
|
9861
|
-
if (err instanceof ExitPromptError) {
|
|
9862
|
-
console.log(`
|
|
9863
|
-
Setup cancelled.
|
|
10983
|
+
if (!options.yes) {
|
|
10984
|
+
let intent;
|
|
10985
|
+
try {
|
|
10986
|
+
intent = await promptService.select(" What do you want to do?", INIT_INTENT_CHOICES, "mcp");
|
|
10987
|
+
} catch (err) {
|
|
10988
|
+
if (err instanceof ExitPromptError) {
|
|
10989
|
+
console.log(`
|
|
10990
|
+
Setup cancelled. No changes made.
|
|
9864
10991
|
`);
|
|
9865
|
-
|
|
9866
|
-
}
|
|
9867
|
-
throw err;
|
|
9868
|
-
}
|
|
9869
|
-
if (choice === "no") {
|
|
9870
|
-
outcomes.push({ id: agent.id, name: agent.name, status: "skipped" });
|
|
9871
|
-
console.log();
|
|
9872
|
-
continue;
|
|
9873
|
-
}
|
|
9874
|
-
if (choice === "always") {
|
|
9875
|
-
alwaysMode = true;
|
|
10992
|
+
return;
|
|
9876
10993
|
}
|
|
10994
|
+
throw err;
|
|
9877
10995
|
}
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
if (!verification.ok) {
|
|
9882
|
-
result = {
|
|
9883
|
-
status: "failed",
|
|
9884
|
-
message: agent.id === "gemini-cli" ? "Gemini installation did not complete. Retry, or run: gemini extensions install --consent https://github.com/githits-com/githits-cli" : verification.message ?? `${agent.name} verification failed after setup.`
|
|
9885
|
-
};
|
|
9886
|
-
}
|
|
10996
|
+
if (intent === "skills") {
|
|
10997
|
+
printSkillsInstructions(useColors);
|
|
10998
|
+
return;
|
|
9887
10999
|
}
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
|
|
11000
|
+
if (intent === "later") {
|
|
11001
|
+
console.log("\n No changes made. Run `npx githits@latest init` whenever you're ready.\n");
|
|
11002
|
+
return;
|
|
11003
|
+
}
|
|
11004
|
+
}
|
|
11005
|
+
printSection(1, "Detect tools", useColors);
|
|
11006
|
+
console.log(" Scanning for compatible AI coding tools...");
|
|
11007
|
+
const progress = createScanProgressReporter(useColors);
|
|
11008
|
+
const scanPromise = startSafeInitScan(fileSystemService, execService, (scanProgress) => progress.onProgress(scanProgress));
|
|
11009
|
+
let scan;
|
|
11010
|
+
try {
|
|
11011
|
+
scan = await unwrapSafeScan(scanPromise);
|
|
11012
|
+
} finally {
|
|
11013
|
+
progress.finish();
|
|
11014
|
+
}
|
|
11015
|
+
printScanSummary(scan, useColors);
|
|
11016
|
+
if (scan.needsSetup.length === 0 && scan.alreadyConfigured.length === 0) {
|
|
11017
|
+
printTask("warning", "No supported AI coding tools detected", "install a supported tool and run `githits init` again", useColors);
|
|
11018
|
+
console.log();
|
|
11019
|
+
return;
|
|
11020
|
+
}
|
|
11021
|
+
let toSetup = scan.needsSetup;
|
|
11022
|
+
printSection(2, "Choose tools", useColors);
|
|
11023
|
+
if (!options.yes && scan.needsSetup.length > 0) {
|
|
11024
|
+
try {
|
|
11025
|
+
toSetup = await promptService.checkbox(" Select which tools should use GitHits:", buildInitAgentChoices(scan));
|
|
11026
|
+
} catch (err) {
|
|
11027
|
+
if (err instanceof ExitPromptError) {
|
|
11028
|
+
console.log(`
|
|
11029
|
+
Setup cancelled. No changes made.
|
|
9902
11030
|
`);
|
|
11031
|
+
return;
|
|
11032
|
+
}
|
|
11033
|
+
throw err;
|
|
9903
11034
|
}
|
|
11035
|
+
} else if (scan.needsSetup.length === 0) {
|
|
11036
|
+
printTask("success", "No tool changes needed", "all detected tools already have GitHits MCP", useColors);
|
|
11037
|
+
} else {
|
|
11038
|
+
printTask("success", "Selected all detected tools", "--yes", useColors);
|
|
11039
|
+
}
|
|
11040
|
+
if (toSetup.length === 0 && scan.needsSetup.length > 0) {
|
|
11041
|
+
printTask("skipped", "Setup skipped", "no tools selected", useColors);
|
|
11042
|
+
console.log();
|
|
11043
|
+
return;
|
|
9904
11044
|
}
|
|
11045
|
+
printSection(3, "Sign in", useColors);
|
|
11046
|
+
const authStatus = await runInitAuthentication(options, promptService, createLoginDeps, useColors);
|
|
11047
|
+
if (authStatus === "cancelled") {
|
|
11048
|
+
return;
|
|
11049
|
+
}
|
|
11050
|
+
printSection(4, "Install and verify", useColors);
|
|
11051
|
+
if (toSetup.length === 0) {
|
|
11052
|
+
printTask("success", "Nothing to install", "all detected tools are already configured", useColors);
|
|
11053
|
+
printPostSetupNextSteps(authStatus, useColors);
|
|
11054
|
+
console.log();
|
|
11055
|
+
return;
|
|
11056
|
+
}
|
|
11057
|
+
const outcomes = await installSelectedAgents(toSetup, scan, fileSystemService, execService, useColors, true);
|
|
11058
|
+
console.log();
|
|
9905
11059
|
const configured = outcomes.filter((o) => o.status === "success").length;
|
|
9906
11060
|
const alreadyDone = outcomes.filter((o) => o.status === "already_configured").length + scan.alreadyConfigured.length;
|
|
9907
11061
|
const failed = outcomes.filter((o) => o.status === "failed").length;
|
|
9908
|
-
const skipped = outcomes.filter((o) => o.status === "skipped").length;
|
|
9909
11062
|
if (failed > 0) {
|
|
9910
11063
|
console.log(" Setup completed with errors.");
|
|
9911
|
-
} else if (continuedWithoutAuth && (configured > 0 || alreadyDone > 0)) {
|
|
9912
|
-
console.log(" MCP is configured, but authentication is still required.");
|
|
9913
|
-
console.log(" Run `githits login` before using GitHits tools.");
|
|
9914
11064
|
} else if (configured > 0 || alreadyDone > 0) {
|
|
9915
|
-
|
|
9916
|
-
} else if (skipped > 0) {
|
|
9917
|
-
console.log(" Setup skipped.");
|
|
11065
|
+
printPostSetupNextSteps(authStatus, useColors);
|
|
9918
11066
|
}
|
|
9919
11067
|
if (failed > 0) {
|
|
9920
|
-
console.log(` ${failed}
|
|
11068
|
+
console.log(` ${failed} tool${failed !== 1 ? "s" : ""} failed to configure.`);
|
|
9921
11069
|
for (const outcome of outcomes.filter((o) => o.status === "failed")) {
|
|
9922
11070
|
console.log(` - ${outcome.name}: ${outcome.message ?? "Unknown error"}`);
|
|
9923
11071
|
}
|
|
9924
11072
|
}
|
|
9925
|
-
if (
|
|
9926
|
-
console.log(` ${
|
|
11073
|
+
if (scan.alreadyConfigured.length > 0) {
|
|
11074
|
+
console.log(` ${scan.alreadyConfigured.length} tool${scan.alreadyConfigured.length !== 1 ? "s" : ""} already configured.`);
|
|
9927
11075
|
}
|
|
9928
11076
|
console.log();
|
|
9929
11077
|
}
|
|
@@ -9931,7 +11079,8 @@ async function initUninstallAction(options, deps) {
|
|
|
9931
11079
|
const useColors = shouldUseColors();
|
|
9932
11080
|
const { fileSystemService, promptService, execService } = deps;
|
|
9933
11081
|
console.log(`
|
|
9934
|
-
${colorize("GitHits", "bold", useColors)}
|
|
11082
|
+
${colorize("Disconnect GitHits from your coding agents.", "bold", useColors)}`);
|
|
11083
|
+
console.log(` ${colorize("Removes the local GitHits MCP configuration.", "dim", useColors)}
|
|
9935
11084
|
`);
|
|
9936
11085
|
console.log(` Scanning for configured agents...
|
|
9937
11086
|
`);
|
|
@@ -10068,33 +11217,27 @@ async function initUninstallAction(options, deps) {
|
|
|
10068
11217
|
}
|
|
10069
11218
|
console.log();
|
|
10070
11219
|
}
|
|
10071
|
-
function printAuthRecoveryHint() {
|
|
11220
|
+
function printAuthRecoveryHint(useColors) {
|
|
10072
11221
|
console.log(" You can still configure MCP, but GitHits tools will require auth.");
|
|
10073
11222
|
console.log(" Recovery steps:");
|
|
10074
|
-
console.log("
|
|
10075
|
-
console.log("
|
|
11223
|
+
console.log(` ${formatCommand("githits auth status", useColors)}`);
|
|
11224
|
+
console.log(` ${formatCommand("githits login --force", useColors)}`);
|
|
10076
11225
|
console.log(" For CI or locked-down machines, set GITHITS_API_TOKEN.");
|
|
10077
11226
|
console.log(` If your system keychain is unavailable, set GITHITS_AUTH_STORAGE=file after accepting plaintext storage.
|
|
10078
11227
|
`);
|
|
10079
11228
|
}
|
|
10080
|
-
var INIT_DESCRIPTION = `
|
|
11229
|
+
var INIT_DESCRIPTION = `Connect GitHits to your coding agents.
|
|
10081
11230
|
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
and sets up unconfigured ones with your confirmation.
|
|
10086
|
-
|
|
10087
|
-
Supports CLI-based setup (Claude Code, Codex, Gemini CLI), Pi package setup,
|
|
10088
|
-
and config
|
|
10089
|
-
file editing (Cursor, Windsurf, VS Code, Cline, Claude Desktop,
|
|
10090
|
-
Google Antigravity) with atomic writes.`;
|
|
11231
|
+
Installs the local GitHits MCP server — the recommended way to connect — or
|
|
11232
|
+
sets up Agent Skills instead. Detects supported coding tools on this machine,
|
|
11233
|
+
signs you in, and configures the tools you select.`;
|
|
10091
11234
|
var INIT_UNINSTALL_DESCRIPTION = `Remove GitHits MCP server configuration from your coding agents.
|
|
10092
11235
|
|
|
10093
11236
|
Scans for available agents that currently have GitHits configured, then removes
|
|
10094
11237
|
only the GitHits MCP/plugin configuration with your confirmation. Authentication
|
|
10095
11238
|
tokens are not removed; use \`githits logout\` to remove stored credentials.`;
|
|
10096
11239
|
function registerInitCommand(program) {
|
|
10097
|
-
const initCommand = program.command("init").summary("
|
|
11240
|
+
const initCommand = program.command("init").summary("Connect GitHits to your coding agents").description(INIT_DESCRIPTION).option("-y, --yes", "Skip prompts, configure all detected tools").option("--skip-login", "Skip authentication step").option("--detect-agents", "Scan supported agents without installing").option("--install-agents <ids>", "Install MCP server for comma-separated agent IDs from --detect-agents").option("--json", "Emit JSON for --detect-agents or --install-agents").action(async (options) => {
|
|
10098
11241
|
const fileSystemService = new FileSystemServiceImpl;
|
|
10099
11242
|
const promptService = new PromptServiceImpl;
|
|
10100
11243
|
const execService = new ExecServiceImpl;
|
|
@@ -10102,7 +11245,8 @@ function registerInitCommand(program) {
|
|
|
10102
11245
|
fileSystemService,
|
|
10103
11246
|
promptService,
|
|
10104
11247
|
execService,
|
|
10105
|
-
createLoginDeps: () => createContainer()
|
|
11248
|
+
createLoginDeps: () => createContainer(),
|
|
11249
|
+
isInteractive: process.stdin.isTTY === true && process.stdout.isTTY === true
|
|
10106
11250
|
});
|
|
10107
11251
|
});
|
|
10108
11252
|
initCommand.command("uninstall").summary("Remove MCP server from your coding agents").description(INIT_UNINSTALL_DESCRIPTION).option("-y, --yes", "Skip prompts, uninstall from all configured agents").action(async (options) => {
|
|
@@ -10165,17 +11309,10 @@ function registerLanguagesCommand(program) {
|
|
|
10165
11309
|
// src/commands/logout.ts
|
|
10166
11310
|
async function logoutAction(deps) {
|
|
10167
11311
|
const { authStorage, mcpUrl } = deps;
|
|
10168
|
-
const auth = await authStorage.loadTokens(mcpUrl);
|
|
10169
11312
|
await authStorage.clearAuthSession(mcpUrl);
|
|
10170
|
-
|
|
10171
|
-
console.log(`Not currently logged in.
|
|
11313
|
+
console.log(`Logged out.
|
|
10172
11314
|
`);
|
|
10173
|
-
|
|
10174
|
-
} else {
|
|
10175
|
-
console.log(`Logged out.
|
|
10176
|
-
`);
|
|
10177
|
-
console.log(` Environment: ${mcpUrl}`);
|
|
10178
|
-
}
|
|
11315
|
+
console.log(` Environment: ${mcpUrl}`);
|
|
10179
11316
|
}
|
|
10180
11317
|
var LOGOUT_DESCRIPTION = `Remove stored credentials.
|
|
10181
11318
|
|
|
@@ -11807,7 +12944,7 @@ function showMcpSetupInstructions() {
|
|
|
11807
12944
|
console.log("Learn more at https://githits.com");
|
|
11808
12945
|
}
|
|
11809
12946
|
function registerMcpCommand(program) {
|
|
11810
|
-
const mcpCommand = program.command("mcp").summary("Show setup instructions or start MCP server").description(`Start the Model Context Protocol (MCP) server using STDIO transport.
|
|
12947
|
+
const mcpCommand = program.command("mcp").summary("Show MCP setup instructions or start the local MCP server").description(`Start the Model Context Protocol (MCP) server using STDIO transport.
|
|
11811
12948
|
|
|
11812
12949
|
When run interactively (TTY), shows setup instructions.
|
|
11813
12950
|
When run via stdio (non-TTY), starts the MCP server.
|
|
@@ -12379,7 +13516,7 @@ async function registerPkgCommandGroup(program, options = {}) {
|
|
|
12379
13516
|
if (!registration.shouldRegister) {
|
|
12380
13517
|
return;
|
|
12381
13518
|
}
|
|
12382
|
-
const pkgCommand = program.command("pkg").summary("Package metadata
|
|
13519
|
+
const pkgCommand = program.command("pkg").summary("Package metadata, dependencies, vulnerabilities and changelogs").description("Inspect package metadata from npm, PyPI, Hex, Crates, NuGet, Maven, Packagist, RubyGems, Go, vcpkg, and Zig: overviews, advisories, dependency graphs, and changelogs. Advisory data is unavailable for vcpkg and Zig. For source-level operations inside a dependency, use `githits code`.");
|
|
12383
13520
|
registerPkgInfoCommand(pkgCommand);
|
|
12384
13521
|
registerPkgVulnsCommand(pkgCommand);
|
|
12385
13522
|
registerPkgDepsCommand(pkgCommand);
|
|
@@ -12408,7 +13545,8 @@ async function searchAction(query, options, deps) {
|
|
|
12408
13545
|
offset: parseOptionalInt(options.offset, "--offset", 0),
|
|
12409
13546
|
waitTimeoutMs: parseWaitMs(options.wait)
|
|
12410
13547
|
});
|
|
12411
|
-
const
|
|
13548
|
+
const spinner = startSpinner(SPINNER_MESSAGES.search, !options.json);
|
|
13549
|
+
const outcome = await service.search(built.params).finally(() => spinner.stop());
|
|
12412
13550
|
const payload = buildUnifiedSearchSuccessPayload(built.params, built.rawQuery, built.compiledQuery, outcome);
|
|
12413
13551
|
if (options.json) {
|
|
12414
13552
|
console.log(JSON.stringify(payload));
|
|
@@ -12470,7 +13608,7 @@ Pass the searchRef returned by githits search when the initial request could
|
|
|
12470
13608
|
not complete within the wait window. This can return progress, partial hits when
|
|
12471
13609
|
the original request used --allow-partial, or final results.`;
|
|
12472
13610
|
function registerSearchCommand(program) {
|
|
12473
|
-
program.command("search").summary("
|
|
13611
|
+
program.command("search").summary("Explore repository code, dependencies, docs and symbols").description(SEARCH_DESCRIPTION).argument("<query>", "Search query").requiredOption("--in <target>", "Search target: registry:name[@version] or https://github.com/org/repo[#ref]", collectRepeatable3, []).addOption(new Option3("--source <source>", "Source to search (repeatable; default: auto)").choices(["docs", "code", "symbol"]).argParser((value, previous = []) => collectRepeatable3(value.toLowerCase(), previous)).default(undefined)).addOption(new Option3("--kind <kind>", "Precise symbol kind filter").choices([
|
|
12474
13612
|
...knownSymbolKindList()
|
|
12475
13613
|
])).addOption(new Option3("--category <category>", "Broad symbol category filter").choices([...knownSymbolCategoryList()])).option("--path-prefix <prefix>", "Repository path prefix filter").addOption(new Option3("--intent <intent>", "File intent filter (omit to search across all intents)").choices([
|
|
12476
13614
|
"production",
|
|
@@ -12485,7 +13623,7 @@ function registerSearchCommand(program) {
|
|
|
12485
13623
|
const deps = await loadContainer2();
|
|
12486
13624
|
await searchAction(query, options, deps);
|
|
12487
13625
|
});
|
|
12488
|
-
program.command("search-status").summary("Check status of a
|
|
13626
|
+
program.command("search-status").summary("Check the status of a previous search").description(SEARCH_STATUS_DESCRIPTION).argument("<search-ref>", "Search reference returned by githits search").option("--json", "Output as JSON").action(async (searchRef, options) => {
|
|
12489
13627
|
const deps = await loadContainer2();
|
|
12490
13628
|
await searchStatusAction(searchRef, options, deps);
|
|
12491
13629
|
});
|
|
@@ -12504,7 +13642,7 @@ function requireSearchService(deps) {
|
|
|
12504
13642
|
return deps.codeNavigationService;
|
|
12505
13643
|
}
|
|
12506
13644
|
async function loadContainer2() {
|
|
12507
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
13645
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-681avsyw.js");
|
|
12508
13646
|
return createContainer2();
|
|
12509
13647
|
}
|
|
12510
13648
|
function parseTargetSpecs(specs) {
|
|
@@ -12969,6 +14107,10 @@ function formatUnifiedSearchMetadata(entry, _useColors) {
|
|
|
12969
14107
|
// src/cli.ts
|
|
12970
14108
|
var program = new Command;
|
|
12971
14109
|
var argv = process.argv.slice(2);
|
|
14110
|
+
if (argv.includes("--no-color")) {
|
|
14111
|
+
process.env.NO_COLOR = "1";
|
|
14112
|
+
}
|
|
14113
|
+
var useColors = shouldUseColors();
|
|
12972
14114
|
var commandSpans = new WeakMap;
|
|
12973
14115
|
var createUpdateCheckService = () => new NpmRegistryUpdateCheckService({
|
|
12974
14116
|
currentVersion: version,
|
|
@@ -13005,21 +14147,23 @@ var rootCliPreAction = createRootCliPreAction({
|
|
|
13005
14147
|
clearAuthSessionMetadata: clearAutoLoginAuthSessionMetadata,
|
|
13006
14148
|
loginFlow: (options, deps) => loginFlow(options, deps, stderrLoginOutput)
|
|
13007
14149
|
});
|
|
13008
|
-
program.name("githits").description("
|
|
14150
|
+
program.name("githits").description("Grounded open-source context for AI coding agents").version(version).option("--no-color", "Disable colored output").configureHelp({
|
|
14151
|
+
styleTitle: (title) => colorizeBrand(title, "primary", useColors, { bold: true })
|
|
14152
|
+
}).hook("preAction", async (thisCommand, actionCommand) => {
|
|
13009
14153
|
const command = actionCommand ?? thisCommand;
|
|
13010
14154
|
commandSpans.set(command, startTelemetrySpan(getTelemetryCommandName(command)));
|
|
13011
14155
|
await rootCliPreAction(thisCommand, actionCommand);
|
|
13012
14156
|
}).hook("postAction", (_thisCommand, actionCommand) => {
|
|
13013
14157
|
endTelemetrySpan(commandSpans.get(actionCommand));
|
|
13014
14158
|
}).addHelpText("after", `
|
|
13015
|
-
Getting started:
|
|
13016
|
-
githits init
|
|
13017
|
-
githits login
|
|
14159
|
+
${colorizeBrand("Getting started:", "primary", useColors, { bold: true })}
|
|
14160
|
+
githits init Connect GitHits to your coding agents
|
|
14161
|
+
githits login Sign in to your GitHits account
|
|
13018
14162
|
githits mcp Show MCP setup instructions
|
|
13019
|
-
githits example "query"
|
|
14163
|
+
githits example "query" Find real-world implementations
|
|
13020
14164
|
|
|
13021
14165
|
Learn more at https://githits.com
|
|
13022
|
-
Docs: https://
|
|
14166
|
+
Docs: https://docs.githits.com
|
|
13023
14167
|
Support: support@githits.com`);
|
|
13024
14168
|
registerInitCommand(program);
|
|
13025
14169
|
registerLoginCommand(program);
|