githits 0.4.10 → 0.4.11

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
@@ -62,11 +62,11 @@ import {
62
62
  shouldRunUpdateCheck,
63
63
  startTelemetrySpan,
64
64
  withTelemetrySpan
65
- } from "./shared/chunk-p9ak72j0.js";
65
+ } from "./shared/chunk-5xd5rcm9.js";
66
66
  import {
67
67
  __require,
68
68
  version
69
- } from "./shared/chunk-fvpvx4x4.js";
69
+ } from "./shared/chunk-agpezzpd.js";
70
70
 
71
71
  // src/cli.ts
72
72
  import { Command } from "commander";
@@ -3619,9 +3619,9 @@ function isSeverityLabel(value) {
3619
3619
  // src/shared/package-upgrade-review-request.ts
3620
3620
  var DEFAULT_CHANGELOG_LIMIT = 20;
3621
3621
  function buildPackageUpgradeReviewRequest(input) {
3622
- const batch = input.packages;
3623
- const hasBatch = batch !== undefined;
3624
- const hasSingle = input.registry !== undefined || input.packageName !== undefined || input.currentVersion !== undefined || input.targetVersion !== undefined;
3622
+ const batch = input.packages?.filter((pkg) => !isBlankPackageInput(pkg));
3623
+ const hasBatch = Array.isArray(batch) && batch.length > 0;
3624
+ const hasSingle = hasNonBlankValue(input.registry) || hasNonBlankValue(input.packageName) || hasNonBlankValue(input.currentVersion) || hasNonBlankValue(input.targetVersion);
3625
3625
  if (hasBatch && hasSingle) {
3626
3626
  throw new InvalidPackageSpecError("Pass either packages[] or registry/package_name/current_version/target_version, not both.");
3627
3627
  }
@@ -3661,6 +3661,12 @@ function buildUpgradeDependencyProbeParams(pkg, version2, options) {
3661
3661
  includeDependencyChanges: options.includeDependencyChanges
3662
3662
  };
3663
3663
  }
3664
+ function hasNonBlankValue(value) {
3665
+ return value !== undefined && value.trim().length > 0;
3666
+ }
3667
+ function isBlankPackageInput(input) {
3668
+ return !(hasNonBlankValue(input.registry) || hasNonBlankValue(input.packageName) || hasNonBlankValue(input.currentVersion) || hasNonBlankValue(input.targetVersion));
3669
+ }
3664
3670
  function parseBatch(packages) {
3665
3671
  if (!Array.isArray(packages) || packages.length === 0) {
3666
3672
  throw new InvalidPackageSpecError("packages[] must contain at least one upgrade.");
@@ -6311,10 +6317,11 @@ function buildUnifiedSearchParams(input) {
6311
6317
  };
6312
6318
  }
6313
6319
  function resolveTargets(target, targets) {
6314
- if (target && targets) {
6320
+ const nonEmptyTargets = targets?.length ? targets : undefined;
6321
+ if (target && nonEmptyTargets) {
6315
6322
  throw new InvalidArgumentError("Provide either `target` for one search target or `targets` for multiple, not both.");
6316
6323
  }
6317
- const resolved = target ? [target] : targets ?? [];
6324
+ const resolved = target ? [target] : nonEmptyTargets ?? [];
6318
6325
  if (resolved.length === 0) {
6319
6326
  throw new InvalidArgumentError("Provide either `target` for one search target or `targets` for multiple; neither was set.");
6320
6327
  }
@@ -8076,7 +8083,7 @@ grep run. --symbol-field hydrates enclosing symbol metadata (appears under each
8076
8083
  match in --verbose output; full payload in --json).`;
8077
8084
  function registerCodeGrepCommand(pkgCommand) {
8078
8085
  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) => {
8079
- const { createContainer: createContainer2 } = await import("./shared/chunk-mw1910qd.js");
8086
+ const { createContainer: createContainer2 } = await import("./shared/chunk-2w48kb4c.js");
8080
8087
  const deps = await createContainer2();
8081
8088
  await pkgGrepAction(arg1, arg2, arg3, options, {
8082
8089
  codeNavigationService: deps.codeNavigationService,
@@ -9044,7 +9051,7 @@ function registerExampleCommand(program) {
9044
9051
  });
9045
9052
  }
9046
9053
  async function loadContainer() {
9047
- const { createContainer: createContainer2 } = await import("./shared/chunk-mw1910qd.js");
9054
+ const { createContainer: createContainer2 } = await import("./shared/chunk-2w48kb4c.js");
9048
9055
  return createContainer2();
9049
9056
  }
9050
9057
  // src/commands/feedback.ts
@@ -9117,6 +9124,7 @@ import {
9117
9124
  parse as parseJsonc,
9118
9125
  printParseErrorCode
9119
9126
  } from "jsonc-parser";
9127
+ import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
9120
9128
  import {
9121
9129
  isMap,
9122
9130
  isScalar,
@@ -9199,6 +9207,23 @@ function parseYamlConfigObject(content) {
9199
9207
  };
9200
9208
  }
9201
9209
  }
9210
+ function parseTomlConfigObject(content) {
9211
+ const normalizedContent = normalizeConfigContent(content);
9212
+ if (normalizedContent.trim() === "") {
9213
+ return { value: {} };
9214
+ }
9215
+ try {
9216
+ const parsed = parseToml2(normalizedContent);
9217
+ if (!isPlainObject(parsed)) {
9218
+ return { error: "Config file root is not a TOML object" };
9219
+ }
9220
+ return { value: parsed };
9221
+ } catch (err) {
9222
+ return {
9223
+ error: `Invalid TOML: ${err instanceof Error ? err.message : String(err)}`
9224
+ };
9225
+ }
9226
+ }
9202
9227
  function formatYamlError(error2) {
9203
9228
  if (error2 instanceof Error) {
9204
9229
  return error2.message;
@@ -9378,6 +9403,9 @@ function parseConfigObjectForFormat(content, format = "json") {
9378
9403
  if (format === "yaml") {
9379
9404
  return parseYamlConfigObject(content);
9380
9405
  }
9406
+ if (format === "toml") {
9407
+ return parseTomlConfigObject(content);
9408
+ }
9381
9409
  const parsed = parseConfigObject(content);
9382
9410
  if (parsed.format === "invalid") {
9383
9411
  return { error: parsed.error };
@@ -9389,11 +9417,24 @@ function renderConfigObjectForFormat(config, format = "json") {
9389
9417
  const rendered = stringifyYaml(config);
9390
9418
  return rendered.endsWith(`
9391
9419
  `) ? rendered : `${rendered}
9420
+ `;
9421
+ }
9422
+ if (format === "toml") {
9423
+ const rendered = stringifyToml(config);
9424
+ return rendered.endsWith(`
9425
+ `) ? rendered : `${rendered}
9392
9426
  `;
9393
9427
  }
9394
9428
  return `${JSON.stringify(config, null, 2)}
9395
9429
  `;
9396
9430
  }
9431
+ function getConfigObjectFormatName(format = "json") {
9432
+ if (format === "yaml")
9433
+ return "YAML";
9434
+ if (format === "toml")
9435
+ return "TOML";
9436
+ return "JSON";
9437
+ }
9397
9438
  function isPlainObject(value) {
9398
9439
  return typeof value === "object" && value !== null && !Array.isArray(value);
9399
9440
  }
@@ -9520,7 +9561,7 @@ function mergeServerConfig(existingContent, serversKey, serverName, serverConfig
9520
9561
  if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
9521
9562
  return {
9522
9563
  status: "parse_error",
9523
- error: `"${serversKey}" is not a JSON object`
9564
+ error: `"${serversKey}" is not a ${getConfigObjectFormatName(format)} object`
9524
9565
  };
9525
9566
  }
9526
9567
  const serversObj = servers;
@@ -9557,7 +9598,7 @@ function removeServerConfig(existingContent, serversKey, serverName, format = "j
9557
9598
  if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
9558
9599
  return {
9559
9600
  status: "parse_error",
9560
- error: `"${serversKey}" is not a JSON object`
9601
+ error: `"${serversKey}" is not a ${getConfigObjectFormatName(format)} object`
9561
9602
  };
9562
9603
  }
9563
9604
  const serversObj = servers;
@@ -9623,7 +9664,7 @@ async function getConfigUninstallCheckStatus(config, fs) {
9623
9664
  if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
9624
9665
  return {
9625
9666
  status: "failed",
9626
- message: `Cannot parse ${config.configPath}: "${config.serversKey}" is not a ${config.format === "yaml" ? "YAML" : "JSON"} object. File left unchanged.`
9667
+ message: `Cannot parse ${config.configPath}: "${config.serversKey}" is not a ${getConfigObjectFormatName(config.format)} object. File left unchanged.`
9627
9668
  };
9628
9669
  }
9629
9670
  const hasEntry = getMatchingServerKeys(servers, config.serverName).length > 0;
@@ -9688,7 +9729,7 @@ var ALREADY_EXISTS_PATTERNS = [
9688
9729
  var ALREADY_ABSENT_PATTERNS = [
9689
9730
  /(?:plugin|extension|server|mcp server)\s+["']?githits["']?\s+(?:was\s+)?not\s+found/i,
9690
9731
  /["']?githits["']?\s+(?:plugin|extension|server)?\s*(?:does\s+not\s+exist|is\s+not\s+installed|not\s+installed)/i,
9691
- /(?:package\s+)?["']?pi-mcp-adapter["']?\s+(?:is\s+)?not\s+installed/i,
9732
+ /(?:package\s+)?["']?pi-mcp-adapter["']?\s+(?:(?:is\s+)?not\s+installed|not\s+found)/i,
9692
9733
  /unknown\s+(?:plugin|extension|server)\s+["']?githits["']?/i,
9693
9734
  /marketplace\s+["']?githits-plugins["']?\s+(?:was\s+)?not\s+found/i
9694
9735
  ];
@@ -9733,15 +9774,15 @@ async function executeCliUninstallCommand(cmd, execService) {
9733
9774
  try {
9734
9775
  const result = await execService.exec(cmd.command, cmd.args);
9735
9776
  const combined = `${result.stdout} ${result.stderr}`;
9736
- if (result.exitCode === 0) {
9737
- return { status: "removed", message: "Removed successfully" };
9738
- }
9739
9777
  if (isAlreadyAbsentOutput(combined)) {
9740
9778
  return {
9741
9779
  status: "not_configured",
9742
9780
  message: `GitHits not configured via ${cmd.command}`
9743
9781
  };
9744
9782
  }
9783
+ if (result.exitCode === 0) {
9784
+ return { status: "removed", message: "Removed successfully" };
9785
+ }
9745
9786
  const detail = result.stderr.trim() || result.stdout.trim();
9746
9787
  return {
9747
9788
  status: "failed",
@@ -10045,6 +10086,48 @@ function getHermesHomeDir(fs) {
10045
10086
  function getHermesConfigPath(fs) {
10046
10087
  return fs.joinPath(getHermesHomeDir(fs), "config.yaml");
10047
10088
  }
10089
+ function getStandardMcpServerConfig() {
10090
+ return {
10091
+ command: GITHITS_MCP_COMMAND,
10092
+ args: [...GITHITS_MCP_ARGS]
10093
+ };
10094
+ }
10095
+ function getVsCodeMcpServerConfig() {
10096
+ return {
10097
+ type: "stdio",
10098
+ ...getStandardMcpServerConfig()
10099
+ };
10100
+ }
10101
+ function getProjectPath(fs) {
10102
+ return fs.getCwd();
10103
+ }
10104
+ function getProjectJsonConfig(fs, relativePath, serversKey, serverConfig = getStandardMcpServerConfig()) {
10105
+ return {
10106
+ method: "config-file",
10107
+ configPath: fs.joinPath(getProjectPath(fs), ...relativePath),
10108
+ serversKey,
10109
+ serverName: GITHITS_SERVER_NAME,
10110
+ serverConfig
10111
+ };
10112
+ }
10113
+ function getUnsupportedProjectSetup(reason) {
10114
+ return { supported: false, reason };
10115
+ }
10116
+ function getAgentSetupConfig(agent, fs, scope = "user", context) {
10117
+ if (scope === "project") {
10118
+ if (agent.projectSetup?.supported) {
10119
+ return agent.projectSetup.getSetupConfig(fs, context);
10120
+ }
10121
+ return null;
10122
+ }
10123
+ return agent.getSetupConfig(fs, context);
10124
+ }
10125
+ function getProjectSetupUnsupportedReason(agent) {
10126
+ if (agent.projectSetup?.supported) {
10127
+ return null;
10128
+ }
10129
+ return agent.projectSetup?.reason ?? "project-level MCP config not verified";
10130
+ }
10048
10131
  function getOpenCodeDesktopDetectPaths(fs) {
10049
10132
  const userDataRoot = getUserDataRoot(fs);
10050
10133
  return [
@@ -10155,7 +10238,11 @@ var claudeCode = {
10155
10238
  args: ["plugin", "marketplace", "remove", CLAUDE_GITHITS_MARKETPLACE]
10156
10239
  }
10157
10240
  ]
10158
- })
10241
+ }),
10242
+ projectSetup: {
10243
+ supported: true,
10244
+ getSetupConfig: (fs) => getProjectJsonConfig(fs, [".mcp.json"], "mcpServers")
10245
+ }
10159
10246
  };
10160
10247
  var cursor = {
10161
10248
  name: "Cursor",
@@ -10168,11 +10255,12 @@ var cursor = {
10168
10255
  configPath: fs.joinPath(fs.getHomeDir(), ".cursor", "mcp.json"),
10169
10256
  serversKey: "mcpServers",
10170
10257
  serverName: GITHITS_SERVER_NAME,
10171
- serverConfig: {
10172
- command: GITHITS_MCP_COMMAND,
10173
- args: [...GITHITS_MCP_ARGS]
10174
- }
10175
- })
10258
+ serverConfig: getStandardMcpServerConfig()
10259
+ }),
10260
+ projectSetup: {
10261
+ supported: true,
10262
+ getSetupConfig: (fs) => getProjectJsonConfig(fs, [".cursor", "mcp.json"], "mcpServers")
10263
+ }
10176
10264
  };
10177
10265
  var windsurf = {
10178
10266
  name: "Windsurf",
@@ -10185,11 +10273,9 @@ var windsurf = {
10185
10273
  configPath: fs.joinPath(fs.getHomeDir(), ".codeium", "windsurf", "mcp_config.json"),
10186
10274
  serversKey: "mcpServers",
10187
10275
  serverName: GITHITS_SERVER_NAME,
10188
- serverConfig: {
10189
- command: GITHITS_MCP_COMMAND,
10190
- args: [...GITHITS_MCP_ARGS]
10191
- }
10192
- })
10276
+ serverConfig: getStandardMcpServerConfig()
10277
+ }),
10278
+ projectSetup: getUnsupportedProjectSetup("project-level MCP config not verified for Windsurf")
10193
10279
  };
10194
10280
  var claudeDesktop = {
10195
10281
  name: "Claude Desktop",
@@ -10216,12 +10302,10 @@ var claudeDesktop = {
10216
10302
  configPath: fs.joinPath(appData, "claude_desktop_config.json"),
10217
10303
  serversKey: "mcpServers",
10218
10304
  serverName: GITHITS_SERVER_NAME,
10219
- serverConfig: {
10220
- command: GITHITS_MCP_COMMAND,
10221
- args: [...GITHITS_MCP_ARGS]
10222
- }
10305
+ serverConfig: getStandardMcpServerConfig()
10223
10306
  };
10224
- }
10307
+ },
10308
+ projectSetup: getUnsupportedProjectSetup("Claude Desktop uses user-level desktop config")
10225
10309
  };
10226
10310
  var codexCli = {
10227
10311
  name: "Codex CLI",
@@ -10251,7 +10335,18 @@ var codexCli = {
10251
10335
  args: ["mcp", "remove", "githits"]
10252
10336
  }
10253
10337
  ]
10254
- })
10338
+ }),
10339
+ projectSetup: {
10340
+ supported: true,
10341
+ getSetupConfig: (fs) => ({
10342
+ method: "config-file",
10343
+ format: "toml",
10344
+ configPath: fs.joinPath(getProjectPath(fs), ".codex", "config.toml"),
10345
+ serversKey: "mcp_servers",
10346
+ serverName: "githits",
10347
+ serverConfig: getStandardMcpServerConfig()
10348
+ })
10349
+ }
10255
10350
  };
10256
10351
  var pi = {
10257
10352
  name: "Pi",
@@ -10284,8 +10379,7 @@ var pi = {
10284
10379
  serversKey: "mcpServers",
10285
10380
  serverName: GITHITS_SERVER_NAME,
10286
10381
  serverConfig: {
10287
- command: GITHITS_MCP_COMMAND,
10288
- args: [...GITHITS_MCP_ARGS],
10382
+ ...getStandardMcpServerConfig(),
10289
10383
  lifecycle: "eager"
10290
10384
  }
10291
10385
  }
@@ -10321,6 +10415,32 @@ var pi = {
10321
10415
  }
10322
10416
  ]
10323
10417
  };
10418
+ },
10419
+ projectSetup: {
10420
+ supported: true,
10421
+ getSetupConfig: (fs, context) => {
10422
+ const piCommand = context?.command ?? "pi";
10423
+ return {
10424
+ method: "composite",
10425
+ steps: [
10426
+ {
10427
+ method: "cli",
10428
+ commands: [
10429
+ {
10430
+ command: piCommand,
10431
+ args: ["install", "npm:pi-mcp-adapter"]
10432
+ }
10433
+ ],
10434
+ checkCommand: {
10435
+ command: piCommand,
10436
+ args: ["list"],
10437
+ configuredPattern: PI_ADAPTER_CONFIGURED_PATTERN
10438
+ }
10439
+ },
10440
+ getProjectJsonConfig(fs, [".mcp.json"], "mcpServers")
10441
+ ]
10442
+ };
10443
+ }
10324
10444
  }
10325
10445
  };
10326
10446
  var vscode = {
@@ -10339,11 +10459,12 @@ var vscode = {
10339
10459
  configPath: fs.joinPath(appData, "User", "mcp.json"),
10340
10460
  serversKey: "servers",
10341
10461
  serverName: GITHITS_SERVER_NAME,
10342
- serverConfig: {
10343
- command: GITHITS_MCP_COMMAND,
10344
- args: [...GITHITS_MCP_ARGS]
10345
- }
10462
+ serverConfig: getVsCodeMcpServerConfig()
10346
10463
  };
10464
+ },
10465
+ projectSetup: {
10466
+ supported: true,
10467
+ getSetupConfig: (fs) => getProjectJsonConfig(fs, [".vscode", "mcp.json"], "servers", getVsCodeMcpServerConfig())
10347
10468
  }
10348
10469
  };
10349
10470
  var cline = {
@@ -10357,11 +10478,9 @@ var cline = {
10357
10478
  configPath: fs.joinPath(fs.getHomeDir(), ".cline", "data", "settings", "cline_mcp_settings.json"),
10358
10479
  serversKey: "mcpServers",
10359
10480
  serverName: GITHITS_SERVER_NAME,
10360
- serverConfig: {
10361
- command: GITHITS_MCP_COMMAND,
10362
- args: [...GITHITS_MCP_ARGS]
10363
- }
10364
- })
10481
+ serverConfig: getStandardMcpServerConfig()
10482
+ }),
10483
+ projectSetup: getUnsupportedProjectSetup("Cline MCP settings are documented as user-level config; project MCP auto-load not verified")
10365
10484
  };
10366
10485
  var geminiCli = {
10367
10486
  name: "Gemini CLI",
@@ -10397,7 +10516,11 @@ var geminiCli = {
10397
10516
  args: ["extensions", "uninstall", "githits"]
10398
10517
  }
10399
10518
  ]
10400
- })
10519
+ }),
10520
+ projectSetup: {
10521
+ supported: true,
10522
+ getSetupConfig: (fs) => getProjectJsonConfig(fs, [".gemini", "settings.json"], "mcpServers")
10523
+ }
10401
10524
  };
10402
10525
  async function isGeminiExtensionInstalledFromFilesystem(fs) {
10403
10526
  const extensionManifestPath = fs.joinPath(fs.getHomeDir(), ".gemini", "extensions", "githits", "gemini-extension.json");
@@ -10414,11 +10537,9 @@ var googleAntigravity = {
10414
10537
  configPath: fs.joinPath(fs.getHomeDir(), ".gemini", "antigravity", "mcp_config.json"),
10415
10538
  serversKey: "mcpServers",
10416
10539
  serverName: GITHITS_SERVER_NAME,
10417
- serverConfig: {
10418
- command: GITHITS_MCP_COMMAND,
10419
- args: [...GITHITS_MCP_ARGS]
10420
- }
10421
- })
10540
+ serverConfig: getStandardMcpServerConfig()
10541
+ }),
10542
+ projectSetup: getUnsupportedProjectSetup("Google Antigravity project-level MCP config not verified")
10422
10543
  };
10423
10544
  var openCode = {
10424
10545
  name: "OpenCode",
@@ -10437,7 +10558,15 @@ var openCode = {
10437
10558
  command: [...GITHITS_MCP_INVOCATION],
10438
10559
  enabled: true
10439
10560
  }
10440
- })
10561
+ }),
10562
+ projectSetup: {
10563
+ supported: true,
10564
+ getSetupConfig: (fs) => getProjectJsonConfig(fs, ["opencode.json"], "mcp", {
10565
+ type: "local",
10566
+ command: [...GITHITS_MCP_INVOCATION],
10567
+ enabled: true
10568
+ })
10569
+ }
10441
10570
  };
10442
10571
  var hermesAgent = {
10443
10572
  name: "Hermes Agent",
@@ -10452,11 +10581,9 @@ var hermesAgent = {
10452
10581
  configPath: getHermesConfigPath(fs),
10453
10582
  serversKey: "mcp_servers",
10454
10583
  serverName: GITHITS_SERVER_NAME,
10455
- serverConfig: {
10456
- command: GITHITS_MCP_COMMAND,
10457
- args: [...GITHITS_MCP_ARGS]
10458
- }
10459
- })
10584
+ serverConfig: getStandardMcpServerConfig()
10585
+ }),
10586
+ projectSetup: getUnsupportedProjectSetup("Hermes Agent project-level MCP config not verified")
10460
10587
  };
10461
10588
  var agentDefinitions = [
10462
10589
  claudeCode,
@@ -10472,7 +10599,7 @@ var agentDefinitions = [
10472
10599
  openCode,
10473
10600
  hermesAgent
10474
10601
  ];
10475
- async function scanSingleAgent(agent, fs, execService) {
10602
+ async function scanSingleAgent(agent, fs, execService, scope) {
10476
10603
  let detected = false;
10477
10604
  let setupContext;
10478
10605
  if (agent.detectCommand) {
@@ -10523,7 +10650,14 @@ async function scanSingleAgent(agent, fs, execService) {
10523
10650
  if (!detected) {
10524
10651
  return { status: "not_detected", agent };
10525
10652
  }
10526
- const config = agent.getSetupConfig(fs, setupContext);
10653
+ const config = getAgentSetupConfig(agent, fs, scope, setupContext);
10654
+ if (!config) {
10655
+ return {
10656
+ status: "unsupported",
10657
+ agent,
10658
+ reason: getProjectSetupUnsupportedReason(agent) ?? "project-level MCP config not verified"
10659
+ };
10660
+ }
10527
10661
  const scannedAgent = {
10528
10662
  ...agent,
10529
10663
  resolvedSetupConfig: config,
@@ -10552,10 +10686,11 @@ async function scanAgents(definitions, fs, execService, options = {}) {
10552
10686
  const result = {
10553
10687
  needsSetup: [],
10554
10688
  alreadyConfigured: [],
10555
- notDetected: []
10689
+ notDetected: [],
10690
+ unsupported: []
10556
10691
  };
10557
10692
  let completed = 0;
10558
- const outcomes = await Promise.all(definitions.map((agent) => scanSingleAgent(agent, fs, execService).then((outcome) => {
10693
+ const outcomes = await Promise.all(definitions.map((agent) => scanSingleAgent(agent, fs, execService, options.scope ?? "user").then((outcome) => {
10559
10694
  completed += 1;
10560
10695
  options.onProgress?.({
10561
10696
  completed,
@@ -10569,6 +10704,11 @@ async function scanAgents(definitions, fs, execService, options = {}) {
10569
10704
  result.alreadyConfigured.push(outcome.agent);
10570
10705
  } else if (outcome.status === "needs_setup") {
10571
10706
  result.needsSetup.push(outcome.agent);
10707
+ } else if (outcome.status === "unsupported") {
10708
+ result.unsupported.push({
10709
+ agent: outcome.agent,
10710
+ reason: outcome.reason
10711
+ });
10572
10712
  } else {
10573
10713
  result.notDetected.push(outcome.agent);
10574
10714
  }
@@ -10594,6 +10734,9 @@ function createInitLoginOutput() {
10594
10734
  function getResolvedSetupConfig(agent, fileSystemService) {
10595
10735
  return agent.resolvedSetupConfig ?? agent.getSetupConfig(fileSystemService, agent.resolvedSetupContext);
10596
10736
  }
10737
+ function getLegacyProjectSetupStatePath(fileSystemService) {
10738
+ return fileSystemService.joinPath(fileSystemService.getCwd(), ".githits", "init", "project-setup.json");
10739
+ }
10597
10740
  function getPiConfigFileUninstall(agent, fileSystemService) {
10598
10741
  if (agent.id !== "pi") {
10599
10742
  return null;
@@ -10609,13 +10752,27 @@ function formatCommand(command, useColors) {
10609
10752
  return colorizeBrand(command, "secondary", useColors, { bold: true });
10610
10753
  }
10611
10754
  var AGENT_SAFE_CLI = "npx -y githits@latest";
10612
- var AGENT_DETECT_COMMAND = `${AGENT_SAFE_CLI} init --detect-agents`;
10613
- var AGENT_INSTALL_COMMAND = `${AGENT_SAFE_CLI} init --install-agents`;
10614
10755
  var AGENT_LOGIN_COMMAND = `${AGENT_SAFE_CLI} login`;
10615
10756
  var AGENT_LOGIN_NO_BROWSER_COMMAND = `${AGENT_SAFE_CLI} login --no-browser`;
10616
10757
  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.";
10617
- var AGENTIC_INIT_VERIFY_INSTRUCTION = "After a successful --install-agents run, verify with --detect-agents --json instead of running init again.";
10618
- var AGENTIC_INIT_JSON_VERIFY_INSTRUCTION = "Do not run init again after a successful --install-agents run; verify with --detect-agents --json instead.";
10758
+ function getAgentDetectCommand(scope) {
10759
+ return `${AGENT_SAFE_CLI} init ${scope === "project" ? "--project " : ""}--detect-agents`;
10760
+ }
10761
+ function getAgentInstallCommand(scope) {
10762
+ return `${AGENT_SAFE_CLI} init ${scope === "project" ? "--project " : ""}--install-agents`;
10763
+ }
10764
+ function getAgenticVerifyCommand(scope) {
10765
+ return `${getAgentDetectCommand(scope)} --json`;
10766
+ }
10767
+ function getAgenticVerifyInstruction(scope) {
10768
+ return `After a successful --install-agents run, verify with ${getAgenticVerifyCommand(scope)} instead of running init again.`;
10769
+ }
10770
+ function getAgenticJsonVerifyInstruction(scope) {
10771
+ return `Do not run init again after a successful --install-agents run; verify with ${getAgenticVerifyCommand(scope)} instead.`;
10772
+ }
10773
+ function formatInstallCommand(ids, scope) {
10774
+ return `${getAgentInstallCommand(scope)} ${ids.join(",")}`;
10775
+ }
10619
10776
  function printReadyNextSteps() {
10620
10777
  console.log(" GitHits is now connected to your coding agents.");
10621
10778
  console.log();
@@ -10648,6 +10805,22 @@ function printAuthNotCheckedNextSteps(useColors) {
10648
10805
  console.log(" If your agent asks you to sign in, run:");
10649
10806
  console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
10650
10807
  }
10808
+ function printProjectAuthRequiredNextSteps(useColors) {
10809
+ console.log(" GitHits MCP is configured for this project, but sign-in is still needed.");
10810
+ console.log();
10811
+ console.log(" Open an MCP-compatible coding agent in this project so it loads the project config.");
10812
+ console.log();
10813
+ console.log(" Sign in when you're ready:");
10814
+ console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
10815
+ }
10816
+ function printProjectAuthNotCheckedNextSteps(useColors) {
10817
+ console.log(" GitHits MCP is configured for this project. Sign-in was not checked.");
10818
+ console.log();
10819
+ console.log(" Open an MCP-compatible coding agent in this project so it loads the project config.");
10820
+ console.log();
10821
+ console.log(" If your agent asks you to sign in, run:");
10822
+ console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
10823
+ }
10651
10824
  function printAgenticLoginInstructions(useColors) {
10652
10825
  console.log(" Authentication is still required before agents can use GitHits.");
10653
10826
  console.log();
@@ -10678,25 +10851,38 @@ function printNonInteractiveInitGuidance(useColors) {
10678
10851
  console.log();
10679
10852
  console.log(" If you are an AI coding agent helping a user install GitHits:");
10680
10853
  console.log();
10681
- console.log(" 1. Run:");
10682
- console.log(` ${formatCommand(AGENT_DETECT_COMMAND, useColors)}`);
10854
+ console.log(" 1. Ask the user whether GitHits should be installed for:");
10855
+ console.log(" - this user account on this machine, or");
10856
+ console.log(" - only this project/repo via project-local MCP files.");
10857
+ console.log();
10858
+ console.log(" 2. For user-level install, run:");
10859
+ console.log(` ${formatCommand(getAgentDetectCommand("user"), useColors)}`);
10683
10860
  console.log();
10684
- console.log(" 2. Show the detected tools to the user.");
10861
+ console.log(" For project-level install, run:");
10862
+ console.log(` ${formatCommand(getAgentDetectCommand("project"), useColors)}`);
10685
10863
  console.log();
10686
- console.log(" 3. Ask which tools should receive the GitHits MCP server.");
10864
+ console.log(" 3. Show the detected tools to the user.");
10687
10865
  console.log();
10688
- console.log(" 4. Only after approval, run:");
10689
- console.log(` ${formatCommand(`${AGENT_INSTALL_COMMAND} <ids>`, useColors)}`);
10866
+ console.log(" 4. Ask which tools should receive the GitHits MCP server.");
10867
+ console.log();
10868
+ console.log(" For project-level install, explain that config files are written into this repo and may be committed.");
10869
+ console.log();
10870
+ console.log(" 5. Only after approval, run the matching install command:");
10871
+ console.log(` ${formatCommand(`${getAgentInstallCommand("user")} <ids>`, useColors)}`);
10872
+ console.log(` ${formatCommand(`${getAgentInstallCommand("project")} <ids>`, useColors)}`);
10690
10873
  console.log();
10691
10874
  console.log(` ${AGENTIC_INIT_YES_WARNING}`);
10692
- console.log(` ${AGENTIC_INIT_VERIFY_INSTRUCTION}`);
10875
+ console.log(` ${getAgenticVerifyInstruction("user")}`);
10876
+ console.log(` ${getAgenticVerifyInstruction("project")}`);
10693
10877
  }
10694
10878
  function printNonInteractiveYesRejected(useColors) {
10695
10879
  console.error("Non-interactive `githits init --yes` is not supported because it can configure tools without explicit per-tool approval.");
10696
10880
  console.error();
10697
10881
  console.error("Use the agent-safe staged flow instead:");
10698
- console.error(` ${formatCommand(AGENT_DETECT_COMMAND, useColors)}`);
10699
- console.error(` ${formatCommand(`${AGENT_INSTALL_COMMAND} <ids>`, useColors)}`);
10882
+ console.error(` ${formatCommand(getAgentDetectCommand("user"), useColors)}`);
10883
+ console.error(` ${formatCommand(`${getAgentInstallCommand("user")} <ids>`, useColors)}`);
10884
+ console.error(` ${formatCommand(getAgentDetectCommand("project"), useColors)}`);
10885
+ console.error(` ${formatCommand(`${getAgentInstallCommand("project")} <ids>`, useColors)}`);
10700
10886
  process.exitCode = 1;
10701
10887
  }
10702
10888
  var GITHITS_ASCII_LOGO = String.raw`
@@ -10787,6 +10973,30 @@ var INIT_INTENT_CHOICES = [
10787
10973
  description: "Leave setup without making changes."
10788
10974
  }
10789
10975
  ];
10976
+ var INIT_SCOPE_CHOICES = [
10977
+ {
10978
+ name: "User-level config",
10979
+ value: "user",
10980
+ description: "Configure GitHits for your detected tools on this machine."
10981
+ },
10982
+ {
10983
+ name: "Project-level config",
10984
+ value: "project",
10985
+ description: "Configure project-local MCP files for tools that support workspace config."
10986
+ }
10987
+ ];
10988
+ var INIT_UNINSTALL_SCOPE_CHOICES = [
10989
+ {
10990
+ name: "User-level config",
10991
+ value: "user",
10992
+ description: "Remove GitHits from detected tools on this machine."
10993
+ },
10994
+ {
10995
+ name: "Project-level config",
10996
+ value: "project",
10997
+ description: "Remove GitHits from supported project-local MCP files."
10998
+ }
10999
+ ];
10790
11000
  var AUTH_RECOVERY_CHOICES = [
10791
11001
  { name: "Retry sign in", value: "retry" },
10792
11002
  {
@@ -10861,8 +11071,9 @@ function printSkillsInstructions(useColors) {
10861
11071
  console.log(` ${formatCommand("npx githits@latest login", useColors)}`);
10862
11072
  console.log();
10863
11073
  }
10864
- function startSafeInitScan(fileSystemService, execService, onProgress) {
11074
+ function startSafeInitScan(fileSystemService, execService, scope = "user", onProgress) {
10865
11075
  return scanAgents(agentDefinitions, fileSystemService, execService, {
11076
+ scope,
10866
11077
  onProgress
10867
11078
  }).then((scan) => ({ ok: true, scan })).catch((error2) => ({
10868
11079
  ok: false,
@@ -10949,50 +11160,85 @@ function buildInitAgentChoices(scan) {
10949
11160
  }))
10950
11161
  ];
10951
11162
  }
10952
- function printScanSummary(scan, useColors) {
10953
- const detected = scan.needsSetup.length + scan.alreadyConfigured.length;
11163
+ function printScanSummary(scan, useColors, scope = "user") {
11164
+ const detected = scan.needsSetup.length + scan.alreadyConfigured.length + scan.unsupported.length;
11165
+ const projectSupported = scan.needsSetup.length + scan.alreadyConfigured.length;
10954
11166
  for (const agent of scan.alreadyConfigured) {
10955
11167
  printTask("success", agent.name, "already configured", useColors);
10956
11168
  }
10957
11169
  for (const agent of scan.needsSetup) {
10958
11170
  printTask("warning", agent.name, "needs setup", useColors);
10959
11171
  }
11172
+ for (const { agent, reason } of scan.unsupported) {
11173
+ printTask("skipped", agent.name, scope === "project" ? "no project-level config" : reason, useColors);
11174
+ }
10960
11175
  if (scan.notDetected.length > 0) {
10961
11176
  printTask("skipped", `${scan.notDetected.length} supported tool${scan.notDetected.length !== 1 ? "s" : ""} not found`, formatAgentNames(scan.notDetected), useColors);
10962
11177
  }
10963
11178
  if (detected > 0) {
10964
11179
  console.log();
10965
- console.log(` Found ${detected} supported tool${detected !== 1 ? "s" : ""}.`);
11180
+ if (scope === "project") {
11181
+ console.log(` Found ${detected} tool${detected !== 1 ? "s" : ""}. ${projectSupported} support${projectSupported === 1 ? "s" : ""} project-level config.`);
11182
+ } else {
11183
+ console.log(` Found ${detected} supported tool${detected !== 1 ? "s" : ""}.`);
11184
+ }
10966
11185
  }
10967
11186
  }
11187
+ function printProjectScopeExplanation(useColors) {
11188
+ console.log();
11189
+ console.log(` ${warning("Project-level config is available for some tools.", useColors)}`);
11190
+ console.log(" Tools without project-level config are shown below but won't be selected.");
11191
+ }
10968
11192
  function buildStagedAgentEntries(scan) {
10969
11193
  const statuses = new Map;
10970
- for (const agent of scan.needsSetup)
10971
- statuses.set(agent.id, "needs_setup");
11194
+ for (const agent of scan.needsSetup) {
11195
+ statuses.set(agent.id, { status: "needs_setup" });
11196
+ }
10972
11197
  for (const agent of scan.alreadyConfigured) {
10973
- statuses.set(agent.id, "already_configured");
10974
- }
10975
- for (const agent of scan.notDetected)
10976
- statuses.set(agent.id, "not_detected");
10977
- return agentDefinitions.map((agent) => ({
10978
- id: agent.id,
10979
- name: agent.name,
10980
- status: statuses.get(agent.id) ?? "not_detected"
10981
- }));
11198
+ statuses.set(agent.id, { status: "already_configured" });
11199
+ }
11200
+ for (const agent of scan.notDetected) {
11201
+ statuses.set(agent.id, { status: "not_detected" });
11202
+ }
11203
+ for (const { agent, reason } of scan.unsupported) {
11204
+ statuses.set(agent.id, {
11205
+ status: "unsupported_project_config",
11206
+ reason
11207
+ });
11208
+ }
11209
+ return agentDefinitions.map((agent) => {
11210
+ const entry = statuses.get(agent.id);
11211
+ return {
11212
+ id: agent.id,
11213
+ name: agent.name,
11214
+ status: entry?.status ?? "not_detected",
11215
+ ...entry?.reason ? { reason: entry.reason } : {}
11216
+ };
11217
+ });
10982
11218
  }
10983
- function printAgenticDetectSummary(scan, useColors) {
11219
+ function printAgenticDetectSummary(scan, useColors, scope) {
10984
11220
  const entries = buildStagedAgentEntries(scan);
10985
11221
  const detected = entries.filter((entry) => entry.status !== "not_detected");
10986
11222
  const installable = entries.filter((entry) => entry.status === "needs_setup");
11223
+ const unsupported = entries.filter((entry) => entry.status === "unsupported_project_config");
11224
+ const configured = entries.filter((entry) => entry.status === "already_configured");
10987
11225
  const notDetected = entries.filter((entry) => entry.status === "not_detected");
10988
- console.log("Detected supported tools:");
11226
+ console.log(`Detected tools (${scope === "project" ? "project-level" : "user-level"} install):`);
10989
11227
  console.log();
11228
+ if (scope === "project") {
11229
+ console.log(" Project-level install writes MCP config files into this repo. These files may be committed.");
11230
+ console.log(" Tools without verified project config are shown as unsupported and cannot be installed with --project.");
11231
+ console.log();
11232
+ }
10990
11233
  if (detected.length === 0) {
10991
11234
  console.log(" None detected.");
10992
11235
  } else {
10993
11236
  console.log(" ID Tool Status");
10994
11237
  for (const entry of detected) {
10995
- console.log(` ${entry.id.padEnd(18)} ${entry.name.padEnd(21)} ${entry.status.replace("_", " ")}`);
11238
+ console.log(` ${entry.id.padEnd(18)} ${entry.name.padEnd(21)} ${entry.status.replaceAll("_", " ")}`);
11239
+ if (entry.status === "unsupported_project_config" && entry.reason) {
11240
+ console.log(` ${"".padEnd(18)} ${"".padEnd(21)} ${entry.reason}`);
11241
+ }
10996
11242
  }
10997
11243
  }
10998
11244
  console.log();
@@ -11007,12 +11253,25 @@ function printAgenticDetectSummary(scan, useColors) {
11007
11253
  return;
11008
11254
  }
11009
11255
  if (installable.length === 0) {
11256
+ if (scope === "project" && unsupported.length > 0) {
11257
+ console.log("No detected tools can be installed with project-level config.");
11258
+ console.log();
11259
+ console.log("Next step for agents:");
11260
+ if (configured.length > 0) {
11261
+ console.log(" Tell the user GitHits is already configured for the detected project-configurable tools.");
11262
+ }
11263
+ console.log(" Tell the user the other detected tools do not have verified project-level MCP support.");
11264
+ console.log(` Offer user-level install with ${getAgentDetectCommand("user")} if they want GitHits for those tools.`);
11265
+ console.log(` ${AGENTIC_INIT_YES_WARNING}`);
11266
+ console.log(` Do not run init again as a verification step; use ${getAgenticVerifyCommand(scope)} if verification is needed.`);
11267
+ return;
11268
+ }
11010
11269
  console.log("No detected tools need setup.");
11011
11270
  console.log();
11012
11271
  console.log("Next step for agents:");
11013
11272
  console.log(" Tell the user that GitHits is already configured for detected tools.");
11014
11273
  console.log(` ${AGENTIC_INIT_YES_WARNING}`);
11015
- console.log(" Do not run init again as a verification step.");
11274
+ console.log(` Do not run init again as a verification step; use ${getAgenticVerifyCommand(scope)} if verification is needed.`);
11016
11275
  return;
11017
11276
  }
11018
11277
  const installableIds = installable.map((entry) => entry.id);
@@ -11022,27 +11281,86 @@ function printAgenticDetectSummary(scan, useColors) {
11022
11281
  console.log(` "GitHits can be installed for ${installable.map((entry) => entry.name).join(", ")}. Which should I configure?"`);
11023
11282
  console.log();
11024
11283
  console.log(" If the user approves all detected tools needing setup, run:");
11025
- console.log(` ${formatCommand(`${AGENT_INSTALL_COMMAND} ${installableIds.join(",")}`, useColors)}`);
11284
+ console.log(` ${formatCommand(formatInstallCommand(installableIds, scope), useColors)}`);
11285
+ if (scope === "project") {
11286
+ console.log();
11287
+ console.log(" Before running it, tell the user this writes project-local MCP files into the current repo and only configures tools with verified project support.");
11288
+ }
11026
11289
  console.log();
11027
11290
  console.log(` ${AGENTIC_INIT_YES_WARNING}`);
11028
- console.log(` ${AGENTIC_INIT_VERIFY_INSTRUCTION}`);
11291
+ console.log(` ${getAgenticVerifyInstruction(scope)}`);
11029
11292
  }
11030
- function printAgenticDetectJson(scan) {
11293
+ function printAgenticDetectJson(scan, scope) {
11031
11294
  const entries = buildStagedAgentEntries(scan);
11032
11295
  const installableIds = entries.filter((entry) => entry.status === "needs_setup").map((entry) => entry.id);
11296
+ const detected = entries.filter((entry) => entry.status !== "not_detected");
11297
+ const configured = entries.filter((entry) => entry.status === "already_configured");
11298
+ const unsupported = entries.filter((entry) => entry.status === "unsupported_project_config");
11299
+ const instructions = buildAgenticDetectJsonInstructions({
11300
+ scope,
11301
+ detectedCount: detected.length,
11302
+ installableCount: installableIds.length,
11303
+ configuredCount: configured.length,
11304
+ unsupportedCount: unsupported.length
11305
+ });
11033
11306
  console.log(JSON.stringify({
11034
11307
  mode: "detect-agents",
11308
+ scope,
11035
11309
  agents: entries,
11036
11310
  installableIds,
11037
- suggestedCommand: installableIds.length > 0 ? `${AGENT_INSTALL_COMMAND} ${installableIds.join(",")}` : null,
11038
- instructions: [
11311
+ suggestedCommand: installableIds.length > 0 ? formatInstallCommand(installableIds, scope) : null,
11312
+ instructions
11313
+ }, null, 2));
11314
+ }
11315
+ function buildAgenticDetectJsonInstructions(input) {
11316
+ const {
11317
+ scope,
11318
+ detectedCount,
11319
+ installableCount,
11320
+ configuredCount,
11321
+ unsupportedCount
11322
+ } = input;
11323
+ if (detectedCount === 0) {
11324
+ return [
11325
+ "No supported AI coding tools were detected.",
11326
+ "Tell the user to install a supported coding tool, then run detection again."
11327
+ ];
11328
+ }
11329
+ if (installableCount === 0) {
11330
+ if (scope === "project" && unsupportedCount > 0) {
11331
+ return [
11332
+ "Show detected tools to the user.",
11333
+ ...configuredCount > 0 ? [
11334
+ "Explain that GitHits is already configured for detected project-configurable tools."
11335
+ ] : [
11336
+ "Explain that no detected tools have verified project-level MCP support."
11337
+ ],
11338
+ "Explain that tools with unsupported_project_config status cannot be installed with --project.",
11339
+ "Do not ask the user to choose project install IDs.",
11340
+ `Offer user-level detection with ${getAgentDetectCommand("user")} if they want GitHits for unsupported project tools.`,
11341
+ AGENTIC_INIT_YES_WARNING,
11342
+ getAgenticJsonVerifyInstruction(scope)
11343
+ ];
11344
+ }
11345
+ return [
11039
11346
  "Show detected tools to the user.",
11040
- "Ask which tools should receive the GitHits MCP server.",
11041
- "Only run --install-agents with user-approved IDs.",
11347
+ "Tell the user that GitHits is already configured for detected tools.",
11348
+ "Do not ask the user to choose install IDs.",
11042
11349
  AGENTIC_INIT_YES_WARNING,
11043
- AGENTIC_INIT_JSON_VERIFY_INSTRUCTION
11044
- ]
11045
- }, null, 2));
11350
+ getAgenticJsonVerifyInstruction(scope)
11351
+ ];
11352
+ }
11353
+ return [
11354
+ "Show detected tools to the user.",
11355
+ ...scope === "project" ? [
11356
+ "Explain that project-level install writes MCP config files into the current repo and those files may be committed.",
11357
+ "Do not offer agent IDs with unsupported_project_config status for project install."
11358
+ ] : [],
11359
+ "Ask which tools should receive the GitHits MCP server.",
11360
+ "Only run --install-agents with user-approved IDs.",
11361
+ AGENTIC_INIT_YES_WARNING,
11362
+ getAgenticJsonVerifyInstruction(scope)
11363
+ ];
11046
11364
  }
11047
11365
  function getStagedModeCount(options) {
11048
11366
  return [
@@ -11086,8 +11404,10 @@ function findAgentsByIds(scan, ids) {
11086
11404
  }
11087
11405
  function validateInstallAgentIds(scan, ids) {
11088
11406
  const supportedIds = new Set(agentDefinitions.map((agent) => agent.id));
11089
- const detectedIds = [...scan.needsSetup, ...scan.alreadyConfigured].map((agent) => agent.id);
11407
+ const installableAgents = [...scan.needsSetup, ...scan.alreadyConfigured];
11408
+ const detectedIds = installableAgents.map((agent) => agent.id);
11090
11409
  const detectedIdSet = new Set(detectedIds);
11410
+ const unsupported = new Map(scan.unsupported.map(({ agent, reason }) => [agent.id, reason]));
11091
11411
  if (ids.length === 0) {
11092
11412
  return {
11093
11413
  ok: false,
@@ -11103,6 +11423,15 @@ function validateInstallAgentIds(scan, ids) {
11103
11423
  detectedIds
11104
11424
  };
11105
11425
  }
11426
+ const unsupportedIds = ids.filter((id) => unsupported.has(id));
11427
+ if (unsupportedIds.length > 0) {
11428
+ const details = unsupportedIds.map((id) => `${id}: ${unsupported.get(id)}`).join("; ");
11429
+ return {
11430
+ ok: false,
11431
+ message: `Agent ID${unsupportedIds.length !== 1 ? "s" : ""} cannot use project-level install: ${details}.`,
11432
+ detectedIds
11433
+ };
11434
+ }
11106
11435
  const undetected = ids.filter((id) => !detectedIdSet.has(id));
11107
11436
  if (undetected.length > 0) {
11108
11437
  return {
@@ -11125,6 +11454,17 @@ function printInstallValidationFailure(failure, json) {
11125
11454
  }
11126
11455
  process.exitCode = 1;
11127
11456
  }
11457
+ function failUnknownInitAction(action) {
11458
+ failInitArgument(`Unknown init action: ${action}. Use "githits init uninstall" to remove GitHits MCP config.`, false);
11459
+ }
11460
+ async function resolveProjectSetupScope(options, fileSystemService) {
11461
+ const projectPath = fileSystemService.getCwd();
11462
+ if (!await fileSystemService.isDirectory(projectPath)) {
11463
+ failInitArgument(`Current directory does not exist or is not a directory: ${projectPath}`, options.json);
11464
+ return null;
11465
+ }
11466
+ return { projectPath };
11467
+ }
11128
11468
  function hasUsableInstallOutcome(outcomes) {
11129
11469
  return outcomes.some((outcome) => outcome.status === "success" || outcome.status === "already_configured");
11130
11470
  }
@@ -11162,46 +11502,54 @@ function buildAgenticInstallAuthPayload(authStatus) {
11162
11502
  noBrowserCommand: AGENT_LOGIN_NO_BROWSER_COMMAND
11163
11503
  };
11164
11504
  }
11165
- function buildAgenticInstallInstructions(authStatus) {
11505
+ function buildAgenticInstallInstructions(authStatus, scope) {
11166
11506
  if (authStatus === "authenticated") {
11167
- return ["Open a new coding agent session so it reloads MCP config."];
11507
+ return [
11508
+ scope === "project" ? "Open a new coding agent session in this project so it reloads project MCP config." : "Open a new coding agent session so it reloads MCP config.",
11509
+ getAgenticJsonVerifyInstruction(scope)
11510
+ ];
11168
11511
  }
11169
11512
  if (authStatus === "required") {
11170
11513
  return [
11171
11514
  `Ask the user before running ${AGENT_LOGIN_COMMAND}.`,
11172
11515
  "Browser sign-in happens outside chat and terminal input.",
11173
- "Do not ask the user to paste passwords, tokens, cookies, or OAuth codes into chat."
11516
+ "Do not ask the user to paste passwords, tokens, cookies, or OAuth codes into chat.",
11517
+ getAgenticJsonVerifyInstruction(scope)
11174
11518
  ];
11175
11519
  }
11176
11520
  return [
11177
11521
  "Sign-in status was not checked.",
11178
- `If the user is not already signed in, ask before running ${AGENT_LOGIN_COMMAND}.`
11522
+ `If the user is not already signed in, ask before running ${AGENT_LOGIN_COMMAND}.`,
11523
+ getAgenticJsonVerifyInstruction(scope)
11179
11524
  ];
11180
11525
  }
11181
- function printAgenticInstallJson(outcomes, authStatus) {
11526
+ function printAgenticInstallJson(outcomes, authStatus, scope) {
11182
11527
  const canAuthenticate = hasUsableInstallOutcome(outcomes);
11183
11528
  console.log(JSON.stringify({
11184
11529
  mode: "install-agents",
11530
+ scope,
11185
11531
  outcomes,
11186
11532
  auth: canAuthenticate ? buildAgenticInstallAuthPayload(authStatus) : {
11187
11533
  required: false,
11188
11534
  status: "not_applicable",
11189
11535
  reason: "Fix installation errors before starting sign-in."
11190
11536
  },
11191
- instructions: canAuthenticate ? buildAgenticInstallInstructions(authStatus) : ["Fix installation errors before asking the user to sign in."]
11537
+ instructions: canAuthenticate ? buildAgenticInstallInstructions(authStatus, scope) : ["Fix installation errors before asking the user to sign in."]
11192
11538
  }, null, 2));
11193
11539
  }
11194
11540
  async function runDetectAgentsMode(options, fileSystemService, execService, useColors) {
11195
- const scan = await scanAgents(agentDefinitions, fileSystemService, execService);
11541
+ const scope = options.project ? "project" : "user";
11542
+ const scan = await scanAgents(agentDefinitions, fileSystemService, execService, { scope });
11196
11543
  if (options.json) {
11197
- printAgenticDetectJson(scan);
11544
+ printAgenticDetectJson(scan, scope);
11198
11545
  return;
11199
11546
  }
11200
- printAgenticDetectSummary(scan, useColors);
11547
+ printAgenticDetectSummary(scan, useColors, scope);
11201
11548
  }
11202
11549
  async function runInstallAgentsMode(options, fileSystemService, execService, createLoginDeps, useColors) {
11550
+ const scope = options.project ? "project" : "user";
11203
11551
  const requestedIds = parseAgentIdList(options.installAgents);
11204
- const scan = await scanAgents(agentDefinitions, fileSystemService, execService);
11552
+ const scan = await scanAgents(agentDefinitions, fileSystemService, execService, { scope });
11205
11553
  const validation = validateInstallAgentIds(scan, requestedIds);
11206
11554
  if (!validation.ok) {
11207
11555
  printInstallValidationFailure(validation, options.json);
@@ -11212,7 +11560,7 @@ async function runInstallAgentsMode(options, fileSystemService, execService, cre
11212
11560
  console.log("Installing GitHits MCP:");
11213
11561
  console.log();
11214
11562
  }
11215
- const outcomes = await installSelectedAgents(agents, scan, fileSystemService, execService, useColors, !options.json);
11563
+ const outcomes = await installSelectedAgents(agents, scan, fileSystemService, execService, useColors, !options.json, scope);
11216
11564
  const failed = outcomes.filter((outcome) => outcome.status === "failed");
11217
11565
  const canAuthenticate = hasUsableInstallOutcome(outcomes);
11218
11566
  const authStatus = canAuthenticate ? await getStagedInstallAuthStatus(createLoginDeps) : "not_checked";
@@ -11220,7 +11568,7 @@ async function runInstallAgentsMode(options, fileSystemService, execService, cre
11220
11568
  process.exitCode = 1;
11221
11569
  }
11222
11570
  if (options.json) {
11223
- printAgenticInstallJson(outcomes, authStatus);
11571
+ printAgenticInstallJson(outcomes, authStatus, scope);
11224
11572
  return;
11225
11573
  }
11226
11574
  console.log();
@@ -11235,12 +11583,19 @@ async function runInstallAgentsMode(options, fileSystemService, execService, cre
11235
11583
  console.log();
11236
11584
  if (canAuthenticate) {
11237
11585
  if (authStatus === "authenticated") {
11238
- printAgenticAlreadyAuthenticated();
11586
+ if (scope === "project") {
11587
+ console.log(" GitHits MCP is installed for this project and you are already signed in.");
11588
+ console.log();
11589
+ console.log(" Open a new coding agent session in this project so it reloads project MCP config.");
11590
+ } else {
11591
+ printAgenticAlreadyAuthenticated();
11592
+ }
11239
11593
  } else if (authStatus === "required") {
11240
11594
  printAgenticLoginInstructions(useColors);
11241
11595
  } else {
11242
11596
  printAgenticAuthNotChecked(useColors);
11243
11597
  }
11598
+ console.log(` ${getAgenticVerifyInstruction(scope)}`);
11244
11599
  } else {
11245
11600
  console.log("Fix installation errors before starting sign-in.");
11246
11601
  }
@@ -11355,66 +11710,460 @@ function printPostSetupNextSteps(authStatus, useColors) {
11355
11710
  printAuthNotCheckedNextSteps(useColors);
11356
11711
  }
11357
11712
  }
11358
- async function verifyAgentConfigured(agent, fileSystemService, execService) {
11359
- const postCheck = await scanAgents([agent], fileSystemService, execService);
11360
- if (postCheck.alreadyConfigured.some((a) => a.id === agent.id)) {
11361
- return { ok: true };
11713
+ function printProjectNextSteps(authStatus, useColors) {
11714
+ printSection(5, shouldPrintReady(authStatus) ? "Ready" : "Next Steps", useColors);
11715
+ if (shouldPrintReady(authStatus)) {
11716
+ printReadyNextSteps();
11717
+ } else if (authStatus === "failed_continue") {
11718
+ printProjectAuthRequiredNextSteps(useColors);
11719
+ } else {
11720
+ printProjectAuthNotCheckedNextSteps(useColors);
11362
11721
  }
11363
- if (postCheck.needsSetup.some((a) => a.id === agent.id)) {
11364
- return {
11365
- ok: false,
11366
- message: `${agent.name} verification failed: not configured after setup.`
11367
- };
11722
+ }
11723
+ function printScopedNextSteps(scope, authStatus, useColors) {
11724
+ if (scope === "project") {
11725
+ printProjectNextSteps(authStatus, useColors);
11726
+ return;
11368
11727
  }
11369
- return {
11370
- ok: false,
11371
- message: `${agent.name} verification failed: agent not detected after setup.`
11372
- };
11728
+ printPostSetupNextSteps(authStatus, useColors);
11373
11729
  }
11374
- async function executeAgentSetupWithVerification(agent, fileSystemService, execService) {
11375
- const config = getResolvedSetupConfig(agent, fileSystemService);
11376
- let result = config.method === "cli" ? await executeCliSetup(config, execService) : config.method === "config-file" ? await executeConfigFileSetup(config, fileSystemService) : await executeCompositeSetup(config, fileSystemService, execService);
11377
- if (result.status === "success" || result.status === "already_configured") {
11378
- const verification = await verifyAgentConfigured(agent, fileSystemService, execService);
11379
- if (!verification.ok) {
11380
- result = {
11381
- status: "failed",
11382
- 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.`
11383
- };
11384
- }
11730
+ function getConfigFileSetups(setup) {
11731
+ if (setup.method === "config-file") {
11732
+ return [setup];
11385
11733
  }
11386
- return result;
11734
+ if (setup.method === "composite") {
11735
+ return setup.steps.filter((step) => step.method === "config-file");
11736
+ }
11737
+ return [];
11387
11738
  }
11388
- async function installSelectedAgents(agents, scan, fileSystemService, execService, useColors, printResults) {
11389
- const alreadyConfiguredIds = new Set(scan.alreadyConfigured.map((agent) => agent.id));
11390
- const outcomes = [];
11391
- const installTasks = createInstallTaskReporter(useColors);
11739
+ function getTomlSetups(setup) {
11740
+ return getConfigFileSetups(setup).filter((step) => step.format === "toml");
11741
+ }
11742
+ async function hasExistingConfigContent(config, fileSystemService) {
11743
+ try {
11744
+ return (await fileSystemService.readFile(config.configPath)).trim().length > 0;
11745
+ } catch {
11746
+ return false;
11747
+ }
11748
+ }
11749
+ async function printTomlRewriteWarnings(agents, fileSystemService, useColors) {
11750
+ const seen = new Set;
11392
11751
  for (const agent of agents) {
11393
- if (alreadyConfiguredIds.has(agent.id)) {
11394
- outcomes.push({
11395
- id: agent.id,
11396
- name: agent.name,
11397
- status: "already_configured"
11398
- });
11399
- if (printResults) {
11400
- printTask("warning", agent.name, "already configured", useColors);
11401
- }
11402
- continue;
11403
- }
11404
- const finishTask = printResults ? installTasks.start(agent.name) : () => {};
11405
- let result;
11406
- try {
11407
- result = await executeAgentSetupWithVerification(agent, fileSystemService, execService);
11408
- } finally {
11409
- finishTask();
11752
+ const setup = getResolvedSetupConfig(agent, fileSystemService);
11753
+ for (const config of getTomlSetups(setup)) {
11754
+ if (seen.has(config.configPath))
11755
+ continue;
11756
+ if (!await hasExistingConfigContent(config, fileSystemService))
11757
+ continue;
11758
+ seen.add(config.configPath);
11759
+ printTask("warning", config.configPath, "existing TOML comments/formatting will not be preserved", useColors);
11410
11760
  }
11411
- outcomes.push({
11412
- id: agent.id,
11413
- name: agent.name,
11414
- status: result.status,
11415
- message: result.status === "failed" ? result.message : undefined
11416
- });
11417
- if (!printResults) {
11761
+ }
11762
+ }
11763
+ async function getProjectUninstallPlan(fileSystemService) {
11764
+ const seenConfig = new Set;
11765
+ const plan = {
11766
+ configRemovals: []
11767
+ };
11768
+ for (const agent of agentDefinitions) {
11769
+ const setup = getAgentSetupConfig(agent, fileSystemService, "project");
11770
+ if (!setup)
11771
+ continue;
11772
+ for (const configSetup of getConfigFileSetups(setup)) {
11773
+ const key = `${configSetup.configPath}\x00${configSetup.serversKey}\x00${configSetup.serverName.toLowerCase()}`;
11774
+ if (seenConfig.has(key))
11775
+ continue;
11776
+ seenConfig.add(key);
11777
+ plan.configRemovals.push(configSetup);
11778
+ }
11779
+ }
11780
+ return plan;
11781
+ }
11782
+ async function cleanupLegacyProjectSetupState(fileSystemService) {
11783
+ const statePath = getLegacyProjectSetupStatePath(fileSystemService);
11784
+ try {
11785
+ if (!await fileSystemService.exists(statePath)) {
11786
+ return { removed: [], failed: [] };
11787
+ }
11788
+ await fileSystemService.deleteFile(statePath);
11789
+ return { removed: [statePath], failed: [] };
11790
+ } catch (err) {
11791
+ return {
11792
+ removed: [],
11793
+ failed: [
11794
+ {
11795
+ path: statePath,
11796
+ reason: `Could not remove legacy project setup marker: ${err instanceof Error ? err.message : String(err)}`
11797
+ }
11798
+ ]
11799
+ };
11800
+ }
11801
+ }
11802
+ function printProjectUninstallSummary(summary) {
11803
+ const totalRemoved = summary.removed.length + summary.legacyRemoved.length;
11804
+ console.log();
11805
+ if (summary.failed.length === 0) {
11806
+ if (summary.removed.length > 0) {
11807
+ console.log(" Done! GitHits MCP configuration was removed from this project.");
11808
+ } else if (summary.legacyRemoved.length > 0) {
11809
+ console.log(" Done! Removed legacy GitHits project setup marker. No project MCP config entries were found.");
11810
+ } else {
11811
+ console.log(" No project GitHits MCP configuration found.");
11812
+ }
11813
+ } else {
11814
+ console.log(" Project uninstall completed with errors.");
11815
+ }
11816
+ console.log(` Removed ${totalRemoved} item${totalRemoved !== 1 ? "s" : ""}. Skipped ${summary.skipped.length} config path${summary.skipped.length !== 1 ? "s" : ""} without GitHits.`);
11817
+ if (summary.failed.length > 0) {
11818
+ console.log(` Failed to remove ${summary.failed.length} item${summary.failed.length !== 1 ? "s" : ""}:`);
11819
+ for (const failure of summary.failed) {
11820
+ console.log(` - ${failure.path}: ${failure.reason}`);
11821
+ }
11822
+ }
11823
+ console.log();
11824
+ }
11825
+ async function runProjectMcpUninstall(options, deps, useColors) {
11826
+ const { fileSystemService, promptService } = deps;
11827
+ const isInteractive = deps.isInteractive ?? true;
11828
+ const scope = await resolveProjectSetupScope({}, fileSystemService);
11829
+ if (!scope)
11830
+ return;
11831
+ const projectPlan = await getProjectUninstallPlan(fileSystemService);
11832
+ console.log(`
11833
+ ${colorize("Remove GitHits from this project's MCP config.", "bold", useColors)}`);
11834
+ console.log(` ${colorize("Removes GitHits entries from supported project-local MCP files.", "dim", useColors)}
11835
+ `);
11836
+ console.log(` Project: ${scope.projectPath}`);
11837
+ for (const setup of projectPlan.configRemovals) {
11838
+ console.log(` Config: ${setup.configPath}`);
11839
+ }
11840
+ console.log();
11841
+ const checks = await Promise.all(projectPlan.configRemovals.map(async (setup) => ({
11842
+ setup,
11843
+ check: await getConfigUninstallCheckStatus(setup, fileSystemService)
11844
+ })));
11845
+ const configured = checks.filter((entry) => entry.check.status === "configured");
11846
+ const failedChecks = checks.filter((entry) => entry.check.status === "failed");
11847
+ for (const { setup, check } of failedChecks) {
11848
+ printTask("failed", setup.configPath, check.message, useColors);
11849
+ }
11850
+ const skipped = checks.filter((entry) => entry.check.status === "not_configured").map((entry) => entry.setup.configPath);
11851
+ const legacyStatePath = getLegacyProjectSetupStatePath(fileSystemService);
11852
+ const legacyProbeFailures = [];
11853
+ let hasLegacyState = false;
11854
+ try {
11855
+ hasLegacyState = await fileSystemService.exists(legacyStatePath);
11856
+ } catch (err) {
11857
+ legacyProbeFailures.push({
11858
+ path: legacyStatePath,
11859
+ reason: `Could not inspect legacy project setup marker: ${err instanceof Error ? err.message : String(err)}`
11860
+ });
11861
+ }
11862
+ const hasWork = configured.length > 0 || hasLegacyState;
11863
+ if (!hasWork && failedChecks.length === 0 && legacyProbeFailures.length === 0) {
11864
+ printProjectUninstallSummary({
11865
+ removed: [],
11866
+ legacyRemoved: [],
11867
+ skipped,
11868
+ failed: []
11869
+ });
11870
+ return;
11871
+ }
11872
+ if (!hasWork) {
11873
+ const summary2 = {
11874
+ removed: [],
11875
+ legacyRemoved: [],
11876
+ skipped,
11877
+ failed: failedChecks.map(({ setup, check }) => ({
11878
+ path: setup.configPath,
11879
+ reason: check.message
11880
+ })).concat(legacyProbeFailures)
11881
+ };
11882
+ process.exitCode = 1;
11883
+ printProjectUninstallSummary(summary2);
11884
+ return;
11885
+ }
11886
+ if (!isInteractive && !options.yes) {
11887
+ console.log(" Project uninstall needs confirmation. Because this session is non-interactive, no changes were made.");
11888
+ console.log();
11889
+ console.log(" To remove GitHits from this project's MCP files, run:");
11890
+ console.log(` ${formatCommand("githits init uninstall --project --yes", useColors)}`);
11891
+ console.log();
11892
+ return;
11893
+ }
11894
+ if (!options.yes) {
11895
+ let accepted;
11896
+ try {
11897
+ accepted = await promptService.confirm("Remove GitHits MCP config from this project?", false);
11898
+ } catch (err) {
11899
+ if (err instanceof ExitPromptError) {
11900
+ console.log(`
11901
+ Uninstall cancelled. No changes made.
11902
+ `);
11903
+ return;
11904
+ }
11905
+ throw err;
11906
+ }
11907
+ if (!accepted) {
11908
+ printTask("skipped", "Project uninstall skipped", "no changes made", useColors);
11909
+ console.log();
11910
+ return;
11911
+ }
11912
+ }
11913
+ const summary = {
11914
+ removed: [],
11915
+ legacyRemoved: [],
11916
+ skipped,
11917
+ failed: failedChecks.map(({ setup, check }) => ({
11918
+ path: setup.configPath,
11919
+ reason: check.message
11920
+ })).concat(legacyProbeFailures)
11921
+ };
11922
+ for (const { setup } of configured) {
11923
+ const result = await executeConfigFileUninstall(setup, fileSystemService);
11924
+ if (result.status === "removed") {
11925
+ summary.removed.push(setup.configPath);
11926
+ printTask("success", "GitHits project config", `removed from ${setup.configPath}`, useColors);
11927
+ } else if (result.status === "not_configured") {
11928
+ summary.skipped.push(setup.configPath);
11929
+ printTask("skipped", setup.configPath, "not configured", useColors);
11930
+ } else {
11931
+ summary.failed.push({
11932
+ path: setup.configPath,
11933
+ reason: result.message
11934
+ });
11935
+ printTask("failed", setup.configPath, result.message, useColors);
11936
+ }
11937
+ }
11938
+ if (legacyProbeFailures.length === 0) {
11939
+ const legacyCleanup = await cleanupLegacyProjectSetupState(fileSystemService);
11940
+ for (const path of legacyCleanup.removed) {
11941
+ summary.legacyRemoved.push(path);
11942
+ printTask("success", "Legacy project setup marker", `removed ${path}`, useColors);
11943
+ }
11944
+ for (const failure of legacyCleanup.failed) {
11945
+ summary.failed.push(failure);
11946
+ printTask("failed", failure.path, failure.reason, useColors);
11947
+ }
11948
+ }
11949
+ if (summary.failed.length > 0) {
11950
+ process.exitCode = 1;
11951
+ }
11952
+ printProjectUninstallSummary(summary);
11953
+ }
11954
+ function printNonInteractiveUninstallGuidance(useColors) {
11955
+ console.log(" Uninstall is interactive. Because this session is non-interactive, no changes were made.");
11956
+ console.log();
11957
+ console.log(" To remove user-level GitHits MCP config, run:");
11958
+ console.log(` ${formatCommand("githits init uninstall --yes", useColors)}`);
11959
+ console.log();
11960
+ console.log(" To remove project-level GitHits MCP config, run:");
11961
+ console.log(` ${formatCommand("githits init uninstall --project --yes", useColors)}`);
11962
+ console.log();
11963
+ }
11964
+ async function runUserMcpUninstall(options, deps, useColors) {
11965
+ const { fileSystemService, promptService, execService } = deps;
11966
+ console.log(`
11967
+ ${colorize("Disconnect GitHits from your coding agents.", "bold", useColors)}`);
11968
+ console.log(` ${colorize("Removes the local GitHits MCP configuration.", "dim", useColors)}
11969
+ `);
11970
+ console.log(` Scanning for configured agents...
11971
+ `);
11972
+ const scan = await scanAgentsForUninstall(fileSystemService, execService);
11973
+ for (const agent of scan.configured) {
11974
+ console.log(` ${colorize(`● ${agent.name} — configured`, "cyan", useColors)}`);
11975
+ }
11976
+ for (const agent of scan.notConfigured) {
11977
+ console.log(` ${warning(`${agent.name} — not configured`, useColors)}`);
11978
+ }
11979
+ for (const outcome of scan.failed) {
11980
+ console.log(` ${error(`${outcome.name} — cannot inspect config`, useColors)}`);
11981
+ }
11982
+ for (const agent of scan.notDetected) {
11983
+ console.log(` ${colorize(`${agent.name} — not detected`, "dim", useColors)}`);
11984
+ }
11985
+ console.log();
11986
+ if (scan.configured.length === 0 && scan.failed.length === 0) {
11987
+ console.log(` No GitHits MCP configurations found. Nothing to uninstall.
11988
+ `);
11989
+ return;
11990
+ }
11991
+ const outcomes = [...scan.failed];
11992
+ let alwaysMode = options.yes ?? false;
11993
+ for (const agent of scan.configured) {
11994
+ console.log(` Uninstalling from ${colorize(agent.name, "bold", useColors)}...
11995
+ `);
11996
+ const setupConfig = getResolvedSetupConfig(agent, fileSystemService);
11997
+ const uninstallConfig = agent.resolvedUninstallConfig ?? (setupConfig.method === "config-file" ? setupConfig : agent.getUninstallConfig?.(fileSystemService, agent.resolvedSetupContext));
11998
+ if (!uninstallConfig) {
11999
+ outcomes.push({
12000
+ id: agent.id,
12001
+ name: agent.name,
12002
+ status: "failed",
12003
+ message: `${agent.name} does not have a verified uninstall command.`
12004
+ });
12005
+ console.log(` ${error(`${agent.name} does not have a verified uninstall command.`, useColors)}
12006
+ `);
12007
+ continue;
12008
+ }
12009
+ const preview2 = formatUninstallPreview(uninstallConfig);
12010
+ for (const line of preview2.split(`
12011
+ `)) {
12012
+ console.log(` ${line}`);
12013
+ }
12014
+ console.log();
12015
+ if (!alwaysMode) {
12016
+ let choice;
12017
+ try {
12018
+ choice = await promptService.confirm3("Proceed?", "no");
12019
+ } catch (err) {
12020
+ if (err instanceof ExitPromptError) {
12021
+ console.log(`
12022
+ Uninstall cancelled.
12023
+ `);
12024
+ return;
12025
+ }
12026
+ throw err;
12027
+ }
12028
+ if (choice === "no") {
12029
+ outcomes.push({ id: agent.id, name: agent.name, status: "skipped" });
12030
+ console.log();
12031
+ continue;
12032
+ }
12033
+ if (choice === "always") {
12034
+ alwaysMode = true;
12035
+ }
12036
+ }
12037
+ let result = uninstallConfig.method === "cli" ? await executeCliUninstall(uninstallConfig, execService) : uninstallConfig.method === "config-file" ? await executeConfigFileUninstall(uninstallConfig, fileSystemService) : await executeCompositeUninstall(uninstallConfig, fileSystemService, execService);
12038
+ if (result.status === "removed" && !agent.skipUninstallVerification) {
12039
+ const verification = await verifyAgentUnconfigured(agent, fileSystemService, execService);
12040
+ if (!verification.ok) {
12041
+ result = {
12042
+ status: "failed",
12043
+ message: verification.message ?? `${agent.name} verification failed after uninstall.`,
12044
+ warnings: result.warnings
12045
+ };
12046
+ }
12047
+ }
12048
+ outcomes.push({
12049
+ id: agent.id,
12050
+ name: agent.name,
12051
+ status: result.status,
12052
+ message: result.status === "failed" ? result.message : undefined,
12053
+ warnings: result.warnings
12054
+ });
12055
+ if (result.status === "removed") {
12056
+ console.log(` ${success(`${agent.name} removed`, useColors)}
12057
+ `);
12058
+ for (const warn of result.warnings ?? []) {
12059
+ console.log(` ${warning(`Warning: ${warn}`, useColors)}`);
12060
+ }
12061
+ } else if (result.status === "not_configured") {
12062
+ console.log(` ${warning(`${agent.name} was not configured`, useColors)}
12063
+ `);
12064
+ } else {
12065
+ console.log(` ${error(result.message, useColors)}
12066
+ `);
12067
+ for (const warn of result.warnings ?? []) {
12068
+ console.log(` ${warning(`Warning: ${warn}`, useColors)}`);
12069
+ }
12070
+ }
12071
+ }
12072
+ const removed = outcomes.filter((o) => o.status === "removed").length;
12073
+ const notConfigured = outcomes.filter((o) => o.status === "not_configured").length + scan.notConfigured.length;
12074
+ const failed = outcomes.filter((o) => o.status === "failed").length;
12075
+ const skipped = outcomes.filter((o) => o.status === "skipped").length;
12076
+ if (failed > 0) {
12077
+ console.log(" Uninstall completed with errors.");
12078
+ } else if (removed > 0) {
12079
+ console.log(" Done! GitHits MCP configuration was removed.");
12080
+ } else if (skipped > 0) {
12081
+ console.log(" Uninstall skipped.");
12082
+ } else if (notConfigured > 0) {
12083
+ console.log(" No GitHits MCP configurations were active. Nothing to remove.");
12084
+ }
12085
+ if (removed > 0) {
12086
+ console.log(` ${removed} agent${removed !== 1 ? "s" : ""} removed.`);
12087
+ }
12088
+ if (notConfigured > 0) {
12089
+ console.log(` ${notConfigured} agent${notConfigured !== 1 ? "s" : ""} not configured.`);
12090
+ }
12091
+ if (skipped > 0) {
12092
+ console.log(` ${skipped} agent${skipped !== 1 ? "s" : ""} skipped.`);
12093
+ }
12094
+ if (failed > 0) {
12095
+ console.log(` ${failed} agent${failed !== 1 ? "s" : ""} failed to uninstall.`);
12096
+ for (const outcome of outcomes.filter((o) => o.status === "failed")) {
12097
+ console.log(` - ${outcome.name}: ${outcome.message ?? "Unknown error"}`);
12098
+ for (const warn of outcome.warnings ?? []) {
12099
+ console.log(` Warning: ${warn}`);
12100
+ }
12101
+ }
12102
+ }
12103
+ console.log();
12104
+ }
12105
+ async function verifyAgentConfigured(agent, fileSystemService, execService, scope) {
12106
+ const postCheck = await scanAgents([agent], fileSystemService, execService, {
12107
+ scope
12108
+ });
12109
+ if (postCheck.alreadyConfigured.some((a) => a.id === agent.id)) {
12110
+ return { ok: true };
12111
+ }
12112
+ if (postCheck.needsSetup.some((a) => a.id === agent.id)) {
12113
+ return {
12114
+ ok: false,
12115
+ message: `${agent.name} verification failed: not configured after setup.`
12116
+ };
12117
+ }
12118
+ return {
12119
+ ok: false,
12120
+ message: `${agent.name} verification failed: agent not detected after setup.`
12121
+ };
12122
+ }
12123
+ async function executeAgentSetupWithVerification(agent, fileSystemService, execService, scope) {
12124
+ const config = getResolvedSetupConfig(agent, fileSystemService);
12125
+ let result = config.method === "cli" ? await executeCliSetup(config, execService) : config.method === "config-file" ? await executeConfigFileSetup(config, fileSystemService) : await executeCompositeSetup(config, fileSystemService, execService);
12126
+ if (result.status === "success" || result.status === "already_configured") {
12127
+ const verification = await verifyAgentConfigured(agent, fileSystemService, execService, scope);
12128
+ if (!verification.ok) {
12129
+ result = {
12130
+ status: "failed",
12131
+ 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.`
12132
+ };
12133
+ }
12134
+ }
12135
+ return result;
12136
+ }
12137
+ async function installSelectedAgents(agents, scan, fileSystemService, execService, useColors, printResults, scope = "user") {
12138
+ const alreadyConfiguredIds = new Set(scan.alreadyConfigured.map((agent) => agent.id));
12139
+ const outcomes = [];
12140
+ const installTasks = createInstallTaskReporter(useColors);
12141
+ for (const agent of agents) {
12142
+ if (alreadyConfiguredIds.has(agent.id)) {
12143
+ outcomes.push({
12144
+ id: agent.id,
12145
+ name: agent.name,
12146
+ status: "already_configured"
12147
+ });
12148
+ if (printResults) {
12149
+ printTask("warning", agent.name, "already configured", useColors);
12150
+ }
12151
+ continue;
12152
+ }
12153
+ const finishTask = printResults ? installTasks.start(agent.name) : () => {};
12154
+ let result;
12155
+ try {
12156
+ result = await executeAgentSetupWithVerification(agent, fileSystemService, execService, scope);
12157
+ } finally {
12158
+ finishTask();
12159
+ }
12160
+ outcomes.push({
12161
+ id: agent.id,
12162
+ name: agent.name,
12163
+ status: result.status,
12164
+ message: result.status === "failed" ? result.message : undefined
12165
+ });
12166
+ if (!printResults) {
11418
12167
  continue;
11419
12168
  }
11420
12169
  if (result.status === "success") {
@@ -11568,6 +12317,7 @@ async function initAction(options, deps) {
11568
12317
  console.log();
11569
12318
  return;
11570
12319
  }
12320
+ let setupScope = options.project ? "project" : "user";
11571
12321
  if (!options.yes) {
11572
12322
  let intent;
11573
12323
  try {
@@ -11589,20 +12339,39 @@ async function initAction(options, deps) {
11589
12339
  console.log("\n No changes made. Run `npx githits@latest init` whenever you're ready.\n");
11590
12340
  return;
11591
12341
  }
12342
+ if (!options.project) {
12343
+ try {
12344
+ setupScope = await promptService.select(" Where should GitHits be configured?", INIT_SCOPE_CHOICES, "user");
12345
+ } catch (err) {
12346
+ if (err instanceof ExitPromptError) {
12347
+ console.log(`
12348
+ Setup cancelled. No changes made.
12349
+ `);
12350
+ return;
12351
+ }
12352
+ throw err;
12353
+ }
12354
+ }
12355
+ }
12356
+ if (setupScope === "project") {
12357
+ const scope = await resolveProjectSetupScope({}, fileSystemService);
12358
+ if (!scope)
12359
+ return;
12360
+ printProjectScopeExplanation(useColors);
11592
12361
  }
11593
12362
  printSection(1, "Detect tools", useColors);
11594
12363
  console.log(" Scanning for compatible AI coding tools...");
11595
12364
  const progress = createScanProgressReporter(useColors);
11596
- const scanPromise = startSafeInitScan(fileSystemService, execService, (scanProgress) => progress.onProgress(scanProgress));
12365
+ const scanPromise = startSafeInitScan(fileSystemService, execService, setupScope, (scanProgress) => progress.onProgress(scanProgress));
11597
12366
  let scan;
11598
12367
  try {
11599
12368
  scan = await unwrapSafeScan(scanPromise);
11600
12369
  } finally {
11601
12370
  progress.finish();
11602
12371
  }
11603
- printScanSummary(scan, useColors);
12372
+ printScanSummary(scan, useColors, setupScope);
11604
12373
  if (scan.needsSetup.length === 0 && scan.alreadyConfigured.length === 0) {
11605
- printTask("warning", "No supported AI coding tools detected", "install a supported tool and run `githits init` again", useColors);
12374
+ printTask("warning", setupScope === "project" ? "No project-configurable tools detected" : "No supported AI coding tools detected", setupScope === "project" ? "choose user-level config or install a project-configurable tool" : "install a supported tool and run `githits init` again", useColors);
11606
12375
  console.log();
11607
12376
  return;
11608
12377
  }
@@ -11630,6 +12399,9 @@ async function initAction(options, deps) {
11630
12399
  console.log();
11631
12400
  return;
11632
12401
  }
12402
+ if (toSetup.length > 0) {
12403
+ await printTomlRewriteWarnings(toSetup, fileSystemService, useColors);
12404
+ }
11633
12405
  printSection(3, "Sign in", useColors);
11634
12406
  const authStatus = await runInitAuthentication(options, promptService, createLoginDeps, useColors);
11635
12407
  if (authStatus === "cancelled") {
@@ -11638,11 +12410,11 @@ async function initAction(options, deps) {
11638
12410
  printSection(4, "Install and verify", useColors);
11639
12411
  if (toSetup.length === 0) {
11640
12412
  printTask("success", "Nothing to install", "all detected tools are already configured", useColors);
11641
- printPostSetupNextSteps(authStatus, useColors);
12413
+ printScopedNextSteps(setupScope, authStatus, useColors);
11642
12414
  console.log();
11643
12415
  return;
11644
12416
  }
11645
- const outcomes = await installSelectedAgents(toSetup, scan, fileSystemService, execService, useColors, true);
12417
+ const outcomes = await installSelectedAgents(toSetup, scan, fileSystemService, execService, useColors, true, setupScope);
11646
12418
  console.log();
11647
12419
  const configured = outcomes.filter((o) => o.status === "success").length;
11648
12420
  const alreadyDone = outcomes.filter((o) => o.status === "already_configured").length + scan.alreadyConfigured.length;
@@ -11650,7 +12422,7 @@ async function initAction(options, deps) {
11650
12422
  if (failed > 0) {
11651
12423
  console.log(" Setup completed with errors.");
11652
12424
  } else if (configured > 0 || alreadyDone > 0) {
11653
- printPostSetupNextSteps(authStatus, useColors);
12425
+ printScopedNextSteps(setupScope, authStatus, useColors);
11654
12426
  }
11655
12427
  if (failed > 0) {
11656
12428
  console.log(` ${failed} tool${failed !== 1 ? "s" : ""} failed to configure.`);
@@ -11665,145 +12437,35 @@ async function initAction(options, deps) {
11665
12437
  }
11666
12438
  async function initUninstallAction(options, deps) {
11667
12439
  const useColors = shouldUseColors();
11668
- const { fileSystemService, promptService, execService } = deps;
11669
- console.log(`
11670
- ${colorize("Disconnect GitHits from your coding agents.", "bold", useColors)}`);
11671
- console.log(` ${colorize("Removes the local GitHits MCP configuration.", "dim", useColors)}
11672
- `);
11673
- console.log(` Scanning for configured agents...
11674
- `);
11675
- const scan = await scanAgentsForUninstall(fileSystemService, execService);
11676
- for (const agent of scan.configured) {
11677
- console.log(` ${colorize(`● ${agent.name} — configured`, "cyan", useColors)}`);
11678
- }
11679
- for (const agent of scan.notConfigured) {
11680
- console.log(` ${warning(`${agent.name} — not configured`, useColors)}`);
11681
- }
11682
- for (const outcome of scan.failed) {
11683
- console.log(` ${error(`${outcome.name} — cannot inspect config`, useColors)}`);
11684
- }
11685
- for (const agent of scan.notDetected) {
11686
- console.log(` ${colorize(`${agent.name} — not detected`, "dim", useColors)}`);
12440
+ const { promptService } = deps;
12441
+ const isInteractive = deps.isInteractive ?? true;
12442
+ if (options.project) {
12443
+ await runProjectMcpUninstall(options, deps, useColors);
12444
+ return;
11687
12445
  }
11688
- console.log();
11689
- if (scan.configured.length === 0 && scan.failed.length === 0) {
11690
- console.log(` No GitHits MCP configurations found. Nothing to uninstall.
11691
- `);
12446
+ if (!isInteractive && !options.yes) {
12447
+ printNonInteractiveUninstallGuidance(useColors);
11692
12448
  return;
11693
12449
  }
11694
- const outcomes = [...scan.failed];
11695
- let alwaysMode = options.yes ?? false;
11696
- for (const agent of scan.configured) {
11697
- console.log(` Uninstalling from ${colorize(agent.name, "bold", useColors)}...
11698
- `);
11699
- const setupConfig = getResolvedSetupConfig(agent, fileSystemService);
11700
- const uninstallConfig = agent.resolvedUninstallConfig ?? (setupConfig.method === "config-file" ? setupConfig : agent.getUninstallConfig?.(fileSystemService, agent.resolvedSetupContext));
11701
- if (!uninstallConfig) {
11702
- outcomes.push({
11703
- id: agent.id,
11704
- name: agent.name,
11705
- status: "failed",
11706
- message: `${agent.name} does not have a verified uninstall command.`
11707
- });
11708
- console.log(` ${error(`${agent.name} does not have a verified uninstall command.`, useColors)}
11709
- `);
11710
- continue;
11711
- }
11712
- const preview2 = formatUninstallPreview(uninstallConfig);
11713
- for (const line of preview2.split(`
11714
- `)) {
11715
- console.log(` ${line}`);
11716
- }
11717
- console.log();
11718
- if (!alwaysMode) {
11719
- let choice;
11720
- try {
11721
- choice = await promptService.confirm3("Proceed?");
11722
- } catch (err) {
11723
- if (err instanceof ExitPromptError) {
11724
- console.log(`
11725
- Uninstall cancelled.
11726
- `);
11727
- return;
11728
- }
11729
- throw err;
11730
- }
11731
- if (choice === "no") {
11732
- outcomes.push({ id: agent.id, name: agent.name, status: "skipped" });
11733
- console.log();
11734
- continue;
11735
- }
11736
- if (choice === "always") {
11737
- alwaysMode = true;
11738
- }
11739
- }
11740
- let result = uninstallConfig.method === "cli" ? await executeCliUninstall(uninstallConfig, execService) : uninstallConfig.method === "config-file" ? await executeConfigFileUninstall(uninstallConfig, fileSystemService) : await executeCompositeUninstall(uninstallConfig, fileSystemService, execService);
11741
- if (result.status === "removed" && !agent.skipUninstallVerification) {
11742
- const verification = await verifyAgentUnconfigured(agent, fileSystemService, execService);
11743
- if (!verification.ok) {
11744
- result = {
11745
- status: "failed",
11746
- message: verification.message ?? `${agent.name} verification failed after uninstall.`,
11747
- warnings: result.warnings
11748
- };
11749
- }
11750
- }
11751
- outcomes.push({
11752
- id: agent.id,
11753
- name: agent.name,
11754
- status: result.status,
11755
- message: result.status === "failed" ? result.message : undefined,
11756
- warnings: result.warnings
11757
- });
11758
- if (result.status === "removed") {
11759
- console.log(` ${success(`${agent.name} removed`, useColors)}
11760
- `);
11761
- for (const warn of result.warnings ?? []) {
11762
- console.log(` ${warning(`Warning: ${warn}`, useColors)}`);
11763
- }
11764
- } else if (result.status === "not_configured") {
11765
- console.log(` ${warning(`${agent.name} was not configured`, useColors)}
11766
- `);
11767
- } else {
11768
- console.log(` ${error(result.message, useColors)}
12450
+ if (!options.yes) {
12451
+ let scope;
12452
+ try {
12453
+ scope = await promptService.select(" What should GitHits be removed from?", INIT_UNINSTALL_SCOPE_CHOICES, "user");
12454
+ } catch (err) {
12455
+ if (err instanceof ExitPromptError) {
12456
+ console.log(`
12457
+ Uninstall cancelled. No changes made.
11769
12458
  `);
11770
- for (const warn of result.warnings ?? []) {
11771
- console.log(` ${warning(`Warning: ${warn}`, useColors)}`);
12459
+ return;
11772
12460
  }
12461
+ throw err;
11773
12462
  }
11774
- }
11775
- const removed = outcomes.filter((o) => o.status === "removed").length;
11776
- const notConfigured = outcomes.filter((o) => o.status === "not_configured").length + scan.notConfigured.length;
11777
- const failed = outcomes.filter((o) => o.status === "failed").length;
11778
- const skipped = outcomes.filter((o) => o.status === "skipped").length;
11779
- if (failed > 0) {
11780
- console.log(" Uninstall completed with errors.");
11781
- } else if (removed > 0) {
11782
- console.log(" Done! GitHits MCP configuration was removed.");
11783
- } else if (skipped > 0) {
11784
- console.log(" Uninstall skipped.");
11785
- } else if (notConfigured > 0) {
11786
- console.log(" No GitHits MCP configurations were active. Nothing to remove.");
11787
- }
11788
- if (removed > 0) {
11789
- console.log(` ${removed} agent${removed !== 1 ? "s" : ""} removed.`);
11790
- }
11791
- if (notConfigured > 0) {
11792
- console.log(` ${notConfigured} agent${notConfigured !== 1 ? "s" : ""} not configured.`);
11793
- }
11794
- if (skipped > 0) {
11795
- console.log(` ${skipped} agent${skipped !== 1 ? "s" : ""} skipped.`);
11796
- }
11797
- if (failed > 0) {
11798
- console.log(` ${failed} agent${failed !== 1 ? "s" : ""} failed to uninstall.`);
11799
- for (const outcome of outcomes.filter((o) => o.status === "failed")) {
11800
- console.log(` - ${outcome.name}: ${outcome.message ?? "Unknown error"}`);
11801
- for (const warn of outcome.warnings ?? []) {
11802
- console.log(` Warning: ${warn}`);
11803
- }
12463
+ if (scope === "project") {
12464
+ await runProjectMcpUninstall(options, deps, useColors);
12465
+ return;
11804
12466
  }
11805
12467
  }
11806
- console.log();
12468
+ await runUserMcpUninstall(options, deps, useColors);
11807
12469
  }
11808
12470
  function printAuthRecoveryHint(useColors) {
11809
12471
  console.log(" You can still configure MCP, but GitHits tools will require auth.");
@@ -11821,30 +12483,45 @@ sets up Agent Skills instead. Detects supported coding tools on this machine,
11821
12483
  signs you in, and configures the tools you select.`;
11822
12484
  var INIT_UNINSTALL_DESCRIPTION = `Remove GitHits MCP server configuration from your coding agents.
11823
12485
 
11824
- Scans for available agents that currently have GitHits configured, then removes
11825
- only the GitHits MCP/plugin configuration with your confirmation. Authentication
11826
- tokens are not removed; use \`githits logout\` to remove stored credentials.`;
12486
+ In interactive mode, asks whether to remove user-level coding-agent config or
12487
+ project-level MCP config. Removes only GitHits MCP/plugin entries with your
12488
+ confirmation. Authentication tokens are not removed; use \`githits logout\` to
12489
+ remove stored credentials.`;
11827
12490
  function registerInitCommand(program) {
11828
- 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) => {
12491
+ const initCommand = program.command("init").argument("[action]", "Compatibility action; use uninstall with --project").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("--project", "Configure project-level MCP in the current directory").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 (action, options) => {
11829
12492
  const fileSystemService = new FileSystemServiceImpl;
11830
12493
  const promptService = new PromptServiceImpl;
11831
12494
  const execService = new ExecServiceImpl;
11832
- await initAction(options, {
12495
+ const deps = {
11833
12496
  fileSystemService,
11834
12497
  promptService,
11835
12498
  execService,
11836
12499
  createLoginDeps: () => createContainer(),
11837
12500
  isInteractive: process.stdin.isTTY === true && process.stdout.isTTY === true
12501
+ };
12502
+ if (action !== undefined) {
12503
+ failUnknownInitAction(action);
12504
+ return;
12505
+ }
12506
+ await initAction(options, {
12507
+ ...deps
11838
12508
  });
11839
12509
  });
11840
- 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) => {
12510
+ initCommand.command("uninstall").summary("Remove MCP server from coding agents or project config").description(INIT_UNINSTALL_DESCRIPTION).option("-y, --yes", "Skip prompts, uninstall user-level config", false).option("--project", "Remove project-level MCP from the current directory", false).action(async (options, command) => {
12511
+ const parentOptions = command.parent?.opts() ?? {};
12512
+ const resolvedOptions = {
12513
+ ...options,
12514
+ yes: options.yes || parentOptions.yes,
12515
+ project: options.project || parentOptions.project
12516
+ };
11841
12517
  const fileSystemService = new FileSystemServiceImpl;
11842
12518
  const promptService = new PromptServiceImpl;
11843
12519
  const execService = new ExecServiceImpl;
11844
- await initUninstallAction(options, {
12520
+ await initUninstallAction(resolvedOptions, {
11845
12521
  fileSystemService,
11846
12522
  promptService,
11847
- execService
12523
+ execService,
12524
+ isInteractive: process.stdin.isTTY === true && process.stdout.isTTY === true
11848
12525
  });
11849
12526
  });
11850
12527
  }
@@ -12088,8 +12765,13 @@ function resolveCodeTarget(target) {
12088
12765
  return mappedInvalidTargetResult(error2);
12089
12766
  }
12090
12767
  }
12091
- const hasPackageTarget = Boolean(target.registry || target.package_name);
12092
- const hasRepoTarget = Boolean(target.repo_url || target.git_ref);
12768
+ const registry = normaliseOptionalValue(target.registry)?.toLowerCase();
12769
+ const packageName = normaliseOptionalValue(target.package_name);
12770
+ const version2 = normaliseOptionalValue(target.version);
12771
+ const repoUrl = normaliseOptionalValue(target.repo_url);
12772
+ const gitRef = normaliseOptionalValue(target.git_ref);
12773
+ const hasPackageTarget = registry !== undefined || packageName !== undefined;
12774
+ const hasRepoTarget = repoUrl !== undefined || gitRef !== undefined;
12093
12775
  if (hasPackageTarget && hasRepoTarget) {
12094
12776
  return invalidTargetResult("Invalid target: provide either registry + package_name or repo_url with optional git_ref, not both.");
12095
12777
  }
@@ -12097,23 +12779,29 @@ function resolveCodeTarget(target) {
12097
12779
  return invalidTargetResult("Missing target: provide registry + package_name or repo_url.");
12098
12780
  }
12099
12781
  if (hasPackageTarget) {
12100
- if (!target.registry || !target.package_name) {
12782
+ if (!registry || !packageName) {
12101
12783
  return invalidTargetResult("Incomplete package target: both registry and package_name are required.");
12102
12784
  }
12103
12785
  return {
12104
- registry: toCodeNavigationRegistry(target.registry),
12105
- packageName: target.package_name,
12106
- version: target.version
12786
+ registry: toCodeNavigationRegistry(registry),
12787
+ packageName,
12788
+ version: version2
12107
12789
  };
12108
12790
  }
12109
- if (!target.repo_url) {
12791
+ if (!repoUrl) {
12110
12792
  return invalidTargetResult("Incomplete repository target: repo_url is required.");
12111
12793
  }
12112
12794
  return {
12113
- repoUrl: target.repo_url,
12114
- gitRef: target.git_ref
12795
+ repoUrl,
12796
+ gitRef
12115
12797
  };
12116
12798
  }
12799
+ function normaliseOptionalValue(value) {
12800
+ if (value === undefined)
12801
+ return;
12802
+ const trimmed = value.trim();
12803
+ return trimmed.length > 0 ? trimmed : undefined;
12804
+ }
12117
12805
  function mappedInvalidTargetResult(error2) {
12118
12806
  const mapped = mapCodeNavigationError(error2);
12119
12807
  return errorResult(JSON.stringify({
@@ -12410,7 +13098,7 @@ function buildPackageChangelogParams(input) {
12410
13098
  };
12411
13099
  }
12412
13100
  function resolveAddressing(input) {
12413
- const hasSpec = Boolean(input.registry || input.packageName);
13101
+ const hasSpec = hasNonBlankValue2(input.registry) || hasNonBlankValue2(input.packageName);
12414
13102
  const hasRepoUrl = Boolean(input.repoUrl?.trim());
12415
13103
  if (hasSpec && hasRepoUrl) {
12416
13104
  throw new InvalidPackageSpecError("Provide either `<spec>` (registry + name) or `--repo-url` / `repo_url`, not both.");
@@ -12436,6 +13124,9 @@ function resolveAddressing(input) {
12436
13124
  const registry = toPkgseerRegistry(normalisedRegistryArg);
12437
13125
  return { registry, packageName };
12438
13126
  }
13127
+ function hasNonBlankValue2(value) {
13128
+ return value !== undefined && value.trim().length > 0;
13129
+ }
12439
13130
  function normaliseGitRef(raw) {
12440
13131
  if (raw === undefined)
12441
13132
  return;
@@ -13247,11 +13938,13 @@ function createSearchTool(service) {
13247
13938
  annotations: { readOnlyHint: true },
13248
13939
  handler: async (args) => {
13249
13940
  try {
13250
- const resolvedTarget = args.target ? resolveSearchTarget(args.target) : undefined;
13941
+ const effectiveTarget = isBlankSearchTarget(args.target) ? undefined : args.target;
13942
+ const resolvedTarget = effectiveTarget ? resolveSearchTarget(effectiveTarget) : undefined;
13251
13943
  if (resolvedTarget && "content" in resolvedTarget)
13252
13944
  return resolvedTarget;
13253
- const effectiveTargets = args.targets?.length ? args.targets : undefined;
13254
- const resolvedTargets = effectiveTargets?.map((entry) => resolveSearchTarget(entry));
13945
+ const effectiveTargets = args.targets?.filter((target) => !isBlankSearchTarget(target));
13946
+ const nonEmptyTargets = effectiveTargets?.length ? effectiveTargets : undefined;
13947
+ const resolvedTargets = nonEmptyTargets?.map((entry) => resolveSearchTarget(entry));
13255
13948
  const resolvedTargetsError = resolvedTargets?.find((entry) => ("content" in entry));
13256
13949
  if (resolvedTargetsError) {
13257
13950
  return resolvedTargetsError;
@@ -13289,6 +13982,19 @@ function createSearchTool(service) {
13289
13982
  }
13290
13983
  };
13291
13984
  }
13985
+ function isBlankSearchTarget(target) {
13986
+ if (target === undefined)
13987
+ return true;
13988
+ if (typeof target === "string")
13989
+ return target.trim().length === 0;
13990
+ return !(normaliseOptionalValue2(target.registry) || normaliseOptionalValue2(target.package_name) || normaliseOptionalValue2(target.version) || normaliseOptionalValue2(target.repo_url) || normaliseOptionalValue2(target.git_ref));
13991
+ }
13992
+ function normaliseOptionalValue2(value) {
13993
+ if (value === undefined)
13994
+ return;
13995
+ const trimmed = value.trim();
13996
+ return trimmed.length > 0 ? trimmed : undefined;
13997
+ }
13292
13998
  function isResolvedSearchTarget(target) {
13293
13999
  return !("content" in target);
13294
14000
  }
@@ -13306,8 +14012,13 @@ function resolveSearchTarget(target) {
13306
14012
  }));
13307
14013
  }
13308
14014
  }
13309
- const hasPackageTarget = Boolean(target.registry || target.package_name);
13310
- const hasRepoTarget = Boolean(target.repo_url || target.git_ref);
14015
+ const registry = normaliseOptionalValue2(target.registry)?.toLowerCase();
14016
+ const packageName = normaliseOptionalValue2(target.package_name);
14017
+ const version2 = normaliseOptionalValue2(target.version);
14018
+ const repoUrl = normaliseOptionalValue2(target.repo_url);
14019
+ const gitRef = normaliseOptionalValue2(target.git_ref);
14020
+ const hasPackageTarget = registry !== undefined || packageName !== undefined;
14021
+ const hasRepoTarget = repoUrl !== undefined || gitRef !== undefined;
13311
14022
  if (hasPackageTarget && hasRepoTarget) {
13312
14023
  return invalidSearchTargetResult("Invalid target: provide either registry + package_name or repo_url with optional git_ref, not both.");
13313
14024
  }
@@ -13315,19 +14026,19 @@ function resolveSearchTarget(target) {
13315
14026
  return invalidSearchTargetResult("Missing target: provide registry + package_name or repo_url.");
13316
14027
  }
13317
14028
  if (hasPackageTarget) {
13318
- if (!target.registry || !target.package_name) {
14029
+ if (!registry || !packageName) {
13319
14030
  return invalidSearchTargetResult("Incomplete package target: both registry and package_name are required.");
13320
14031
  }
13321
14032
  return {
13322
- registry: toCodeNavigationRegistry(target.registry),
13323
- packageName: target.package_name,
13324
- version: target.version
14033
+ registry: toCodeNavigationRegistry(registry),
14034
+ packageName,
14035
+ version: version2
13325
14036
  };
13326
14037
  }
13327
- if (!target.repo_url) {
14038
+ if (!repoUrl) {
13328
14039
  return invalidSearchTargetResult("Incomplete repository target: repo_url is required.");
13329
14040
  }
13330
- return { repoUrl: target.repo_url, gitRef: target.git_ref };
14041
+ return { repoUrl, gitRef };
13331
14042
  }
13332
14043
  function invalidSearchTargetResult(message) {
13333
14044
  return errorResult(JSON.stringify({
@@ -14292,7 +15003,7 @@ function requireSearchService(deps) {
14292
15003
  return deps.codeNavigationService;
14293
15004
  }
14294
15005
  async function loadContainer2() {
14295
- const { createContainer: createContainer2 } = await import("./shared/chunk-mw1910qd.js");
15006
+ const { createContainer: createContainer2 } = await import("./shared/chunk-2w48kb4c.js");
14296
15007
  return createContainer2();
14297
15008
  }
14298
15009
  function parseTargetSpecs(specs) {