githits 0.4.6 → 0.4.7

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-tm11qwgr.js";
55
55
  import {
56
56
  __require,
57
57
  version
58
- } from "./shared/chunk-a1hzwt2m.js";
58
+ } from "./shared/chunk-7b4f7fd5.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-70bn2pmx.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-70bn2pmx.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);
@@ -8631,20 +8756,6 @@ function removeServerConfig(existingContent, serversKey, serverName) {
8631
8756
  `
8632
8757
  };
8633
8758
  }
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
8759
  function formatUninstallPreview(config) {
8649
8760
  if (config.method === "composite") {
8650
8761
  return config.steps.map(({ step }) => formatUninstallPreview(step)).join(`
@@ -9514,89 +9625,105 @@ var agentDefinitions = [
9514
9625
  googleAntigravity,
9515
9626
  openCode
9516
9627
  ];
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;
9628
+ async function scanSingleAgent(agent, fs, execService) {
9629
+ let detected = false;
9630
+ let setupContext;
9631
+ if (agent.detectCommand) {
9632
+ try {
9633
+ const resolvedCommand = await agent.detectCommand(execService, fs);
9634
+ if (resolvedCommand) {
9635
+ detected = true;
9636
+ setupContext = { command: resolvedCommand.command };
9535
9637
  }
9536
- } else if (agent.detectionMethod === "binary" && agent.detectBinary) {
9638
+ } catch {
9639
+ detected = false;
9640
+ }
9641
+ } else if (agent.detectionMethod === "binary" && agent.detectBinary) {
9642
+ try {
9643
+ detected = await agent.detectBinary(execService);
9644
+ } catch {
9645
+ detected = false;
9646
+ }
9647
+ } else if (agent.detectionMethod === "path" && agent.detectPaths) {
9648
+ const paths = agent.detectPaths(fs);
9649
+ for (const path of paths) {
9650
+ if (await fs.isDirectory(path)) {
9651
+ detected = true;
9652
+ break;
9653
+ }
9654
+ }
9655
+ } else if (agent.detectionMethod === "hybrid") {
9656
+ let binaryDetected = false;
9657
+ let pathDetected = false;
9658
+ if (agent.detectBinary) {
9537
9659
  try {
9538
- detected = await agent.detectBinary(execService);
9660
+ binaryDetected = await agent.detectBinary(execService);
9539
9661
  } catch {
9540
- detected = false;
9662
+ binaryDetected = false;
9541
9663
  }
9542
- } else if (agent.detectionMethod === "path" && agent.detectPaths) {
9664
+ }
9665
+ if (!binaryDetected && agent.detectPaths) {
9543
9666
  const paths = agent.detectPaths(fs);
9544
9667
  for (const path of paths) {
9545
9668
  if (await fs.isDirectory(path)) {
9546
- detected = true;
9669
+ pathDetected = true;
9547
9670
  break;
9548
9671
  }
9549
9672
  }
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
9673
  }
9571
- if (!detected) {
9572
- result.notDetected.push(agent);
9573
- continue;
9674
+ detected = binaryDetected || pathDetected;
9675
+ }
9676
+ if (!detected) {
9677
+ return { status: "not_detected", agent };
9678
+ }
9679
+ const config = agent.getSetupConfig(fs, setupContext);
9680
+ const scannedAgent = {
9681
+ ...agent,
9682
+ resolvedSetupConfig: config,
9683
+ resolvedSetupContext: setupContext
9684
+ };
9685
+ if (agent.id === "gemini-cli" && config.method === "cli") {
9686
+ if (!config.checkCommand) {
9687
+ return { status: "needs_setup", agent: scannedAgent };
9688
+ }
9689
+ const checkStatus = await getCliCheckStatus(config.checkCommand, execService);
9690
+ let configured = checkStatus === "configured";
9691
+ if (!configured && checkStatus === "probe_failed") {
9692
+ configured = await isGeminiExtensionInstalledFromFilesystem(fs);
9574
9693
  }
9575
- const config = agent.getSetupConfig(fs, setupContext);
9576
- const scannedAgent = {
9577
- ...agent,
9578
- resolvedSetupConfig: config,
9579
- resolvedSetupContext: setupContext
9694
+ return {
9695
+ status: configured ? "already_configured" : "needs_setup",
9696
+ agent: scannedAgent
9580
9697
  };
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);
9698
+ }
9699
+ if (await isSetupAlreadyConfigured(config, fs, execService)) {
9700
+ return { status: "already_configured", agent: scannedAgent };
9701
+ }
9702
+ return { status: "needs_setup", agent: scannedAgent };
9703
+ }
9704
+ async function scanAgents(definitions, fs, execService, options = {}) {
9705
+ const result = {
9706
+ needsSetup: [],
9707
+ alreadyConfigured: [],
9708
+ notDetected: []
9709
+ };
9710
+ let completed = 0;
9711
+ const outcomes = await Promise.all(definitions.map((agent) => scanSingleAgent(agent, fs, execService).then((outcome) => {
9712
+ completed += 1;
9713
+ options.onProgress?.({
9714
+ completed,
9715
+ total: definitions.length,
9716
+ agent: outcome.agent
9717
+ });
9718
+ return outcome;
9719
+ })));
9720
+ for (const outcome of outcomes) {
9721
+ if (outcome.status === "already_configured") {
9722
+ result.alreadyConfigured.push(outcome.agent);
9723
+ } else if (outcome.status === "needs_setup") {
9724
+ result.needsSetup.push(outcome.agent);
9598
9725
  } else {
9599
- result.needsSetup.push(scannedAgent);
9726
+ result.notDetected.push(outcome.agent);
9600
9727
  }
9601
9728
  }
9602
9729
  return result;
@@ -9606,6 +9733,17 @@ class ExitPromptError extends Error {
9606
9733
  name = "ExitPromptError";
9607
9734
  }
9608
9735
  // src/commands/init/init.ts
9736
+ function createInitLoginOutput() {
9737
+ return {
9738
+ write: (message) => {
9739
+ const lines = message.replace(/\n$/, "").split(`
9740
+ `);
9741
+ for (const line of lines) {
9742
+ console.log(line.length > 0 ? ` ${line}` : "");
9743
+ }
9744
+ }
9745
+ };
9746
+ }
9609
9747
  function getResolvedSetupConfig(agent, fileSystemService) {
9610
9748
  return agent.resolvedSetupConfig ?? agent.getSetupConfig(fileSystemService, agent.resolvedSetupContext);
9611
9749
  }
@@ -9620,14 +9758,415 @@ function getPiConfigFileUninstall(agent, fileSystemService) {
9620
9758
  const configStep = uninstallConfig.steps.map(({ step }) => step).find((step) => step.method === "config-file");
9621
9759
  return configStep ?? null;
9622
9760
  }
9761
+ function formatCommand(command, useColors) {
9762
+ return colorizeBrand(command, "secondary", useColors, { bold: true });
9763
+ }
9623
9764
  function printReadyNextSteps() {
9624
- console.log(" Setup complete. You're ready to use GitHits.");
9765
+ console.log(" GitHits is now connected to your coding agents.");
9766
+ console.log();
9767
+ console.log(" Here are some examples of the new abilities that your agent just got:");
9768
+ console.log();
9769
+ console.log(" • Find usage examples");
9770
+ console.log(" -> “Find a real example of using Azure Speech SDK TranscribeDefinition”");
9771
+ console.log();
9772
+ console.log(" • Search, grep, list files, and read exact lines in any repo or package to gather information");
9773
+ console.log(" -> “How does Next.js implement route prefetching internally?”");
9774
+ console.log();
9775
+ console.log(" • Inspect dependency versions, changelogs, and upgrade changes");
9776
+ console.log(" -> “What changed between pydantic-ai 1.95 and 1.99?”");
9777
+ console.log();
9778
+ console.log(" Open a new coding agent session and try out one of the above.");
9779
+ console.log();
9780
+ 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".');
9781
+ console.log();
9782
+ console.log(" See docs for more use cases and trigger guides: https://docs.githits.com");
9783
+ }
9784
+ function printAuthRequiredNextSteps(useColors) {
9785
+ console.log(" GitHits MCP is configured, but sign-in is still needed.");
9786
+ console.log();
9787
+ console.log(" Sign in when you're ready:");
9788
+ console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
9789
+ }
9790
+ function printAuthNotCheckedNextSteps(useColors) {
9791
+ console.log(" GitHits MCP is configured. Sign-in was not checked.");
9792
+ console.log();
9793
+ console.log(" If your agent asks you to sign in, run:");
9794
+ console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
9795
+ }
9796
+ var GITHITS_ASCII_LOGO = String.raw`
9797
+ ____ _ _ _ _ _ _
9798
+ / ___(_) |_| | | (_) |_ ___
9799
+ | | _| | __| |_| | | __/ __|
9800
+ | |_| | | |_| _ | | |_\__ \
9801
+ \____|_|\__|_| |_|_|\__|___/
9802
+ `;
9803
+ var LOGO_GRADIENT = [
9804
+ {
9805
+ until: 13,
9806
+ color: {
9807
+ hex: "#FF4FAE",
9808
+ rgb: [255, 79, 174],
9809
+ ansi256: 205,
9810
+ ansi16: "magenta"
9811
+ }
9812
+ },
9813
+ {
9814
+ until: 19,
9815
+ color: {
9816
+ hex: "#FF5D8E",
9817
+ rgb: [255, 93, 142],
9818
+ ansi256: 204,
9819
+ ansi16: "magenta"
9820
+ }
9821
+ },
9822
+ {
9823
+ until: 21,
9824
+ color: {
9825
+ hex: "#FF6B6F",
9826
+ rgb: [255, 107, 111],
9827
+ ansi256: 203,
9828
+ ansi16: "red"
9829
+ }
9830
+ },
9831
+ {
9832
+ until: 25,
9833
+ color: { hex: "#FF794F", rgb: [255, 121, 79], ansi256: 209, ansi16: "red" }
9834
+ },
9835
+ {
9836
+ until: 30,
9837
+ color: {
9838
+ hex: "#FF872F",
9839
+ rgb: [255, 135, 47],
9840
+ ansi256: 208,
9841
+ ansi16: "yellow"
9842
+ }
9843
+ }
9844
+ ];
9845
+ function colorizeLogo(logo, useColors) {
9846
+ if (!useColors) {
9847
+ return logo;
9848
+ }
9849
+ return logo.split(`
9850
+ `).map((line) => {
9851
+ if (line.length === 0) {
9852
+ return line;
9853
+ }
9854
+ let cursor2 = 0;
9855
+ let colored = "";
9856
+ for (const { until, color } of LOGO_GRADIENT) {
9857
+ if (cursor2 >= line.length) {
9858
+ break;
9859
+ }
9860
+ colored += colorizeTerminal(line.slice(cursor2, until), color, useColors);
9861
+ cursor2 = until;
9862
+ }
9863
+ return cursor2 < line.length ? colored + line.slice(cursor2) : colored;
9864
+ }).join(`
9865
+ `);
9866
+ }
9867
+ var INIT_INTENT_CHOICES = [
9868
+ {
9869
+ name: "Connect GitHits to my agent (Recommended)",
9870
+ value: "mcp",
9871
+ description: "Install the local GitHits MCP server for your coding agents. Allows agents to seamlessly use GitHits."
9872
+ },
9873
+ {
9874
+ name: "Use Agent Skills instead",
9875
+ value: "skills",
9876
+ description: "Use Skills instead of the local MCP server."
9877
+ },
9878
+ {
9879
+ name: "Exit",
9880
+ value: "later",
9881
+ description: "Leave setup without making changes."
9882
+ }
9883
+ ];
9884
+ var AUTH_RECOVERY_CHOICES = [
9885
+ { name: "Retry sign in", value: "retry" },
9886
+ {
9887
+ name: "Configure MCP without signing in",
9888
+ value: "continue_without_auth",
9889
+ description: "Your agent will ask you to sign in before using GitHits."
9890
+ },
9891
+ { name: "Cancel setup", value: "cancel" }
9892
+ ];
9893
+ var AUTH_START_CHOICES = [
9894
+ {
9895
+ name: "Sign in now",
9896
+ value: "sign_in",
9897
+ description: "Open your browser and connect this CLI to GitHits."
9898
+ },
9899
+ {
9900
+ name: "Skip for now",
9901
+ value: "skip",
9902
+ description: "Finish MCP setup and sign in later with `githits login`."
9903
+ },
9904
+ { name: "Cancel setup", value: "cancel" }
9905
+ ];
9906
+ function printSection(index, title, useColors) {
9907
+ console.log();
9908
+ console.log(` ${colorizeBrand(`${index}. ${title}`, "primary", useColors, { bold: true })}`);
9909
+ console.log(` ${colorize("-".repeat(title.length + 3), "dim", useColors)}`);
9910
+ }
9911
+ function printTask(status, label, detail, useColors) {
9912
+ const suffix = detail ? ` ${colorize(detail, "dim", useColors)}` : "";
9913
+ if (status === "success") {
9914
+ console.log(` ${success(label, useColors)}${suffix}`);
9915
+ } else if (status === "failed") {
9916
+ console.log(` ${error(label, useColors)}${suffix}`);
9917
+ } else if (status === "warning") {
9918
+ console.log(` ${warning(label, useColors)}${suffix}`);
9919
+ } else {
9920
+ console.log(` ${colorize("○", "dim", useColors)} ${label}${suffix}`);
9921
+ }
9922
+ }
9923
+ function printInitIntro(useColors) {
9924
+ console.log(colorizeLogo(GITHITS_ASCII_LOGO, useColors));
9925
+ console.log(" Your agent can read your local codebase.");
9926
+ console.log();
9927
+ console.log(" GitHits lets it navigate the open-source code your app depends on.");
9928
+ console.log();
9929
+ console.log(` ${colorizeBrand("With GitHits, your agent can:", "primary", useColors)}`);
9930
+ console.log(" • Find implementation examples from open-source code, issues, discussions, and pull requests");
9931
+ console.log(" • Search, grep, list files, and read exact lines in any repo or package");
9932
+ console.log(" • Inspect dependency internals, versions, changelogs, and upgrade changes");
9933
+ console.log(" • Access package documentation");
9934
+ console.log();
9935
+ console.log(" No cloning or local indexing required. GitHits handles everything automatically.");
9936
+ console.log();
9937
+ console.log(" Works with Cursor, Claude Code, Codex, OpenCode, Pi, VS Code, Windsurf, and more.");
9938
+ console.log();
9939
+ console.log(" More info: https://docs.githits.com");
9940
+ console.log();
9941
+ }
9942
+ function printSkillsInstructions(useColors) {
9943
+ console.log(`
9944
+ Install GitHits Agent Skills:`);
9945
+ console.log();
9946
+ console.log(` ${formatCommand("npx skills add githits-com/githits-cli", useColors)}`);
9947
+ console.log();
9948
+ console.log(" During setup, choose where you want to enable GitHits.");
9949
+ console.log();
9950
+ console.log(" IMPORTANT: Use either Agent Skills or the local MCP server in the same");
9951
+ console.log(" coding tool, not both.");
9952
+ console.log();
9953
+ console.log(" Then sign in so your agent can use GitHits:");
9954
+ console.log();
9955
+ console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
9956
+ console.log();
9957
+ }
9958
+ function startSafeInitScan(fileSystemService, execService, onProgress) {
9959
+ return scanAgents(agentDefinitions, fileSystemService, execService, {
9960
+ onProgress
9961
+ }).then((scan) => ({ ok: true, scan })).catch((error2) => ({
9962
+ ok: false,
9963
+ error: error2 instanceof Error ? error2 : new Error(String(error2))
9964
+ }));
9965
+ }
9966
+ function createScanProgressReporter(useColors) {
9967
+ if (!process.stdout.isTTY) {
9968
+ return { onProgress: () => {}, finish: () => {} };
9969
+ }
9970
+ let wrote = false;
9971
+ return {
9972
+ onProgress: (progress) => {
9973
+ const width = 20;
9974
+ const filled = Math.round(progress.completed / progress.total * width);
9975
+ const bar = `${colorizeBrand("#".repeat(filled), "primary", useColors)}${"-".repeat(width - filled)}`;
9976
+ const line = ` Scanning tools [${bar}] ${progress.completed}/${progress.total} ${progress.agent.name}`;
9977
+ process.stdout.write(`\r\x1B[2K${line}`);
9978
+ wrote = true;
9979
+ },
9980
+ finish: () => {
9981
+ if (wrote) {
9982
+ process.stdout.write("\r\x1B[2K");
9983
+ }
9984
+ }
9985
+ };
9986
+ }
9987
+ function createInstallTaskReporter(useColors) {
9988
+ if (!process.stdout.isTTY) {
9989
+ return {
9990
+ start: (label) => {
9991
+ printTask("skipped", label, "installing...", useColors);
9992
+ return () => {};
9993
+ }
9994
+ };
9995
+ }
9996
+ const frames = ["-", "\\", "|", "/"];
9997
+ return {
9998
+ start: (label) => {
9999
+ let frame = 0;
10000
+ const render = () => {
10001
+ const spinner = colorizeBrand(frames[frame % frames.length] ?? "-", "primary", useColors);
10002
+ frame += 1;
10003
+ process.stdout.write(`\r\x1B[2K ${spinner} ${label} installing...`);
10004
+ };
10005
+ render();
10006
+ const interval = setInterval(render, 80);
10007
+ return () => {
10008
+ clearInterval(interval);
10009
+ process.stdout.write("\r\x1B[2K");
10010
+ };
10011
+ }
10012
+ };
10013
+ }
10014
+ async function unwrapSafeScan(scanPromise) {
10015
+ const result = await scanPromise;
10016
+ if (!result.ok) {
10017
+ throw result.error;
10018
+ }
10019
+ return result.scan;
10020
+ }
10021
+ function formatAgentNames(agents) {
10022
+ if (agents.length === 0)
10023
+ return "none";
10024
+ if (agents.length === 1)
10025
+ return agents[0]?.name ?? "unknown";
10026
+ if (agents.length === 2) {
10027
+ return `${agents[0]?.name ?? "unknown"} and ${agents[1]?.name ?? "unknown"}`;
10028
+ }
10029
+ const names = agents.map((agent) => agent.name);
10030
+ return `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`;
10031
+ }
10032
+ function buildInitAgentChoices(scan) {
10033
+ return [
10034
+ ...scan.needsSetup.map((agent) => ({
10035
+ name: `${agent.name} (detected)`,
10036
+ value: agent,
10037
+ checked: true
10038
+ })),
10039
+ ...scan.alreadyConfigured.map((agent) => ({
10040
+ name: `${agent.name} (already configured)`,
10041
+ value: agent,
10042
+ disabled: "already configured"
10043
+ }))
10044
+ ];
10045
+ }
10046
+ function printScanSummary(scan, useColors) {
10047
+ const detected = scan.needsSetup.length + scan.alreadyConfigured.length;
10048
+ for (const agent of scan.alreadyConfigured) {
10049
+ printTask("success", agent.name, "already configured", useColors);
10050
+ }
10051
+ for (const agent of scan.needsSetup) {
10052
+ printTask("warning", agent.name, "needs setup", useColors);
10053
+ }
10054
+ if (scan.notDetected.length > 0) {
10055
+ printTask("skipped", `${scan.notDetected.length} supported tool${scan.notDetected.length !== 1 ? "s" : ""} not found`, formatAgentNames(scan.notDetected), useColors);
10056
+ }
10057
+ if (detected > 0) {
10058
+ console.log();
10059
+ console.log(` Found ${detected} supported tool${detected !== 1 ? "s" : ""}.`);
10060
+ }
10061
+ }
10062
+ function printAuthExplanation() {
10063
+ console.log(" GitHits authentication is required before your agent can use GitHits tools.");
10064
+ console.log();
10065
+ console.log(" We'll open your browser to connect your account.");
10066
+ console.log(" Credentials are stored securely in your OS keychain.");
9625
10067
  console.log();
9626
- console.log(" Try a quick code example search:");
9627
- console.log(' npx githits@latest example "How do I use useEffect cleanup?"');
10068
+ console.log(" No API keys or secrets are written into your MCP config.");
9628
10069
  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.");
10070
+ }
10071
+ async function runInitAuthentication(options, promptService, createLoginDeps, useColors) {
10072
+ if (options.skipLogin) {
10073
+ console.log(` Skipping authentication (--skip-login).
10074
+ `);
10075
+ return "skipped";
10076
+ }
10077
+ if (!createLoginDeps) {
10078
+ printTask("warning", "Sign-in unavailable", "sign in later with `githits login`", useColors);
10079
+ return "unavailable";
10080
+ }
10081
+ while (true) {
10082
+ let loginResult;
10083
+ try {
10084
+ const loginDeps = await createLoginDeps();
10085
+ if (loginDeps.hasValidToken) {
10086
+ printTask("success", "Already signed in", undefined, useColors);
10087
+ return "authenticated";
10088
+ }
10089
+ printAuthExplanation();
10090
+ if (!options.yes) {
10091
+ let authChoice;
10092
+ try {
10093
+ authChoice = await promptService.select(" Continue with browser sign-in?", AUTH_START_CHOICES, "sign_in");
10094
+ } catch (err) {
10095
+ if (err instanceof ExitPromptError) {
10096
+ console.log(`
10097
+ Setup cancelled.
10098
+ `);
10099
+ return "cancelled";
10100
+ }
10101
+ throw err;
10102
+ }
10103
+ if (authChoice === "skip") {
10104
+ printTask("warning", "Sign-in skipped", "your agent will ask you to sign in later", useColors);
10105
+ return "skipped";
10106
+ }
10107
+ if (authChoice === "cancel") {
10108
+ console.log("\n Setup cancelled. Run `githits login` to authenticate.\n");
10109
+ return "cancelled";
10110
+ }
10111
+ }
10112
+ loginResult = await loginFlow({}, loginDeps, createInitLoginOutput());
10113
+ } catch (error2) {
10114
+ const msg = error2 instanceof Error ? error2.message : String(error2);
10115
+ loginResult = { status: "failed", message: msg };
10116
+ }
10117
+ if (loginResult.status === "already_authenticated") {
10118
+ printTask("success", "Already signed in", undefined, useColors);
10119
+ return "authenticated";
10120
+ }
10121
+ if (loginResult.status === "success") {
10122
+ printTask("success", "Signed in successfully", undefined, useColors);
10123
+ return "authenticated";
10124
+ }
10125
+ console.log(` ${warning(`Login failed: ${loginResult.message}`, useColors)}
10126
+ `);
10127
+ printAuthRecoveryHint(useColors);
10128
+ if (options.yes) {
10129
+ console.log(` Continuing without authentication...
10130
+ `);
10131
+ return "failed_continue";
10132
+ }
10133
+ let choice;
10134
+ try {
10135
+ choice = await promptService.select(" Authentication failed. What would you like to do?", AUTH_RECOVERY_CHOICES, "retry");
10136
+ } catch (err) {
10137
+ if (err instanceof ExitPromptError) {
10138
+ console.log(`
10139
+ Setup cancelled.
10140
+ `);
10141
+ return "cancelled";
10142
+ }
10143
+ throw err;
10144
+ }
10145
+ if (choice === "retry") {
10146
+ console.log();
10147
+ continue;
10148
+ }
10149
+ if (choice === "cancel") {
10150
+ console.log("\n Setup cancelled. Run `githits login` to authenticate.\n");
10151
+ return "cancelled";
10152
+ }
10153
+ console.log(` Continuing without authentication...
10154
+ `);
10155
+ return "failed_continue";
10156
+ }
10157
+ }
10158
+ function shouldPrintReady(authStatus) {
10159
+ return authStatus === "authenticated";
10160
+ }
10161
+ function printPostSetupNextSteps(authStatus, useColors) {
10162
+ printSection(5, shouldPrintReady(authStatus) ? "Ready" : "Next Steps", useColors);
10163
+ if (shouldPrintReady(authStatus)) {
10164
+ printReadyNextSteps();
10165
+ } else if (authStatus === "failed_continue") {
10166
+ printAuthRequiredNextSteps(useColors);
10167
+ } else {
10168
+ printAuthNotCheckedNextSteps(useColors);
10169
+ }
9631
10170
  }
9632
10171
  async function verifyAgentConfigured(agent, fileSystemService, execService) {
9633
10172
  const postCheck = await scanAgents([agent], fileSystemService, execService);
@@ -9764,126 +10303,100 @@ async function scanAgentsForUninstall(fileSystemService, execService) {
9764
10303
  async function initAction(options, deps) {
9765
10304
  const useColors = shouldUseColors();
9766
10305
  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;
10306
+ printInitIntro(useColors);
10307
+ if (!options.yes) {
10308
+ let intent;
9775
10309
  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.
10310
+ intent = await promptService.select(" What do you want to do?", INIT_INTENT_CHOICES, "mcp");
10311
+ } catch (err) {
10312
+ if (err instanceof ExitPromptError) {
10313
+ console.log(`
10314
+ Setup cancelled. No changes made.
9803
10315
  `);
9804
- return;
9805
- }
9806
- throw err;
9807
- }
10316
+ return;
9808
10317
  }
9809
- continuedWithoutAuth = true;
9810
- console.log(` Continuing without authentication...
9811
- `);
10318
+ throw err;
10319
+ }
10320
+ if (intent === "skills") {
10321
+ printSkillsInstructions(useColors);
10322
+ return;
10323
+ }
10324
+ if (intent === "later") {
10325
+ console.log("\n No changes made. Run `githits init` whenever you're ready.\n");
10326
+ return;
9812
10327
  }
9813
10328
  }
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)}`);
9822
- }
9823
- for (const agent of scan.notDetected) {
9824
- console.log(` ${colorize(`${agent.name} — not detected`, "dim", useColors)}`);
10329
+ printSection(1, "Detect tools", useColors);
10330
+ console.log(" Scanning for compatible AI coding tools...");
10331
+ const progress = createScanProgressReporter(useColors);
10332
+ const scanPromise = startSafeInitScan(fileSystemService, execService, (scanProgress) => progress.onProgress(scanProgress));
10333
+ let scan;
10334
+ try {
10335
+ scan = await unwrapSafeScan(scanPromise);
10336
+ } finally {
10337
+ progress.finish();
9825
10338
  }
9826
- console.log();
10339
+ printScanSummary(scan, useColors);
9827
10340
  if (scan.needsSetup.length === 0 && scan.alreadyConfigured.length === 0) {
9828
- console.log(` No coding agents detected. Install an agent and try again.
9829
- `);
10341
+ printTask("warning", "No supported AI coding tools detected", "install a supported tool and run `githits init` again", useColors);
10342
+ console.log();
9830
10343
  return;
9831
10344
  }
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");
9836
- return;
10345
+ let toSetup = scan.needsSetup;
10346
+ printSection(2, "Choose tools", useColors);
10347
+ if (!options.yes && scan.needsSetup.length > 0) {
10348
+ try {
10349
+ toSetup = await promptService.checkbox(" Select which tools should use GitHits:", buildInitAgentChoices(scan));
10350
+ } catch (err) {
10351
+ if (err instanceof ExitPromptError) {
10352
+ console.log(`
10353
+ Setup cancelled. No changes made.
10354
+ `);
10355
+ return;
10356
+ }
10357
+ throw err;
9837
10358
  }
9838
- console.log(" All detected agents are already configured.");
9839
- printReadyNextSteps();
10359
+ } else if (scan.needsSetup.length === 0) {
10360
+ printTask("success", "No tool changes needed", "all detected tools already have GitHits MCP", useColors);
10361
+ } else {
10362
+ printTask("success", "Selected all detected tools", "--yes", useColors);
10363
+ }
10364
+ const outcomes = [];
10365
+ if (toSetup.length === 0 && scan.needsSetup.length > 0) {
10366
+ printTask("skipped", "Setup skipped", "no tools selected", useColors);
9840
10367
  console.log();
9841
10368
  return;
9842
10369
  }
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
- }
10370
+ printSection(3, "Sign in", useColors);
10371
+ const authStatus = await runInitAuthentication(options, promptService, createLoginDeps, useColors);
10372
+ if (authStatus === "cancelled") {
10373
+ return;
10374
+ }
10375
+ printSection(4, "Install and verify", useColors);
10376
+ if (toSetup.length === 0) {
10377
+ printTask("success", "Nothing to install", "all detected tools are already configured", useColors);
10378
+ printPostSetupNextSteps(authStatus, useColors);
9855
10379
  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.
9864
- `);
9865
- return;
10380
+ return;
10381
+ }
10382
+ const installTasks = createInstallTaskReporter(useColors);
10383
+ for (const agent of toSetup) {
10384
+ const config = getResolvedSetupConfig(agent, fileSystemService);
10385
+ const finishTask = installTasks.start(agent.name);
10386
+ let result;
10387
+ try {
10388
+ result = config.method === "cli" ? await executeCliSetup(config, execService) : config.method === "config-file" ? await executeConfigFileSetup(config, fileSystemService) : await executeCompositeSetup(config, fileSystemService, execService);
10389
+ if (result.status === "success" || result.status === "already_configured") {
10390
+ const verification = await verifyAgentConfigured(agent, fileSystemService, execService);
10391
+ if (!verification.ok) {
10392
+ result = {
10393
+ status: "failed",
10394
+ message: agent.id === "gemini-cli" ? "Gemini installation did not complete. Retry, or run: gemini extensions install --consent https://github.com/githits-com/githits-cli" : verification.message ?? `${agent.name} verification failed after setup.`
10395
+ };
9866
10396
  }
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;
9876
- }
9877
- }
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
10397
  }
10398
+ } finally {
10399
+ finishTask();
9887
10400
  }
9888
10401
  outcomes.push({
9889
10402
  id: agent.id,
@@ -9892,38 +10405,30 @@ async function initAction(options, deps) {
9892
10405
  message: result.status === "failed" ? result.message : undefined
9893
10406
  });
9894
10407
  if (result.status === "success") {
9895
- console.log(` ${success(`${agent.name} configured`, useColors)}
9896
- `);
10408
+ printTask("success", agent.name, "configured and verified", useColors);
9897
10409
  } else if (result.status === "already_configured") {
9898
- console.log(` ${warning(`${agent.name} already configured`, useColors)}
9899
- `);
10410
+ printTask("warning", agent.name, "already configured", useColors);
9900
10411
  } else {
9901
- console.log(` ${error(result.message, useColors)}
9902
- `);
10412
+ printTask("failed", agent.name, result.message, useColors);
9903
10413
  }
9904
10414
  }
10415
+ console.log();
9905
10416
  const configured = outcomes.filter((o) => o.status === "success").length;
9906
10417
  const alreadyDone = outcomes.filter((o) => o.status === "already_configured").length + scan.alreadyConfigured.length;
9907
10418
  const failed = outcomes.filter((o) => o.status === "failed").length;
9908
- const skipped = outcomes.filter((o) => o.status === "skipped").length;
9909
10419
  if (failed > 0) {
9910
10420
  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
10421
  } else if (configured > 0 || alreadyDone > 0) {
9915
- printReadyNextSteps();
9916
- } else if (skipped > 0) {
9917
- console.log(" Setup skipped.");
10422
+ printPostSetupNextSteps(authStatus, useColors);
9918
10423
  }
9919
10424
  if (failed > 0) {
9920
- console.log(` ${failed} agent${failed !== 1 ? "s" : ""} failed to configure.`);
10425
+ console.log(` ${failed} tool${failed !== 1 ? "s" : ""} failed to configure.`);
9921
10426
  for (const outcome of outcomes.filter((o) => o.status === "failed")) {
9922
10427
  console.log(` - ${outcome.name}: ${outcome.message ?? "Unknown error"}`);
9923
10428
  }
9924
10429
  }
9925
- if (skipped > 0) {
9926
- console.log(` ${skipped} agent${skipped !== 1 ? "s" : ""} skipped.`);
10430
+ if (scan.alreadyConfigured.length > 0) {
10431
+ console.log(` ${scan.alreadyConfigured.length} tool${scan.alreadyConfigured.length !== 1 ? "s" : ""} already configured.`);
9927
10432
  }
9928
10433
  console.log();
9929
10434
  }
@@ -9931,7 +10436,8 @@ async function initUninstallAction(options, deps) {
9931
10436
  const useColors = shouldUseColors();
9932
10437
  const { fileSystemService, promptService, execService } = deps;
9933
10438
  console.log(`
9934
- ${colorize("GitHits", "bold", useColors)} — Remove MCP server from your coding agents
10439
+ ${colorize("Disconnect GitHits from your coding agents.", "bold", useColors)}`);
10440
+ console.log(` ${colorize("Removes the local GitHits MCP configuration.", "dim", useColors)}
9935
10441
  `);
9936
10442
  console.log(` Scanning for configured agents...
9937
10443
  `);
@@ -10068,33 +10574,27 @@ async function initUninstallAction(options, deps) {
10068
10574
  }
10069
10575
  console.log();
10070
10576
  }
10071
- function printAuthRecoveryHint() {
10577
+ function printAuthRecoveryHint(useColors) {
10072
10578
  console.log(" You can still configure MCP, but GitHits tools will require auth.");
10073
10579
  console.log(" Recovery steps:");
10074
- console.log(" githits auth status");
10075
- console.log(" githits login --force");
10580
+ console.log(` ${formatCommand("githits auth status", useColors)}`);
10581
+ console.log(` ${formatCommand("githits login --force", useColors)}`);
10076
10582
  console.log(" For CI or locked-down machines, set GITHITS_API_TOKEN.");
10077
10583
  console.log(` If your system keychain is unavailable, set GITHITS_AUTH_STORAGE=file after accepting plaintext storage.
10078
10584
  `);
10079
10585
  }
10080
- var INIT_DESCRIPTION = `Set up GitHits MCP server for your coding agents.
10586
+ var INIT_DESCRIPTION = `Connect GitHits to your coding agents.
10081
10587
 
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.`;
10588
+ Installs the local GitHits MCP server the recommended way to connect — or
10589
+ sets up Agent Skills instead. Detects supported coding tools on this machine,
10590
+ signs you in, and configures the tools you select.`;
10091
10591
  var INIT_UNINSTALL_DESCRIPTION = `Remove GitHits MCP server configuration from your coding agents.
10092
10592
 
10093
10593
  Scans for available agents that currently have GitHits configured, then removes
10094
10594
  only the GitHits MCP/plugin configuration with your confirmation. Authentication
10095
10595
  tokens are not removed; use \`githits logout\` to remove stored credentials.`;
10096
10596
  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) => {
10597
+ const initCommand = program.command("init").summary("Connect GitHits to your coding agents").description(INIT_DESCRIPTION).option("-y, --yes", "Skip prompts, configure all detected tools").option("--skip-login", "Skip authentication step").action(async (options) => {
10098
10598
  const fileSystemService = new FileSystemServiceImpl;
10099
10599
  const promptService = new PromptServiceImpl;
10100
10600
  const execService = new ExecServiceImpl;
@@ -11807,7 +12307,7 @@ function showMcpSetupInstructions() {
11807
12307
  console.log("Learn more at https://githits.com");
11808
12308
  }
11809
12309
  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.
12310
+ 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
12311
 
11812
12312
  When run interactively (TTY), shows setup instructions.
11813
12313
  When run via stdio (non-TTY), starts the MCP server.
@@ -12379,7 +12879,7 @@ async function registerPkgCommandGroup(program, options = {}) {
12379
12879
  if (!registration.shouldRegister) {
12380
12880
  return;
12381
12881
  }
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`.");
12882
+ 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
12883
  registerPkgInfoCommand(pkgCommand);
12384
12884
  registerPkgVulnsCommand(pkgCommand);
12385
12885
  registerPkgDepsCommand(pkgCommand);
@@ -12408,7 +12908,8 @@ async function searchAction(query, options, deps) {
12408
12908
  offset: parseOptionalInt(options.offset, "--offset", 0),
12409
12909
  waitTimeoutMs: parseWaitMs(options.wait)
12410
12910
  });
12411
- const outcome = await service.search(built.params);
12911
+ const spinner = startSpinner(SPINNER_MESSAGES.search, !options.json);
12912
+ const outcome = await service.search(built.params).finally(() => spinner.stop());
12412
12913
  const payload = buildUnifiedSearchSuccessPayload(built.params, built.rawQuery, built.compiledQuery, outcome);
12413
12914
  if (options.json) {
12414
12915
  console.log(JSON.stringify(payload));
@@ -12470,7 +12971,7 @@ Pass the searchRef returned by githits search when the initial request could
12470
12971
  not complete within the wait window. This can return progress, partial hits when
12471
12972
  the original request used --allow-partial, or final results.`;
12472
12973
  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([
12974
+ 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
12975
  ...knownSymbolKindList()
12475
12976
  ])).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
12977
  "production",
@@ -12485,7 +12986,7 @@ function registerSearchCommand(program) {
12485
12986
  const deps = await loadContainer2();
12486
12987
  await searchAction(query, options, deps);
12487
12988
  });
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) => {
12989
+ 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
12990
  const deps = await loadContainer2();
12490
12991
  await searchStatusAction(searchRef, options, deps);
12491
12992
  });
@@ -12504,7 +13005,7 @@ function requireSearchService(deps) {
12504
13005
  return deps.codeNavigationService;
12505
13006
  }
12506
13007
  async function loadContainer2() {
12507
- const { createContainer: createContainer2 } = await import("./shared/chunk-2fdxv7pe.js");
13008
+ const { createContainer: createContainer2 } = await import("./shared/chunk-70bn2pmx.js");
12508
13009
  return createContainer2();
12509
13010
  }
12510
13011
  function parseTargetSpecs(specs) {
@@ -12969,6 +13470,10 @@ function formatUnifiedSearchMetadata(entry, _useColors) {
12969
13470
  // src/cli.ts
12970
13471
  var program = new Command;
12971
13472
  var argv = process.argv.slice(2);
13473
+ if (argv.includes("--no-color")) {
13474
+ process.env.NO_COLOR = "1";
13475
+ }
13476
+ var useColors = shouldUseColors();
12972
13477
  var commandSpans = new WeakMap;
12973
13478
  var createUpdateCheckService = () => new NpmRegistryUpdateCheckService({
12974
13479
  currentVersion: version,
@@ -13005,21 +13510,23 @@ var rootCliPreAction = createRootCliPreAction({
13005
13510
  clearAuthSessionMetadata: clearAutoLoginAuthSessionMetadata,
13006
13511
  loginFlow: (options, deps) => loginFlow(options, deps, stderrLoginOutput)
13007
13512
  });
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) => {
13513
+ program.name("githits").description("Grounded open-source context for AI coding agents").version(version).option("--no-color", "Disable colored output").configureHelp({
13514
+ styleTitle: (title) => colorizeBrand(title, "primary", useColors, { bold: true })
13515
+ }).hook("preAction", async (thisCommand, actionCommand) => {
13009
13516
  const command = actionCommand ?? thisCommand;
13010
13517
  commandSpans.set(command, startTelemetrySpan(getTelemetryCommandName(command)));
13011
13518
  await rootCliPreAction(thisCommand, actionCommand);
13012
13519
  }).hook("postAction", (_thisCommand, actionCommand) => {
13013
13520
  endTelemetrySpan(commandSpans.get(actionCommand));
13014
13521
  }).addHelpText("after", `
13015
- Getting started:
13016
- githits init Set up MCP for your coding agents
13017
- githits login Authenticate with your GitHits account
13522
+ ${colorizeBrand("Getting started:", "primary", useColors, { bold: true })}
13523
+ githits init Connect GitHits to your coding agents
13524
+ githits login Sign in to your GitHits account
13018
13525
  githits mcp Show MCP setup instructions
13019
- githits example "query" Get code examples
13526
+ githits example "query" Find real-world implementations
13020
13527
 
13021
13528
  Learn more at https://githits.com
13022
- Docs: https://app.githits.com/docs/
13529
+ Docs: https://docs.githits.com
13023
13530
  Support: support@githits.com`);
13024
13531
  registerInitCommand(program);
13025
13532
  registerLoginCommand(program);