githits 0.4.7 → 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 +1 -1
- package/dist/cli.js +712 -75
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-70bn2pmx.js → chunk-681avsyw.js} +2 -2
- package/dist/shared/{chunk-tm11qwgr.js → chunk-6hq1gf0g.js} +1 -1
- package/dist/shared/{chunk-7b4f7fd5.js → chunk-wyphcypv.js} +1 -1
- package/gemini-extension.json +1 -1
- package/package.json +2 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
package/.plugin/plugin.json
CHANGED
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npx githits init
|
|
|
12
12
|
|
|
13
13
|
`init` walks you through setup: choose the local MCP server or Agent Skills, detect your coding tools, sign in, and connect everything to GitHits.
|
|
14
14
|
|
|
15
|
-
Supported tools: Claude Code, Cursor, Windsurf, VS Code / Copilot, Cline, Claude Desktop, Codex CLI, Pi, Gemini CLI, Google Antigravity, and
|
|
15
|
+
Supported tools: Claude Code, Cursor, Windsurf, VS Code / Copilot, Cline, Claude Desktop, Codex CLI, Pi, Gemini CLI, Google Antigravity, OpenCode, and Hermes Agent.
|
|
16
16
|
|
|
17
17
|
If you are using a tool that is not listed above, use the manual MCP setup instructions near the end of this README.
|
|
18
18
|
|
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";
|
|
@@ -8001,7 +8001,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
|
|
|
8001
8001
|
match in --verbose output; full payload in --json).`;
|
|
8002
8002
|
function registerCodeGrepCommand(pkgCommand) {
|
|
8003
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) => {
|
|
8004
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
8004
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-681avsyw.js");
|
|
8005
8005
|
const deps = await createContainer2();
|
|
8006
8006
|
await pkgGrepAction(arg1, arg2, arg3, options, {
|
|
8007
8007
|
codeNavigationService: deps.codeNavigationService,
|
|
@@ -8465,7 +8465,7 @@ function registerExampleCommand(program) {
|
|
|
8465
8465
|
});
|
|
8466
8466
|
}
|
|
8467
8467
|
async function loadContainer() {
|
|
8468
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
8468
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-681avsyw.js");
|
|
8469
8469
|
return createContainer2();
|
|
8470
8470
|
}
|
|
8471
8471
|
// src/commands/feedback.ts
|
|
@@ -8529,11 +8529,21 @@ import {
|
|
|
8529
8529
|
parse as parseJsonc,
|
|
8530
8530
|
printParseErrorCode
|
|
8531
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
|
+
}
|
|
8532
8545
|
function parseConfigObject(content) {
|
|
8533
|
-
|
|
8534
|
-
if (normalizedContent.charCodeAt(0) === 65279) {
|
|
8535
|
-
normalizedContent = normalizedContent.slice(1);
|
|
8536
|
-
}
|
|
8546
|
+
const normalizedContent = normalizeConfigContent(content);
|
|
8537
8547
|
const trimmed = normalizedContent.trim();
|
|
8538
8548
|
if (trimmed === "") {
|
|
8539
8549
|
return {
|
|
@@ -8581,6 +8591,221 @@ function parseConfigObject(content) {
|
|
|
8581
8591
|
};
|
|
8582
8592
|
}
|
|
8583
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
|
+
}
|
|
8584
8809
|
function isPlainObject(value) {
|
|
8585
8810
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8586
8811
|
}
|
|
@@ -8688,9 +8913,12 @@ function getMatchingServerKeys(servers, serverName) {
|
|
|
8688
8913
|
const normalizedTarget = serverName.toLowerCase();
|
|
8689
8914
|
return Object.keys(servers).filter((key) => key.toLowerCase() === normalizedTarget);
|
|
8690
8915
|
}
|
|
8691
|
-
function mergeServerConfig(existingContent, serversKey, serverName, serverConfig) {
|
|
8692
|
-
|
|
8693
|
-
|
|
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) {
|
|
8694
8922
|
return {
|
|
8695
8923
|
status: "parse_error",
|
|
8696
8924
|
error: parsedConfig.error
|
|
@@ -8719,13 +8947,15 @@ function mergeServerConfig(existingContent, serversKey, serverName, serverConfig
|
|
|
8719
8947
|
serversObj[serverName] = serverConfig;
|
|
8720
8948
|
return {
|
|
8721
8949
|
status: hadExisting ? "updated" : "added",
|
|
8722
|
-
content:
|
|
8723
|
-
`
|
|
8950
|
+
content: renderConfigObjectForFormat(config, format)
|
|
8724
8951
|
};
|
|
8725
8952
|
}
|
|
8726
|
-
function removeServerConfig(existingContent, serversKey, serverName) {
|
|
8727
|
-
|
|
8728
|
-
|
|
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) {
|
|
8729
8959
|
return {
|
|
8730
8960
|
status: "parse_error",
|
|
8731
8961
|
error: parsedConfig.error
|
|
@@ -8752,8 +8982,7 @@ function removeServerConfig(existingContent, serversKey, serverName) {
|
|
|
8752
8982
|
}
|
|
8753
8983
|
return {
|
|
8754
8984
|
status: "removed",
|
|
8755
|
-
content:
|
|
8756
|
-
`
|
|
8985
|
+
content: renderConfigObjectForFormat(config, format)
|
|
8757
8986
|
};
|
|
8758
8987
|
}
|
|
8759
8988
|
function formatUninstallPreview(config) {
|
|
@@ -8770,8 +8999,8 @@ function formatUninstallPreview(config) {
|
|
|
8770
8999
|
async function isAlreadyConfigured(config, fs) {
|
|
8771
9000
|
try {
|
|
8772
9001
|
const content = await fs.readFile(config.configPath);
|
|
8773
|
-
const parsedConfig =
|
|
8774
|
-
if (
|
|
9002
|
+
const parsedConfig = parseConfigObjectForFormat(content, config.format);
|
|
9003
|
+
if ("error" in parsedConfig) {
|
|
8775
9004
|
return false;
|
|
8776
9005
|
}
|
|
8777
9006
|
const parsed = parsedConfig.value;
|
|
@@ -8792,21 +9021,21 @@ async function isAlreadyConfigured(config, fs) {
|
|
|
8792
9021
|
async function getConfigUninstallCheckStatus(config, fs) {
|
|
8793
9022
|
try {
|
|
8794
9023
|
const content = await fs.readFile(config.configPath);
|
|
8795
|
-
const parsedConfig =
|
|
8796
|
-
if (
|
|
9024
|
+
const parsedConfig = parseConfigObjectForFormat(content, config.format);
|
|
9025
|
+
if ("error" in parsedConfig) {
|
|
8797
9026
|
return {
|
|
8798
9027
|
status: "failed",
|
|
8799
9028
|
message: `Cannot parse ${config.configPath}: ${parsedConfig.error}. File left unchanged.`
|
|
8800
9029
|
};
|
|
8801
9030
|
}
|
|
8802
9031
|
const servers = parsedConfig.value[config.serversKey];
|
|
8803
|
-
if (servers === undefined) {
|
|
9032
|
+
if (servers === undefined || servers === null) {
|
|
8804
9033
|
return { status: "not_configured" };
|
|
8805
9034
|
}
|
|
8806
9035
|
if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
|
|
8807
9036
|
return {
|
|
8808
9037
|
status: "failed",
|
|
8809
|
-
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.`
|
|
8810
9039
|
};
|
|
8811
9040
|
}
|
|
8812
9041
|
const hasEntry = getMatchingServerKeys(servers, config.serverName).length > 0;
|
|
@@ -9065,7 +9294,7 @@ async function executeConfigFileSetup(setup, fs) {
|
|
|
9065
9294
|
};
|
|
9066
9295
|
}
|
|
9067
9296
|
}
|
|
9068
|
-
const result = mergeServerConfig(existingContent, setup.serversKey, setup.serverName, setup.serverConfig);
|
|
9297
|
+
const result = mergeServerConfig(existingContent, setup.serversKey, setup.serverName, setup.serverConfig, setup.format);
|
|
9069
9298
|
if (result.status === "already_configured") {
|
|
9070
9299
|
return {
|
|
9071
9300
|
status: "already_configured",
|
|
@@ -9130,7 +9359,7 @@ async function executeConfigFileUninstall(setup, fs) {
|
|
|
9130
9359
|
message: `Cannot read ${setup.configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
9131
9360
|
};
|
|
9132
9361
|
}
|
|
9133
|
-
const result = removeServerConfig(existingContent, setup.serversKey, setup.serverName);
|
|
9362
|
+
const result = removeServerConfig(existingContent, setup.serversKey, setup.serverName, setup.format);
|
|
9134
9363
|
if (result.status === "not_configured") {
|
|
9135
9364
|
return {
|
|
9136
9365
|
status: "not_configured",
|
|
@@ -9218,6 +9447,16 @@ function getPiAgentDir(fs) {
|
|
|
9218
9447
|
function getPiMcpConfigPath(fs) {
|
|
9219
9448
|
return fs.joinPath(getPiAgentDir(fs), "mcp.json");
|
|
9220
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
|
+
}
|
|
9221
9460
|
function getOpenCodeDesktopDetectPaths(fs) {
|
|
9222
9461
|
const userDataRoot = getUserDataRoot(fs);
|
|
9223
9462
|
return [
|
|
@@ -9612,6 +9851,25 @@ var openCode = {
|
|
|
9612
9851
|
}
|
|
9613
9852
|
})
|
|
9614
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
|
+
};
|
|
9615
9873
|
var agentDefinitions = [
|
|
9616
9874
|
claudeCode,
|
|
9617
9875
|
cursor,
|
|
@@ -9623,7 +9881,8 @@ var agentDefinitions = [
|
|
|
9623
9881
|
pi,
|
|
9624
9882
|
geminiCli,
|
|
9625
9883
|
googleAntigravity,
|
|
9626
|
-
openCode
|
|
9884
|
+
openCode,
|
|
9885
|
+
hermesAgent
|
|
9627
9886
|
];
|
|
9628
9887
|
async function scanSingleAgent(agent, fs, execService) {
|
|
9629
9888
|
let detected = false;
|
|
@@ -9761,6 +10020,14 @@ function getPiConfigFileUninstall(agent, fileSystemService) {
|
|
|
9761
10020
|
function formatCommand(command, useColors) {
|
|
9762
10021
|
return colorizeBrand(command, "secondary", useColors, { bold: true });
|
|
9763
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.";
|
|
9764
10031
|
function printReadyNextSteps() {
|
|
9765
10032
|
console.log(" GitHits is now connected to your coding agents.");
|
|
9766
10033
|
console.log();
|
|
@@ -9793,6 +10060,57 @@ function printAuthNotCheckedNextSteps(useColors) {
|
|
|
9793
10060
|
console.log(" If your agent asks you to sign in, run:");
|
|
9794
10061
|
console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
|
|
9795
10062
|
}
|
|
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
|
+
}
|
|
9796
10114
|
var GITHITS_ASCII_LOGO = String.raw`
|
|
9797
10115
|
____ _ _ _ _ _ _
|
|
9798
10116
|
/ ___(_) |_| | | (_) |_ ___
|
|
@@ -9922,7 +10240,7 @@ function printTask(status, label, detail, useColors) {
|
|
|
9922
10240
|
}
|
|
9923
10241
|
function printInitIntro(useColors) {
|
|
9924
10242
|
console.log(colorizeLogo(GITHITS_ASCII_LOGO, useColors));
|
|
9925
|
-
console.log(" Your agent can read your local codebase.");
|
|
10243
|
+
console.log(" Your agent can only read your local codebase.");
|
|
9926
10244
|
console.log();
|
|
9927
10245
|
console.log(" GitHits lets it navigate the open-source code your app depends on.");
|
|
9928
10246
|
console.log();
|
|
@@ -10059,6 +10377,287 @@ function printScanSummary(scan, useColors) {
|
|
|
10059
10377
|
console.log(` Found ${detected} supported tool${detected !== 1 ? "s" : ""}.`);
|
|
10060
10378
|
}
|
|
10061
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
|
+
}
|
|
10062
10661
|
function printAuthExplanation() {
|
|
10063
10662
|
console.log(" GitHits authentication is required before your agent can use GitHits tools.");
|
|
10064
10663
|
console.log();
|
|
@@ -10184,6 +10783,62 @@ async function verifyAgentConfigured(agent, fileSystemService, execService) {
|
|
|
10184
10783
|
message: `${agent.name} verification failed: agent not detected after setup.`
|
|
10185
10784
|
};
|
|
10186
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
|
+
}
|
|
10187
10842
|
async function verifyAgentUnconfigured(agent, fileSystemService, execService) {
|
|
10188
10843
|
const postCheck = await scanAgents([agent], fileSystemService, execService);
|
|
10189
10844
|
if (postCheck.needsSetup.some((a) => a.id === agent.id)) {
|
|
@@ -10303,7 +10958,28 @@ async function scanAgentsForUninstall(fileSystemService, execService) {
|
|
|
10303
10958
|
async function initAction(options, deps) {
|
|
10304
10959
|
const useColors = shouldUseColors();
|
|
10305
10960
|
const { fileSystemService, promptService, execService, createLoginDeps } = deps;
|
|
10961
|
+
const isInteractive = deps.isInteractive ?? true;
|
|
10962
|
+
if (!validateInitModeOptions(options)) {
|
|
10963
|
+
return;
|
|
10964
|
+
}
|
|
10965
|
+
if (options.detectAgents) {
|
|
10966
|
+
await runDetectAgentsMode(options, fileSystemService, execService, useColors);
|
|
10967
|
+
return;
|
|
10968
|
+
}
|
|
10969
|
+
if (options.installAgents !== undefined) {
|
|
10970
|
+
await runInstallAgentsMode(options, fileSystemService, execService, createLoginDeps, useColors);
|
|
10971
|
+
return;
|
|
10972
|
+
}
|
|
10306
10973
|
printInitIntro(useColors);
|
|
10974
|
+
if (!isInteractive) {
|
|
10975
|
+
if (options.yes) {
|
|
10976
|
+
printNonInteractiveYesRejected(useColors);
|
|
10977
|
+
return;
|
|
10978
|
+
}
|
|
10979
|
+
printNonInteractiveInitGuidance(useColors);
|
|
10980
|
+
console.log();
|
|
10981
|
+
return;
|
|
10982
|
+
}
|
|
10307
10983
|
if (!options.yes) {
|
|
10308
10984
|
let intent;
|
|
10309
10985
|
try {
|
|
@@ -10322,7 +10998,7 @@ async function initAction(options, deps) {
|
|
|
10322
10998
|
return;
|
|
10323
10999
|
}
|
|
10324
11000
|
if (intent === "later") {
|
|
10325
|
-
console.log("\n No changes made. Run `githits init` whenever you're ready.\n");
|
|
11001
|
+
console.log("\n No changes made. Run `npx githits@latest init` whenever you're ready.\n");
|
|
10326
11002
|
return;
|
|
10327
11003
|
}
|
|
10328
11004
|
}
|
|
@@ -10361,7 +11037,6 @@ async function initAction(options, deps) {
|
|
|
10361
11037
|
} else {
|
|
10362
11038
|
printTask("success", "Selected all detected tools", "--yes", useColors);
|
|
10363
11039
|
}
|
|
10364
|
-
const outcomes = [];
|
|
10365
11040
|
if (toSetup.length === 0 && scan.needsSetup.length > 0) {
|
|
10366
11041
|
printTask("skipped", "Setup skipped", "no tools selected", useColors);
|
|
10367
11042
|
console.log();
|
|
@@ -10379,39 +11054,7 @@ async function initAction(options, deps) {
|
|
|
10379
11054
|
console.log();
|
|
10380
11055
|
return;
|
|
10381
11056
|
}
|
|
10382
|
-
const
|
|
10383
|
-
for (const agent of toSetup) {
|
|
10384
|
-
const config = getResolvedSetupConfig(agent, fileSystemService);
|
|
10385
|
-
const finishTask = installTasks.start(agent.name);
|
|
10386
|
-
let result;
|
|
10387
|
-
try {
|
|
10388
|
-
result = config.method === "cli" ? await executeCliSetup(config, execService) : config.method === "config-file" ? await executeConfigFileSetup(config, fileSystemService) : await executeCompositeSetup(config, fileSystemService, execService);
|
|
10389
|
-
if (result.status === "success" || result.status === "already_configured") {
|
|
10390
|
-
const verification = await verifyAgentConfigured(agent, fileSystemService, execService);
|
|
10391
|
-
if (!verification.ok) {
|
|
10392
|
-
result = {
|
|
10393
|
-
status: "failed",
|
|
10394
|
-
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.`
|
|
10395
|
-
};
|
|
10396
|
-
}
|
|
10397
|
-
}
|
|
10398
|
-
} finally {
|
|
10399
|
-
finishTask();
|
|
10400
|
-
}
|
|
10401
|
-
outcomes.push({
|
|
10402
|
-
id: agent.id,
|
|
10403
|
-
name: agent.name,
|
|
10404
|
-
status: result.status,
|
|
10405
|
-
message: result.status === "failed" ? result.message : undefined
|
|
10406
|
-
});
|
|
10407
|
-
if (result.status === "success") {
|
|
10408
|
-
printTask("success", agent.name, "configured and verified", useColors);
|
|
10409
|
-
} else if (result.status === "already_configured") {
|
|
10410
|
-
printTask("warning", agent.name, "already configured", useColors);
|
|
10411
|
-
} else {
|
|
10412
|
-
printTask("failed", agent.name, result.message, useColors);
|
|
10413
|
-
}
|
|
10414
|
-
}
|
|
11057
|
+
const outcomes = await installSelectedAgents(toSetup, scan, fileSystemService, execService, useColors, true);
|
|
10415
11058
|
console.log();
|
|
10416
11059
|
const configured = outcomes.filter((o) => o.status === "success").length;
|
|
10417
11060
|
const alreadyDone = outcomes.filter((o) => o.status === "already_configured").length + scan.alreadyConfigured.length;
|
|
@@ -10594,7 +11237,7 @@ Scans for available agents that currently have GitHits configured, then removes
|
|
|
10594
11237
|
only the GitHits MCP/plugin configuration with your confirmation. Authentication
|
|
10595
11238
|
tokens are not removed; use \`githits logout\` to remove stored credentials.`;
|
|
10596
11239
|
function registerInitCommand(program) {
|
|
10597
|
-
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").action(async (options) => {
|
|
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) => {
|
|
10598
11241
|
const fileSystemService = new FileSystemServiceImpl;
|
|
10599
11242
|
const promptService = new PromptServiceImpl;
|
|
10600
11243
|
const execService = new ExecServiceImpl;
|
|
@@ -10602,7 +11245,8 @@ function registerInitCommand(program) {
|
|
|
10602
11245
|
fileSystemService,
|
|
10603
11246
|
promptService,
|
|
10604
11247
|
execService,
|
|
10605
|
-
createLoginDeps: () => createContainer()
|
|
11248
|
+
createLoginDeps: () => createContainer(),
|
|
11249
|
+
isInteractive: process.stdin.isTTY === true && process.stdout.isTTY === true
|
|
10606
11250
|
});
|
|
10607
11251
|
});
|
|
10608
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) => {
|
|
@@ -10665,17 +11309,10 @@ function registerLanguagesCommand(program) {
|
|
|
10665
11309
|
// src/commands/logout.ts
|
|
10666
11310
|
async function logoutAction(deps) {
|
|
10667
11311
|
const { authStorage, mcpUrl } = deps;
|
|
10668
|
-
const auth = await authStorage.loadTokens(mcpUrl);
|
|
10669
11312
|
await authStorage.clearAuthSession(mcpUrl);
|
|
10670
|
-
|
|
10671
|
-
console.log(`Not currently logged in.
|
|
11313
|
+
console.log(`Logged out.
|
|
10672
11314
|
`);
|
|
10673
|
-
|
|
10674
|
-
} else {
|
|
10675
|
-
console.log(`Logged out.
|
|
10676
|
-
`);
|
|
10677
|
-
console.log(` Environment: ${mcpUrl}`);
|
|
10678
|
-
}
|
|
11315
|
+
console.log(` Environment: ${mcpUrl}`);
|
|
10679
11316
|
}
|
|
10680
11317
|
var LOGOUT_DESCRIPTION = `Remove stored credentials.
|
|
10681
11318
|
|
|
@@ -13005,7 +13642,7 @@ function requireSearchService(deps) {
|
|
|
13005
13642
|
return deps.codeNavigationService;
|
|
13006
13643
|
}
|
|
13007
13644
|
async function loadContainer2() {
|
|
13008
|
-
const { createContainer: createContainer2 } = await import("./shared/chunk-
|
|
13645
|
+
const { createContainer: createContainer2 } = await import("./shared/chunk-681avsyw.js");
|
|
13009
13646
|
return createContainer2();
|
|
13010
13647
|
}
|
|
13011
13648
|
function parseTargetSpecs(specs) {
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
createAuthStatusDependencies,
|
|
5
5
|
createContainer,
|
|
6
6
|
loadAutoLoginAuthSessionMetadata
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import"./chunk-
|
|
7
|
+
} from "./chunk-6hq1gf0g.js";
|
|
8
|
+
import"./chunk-wyphcypv.js";
|
|
9
9
|
export {
|
|
10
10
|
loadAutoLoginAuthSessionMetadata,
|
|
11
11
|
createContainer,
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "githits",
|
|
3
3
|
"description": "CLI companion for GitHits - code examples from global open source for developers and AI assistants",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.8",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"open": "^11.0.0",
|
|
84
84
|
"semver": "^7.8.0",
|
|
85
85
|
"smol-toml": "^1.6.1",
|
|
86
|
+
"yaml": "^2.9.0",
|
|
86
87
|
"zod": "^4.4.3"
|
|
87
88
|
},
|
|
88
89
|
"devDependencies": {
|