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/dist/cli.js CHANGED
@@ -51,11 +51,11 @@ import {
51
51
  shouldRunUpdateCheck,
52
52
  startTelemetrySpan,
53
53
  withTelemetrySpan
54
- } from "./shared/chunk-f16s86ze.js";
54
+ } from "./shared/chunk-6hq1gf0g.js";
55
55
  import {
56
56
  __require,
57
57
  version
58
- } from "./shared/chunk-a1hzwt2m.js";
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("Authenticate with your GitHits account").description(LOGIN_DESCRIPTION).option("--no-browser", "Print URL instead of opening browser").option("--port <port>", "Port for local callback server", parseInt).option("--force", "Re-authenticate even if already logged in").action(async (options) => {
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 result = await deps.codeNavigationService.listFiles(build.params);
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 result = await deps.codeNavigationService.grepRepo(build.params);
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-2fdxv7pe.js");
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 result = await deps.codeNavigationService.readFile(build.params);
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("Source-level operations on indexed dependencies").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`.");
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 result = await deps.packageIntelligenceService.listPackageDocs(build.params);
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 result = await deps.packageIntelligenceService.readPackageDoc(build.params);
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 mixed 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.");
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("Get code examples from global open source").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) => {
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-2fdxv7pe.js");
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 on a tool result or the GitHits 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) => {
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
- let normalizedContent = content;
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
- const parsedConfig = parseConfigObject(existingContent);
8568
- if (parsedConfig.format === "invalid") {
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: `${JSON.stringify(config, null, 2)}
8598
- `
8950
+ content: renderConfigObjectForFormat(config, format)
8599
8951
  };
8600
8952
  }
8601
- function removeServerConfig(existingContent, serversKey, serverName) {
8602
- const parsedConfig = parseConfigObject(existingContent);
8603
- if (parsedConfig.format === "invalid") {
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: `${JSON.stringify(config, null, 2)}
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 = parseConfigObject(content);
8663
- if (parsedConfig.format === "invalid") {
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 = parseConfigObject(content);
8685
- if (parsedConfig.format === "invalid") {
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 scanAgents(definitions, fs, execService) {
9518
- const result = {
9519
- needsSetup: [],
9520
- alreadyConfigured: [],
9521
- notDetected: []
9522
- };
9523
- for (const agent of definitions) {
9524
- let detected = false;
9525
- let setupContext;
9526
- if (agent.detectCommand) {
9527
- try {
9528
- const resolvedCommand = await agent.detectCommand(execService, fs);
9529
- if (resolvedCommand) {
9530
- detected = true;
9531
- setupContext = { command: resolvedCommand.command };
9532
- }
9533
- } catch {
9534
- detected = false;
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
- } else if (agent.detectionMethod === "binary" && agent.detectBinary) {
9913
+ }
9914
+ } else if (agent.detectionMethod === "hybrid") {
9915
+ let binaryDetected = false;
9916
+ let pathDetected = false;
9917
+ if (agent.detectBinary) {
9537
9918
  try {
9538
- detected = await agent.detectBinary(execService);
9919
+ binaryDetected = await agent.detectBinary(execService);
9539
9920
  } catch {
9540
- detected = false;
9921
+ binaryDetected = false;
9541
9922
  }
9542
- } else if (agent.detectionMethod === "path" && agent.detectPaths) {
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
- detected = true;
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
- if (!detected) {
9572
- result.notDetected.push(agent);
9573
- continue;
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
- const config = agent.getSetupConfig(fs, setupContext);
9576
- const scannedAgent = {
9577
- ...agent,
9578
- resolvedSetupConfig: config,
9579
- resolvedSetupContext: setupContext
9953
+ return {
9954
+ status: configured ? "already_configured" : "needs_setup",
9955
+ agent: scannedAgent
9580
9956
  };
9581
- if (agent.id === "gemini-cli" && config.method === "cli") {
9582
- if (config.checkCommand) {
9583
- const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
9584
- let configured = checkStatus === "configured";
9585
- if (!configured && checkStatus === "probe_failed") {
9586
- configured = await isGeminiExtensionInstalledFromFilesystem(fs);
9587
- }
9588
- if (configured) {
9589
- result.alreadyConfigured.push(scannedAgent);
9590
- } else {
9591
- result.needsSetup.push(scannedAgent);
9592
- }
9593
- } else {
9594
- result.needsSetup.push(scannedAgent);
9595
- }
9596
- } else if (await isSetupAlreadyConfigured(config, fs, execService)) {
9597
- result.alreadyConfigured.push(scannedAgent);
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.needsSetup.push(scannedAgent);
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(" Setup complete. You're ready to use GitHits.");
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(" Try a quick code example search:");
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(" Or ask your agent to explore a real codebase:");
9630
- console.log(" Use GitHits to inspect postgres/postgres and explain how the query planner selects join strategies.");
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
- async function verifyAgentConfigured(agent, fileSystemService, execService) {
9633
- const postCheck = await scanAgents([agent], fileSystemService, execService);
9634
- if (postCheck.alreadyConfigured.some((a) => a.id === agent.id)) {
9635
- return { ok: true };
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
- async function verifyAgentUnconfigured(agent, fileSystemService, execService) {
9649
- const postCheck = await scanAgents([agent], fileSystemService, execService);
9650
- if (postCheck.needsSetup.some((a) => a.id === agent.id)) {
9651
- return { ok: true };
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
- async function inspectSetupForUninstall(agent, config, fileSystemService, execService) {
9665
- if (config.method === "config-file") {
9666
- const check = await getConfigUninstallCheckStatus(config, fileSystemService);
9667
- if (check.status === "configured")
9668
- return "configured";
9669
- if (check.status === "not_configured")
9670
- return "not_configured";
9671
- return { status: "failed", message: check.message };
9672
- }
9673
- if (config.method === "cli") {
9674
- if (!config.checkCommand) {
9675
- return {
9676
- status: "failed",
9677
- message: `${agent.name} does not have a verified uninstall check command.`
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
- let continuedWithoutAuth = false;
9768
- console.log(`
9769
- ${colorize("GitHits", "bold", useColors)} — Set up MCP server for your coding agents
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
- for (const agent of scan.notDetected) {
9824
- console.log(` ${colorize(`${agent.name} not detected`, "dim", useColors)}`);
10965
+ if (options.detectAgents) {
10966
+ await runDetectAgentsMode(options, fileSystemService, execService, useColors);
10967
+ return;
9825
10968
  }
9826
- console.log();
9827
- if (scan.needsSetup.length === 0 && scan.alreadyConfigured.length === 0) {
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
- if (scan.needsSetup.length === 0) {
9833
- if (continuedWithoutAuth) {
9834
- console.log(" MCP is already configured, but authentication is still required.");
9835
- console.log(" Run `githits login` before using GitHits tools.\n");
10973
+ printInitIntro(useColors);
10974
+ if (!isInteractive) {
10975
+ if (options.yes) {
10976
+ printNonInteractiveYesRejected(useColors);
9836
10977
  return;
9837
10978
  }
9838
- console.log(" All detected agents are already configured.");
9839
- printReadyNextSteps();
10979
+ printNonInteractiveInitGuidance(useColors);
9840
10980
  console.log();
9841
10981
  return;
9842
10982
  }
9843
- const toSetup = scan.needsSetup;
9844
- const outcomes = [];
9845
- let alwaysMode = options.yes ?? false;
9846
- for (const agent of toSetup) {
9847
- console.log(` Setting up ${colorize(agent.name, "bold", useColors)}...
9848
- `);
9849
- const config = agent.resolvedSetupConfig ?? agent.getSetupConfig(fileSystemService);
9850
- const preview2 = formatSetupPreview(config);
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
- return;
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
- let result = config.method === "cli" ? await executeCliSetup(config, execService) : config.method === "config-file" ? await executeConfigFileSetup(config, fileSystemService) : await executeCompositeSetup(config, fileSystemService, execService);
9879
- if (result.status === "success" || result.status === "already_configured") {
9880
- const verification = await verifyAgentConfigured(agent, fileSystemService, execService);
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
- outcomes.push({
9889
- id: agent.id,
9890
- name: agent.name,
9891
- status: result.status,
9892
- message: result.status === "failed" ? result.message : undefined
9893
- });
9894
- if (result.status === "success") {
9895
- console.log(` ${success(`${agent.name} configured`, useColors)}
9896
- `);
9897
- } else if (result.status === "already_configured") {
9898
- console.log(` ${warning(`${agent.name} already configured`, useColors)}
9899
- `);
9900
- } else {
9901
- console.log(` ${error(result.message, useColors)}
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
- printReadyNextSteps();
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} agent${failed !== 1 ? "s" : ""} failed to configure.`);
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 (skipped > 0) {
9926
- console.log(` ${skipped} agent${skipped !== 1 ? "s" : ""} skipped.`);
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)} — Remove MCP server from your coding agents
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(" githits auth status");
10075
- console.log(" githits login --force");
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 = `Set up GitHits MCP server for your coding agents.
11229
+ var INIT_DESCRIPTION = `Connect GitHits to your coding agents.
10081
11230
 
10082
- Authenticates with your GitHits account, then scans for available agents
10083
- (Claude Code, Cursor, Windsurf, VS Code, Cline, Claude Desktop, Codex CLI,
10084
- Pi, Gemini CLI, Google Antigravity), checks which are already configured,
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("Set up MCP server for your coding agents").description(INIT_DESCRIPTION).option("-y, --yes", "Skip prompts, configure all detected agents").option("--skip-login", "Skip authentication step").action(async (options) => {
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
- if (!auth) {
10171
- console.log(`Not currently logged in.
11313
+ console.log(`Logged out.
10172
11314
  `);
10173
- console.log(` Environment: ${mcpUrl}`);
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: info, vulnerabilities, dependencies, changelog, upgrade reviews").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`.");
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 outcome = await service.search(built.params);
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("Search indexed dependency and repository code, 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([
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 prior search").description(SEARCH_STATUS_DESCRIPTION).argument("<search-ref>", "Search reference returned by githits search").option("--json", "Output as JSON").action(async (searchRef, options) => {
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-2fdxv7pe.js");
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("Code examples from global open source for your AI assistant").version(version).option("--no-color", "Disable colored output").hook("preAction", async (thisCommand, actionCommand) => {
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 Set up MCP for your coding agents
13017
- githits login Authenticate with your GitHits account
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" Get code examples
14163
+ githits example "query" Find real-world implementations
13020
14164
 
13021
14165
  Learn more at https://githits.com
13022
- Docs: https://app.githits.com/docs/
14166
+ Docs: https://docs.githits.com
13023
14167
  Support: support@githits.com`);
13024
14168
  registerInitCommand(program);
13025
14169
  registerLoginCommand(program);