claudekit-cli 3.9.2 → 3.10.1

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.
Files changed (2) hide show
  1. package/dist/index.js +431 -143
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12363,6 +12363,231 @@ var init_install_error_handler = __esm(() => {
12363
12363
  init_logger();
12364
12364
  });
12365
12365
 
12366
+ // src/services/package-installer/gemini-mcp-linker.ts
12367
+ var exports_gemini_mcp_linker = {};
12368
+ __export(exports_gemini_mcp_linker, {
12369
+ processGeminiMcpLinking: () => processGeminiMcpLinking,
12370
+ linkGeminiMcpConfig: () => linkGeminiMcpConfig,
12371
+ findMcpConfigPath: () => findMcpConfigPath,
12372
+ checkExistingGeminiConfig: () => checkExistingGeminiConfig,
12373
+ addGeminiToGitignore: () => addGeminiToGitignore
12374
+ });
12375
+ import { existsSync as existsSync7, lstatSync, readlinkSync } from "node:fs";
12376
+ import { mkdir as mkdir9, readFile as readFile14, symlink as symlink2, writeFile as writeFile12 } from "node:fs/promises";
12377
+ import { homedir as homedir4 } from "node:os";
12378
+ import { dirname as dirname6, join as join25, resolve as resolve3 } from "node:path";
12379
+ function getGlobalMcpConfigPath() {
12380
+ return join25(homedir4(), ".claude", ".mcp.json");
12381
+ }
12382
+ function getLocalMcpConfigPath(projectDir) {
12383
+ return join25(projectDir, ".mcp.json");
12384
+ }
12385
+ function findMcpConfigPath(projectDir) {
12386
+ const localPath = getLocalMcpConfigPath(projectDir);
12387
+ if (existsSync7(localPath)) {
12388
+ logger.debug(`Found local MCP config: ${localPath}`);
12389
+ return localPath;
12390
+ }
12391
+ const globalPath = getGlobalMcpConfigPath();
12392
+ if (existsSync7(globalPath)) {
12393
+ logger.debug(`Found global MCP config: ${globalPath}`);
12394
+ return globalPath;
12395
+ }
12396
+ logger.debug("No MCP config found (local or global)");
12397
+ return null;
12398
+ }
12399
+ function checkExistingGeminiConfig(projectDir) {
12400
+ const geminiSettingsPath = join25(projectDir, ".gemini", "settings.json");
12401
+ if (!existsSync7(geminiSettingsPath)) {
12402
+ return { exists: false, isSymlink: false };
12403
+ }
12404
+ try {
12405
+ const stats = lstatSync(geminiSettingsPath);
12406
+ if (stats.isSymbolicLink()) {
12407
+ const target = readlinkSync(geminiSettingsPath);
12408
+ return { exists: true, isSymlink: true, currentTarget: target };
12409
+ }
12410
+ return { exists: true, isSymlink: false };
12411
+ } catch {
12412
+ return { exists: true, isSymlink: false };
12413
+ }
12414
+ }
12415
+ async function readJsonFile(filePath) {
12416
+ try {
12417
+ const content = await readFile14(filePath, "utf-8");
12418
+ return JSON.parse(content);
12419
+ } catch (error) {
12420
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
12421
+ logger.debug(`Failed to read/parse JSON file ${filePath}: ${errorMessage}`);
12422
+ return null;
12423
+ }
12424
+ }
12425
+ async function createSymlink(targetPath, linkPath, projectDir) {
12426
+ const isWindows5 = process.platform === "win32";
12427
+ const linkDir = dirname6(linkPath);
12428
+ if (!existsSync7(linkDir)) {
12429
+ await mkdir9(linkDir, { recursive: true });
12430
+ logger.debug(`Created directory: ${linkDir}`);
12431
+ }
12432
+ const localMcpPath = join25(projectDir, ".mcp.json");
12433
+ const isLocalConfig = targetPath === localMcpPath;
12434
+ const symlinkTarget = isLocalConfig ? "../.mcp.json" : targetPath;
12435
+ try {
12436
+ await symlink2(symlinkTarget, linkPath, isWindows5 ? "file" : undefined);
12437
+ logger.debug(`Created symlink: ${linkPath} → ${symlinkTarget}`);
12438
+ return { success: true, method: "symlink", targetPath };
12439
+ } catch (error) {
12440
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
12441
+ return {
12442
+ success: false,
12443
+ method: "symlink",
12444
+ error: `Failed to create symlink: ${errorMessage}`
12445
+ };
12446
+ }
12447
+ }
12448
+ async function createNewSettingsWithMerge(geminiSettingsPath, mcpConfigPath) {
12449
+ const linkDir = dirname6(geminiSettingsPath);
12450
+ if (!existsSync7(linkDir)) {
12451
+ await mkdir9(linkDir, { recursive: true });
12452
+ logger.debug(`Created directory: ${linkDir}`);
12453
+ }
12454
+ const mcpConfig = await readJsonFile(mcpConfigPath);
12455
+ if (!mcpConfig) {
12456
+ return { success: false, method: "merge", error: "Failed to read MCP config" };
12457
+ }
12458
+ const mcpServers = mcpConfig.mcpServers;
12459
+ if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
12460
+ return { success: false, method: "merge", error: "MCP config has no valid mcpServers object" };
12461
+ }
12462
+ const newSettings = { mcpServers };
12463
+ try {
12464
+ await writeFile12(geminiSettingsPath, JSON.stringify(newSettings, null, 2), "utf-8");
12465
+ logger.debug(`Created new Gemini settings with mcpServers: ${geminiSettingsPath}`);
12466
+ return { success: true, method: "merge", targetPath: mcpConfigPath };
12467
+ } catch (error) {
12468
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
12469
+ return {
12470
+ success: false,
12471
+ method: "merge",
12472
+ error: `Failed to write settings: ${errorMessage}`
12473
+ };
12474
+ }
12475
+ }
12476
+ async function mergeGeminiSettings(geminiSettingsPath, mcpConfigPath) {
12477
+ const geminiSettings = await readJsonFile(geminiSettingsPath);
12478
+ if (!geminiSettings) {
12479
+ return { success: false, method: "merge", error: "Failed to read existing Gemini settings" };
12480
+ }
12481
+ const mcpConfig = await readJsonFile(mcpConfigPath);
12482
+ if (!mcpConfig) {
12483
+ return { success: false, method: "merge", error: "Failed to read MCP config" };
12484
+ }
12485
+ const mcpServers = mcpConfig.mcpServers;
12486
+ if (!mcpServers || typeof mcpServers !== "object" || Array.isArray(mcpServers)) {
12487
+ return { success: false, method: "merge", error: "MCP config has no valid mcpServers object" };
12488
+ }
12489
+ const mergedSettings = {
12490
+ ...geminiSettings,
12491
+ mcpServers
12492
+ };
12493
+ try {
12494
+ await writeFile12(geminiSettingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
12495
+ logger.debug(`Merged mcpServers into: ${geminiSettingsPath}`);
12496
+ return { success: true, method: "merge", targetPath: mcpConfigPath };
12497
+ } catch (error) {
12498
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
12499
+ return {
12500
+ success: false,
12501
+ method: "merge",
12502
+ error: `Failed to write merged settings: ${errorMessage}`
12503
+ };
12504
+ }
12505
+ }
12506
+ async function addGeminiToGitignore(projectDir) {
12507
+ const gitignorePath = join25(projectDir, ".gitignore");
12508
+ const geminiPattern = ".gemini/";
12509
+ try {
12510
+ let content = "";
12511
+ if (existsSync7(gitignorePath)) {
12512
+ content = await readFile14(gitignorePath, "utf-8");
12513
+ const lines = content.split(`
12514
+ `).map((line) => line.trim()).filter((line) => !line.startsWith("#"));
12515
+ const geminiPatterns = [".gemini/", ".gemini", "/.gemini/", "/.gemini"];
12516
+ if (lines.some((line) => geminiPatterns.includes(line))) {
12517
+ logger.debug(".gemini/ already in .gitignore");
12518
+ return;
12519
+ }
12520
+ }
12521
+ const newLine = content.endsWith(`
12522
+ `) || content === "" ? "" : `
12523
+ `;
12524
+ const comment = "# Gemini CLI settings (contains user-specific config)";
12525
+ await writeFile12(gitignorePath, `${content}${newLine}${comment}
12526
+ ${geminiPattern}
12527
+ `, "utf-8");
12528
+ logger.debug(`Added ${geminiPattern} to .gitignore`);
12529
+ } catch (error) {
12530
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
12531
+ logger.warning(`Failed to update .gitignore: ${errorMessage}`);
12532
+ }
12533
+ }
12534
+ async function linkGeminiMcpConfig(projectDir, options = {}) {
12535
+ const { skipGitignore = false } = options;
12536
+ const resolvedProjectDir = resolve3(projectDir);
12537
+ const geminiSettingsPath = join25(resolvedProjectDir, ".gemini", "settings.json");
12538
+ const mcpConfigPath = findMcpConfigPath(resolvedProjectDir);
12539
+ if (!mcpConfigPath) {
12540
+ return {
12541
+ success: false,
12542
+ method: "symlink",
12543
+ error: "No MCP config found. Create .mcp.json or ~/.claude/.mcp.json first."
12544
+ };
12545
+ }
12546
+ const existing = checkExistingGeminiConfig(resolvedProjectDir);
12547
+ let result;
12548
+ if (!existing.exists) {
12549
+ result = await createSymlink(mcpConfigPath, geminiSettingsPath, resolvedProjectDir);
12550
+ if (!result.success && process.platform === "win32") {
12551
+ logger.debug("Symlink failed on Windows, falling back to creating new settings with mcpServers");
12552
+ result = await createNewSettingsWithMerge(geminiSettingsPath, mcpConfigPath);
12553
+ }
12554
+ } else if (existing.isSymlink) {
12555
+ logger.debug(`Gemini config already symlinked: ${existing.currentTarget}`);
12556
+ result = { success: true, method: "skipped", targetPath: existing.currentTarget };
12557
+ } else {
12558
+ result = await mergeGeminiSettings(geminiSettingsPath, mcpConfigPath);
12559
+ }
12560
+ if (result.success && !skipGitignore) {
12561
+ await addGeminiToGitignore(resolvedProjectDir);
12562
+ }
12563
+ return result;
12564
+ }
12565
+ async function processGeminiMcpLinking(projectDir) {
12566
+ logger.info("Setting up Gemini CLI MCP integration...");
12567
+ const result = await linkGeminiMcpConfig(projectDir);
12568
+ if (result.success) {
12569
+ switch (result.method) {
12570
+ case "symlink":
12571
+ logger.success(`Gemini MCP linked: .gemini/settings.json → ${result.targetPath}`);
12572
+ logger.info("MCP servers will auto-sync with your Claude config.");
12573
+ break;
12574
+ case "merge":
12575
+ logger.success("Gemini MCP config updated (merged mcpServers, preserved your settings)");
12576
+ logger.info("Note: Run 'ck init' again to sync MCP config changes.");
12577
+ break;
12578
+ case "skipped":
12579
+ logger.info("Gemini MCP config already configured.");
12580
+ break;
12581
+ }
12582
+ } else {
12583
+ logger.warning(`Gemini MCP setup incomplete: ${result.error}`);
12584
+ logger.info("Manual setup: mkdir -p .gemini && ln -sf .claude/.mcp.json .gemini/settings.json");
12585
+ }
12586
+ }
12587
+ var init_gemini_mcp_linker = __esm(() => {
12588
+ init_logger();
12589
+ });
12590
+
12366
12591
  // src/services/package-installer/package-installer.ts
12367
12592
  var exports_package_installer = {};
12368
12593
  __export(exports_package_installer, {
@@ -12379,10 +12604,10 @@ __export(exports_package_installer, {
12379
12604
  getPackageVersion: () => getPackageVersion
12380
12605
  });
12381
12606
  import { exec as exec5, execFile as execFile2, spawn } from "node:child_process";
12382
- import { join as join25, resolve as resolve3 } from "node:path";
12607
+ import { join as join26, resolve as resolve4 } from "node:path";
12383
12608
  import { promisify as promisify5 } from "node:util";
12384
12609
  function executeInteractiveScript(command, args, options) {
12385
- return new Promise((resolve4, reject) => {
12610
+ return new Promise((resolve5, reject) => {
12386
12611
  const child = spawn(command, args, {
12387
12612
  stdio: ["ignore", "inherit", "inherit"],
12388
12613
  cwd: options?.cwd,
@@ -12403,7 +12628,7 @@ function executeInteractiveScript(command, args, options) {
12403
12628
  } else if (code2 !== 0) {
12404
12629
  reject(new Error(`Command exited with code ${code2}`));
12405
12630
  } else {
12406
- resolve4();
12631
+ resolve5();
12407
12632
  }
12408
12633
  });
12409
12634
  child.on("error", (error) => {
@@ -12574,7 +12799,7 @@ async function installOpenCode() {
12574
12799
  logger.info(`Installing ${displayName}...`);
12575
12800
  const { unlink: unlink5 } = await import("node:fs/promises");
12576
12801
  const { tmpdir: tmpdir3 } = await import("node:os");
12577
- const tempScriptPath = join25(tmpdir3(), "opencode-install.sh");
12802
+ const tempScriptPath = join26(tmpdir3(), "opencode-install.sh");
12578
12803
  try {
12579
12804
  logger.info("Downloading OpenCode installation script...");
12580
12805
  await execFileAsync("curl", ["-fsSL", "https://opencode.ai/install", "-o", tempScriptPath], {
@@ -12618,7 +12843,7 @@ async function installOpenCode() {
12618
12843
  async function installGemini() {
12619
12844
  return installPackageGlobally("@google/gemini-cli", "Google Gemini CLI");
12620
12845
  }
12621
- async function processPackageInstallations(shouldInstallOpenCode, shouldInstallGemini) {
12846
+ async function processPackageInstallations(shouldInstallOpenCode, shouldInstallGemini, projectDir) {
12622
12847
  const results = {};
12623
12848
  if (shouldInstallOpenCode) {
12624
12849
  const alreadyInstalled = await isOpenCodeInstalled();
@@ -12643,12 +12868,17 @@ async function processPackageInstallations(shouldInstallOpenCode, shouldInstallG
12643
12868
  } else {
12644
12869
  results.gemini = await installGemini();
12645
12870
  }
12871
+ const geminiAvailable = alreadyInstalled || results.gemini?.success;
12872
+ if (projectDir && geminiAvailable) {
12873
+ const { processGeminiMcpLinking: processGeminiMcpLinking2 } = await Promise.resolve().then(() => (init_gemini_mcp_linker(), exports_gemini_mcp_linker));
12874
+ await processGeminiMcpLinking2(projectDir);
12875
+ }
12646
12876
  }
12647
12877
  return results;
12648
12878
  }
12649
12879
  function validateScriptPath(skillsDir, scriptPath) {
12650
- const skillsDirResolved = resolve3(skillsDir);
12651
- const scriptPathResolved = resolve3(scriptPath);
12880
+ const skillsDirResolved = resolve4(skillsDir);
12881
+ const scriptPathResolved = resolve4(scriptPath);
12652
12882
  const isWindows5 = process.platform === "win32";
12653
12883
  const skillsDirNormalized = isWindows5 ? skillsDirResolved.toLowerCase() : skillsDirResolved;
12654
12884
  const scriptPathNormalized = isWindows5 ? scriptPathResolved.toLowerCase() : scriptPathResolved;
@@ -12684,11 +12914,11 @@ async function installSkillsDependencies(skillsDir) {
12684
12914
  };
12685
12915
  }
12686
12916
  try {
12687
- const { existsSync: existsSync7 } = await import("node:fs");
12917
+ const { existsSync: existsSync8 } = await import("node:fs");
12688
12918
  const clack = await Promise.resolve().then(() => (init_dist2(), exports_dist));
12689
12919
  const platform9 = process.platform;
12690
12920
  const scriptName = platform9 === "win32" ? "install.ps1" : "install.sh";
12691
- const scriptPath = join25(skillsDir, scriptName);
12921
+ const scriptPath = join26(skillsDir, scriptName);
12692
12922
  try {
12693
12923
  validateScriptPath(skillsDir, scriptPath);
12694
12924
  } catch (error) {
@@ -12700,11 +12930,11 @@ async function installSkillsDependencies(skillsDir) {
12700
12930
  error: `Path validation failed: ${errorMessage}`
12701
12931
  };
12702
12932
  }
12703
- if (!existsSync7(scriptPath)) {
12933
+ if (!existsSync8(scriptPath)) {
12704
12934
  logger.warning(`Skills installation script not found: ${scriptPath}`);
12705
12935
  logger.info("");
12706
12936
  logger.info("\uD83D\uDCD6 Manual Installation Instructions:");
12707
- logger.info(` See: ${join25(skillsDir, "INSTALLATION.md")}`);
12937
+ logger.info(` See: ${join26(skillsDir, "INSTALLATION.md")}`);
12708
12938
  logger.info("");
12709
12939
  logger.info("Quick start:");
12710
12940
  logger.info(" cd .claude/skills/ai-multimodal/scripts");
@@ -12720,8 +12950,8 @@ async function installSkillsDependencies(skillsDir) {
12720
12950
  logger.info(` Platform: ${platform9 === "win32" ? "Windows (PowerShell)" : "Unix (bash)"}`);
12721
12951
  if (logger.isVerbose()) {
12722
12952
  try {
12723
- const { readFile: readFile14 } = await import("node:fs/promises");
12724
- const scriptContent = await readFile14(scriptPath, "utf-8");
12953
+ const { readFile: readFile15 } = await import("node:fs/promises");
12954
+ const scriptContent = await readFile15(scriptPath, "utf-8");
12725
12955
  const previewLines = scriptContent.split(`
12726
12956
  `).slice(0, 20);
12727
12957
  logger.verbose("Script preview (first 20 lines):");
@@ -12747,7 +12977,7 @@ async function installSkillsDependencies(skillsDir) {
12747
12977
  logger.info(` ${platform9 === "win32" ? `powershell -File "${scriptPath}"` : `bash ${scriptPath}`}`);
12748
12978
  logger.info("");
12749
12979
  logger.info("Or see complete guide:");
12750
- logger.info(` ${join25(skillsDir, "INSTALLATION.md")}`);
12980
+ logger.info(` ${join26(skillsDir, "INSTALLATION.md")}`);
12751
12981
  return {
12752
12982
  success: false,
12753
12983
  package: displayName,
@@ -12848,7 +13078,7 @@ async function installSkillsDependencies(skillsDir) {
12848
13078
  logger.info("\uD83D\uDCD6 Manual Installation Instructions:");
12849
13079
  logger.info("");
12850
13080
  logger.info("See complete guide:");
12851
- logger.info(` cat ${join25(skillsDir, "INSTALLATION.md")}`);
13081
+ logger.info(` cat ${join26(skillsDir, "INSTALLATION.md")}`);
12852
13082
  logger.info("");
12853
13083
  logger.info("Quick start:");
12854
13084
  logger.info(" cd .claude/skills/ai-multimodal/scripts");
@@ -13524,7 +13754,7 @@ function getPagerArgs(pagerCmd) {
13524
13754
  return [];
13525
13755
  }
13526
13756
  async function trySystemPager(content) {
13527
- return new Promise((resolve7) => {
13757
+ return new Promise((resolve8) => {
13528
13758
  const pagerCmd = process.env.PAGER || "less";
13529
13759
  const pagerArgs = getPagerArgs(pagerCmd);
13530
13760
  try {
@@ -13534,20 +13764,20 @@ async function trySystemPager(content) {
13534
13764
  });
13535
13765
  const timeout = setTimeout(() => {
13536
13766
  pager.kill();
13537
- resolve7(false);
13767
+ resolve8(false);
13538
13768
  }, 30000);
13539
13769
  pager.stdin.write(content);
13540
13770
  pager.stdin.end();
13541
13771
  pager.on("close", (code2) => {
13542
13772
  clearTimeout(timeout);
13543
- resolve7(code2 === 0);
13773
+ resolve8(code2 === 0);
13544
13774
  });
13545
13775
  pager.on("error", () => {
13546
13776
  clearTimeout(timeout);
13547
- resolve7(false);
13777
+ resolve8(false);
13548
13778
  });
13549
13779
  } catch {
13550
- resolve7(false);
13780
+ resolve8(false);
13551
13781
  }
13552
13782
  });
13553
13783
  }
@@ -13574,16 +13804,16 @@ async function basicPager(content) {
13574
13804
  break;
13575
13805
  }
13576
13806
  const remaining = lines.length - currentLine;
13577
- await new Promise((resolve7) => {
13807
+ await new Promise((resolve8) => {
13578
13808
  rl.question(`-- More (${remaining} lines) [Enter/q] --`, (answer) => {
13579
13809
  if (answer.toLowerCase() === "q") {
13580
13810
  rl.close();
13581
13811
  process.exitCode = 0;
13582
- resolve7();
13812
+ resolve8();
13583
13813
  return;
13584
13814
  }
13585
13815
  process.stdout.write("\x1B[1A\x1B[2K");
13586
- resolve7();
13816
+ resolve8();
13587
13817
  });
13588
13818
  });
13589
13819
  }
@@ -13841,8 +14071,8 @@ var init_help_interceptor = __esm(() => {
13841
14071
  });
13842
14072
 
13843
14073
  // src/index.ts
13844
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
13845
- import { join as join35 } from "path";
14074
+ import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
14075
+ import { join as join36 } from "path";
13846
14076
 
13847
14077
  // node_modules/cac/dist/index.mjs
13848
14078
  import { EventEmitter } from "events";
@@ -14447,7 +14677,7 @@ var cac = (name = "") => new CAC(name);
14447
14677
  // package.json
14448
14678
  var package_default = {
14449
14679
  name: "claudekit-cli",
14450
- version: "3.9.2",
14680
+ version: "3.10.1",
14451
14681
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
14452
14682
  type: "module",
14453
14683
  repository: {
@@ -18291,7 +18521,7 @@ async function doctorCommand(options = {}) {
18291
18521
  }
18292
18522
 
18293
18523
  // src/commands/init.ts
18294
- import { join as join30, resolve as resolve5 } from "node:path";
18524
+ import { join as join31, resolve as resolve6 } from "node:path";
18295
18525
 
18296
18526
  // src/domains/config/config-manager.ts
18297
18527
  init_logger();
@@ -27327,30 +27557,75 @@ class SettingsMerger {
27327
27557
  return merged;
27328
27558
  }
27329
27559
  static mergeHookEntries(sourceEntries, destEntries, eventName, result) {
27330
- const existingCommands = new Set;
27331
- SettingsMerger.extractCommands(destEntries, existingCommands);
27332
27560
  if (destEntries.length > 0) {
27333
27561
  result.hooksPreserved += destEntries.length;
27334
27562
  }
27335
- const merged = [...destEntries];
27563
+ const merged = destEntries.map((entry) => SettingsMerger.deepCopyEntry(entry));
27564
+ const matcherIndex = new Map;
27565
+ for (let i = 0;i < merged.length; i++) {
27566
+ const entry = merged[i];
27567
+ if ("matcher" in entry && entry.matcher) {
27568
+ matcherIndex.set(entry.matcher, i);
27569
+ }
27570
+ }
27571
+ const existingCommands = new Set;
27572
+ SettingsMerger.extractCommands(destEntries, existingCommands);
27336
27573
  for (const entry of sourceEntries) {
27574
+ const sourceMatcher = "matcher" in entry ? entry.matcher : undefined;
27337
27575
  const commands = SettingsMerger.getEntryCommands(entry);
27338
- const isFullyDuplicated = commands.length > 0 && commands.every((cmd) => existingCommands.has(cmd));
27339
- const duplicateCommands = commands.filter((cmd) => existingCommands.has(cmd));
27340
- if (duplicateCommands.length > 0) {
27341
- const summary = duplicateCommands.length === 1 ? `"${SettingsMerger.truncateCommand(duplicateCommands[0])}"` : `${duplicateCommands.length} commands`;
27342
- result.conflictsDetected.push(`${eventName}: duplicate ${summary}`);
27343
- }
27344
- if (!isFullyDuplicated) {
27345
- merged.push(entry);
27346
- result.hooksAdded++;
27347
- for (const cmd of commands) {
27348
- existingCommands.add(cmd);
27576
+ if (sourceMatcher && matcherIndex.has(sourceMatcher)) {
27577
+ const existingIdx = matcherIndex.get(sourceMatcher);
27578
+ if (existingIdx === undefined)
27579
+ continue;
27580
+ const existingEntry = merged[existingIdx];
27581
+ const newCommands = commands.filter((cmd) => !existingCommands.has(cmd));
27582
+ const duplicateCommands = commands.filter((cmd) => existingCommands.has(cmd));
27583
+ if (duplicateCommands.length > 0) {
27584
+ const summary = duplicateCommands.length === 1 ? `"${SettingsMerger.truncateCommand(duplicateCommands[0])}"` : `${duplicateCommands.length} commands`;
27585
+ result.conflictsDetected.push(`${eventName}: duplicate ${summary}`);
27586
+ }
27587
+ if (newCommands.length > 0 && "hooks" in entry && entry.hooks) {
27588
+ if (!existingEntry.hooks) {
27589
+ existingEntry.hooks = [];
27590
+ }
27591
+ for (const hook of entry.hooks) {
27592
+ if (hook.command && !existingCommands.has(hook.command)) {
27593
+ existingEntry.hooks.push(hook);
27594
+ existingCommands.add(hook.command);
27595
+ }
27596
+ }
27597
+ result.hooksAdded++;
27598
+ }
27599
+ } else {
27600
+ const isFullyDuplicated = commands.length > 0 && commands.every((cmd) => existingCommands.has(cmd));
27601
+ const duplicateCommands = commands.filter((cmd) => existingCommands.has(cmd));
27602
+ if (duplicateCommands.length > 0) {
27603
+ const summary = duplicateCommands.length === 1 ? `"${SettingsMerger.truncateCommand(duplicateCommands[0])}"` : `${duplicateCommands.length} commands`;
27604
+ result.conflictsDetected.push(`${eventName}: duplicate ${summary}`);
27605
+ }
27606
+ if (!isFullyDuplicated) {
27607
+ merged.push(entry);
27608
+ result.hooksAdded++;
27609
+ if (sourceMatcher) {
27610
+ matcherIndex.set(sourceMatcher, merged.length - 1);
27611
+ }
27612
+ for (const cmd of commands) {
27613
+ existingCommands.add(cmd);
27614
+ }
27349
27615
  }
27350
27616
  }
27351
27617
  }
27352
27618
  return merged;
27353
27619
  }
27620
+ static deepCopyEntry(entry) {
27621
+ if ("hooks" in entry) {
27622
+ return {
27623
+ ...entry,
27624
+ hooks: entry.hooks ? [...entry.hooks.map((h2) => ({ ...h2 }))] : undefined
27625
+ };
27626
+ }
27627
+ return { ...entry };
27628
+ }
27354
27629
  static extractCommands(entries, commands) {
27355
27630
  for (const entry of entries) {
27356
27631
  if ("command" in entry && entry.command) {
@@ -31577,7 +31852,7 @@ class PromptsManager {
31577
31852
 
31578
31853
  // src/services/file-operations/file-scanner.ts
31579
31854
  init_logger();
31580
- import { join as join26, relative as relative7, resolve as resolve4 } from "node:path";
31855
+ import { join as join27, relative as relative7, resolve as resolve5 } from "node:path";
31581
31856
  var import_fs_extra15 = __toESM(require_lib(), 1);
31582
31857
 
31583
31858
  class FileScanner {
@@ -31594,7 +31869,7 @@ class FileScanner {
31594
31869
  logger.debug(`Skipping directory: ${entry}`);
31595
31870
  continue;
31596
31871
  }
31597
- const fullPath = join26(dirPath, entry);
31872
+ const fullPath = join27(dirPath, entry);
31598
31873
  if (!FileScanner.isSafePath(basePath, fullPath)) {
31599
31874
  logger.warning(`Skipping potentially unsafe path: ${entry}`);
31600
31875
  continue;
@@ -31629,8 +31904,8 @@ class FileScanner {
31629
31904
  return files;
31630
31905
  }
31631
31906
  static async findCustomFiles(destDir, sourceDir, subPath) {
31632
- const destSubDir = join26(destDir, subPath);
31633
- const sourceSubDir = join26(sourceDir, subPath);
31907
+ const destSubDir = join27(destDir, subPath);
31908
+ const sourceSubDir = join27(sourceDir, subPath);
31634
31909
  logger.debug(`findCustomFiles - destDir: ${destDir}`);
31635
31910
  logger.debug(`findCustomFiles - sourceDir: ${sourceDir}`);
31636
31911
  logger.debug(`findCustomFiles - subPath: "${subPath}"`);
@@ -31658,8 +31933,8 @@ class FileScanner {
31658
31933
  return customFiles;
31659
31934
  }
31660
31935
  static isSafePath(basePath, targetPath) {
31661
- const resolvedBase = resolve4(basePath);
31662
- const resolvedTarget = resolve4(targetPath);
31936
+ const resolvedBase = resolve5(basePath);
31937
+ const resolvedTarget = resolve5(targetPath);
31663
31938
  return resolvedTarget.startsWith(resolvedBase);
31664
31939
  }
31665
31940
  static toPosixPath(path9) {
@@ -31668,8 +31943,8 @@ class FileScanner {
31668
31943
  }
31669
31944
 
31670
31945
  // src/services/transformers/commands-prefix.ts
31671
- import { lstat as lstat3, mkdir as mkdir9, readdir as readdir11, stat as stat4 } from "node:fs/promises";
31672
- import { join as join27 } from "node:path";
31946
+ import { lstat as lstat3, mkdir as mkdir10, readdir as readdir11, stat as stat4 } from "node:fs/promises";
31947
+ import { join as join28 } from "node:path";
31673
31948
  init_logger();
31674
31949
  var import_fs_extra16 = __toESM(require_lib(), 1);
31675
31950
  function stripWindowsDrivePrefix(path9) {
@@ -31710,14 +31985,14 @@ function validatePath4(path9, paramName) {
31710
31985
  class CommandsPrefix {
31711
31986
  static async applyPrefix(extractDir) {
31712
31987
  validatePath4(extractDir, "extractDir");
31713
- const commandsDir = join27(extractDir, ".claude", "commands");
31988
+ const commandsDir = join28(extractDir, ".claude", "commands");
31714
31989
  if (!await import_fs_extra16.pathExists(commandsDir)) {
31715
31990
  logger.verbose("No commands directory found, skipping prefix application");
31716
31991
  return;
31717
31992
  }
31718
31993
  logger.info("Applying /ck: prefix to slash commands...");
31719
- const backupDir = join27(extractDir, ".commands-backup");
31720
- const tempDir = join27(extractDir, ".commands-prefix-temp");
31994
+ const backupDir = join28(extractDir, ".commands-backup");
31995
+ const tempDir = join28(extractDir, ".commands-prefix-temp");
31721
31996
  try {
31722
31997
  const entries = await readdir11(commandsDir);
31723
31998
  if (entries.length === 0) {
@@ -31725,7 +32000,7 @@ class CommandsPrefix {
31725
32000
  return;
31726
32001
  }
31727
32002
  if (entries.length === 1 && entries[0] === "ck") {
31728
- const ckDir2 = join27(commandsDir, "ck");
32003
+ const ckDir2 = join28(commandsDir, "ck");
31729
32004
  const ckStat = await stat4(ckDir2);
31730
32005
  if (ckStat.isDirectory()) {
31731
32006
  logger.verbose("Commands already have /ck: prefix, skipping");
@@ -31734,18 +32009,18 @@ class CommandsPrefix {
31734
32009
  }
31735
32010
  await import_fs_extra16.copy(commandsDir, backupDir);
31736
32011
  logger.verbose("Created backup of commands directory");
31737
- await mkdir9(tempDir, { recursive: true });
31738
- const ckDir = join27(tempDir, "ck");
31739
- await mkdir9(ckDir, { recursive: true });
32012
+ await mkdir10(tempDir, { recursive: true });
32013
+ const ckDir = join28(tempDir, "ck");
32014
+ await mkdir10(ckDir, { recursive: true });
31740
32015
  let processedCount = 0;
31741
32016
  for (const entry of entries) {
31742
- const sourcePath = join27(commandsDir, entry);
32017
+ const sourcePath = join28(commandsDir, entry);
31743
32018
  const stats = await lstat3(sourcePath);
31744
32019
  if (stats.isSymbolicLink()) {
31745
32020
  logger.warning(`Skipping symlink for security: ${entry}`);
31746
32021
  continue;
31747
32022
  }
31748
- const destPath = join27(ckDir, entry);
32023
+ const destPath = join28(ckDir, entry);
31749
32024
  await import_fs_extra16.copy(sourcePath, destPath, {
31750
32025
  overwrite: false,
31751
32026
  errorOnExist: true
@@ -31793,8 +32068,8 @@ class CommandsPrefix {
31793
32068
  static async cleanupCommandsDirectory(targetDir, isGlobal, options = {}) {
31794
32069
  const { dryRun = false, forceOverwrite = false } = options;
31795
32070
  validatePath4(targetDir, "targetDir");
31796
- const claudeDir = isGlobal ? targetDir : join27(targetDir, ".claude");
31797
- const commandsDir = join27(claudeDir, "commands");
32071
+ const claudeDir = isGlobal ? targetDir : join28(targetDir, ".claude");
32072
+ const commandsDir = join28(claudeDir, "commands");
31798
32073
  const result = {
31799
32074
  results: [],
31800
32075
  deletedCount: 0,
@@ -31822,7 +32097,7 @@ class CommandsPrefix {
31822
32097
  return result;
31823
32098
  }
31824
32099
  for (const entry of entries) {
31825
- const entryPath = join27(commandsDir, entry);
32100
+ const entryPath = join28(commandsDir, entry);
31826
32101
  const stats = await lstat3(entryPath);
31827
32102
  if (stats.isSymbolicLink()) {
31828
32103
  logger.warning(`Skipping symlink: ${entry}`);
@@ -31988,7 +32263,7 @@ class CommandsPrefix {
31988
32263
  const files = [];
31989
32264
  const entries = await readdir11(dir);
31990
32265
  for (const entry of entries) {
31991
- const fullPath = join27(dir, entry);
32266
+ const fullPath = join28(dir, entry);
31992
32267
  const stats = await lstat3(fullPath);
31993
32268
  if (stats.isSymbolicLink()) {
31994
32269
  continue;
@@ -32007,8 +32282,8 @@ class CommandsPrefix {
32007
32282
  init_logger();
32008
32283
  init_types2();
32009
32284
  var import_fs_extra17 = __toESM(require_lib(), 1);
32010
- import { readFile as readFile14, readdir as readdir12, rename as rename3, writeFile as writeFile12 } from "node:fs/promises";
32011
- import { join as join28, relative as relative8 } from "node:path";
32285
+ import { readFile as readFile15, readdir as readdir12, rename as rename3, writeFile as writeFile13 } from "node:fs/promises";
32286
+ import { join as join29, relative as relative8 } from "node:path";
32012
32287
  var TRANSFORMABLE_FILE_PATTERNS = [
32013
32288
  ".md",
32014
32289
  ".txt",
@@ -32054,34 +32329,34 @@ async function transformFolderPaths(extractDir, folders, options = {}) {
32054
32329
  }
32055
32330
  const dirsToRename = [];
32056
32331
  if (folders.docs !== DEFAULT_FOLDERS.docs) {
32057
- const docsPath = join28(extractDir, DEFAULT_FOLDERS.docs);
32332
+ const docsPath = join29(extractDir, DEFAULT_FOLDERS.docs);
32058
32333
  if (await import_fs_extra17.pathExists(docsPath)) {
32059
32334
  dirsToRename.push({
32060
32335
  from: docsPath,
32061
- to: join28(extractDir, folders.docs)
32336
+ to: join29(extractDir, folders.docs)
32062
32337
  });
32063
32338
  }
32064
- const claudeDocsPath = join28(extractDir, ".claude", DEFAULT_FOLDERS.docs);
32339
+ const claudeDocsPath = join29(extractDir, ".claude", DEFAULT_FOLDERS.docs);
32065
32340
  if (await import_fs_extra17.pathExists(claudeDocsPath)) {
32066
32341
  dirsToRename.push({
32067
32342
  from: claudeDocsPath,
32068
- to: join28(extractDir, ".claude", folders.docs)
32343
+ to: join29(extractDir, ".claude", folders.docs)
32069
32344
  });
32070
32345
  }
32071
32346
  }
32072
32347
  if (folders.plans !== DEFAULT_FOLDERS.plans) {
32073
- const plansPath = join28(extractDir, DEFAULT_FOLDERS.plans);
32348
+ const plansPath = join29(extractDir, DEFAULT_FOLDERS.plans);
32074
32349
  if (await import_fs_extra17.pathExists(plansPath)) {
32075
32350
  dirsToRename.push({
32076
32351
  from: plansPath,
32077
- to: join28(extractDir, folders.plans)
32352
+ to: join29(extractDir, folders.plans)
32078
32353
  });
32079
32354
  }
32080
- const claudePlansPath = join28(extractDir, ".claude", DEFAULT_FOLDERS.plans);
32355
+ const claudePlansPath = join29(extractDir, ".claude", DEFAULT_FOLDERS.plans);
32081
32356
  if (await import_fs_extra17.pathExists(claudePlansPath)) {
32082
32357
  dirsToRename.push({
32083
32358
  from: claudePlansPath,
32084
- to: join28(extractDir, ".claude", folders.plans)
32359
+ to: join29(extractDir, ".claude", folders.plans)
32085
32360
  });
32086
32361
  }
32087
32362
  }
@@ -32118,7 +32393,7 @@ async function transformFileContents(dir, compiledReplacements, options) {
32118
32393
  let replacementsCount = 0;
32119
32394
  const entries = await readdir12(dir, { withFileTypes: true });
32120
32395
  for (const entry of entries) {
32121
- const fullPath = join28(dir, entry.name);
32396
+ const fullPath = join29(dir, entry.name);
32122
32397
  if (entry.isDirectory()) {
32123
32398
  if (entry.name === "node_modules" || entry.name === ".git") {
32124
32399
  continue;
@@ -32131,7 +32406,7 @@ async function transformFileContents(dir, compiledReplacements, options) {
32131
32406
  if (!shouldTransform)
32132
32407
  continue;
32133
32408
  try {
32134
- const content = await readFile14(fullPath, "utf-8");
32409
+ const content = await readFile15(fullPath, "utf-8");
32135
32410
  let newContent = content;
32136
32411
  let changeCount = 0;
32137
32412
  for (const { regex: regex2, replacement } of compiledReplacements) {
@@ -32147,7 +32422,7 @@ async function transformFileContents(dir, compiledReplacements, options) {
32147
32422
  if (options.dryRun) {
32148
32423
  logger.debug(`[dry-run] Would update ${relative8(dir, fullPath)}: ${changeCount} replacement(s)`);
32149
32424
  } else {
32150
- await writeFile12(fullPath, newContent, "utf-8");
32425
+ await writeFile13(fullPath, newContent, "utf-8");
32151
32426
  logger.debug(`Updated ${relative8(dir, fullPath)}: ${changeCount} replacement(s)`);
32152
32427
  }
32153
32428
  filesChanged++;
@@ -32227,9 +32502,9 @@ function validateFolderName(name2) {
32227
32502
 
32228
32503
  // src/services/transformers/global-path-transformer.ts
32229
32504
  init_logger();
32230
- import { readFile as readFile15, readdir as readdir13, writeFile as writeFile13 } from "node:fs/promises";
32505
+ import { readFile as readFile16, readdir as readdir13, writeFile as writeFile14 } from "node:fs/promises";
32231
32506
  import { platform as platform9 } from "node:os";
32232
- import { extname, join as join29 } from "node:path";
32507
+ import { extname, join as join30 } from "node:path";
32233
32508
  var IS_WINDOWS2 = platform9() === "win32";
32234
32509
  var HOME_PREFIX = IS_WINDOWS2 ? "%USERPROFILE%" : "$HOME";
32235
32510
  function getHomeDirPrefix() {
@@ -32321,7 +32596,7 @@ async function transformPathsForGlobalInstall(directory, options = {}) {
32321
32596
  async function processDirectory(dir) {
32322
32597
  const entries = await readdir13(dir, { withFileTypes: true });
32323
32598
  for (const entry of entries) {
32324
- const fullPath = join29(dir, entry.name);
32599
+ const fullPath = join30(dir, entry.name);
32325
32600
  if (entry.isDirectory()) {
32326
32601
  if (entry.name === "node_modules" || entry.name.startsWith(".") && entry.name !== ".claude") {
32327
32602
  continue;
@@ -32329,10 +32604,10 @@ async function transformPathsForGlobalInstall(directory, options = {}) {
32329
32604
  await processDirectory(fullPath);
32330
32605
  } else if (entry.isFile() && shouldTransformFile(entry.name)) {
32331
32606
  try {
32332
- const content = await readFile15(fullPath, "utf-8");
32607
+ const content = await readFile16(fullPath, "utf-8");
32333
32608
  const { transformed, changes } = transformContent(content);
32334
32609
  if (changes > 0) {
32335
- await writeFile13(fullPath, transformed, "utf-8");
32610
+ await writeFile14(fullPath, transformed, "utf-8");
32336
32611
  filesTransformed++;
32337
32612
  totalChanges += changes;
32338
32613
  if (options.verbose) {
@@ -32380,9 +32655,9 @@ async function initCommand(options) {
32380
32655
  }
32381
32656
  if (validOptions.global) {
32382
32657
  const globalKitDir = PathResolver.getGlobalKitDir();
32383
- const cwdResolved = resolve5(process.cwd());
32384
- const isInGlobalDir = cwdResolved === globalKitDir || cwdResolved === resolve5(globalKitDir, "..");
32385
- const localSettingsPath = join30(process.cwd(), ".claude", "settings.json");
32658
+ const cwdResolved = resolve6(process.cwd());
32659
+ const isInGlobalDir = cwdResolved === globalKitDir || cwdResolved === resolve6(globalKitDir, "..");
32660
+ const localSettingsPath = join31(process.cwd(), ".claude", "settings.json");
32386
32661
  if (!isInGlobalDir && await import_fs_extra18.pathExists(localSettingsPath)) {
32387
32662
  if (isNonInteractive2) {
32388
32663
  logger.warning("Local .claude/settings.json detected. Local settings take precedence over global.");
@@ -32394,7 +32669,7 @@ async function initCommand(options) {
32394
32669
  return;
32395
32670
  }
32396
32671
  if (choice === "remove") {
32397
- const localClaudeDir = join30(process.cwd(), ".claude");
32672
+ const localClaudeDir = join31(process.cwd(), ".claude");
32398
32673
  try {
32399
32674
  await import_fs_extra18.remove(localClaudeDir);
32400
32675
  logger.success("Removed local .claude/ directory");
@@ -32438,12 +32713,12 @@ async function initCommand(options) {
32438
32713
  }
32439
32714
  }
32440
32715
  }
32441
- const resolvedDir = resolve5(targetDir);
32716
+ const resolvedDir = resolve6(targetDir);
32442
32717
  logger.info(`Target directory: ${resolvedDir}`);
32443
32718
  if (!await import_fs_extra18.pathExists(resolvedDir)) {
32444
32719
  if (validOptions.global) {
32445
- const { mkdir: mkdir10 } = await import("node:fs/promises");
32446
- await mkdir10(resolvedDir, { recursive: true });
32720
+ const { mkdir: mkdir11 } = await import("node:fs/promises");
32721
+ await mkdir11(resolvedDir, { recursive: true });
32447
32722
  logger.info(`Created global directory: ${resolvedDir}`);
32448
32723
  } else {
32449
32724
  logger.error(`Directory does not exist: ${resolvedDir}`);
@@ -32453,7 +32728,7 @@ async function initCommand(options) {
32453
32728
  }
32454
32729
  if (validOptions.fresh) {
32455
32730
  const prefix = PathResolver.getPathPrefix(validOptions.global);
32456
- const claudeDir2 = prefix ? join30(resolvedDir, prefix) : resolvedDir;
32731
+ const claudeDir2 = prefix ? join31(resolvedDir, prefix) : resolvedDir;
32457
32732
  const canProceed = await handleFreshInstallation(claudeDir2, prompts);
32458
32733
  if (!canProceed) {
32459
32734
  return;
@@ -32481,7 +32756,7 @@ async function initCommand(options) {
32481
32756
  logger.info("Fetching available versions...");
32482
32757
  let currentVersion = null;
32483
32758
  try {
32484
- const metadataPath = validOptions.global ? join30(PathResolver.getGlobalKitDir(), "metadata.json") : join30(resolvedDir, ".claude", "metadata.json");
32759
+ const metadataPath = validOptions.global ? join31(PathResolver.getGlobalKitDir(), "metadata.json") : join31(resolvedDir, ".claude", "metadata.json");
32485
32760
  const metadata = await readClaudeKitMetadata(metadataPath);
32486
32761
  currentVersion = metadata?.version || null;
32487
32762
  if (currentVersion) {
@@ -32606,7 +32881,7 @@ async function initCommand(options) {
32606
32881
  }
32607
32882
  }
32608
32883
  if (!validOptions.fresh) {
32609
- const newSkillsDir = join30(extractDir, ".claude", "skills");
32884
+ const newSkillsDir = join31(extractDir, ".claude", "skills");
32610
32885
  const currentSkillsDir = PathResolver.buildSkillsPath(resolvedDir, validOptions.global);
32611
32886
  if (await import_fs_extra18.pathExists(newSkillsDir) && await import_fs_extra18.pathExists(currentSkillsDir)) {
32612
32887
  logger.info("Checking for skills directory migration...");
@@ -32631,7 +32906,7 @@ async function initCommand(options) {
32631
32906
  let customClaudeFiles = [];
32632
32907
  if (!validOptions.fresh) {
32633
32908
  logger.info("Scanning for custom .claude files...");
32634
- const scanSourceDir = validOptions.global ? join30(extractDir, ".claude") : extractDir;
32909
+ const scanSourceDir = validOptions.global ? join31(extractDir, ".claude") : extractDir;
32635
32910
  const scanTargetSubdir = validOptions.global ? "" : ".claude";
32636
32911
  customClaudeFiles = await FileScanner.findCustomFiles(resolvedDir, scanSourceDir, scanTargetSubdir);
32637
32912
  } else {
@@ -32666,7 +32941,7 @@ async function initCommand(options) {
32666
32941
  }
32667
32942
  merger.setGlobalFlag(validOptions.global);
32668
32943
  merger.setForceOverwriteSettings(validOptions.forceOverwriteSettings);
32669
- const claudeDir = validOptions.global ? resolvedDir : join30(resolvedDir, ".claude");
32944
+ const claudeDir = validOptions.global ? resolvedDir : join31(resolvedDir, ".claude");
32670
32945
  const releaseManifest = await ReleaseManifestLoader.load(extractDir);
32671
32946
  if (!validOptions.fresh && await import_fs_extra18.pathExists(claudeDir)) {
32672
32947
  const legacyDetection = await LegacyMigration.detectLegacy(claudeDir);
@@ -32688,7 +32963,7 @@ async function initCommand(options) {
32688
32963
  return;
32689
32964
  }
32690
32965
  }
32691
- const sourceDir = validOptions.global ? join30(extractDir, ".claude") : extractDir;
32966
+ const sourceDir = validOptions.global ? join31(extractDir, ".claude") : extractDir;
32692
32967
  await merger.merge(sourceDir, resolvedDir, false);
32693
32968
  const manifestWriter = new ManifestWriter;
32694
32969
  const installedFiles = merger.getAllInstalledFiles();
@@ -32697,7 +32972,7 @@ async function initCommand(options) {
32697
32972
  if (!validOptions.global && !installedPath.startsWith(".claude/"))
32698
32973
  continue;
32699
32974
  const relativePath = validOptions.global ? installedPath : installedPath.replace(/^\.claude\//, "");
32700
- const filePath = join30(claudeDir, relativePath);
32975
+ const filePath = join31(claudeDir, relativePath);
32701
32976
  const manifestEntry = releaseManifest ? ReleaseManifestLoader.findFile(releaseManifest, installedPath) : null;
32702
32977
  const ownership = manifestEntry ? "ck" : "user";
32703
32978
  filesToTrack.push({
@@ -32718,8 +32993,8 @@ async function initCommand(options) {
32718
32993
  trackingSpinner.succeed(`Tracked ${trackResult.success} files`);
32719
32994
  await manifestWriter.writeManifest(claudeDir, kitConfig.name, release.tag_name, validOptions.global ? "global" : "local");
32720
32995
  if (validOptions.global) {
32721
- const claudeMdSource = join30(extractDir, "CLAUDE.md");
32722
- const claudeMdDest = join30(resolvedDir, "CLAUDE.md");
32996
+ const claudeMdSource = join31(extractDir, "CLAUDE.md");
32997
+ const claudeMdDest = join31(resolvedDir, "CLAUDE.md");
32723
32998
  if (await import_fs_extra18.pathExists(claudeMdSource)) {
32724
32999
  if (!await import_fs_extra18.pathExists(claudeMdDest)) {
32725
33000
  await import_fs_extra18.copy(claudeMdSource, claudeMdDest);
@@ -32738,8 +33013,21 @@ async function initCommand(options) {
32738
33013
  const skillsDir = PathResolver.buildSkillsPath(resolvedDir, validOptions.global);
32739
33014
  await handleSkillsInstallation2(skillsDir);
32740
33015
  }
33016
+ if (!isNonInteractive2) {
33017
+ const { isGeminiInstalled: isGeminiInstalled2 } = await Promise.resolve().then(() => (init_package_installer(), exports_package_installer));
33018
+ const { checkExistingGeminiConfig: checkExistingGeminiConfig2, findMcpConfigPath: findMcpConfigPath2, processGeminiMcpLinking: processGeminiMcpLinking2 } = await Promise.resolve().then(() => (init_gemini_mcp_linker(), exports_gemini_mcp_linker));
33019
+ const geminiInstalled = await isGeminiInstalled2();
33020
+ const existingConfig = checkExistingGeminiConfig2(resolvedDir);
33021
+ const mcpConfigExists = findMcpConfigPath2(resolvedDir) !== null;
33022
+ if (geminiInstalled && !existingConfig.exists && mcpConfigExists) {
33023
+ const shouldSetupGemini = await prompts.confirm("Gemini CLI detected. Set up MCP integration? (creates .gemini/settings.json symlink)");
33024
+ if (shouldSetupGemini) {
33025
+ await processGeminiMcpLinking2(resolvedDir);
33026
+ }
33027
+ }
33028
+ }
32741
33029
  if (!validOptions.skipSetup && !isNonInteractive2) {
32742
- const envPath = join30(claudeDir, ".env");
33030
+ const envPath = join31(claudeDir, ".env");
32743
33031
  if (!await import_fs_extra18.pathExists(envPath)) {
32744
33032
  const shouldSetup = await prompts.confirm("Set up API keys now? (Gemini API key for ai-multimodal skill, optional webhooks)");
32745
33033
  if (shouldSetup) {
@@ -32770,7 +33058,7 @@ Protected files (.env, etc.) were not modified.`;
32770
33058
  }
32771
33059
 
32772
33060
  // src/commands/new.ts
32773
- import { join as join31, resolve as resolve6 } from "node:path";
33061
+ import { join as join32, resolve as resolve7 } from "node:path";
32774
33062
  init_package_installer();
32775
33063
  init_environment();
32776
33064
  init_logger();
@@ -32801,7 +33089,7 @@ async function newCommand(options) {
32801
33089
  targetDir = await prompts.getDirectory(targetDir);
32802
33090
  }
32803
33091
  }
32804
- const resolvedDir = resolve6(targetDir);
33092
+ const resolvedDir = resolve7(targetDir);
32805
33093
  logger.info(`Target directory: ${resolvedDir}`);
32806
33094
  if (await import_fs_extra19.pathExists(resolvedDir)) {
32807
33095
  const files = await import_fs_extra19.readdir(resolvedDir);
@@ -32951,7 +33239,7 @@ async function newCommand(options) {
32951
33239
  await CommandsPrefix.cleanupCommandsDirectory(resolvedDir, false);
32952
33240
  }
32953
33241
  await merger.merge(extractDir, resolvedDir, true);
32954
- const claudeDir = join31(resolvedDir, ".claude");
33242
+ const claudeDir = join32(resolvedDir, ".claude");
32955
33243
  const manifestWriter = new ManifestWriter;
32956
33244
  const releaseManifest = await ReleaseManifestLoader.load(extractDir);
32957
33245
  const installedFiles = merger.getAllInstalledFiles();
@@ -32960,7 +33248,7 @@ async function newCommand(options) {
32960
33248
  if (!installedPath.startsWith(".claude/"))
32961
33249
  continue;
32962
33250
  const relativePath = installedPath.replace(/^\.claude\//, "");
32963
- const filePath = join31(claudeDir, relativePath);
33251
+ const filePath = join32(claudeDir, relativePath);
32964
33252
  const manifestEntry = releaseManifest ? ReleaseManifestLoader.findFile(releaseManifest, installedPath) : null;
32965
33253
  const ownership = manifestEntry ? "ck" : "user";
32966
33254
  filesToTrack.push({
@@ -32992,7 +33280,7 @@ async function newCommand(options) {
32992
33280
  if (installOpenCode2 || installGemini2) {
32993
33281
  logger.info("Installing optional packages...");
32994
33282
  try {
32995
- const installationResults = await processPackageInstallations(installOpenCode2, installGemini2);
33283
+ const installationResults = await processPackageInstallations(installOpenCode2, installGemini2, resolvedDir);
32996
33284
  prompts.showPackageInstallationResults(installationResults);
32997
33285
  } catch (error) {
32998
33286
  logger.warning(`Package installation failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -33013,7 +33301,7 @@ async function newCommand(options) {
33013
33301
 
33014
33302
  // src/commands/uninstall.ts
33015
33303
  import { readdirSync, rmSync } from "node:fs";
33016
- import { dirname as dirname6, join as join32 } from "node:path";
33304
+ import { dirname as dirname7, join as join33 } from "node:path";
33017
33305
  init_logger();
33018
33306
  init_types2();
33019
33307
  var import_fs_extra20 = __toESM(require_lib(), 1);
@@ -33075,7 +33363,7 @@ async function confirmUninstall(scope) {
33075
33363
  }
33076
33364
  async function cleanupEmptyDirectories(filePath, installationRoot) {
33077
33365
  let cleaned = 0;
33078
- let currentDir = dirname6(filePath);
33366
+ let currentDir = dirname7(filePath);
33079
33367
  while (currentDir !== installationRoot && currentDir.startsWith(installationRoot)) {
33080
33368
  try {
33081
33369
  const entries = readdirSync(currentDir);
@@ -33083,7 +33371,7 @@ async function cleanupEmptyDirectories(filePath, installationRoot) {
33083
33371
  rmSync(currentDir, { recursive: true });
33084
33372
  cleaned++;
33085
33373
  logger.debug(`Removed empty directory: ${currentDir}`);
33086
- currentDir = dirname6(currentDir);
33374
+ currentDir = dirname7(currentDir);
33087
33375
  } else {
33088
33376
  break;
33089
33377
  }
@@ -33106,7 +33394,7 @@ async function analyzeInstallation(installation, forceOverwrite) {
33106
33394
  return result;
33107
33395
  }
33108
33396
  for (const trackedFile of metadata.files) {
33109
- const filePath = join32(installation.path, trackedFile.path);
33397
+ const filePath = join33(installation.path, trackedFile.path);
33110
33398
  const ownershipResult = await OwnershipChecker.checkOwnership(filePath, metadata, installation.path);
33111
33399
  if (!ownershipResult.exists)
33112
33400
  continue;
@@ -33164,7 +33452,7 @@ async function removeInstallations(installations, options) {
33164
33452
  let removedCount = 0;
33165
33453
  let cleanedDirs = 0;
33166
33454
  for (const item of analysis.toDelete) {
33167
- const filePath = join32(installation.path, item.path);
33455
+ const filePath = join33(installation.path, item.path);
33168
33456
  if (await import_fs_extra20.pathExists(filePath)) {
33169
33457
  await import_fs_extra20.remove(filePath);
33170
33458
  removedCount++;
@@ -33405,7 +33693,7 @@ var import_compare_versions2 = __toESM(require_umd(), 1);
33405
33693
  // package.json
33406
33694
  var package_default2 = {
33407
33695
  name: "claudekit-cli",
33408
- version: "3.9.2",
33696
+ version: "3.10.1",
33409
33697
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
33410
33698
  type: "module",
33411
33699
  repository: {
@@ -33712,24 +34000,24 @@ var import_picocolors15 = __toESM(require_picocolors(), 1);
33712
34000
 
33713
34001
  // src/domains/versioning/version-cache.ts
33714
34002
  init_logger();
33715
- import { existsSync as existsSync7 } from "node:fs";
33716
- import { mkdir as mkdir10, readFile as readFile16, writeFile as writeFile14 } from "node:fs/promises";
33717
- import { join as join33 } from "node:path";
34003
+ import { existsSync as existsSync8 } from "node:fs";
34004
+ import { mkdir as mkdir11, readFile as readFile17, writeFile as writeFile15 } from "node:fs/promises";
34005
+ import { join as join34 } from "node:path";
33718
34006
  class VersionCacheManager {
33719
34007
  static CACHE_FILENAME = "version-check.json";
33720
34008
  static CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
33721
34009
  static getCacheFile() {
33722
34010
  const cacheDir = PathResolver.getCacheDir(false);
33723
- return join33(cacheDir, VersionCacheManager.CACHE_FILENAME);
34011
+ return join34(cacheDir, VersionCacheManager.CACHE_FILENAME);
33724
34012
  }
33725
34013
  static async load() {
33726
34014
  const cacheFile = VersionCacheManager.getCacheFile();
33727
34015
  try {
33728
- if (!existsSync7(cacheFile)) {
34016
+ if (!existsSync8(cacheFile)) {
33729
34017
  logger.debug("Version check cache not found");
33730
34018
  return null;
33731
34019
  }
33732
- const content = await readFile16(cacheFile, "utf-8");
34020
+ const content = await readFile17(cacheFile, "utf-8");
33733
34021
  const cache2 = JSON.parse(content);
33734
34022
  if (!cache2.lastCheck || !cache2.currentVersion || !cache2.latestVersion) {
33735
34023
  logger.debug("Invalid cache structure, ignoring");
@@ -33746,10 +34034,10 @@ class VersionCacheManager {
33746
34034
  const cacheFile = VersionCacheManager.getCacheFile();
33747
34035
  const cacheDir = PathResolver.getCacheDir(false);
33748
34036
  try {
33749
- if (!existsSync7(cacheDir)) {
33750
- await mkdir10(cacheDir, { recursive: true, mode: 448 });
34037
+ if (!existsSync8(cacheDir)) {
34038
+ await mkdir11(cacheDir, { recursive: true, mode: 448 });
33751
34039
  }
33752
- await writeFile14(cacheFile, JSON.stringify(cache2, null, 2), "utf-8");
34040
+ await writeFile15(cacheFile, JSON.stringify(cache2, null, 2), "utf-8");
33753
34041
  logger.debug(`Version check cache saved to ${cacheFile}`);
33754
34042
  } catch (error) {
33755
34043
  logger.debug(`Failed to save version check cache: ${error}`);
@@ -33768,7 +34056,7 @@ class VersionCacheManager {
33768
34056
  static async clear() {
33769
34057
  const cacheFile = VersionCacheManager.getCacheFile();
33770
34058
  try {
33771
- if (existsSync7(cacheFile)) {
34059
+ if (existsSync8(cacheFile)) {
33772
34060
  const fs12 = await import("node:fs/promises");
33773
34061
  await fs12.unlink(cacheFile);
33774
34062
  logger.debug("Version check cache cleared");
@@ -34231,8 +34519,8 @@ class OutputManager2 {
34231
34519
  var output2 = new OutputManager2;
34232
34520
 
34233
34521
  // src/shared/path-resolver.ts
34234
- import { homedir as homedir4, platform as platform10 } from "node:os";
34235
- import { join as join34, normalize as normalize6 } from "node:path";
34522
+ import { homedir as homedir5, platform as platform10 } from "node:os";
34523
+ import { join as join35, normalize as normalize6 } from "node:path";
34236
34524
 
34237
34525
  class PathResolver2 {
34238
34526
  static getTestHomeDir() {
@@ -34265,50 +34553,50 @@ class PathResolver2 {
34265
34553
  static getConfigDir(global3 = false) {
34266
34554
  const testHome = PathResolver2.getTestHomeDir();
34267
34555
  if (testHome) {
34268
- return global3 ? join34(testHome, ".config", "claude") : join34(testHome, ".claudekit");
34556
+ return global3 ? join35(testHome, ".config", "claude") : join35(testHome, ".claudekit");
34269
34557
  }
34270
34558
  if (!global3) {
34271
- return join34(homedir4(), ".claudekit");
34559
+ return join35(homedir5(), ".claudekit");
34272
34560
  }
34273
34561
  const os2 = platform10();
34274
34562
  if (os2 === "win32") {
34275
- const localAppData = process.env.LOCALAPPDATA || join34(homedir4(), "AppData", "Local");
34276
- return join34(localAppData, "claude");
34563
+ const localAppData = process.env.LOCALAPPDATA || join35(homedir5(), "AppData", "Local");
34564
+ return join35(localAppData, "claude");
34277
34565
  }
34278
34566
  const xdgConfigHome = process.env.XDG_CONFIG_HOME;
34279
34567
  if (xdgConfigHome) {
34280
- return join34(xdgConfigHome, "claude");
34568
+ return join35(xdgConfigHome, "claude");
34281
34569
  }
34282
- return join34(homedir4(), ".config", "claude");
34570
+ return join35(homedir5(), ".config", "claude");
34283
34571
  }
34284
34572
  static getConfigFile(global3 = false) {
34285
- return join34(PathResolver2.getConfigDir(global3), "config.json");
34573
+ return join35(PathResolver2.getConfigDir(global3), "config.json");
34286
34574
  }
34287
34575
  static getCacheDir(global3 = false) {
34288
34576
  const testHome = PathResolver2.getTestHomeDir();
34289
34577
  if (testHome) {
34290
- return global3 ? join34(testHome, ".cache", "claude") : join34(testHome, ".claudekit", "cache");
34578
+ return global3 ? join35(testHome, ".cache", "claude") : join35(testHome, ".claudekit", "cache");
34291
34579
  }
34292
34580
  if (!global3) {
34293
- return join34(homedir4(), ".claudekit", "cache");
34581
+ return join35(homedir5(), ".claudekit", "cache");
34294
34582
  }
34295
34583
  const os2 = platform10();
34296
34584
  if (os2 === "win32") {
34297
- const localAppData = process.env.LOCALAPPDATA || join34(homedir4(), "AppData", "Local");
34298
- return join34(localAppData, "claude", "cache");
34585
+ const localAppData = process.env.LOCALAPPDATA || join35(homedir5(), "AppData", "Local");
34586
+ return join35(localAppData, "claude", "cache");
34299
34587
  }
34300
34588
  const xdgCacheHome = process.env.XDG_CACHE_HOME;
34301
34589
  if (xdgCacheHome) {
34302
- return join34(xdgCacheHome, "claude");
34590
+ return join35(xdgCacheHome, "claude");
34303
34591
  }
34304
- return join34(homedir4(), ".cache", "claude");
34592
+ return join35(homedir5(), ".cache", "claude");
34305
34593
  }
34306
34594
  static getGlobalKitDir() {
34307
34595
  const testHome = PathResolver2.getTestHomeDir();
34308
34596
  if (testHome) {
34309
- return join34(testHome, ".claude");
34597
+ return join35(testHome, ".claude");
34310
34598
  }
34311
- return join34(homedir4(), ".claude");
34599
+ return join35(homedir5(), ".claude");
34312
34600
  }
34313
34601
  static getPathPrefix(global3) {
34314
34602
  return global3 ? "" : ".claude";
@@ -34316,9 +34604,9 @@ class PathResolver2 {
34316
34604
  static buildSkillsPath(baseDir, global3) {
34317
34605
  const prefix = PathResolver2.getPathPrefix(global3);
34318
34606
  if (prefix) {
34319
- return join34(baseDir, prefix, "skills");
34607
+ return join35(baseDir, prefix, "skills");
34320
34608
  }
34321
- return join34(baseDir, "skills");
34609
+ return join35(baseDir, "skills");
34322
34610
  }
34323
34611
  static buildComponentPath(baseDir, component, global3) {
34324
34612
  if (!PathResolver2.isPathSafe(component)) {
@@ -34326,9 +34614,9 @@ class PathResolver2 {
34326
34614
  }
34327
34615
  const prefix = PathResolver2.getPathPrefix(global3);
34328
34616
  if (prefix) {
34329
- return join34(baseDir, prefix, component);
34617
+ return join35(baseDir, prefix, component);
34330
34618
  }
34331
- return join34(baseDir, component);
34619
+ return join35(baseDir, component);
34332
34620
  }
34333
34621
  }
34334
34622
 
@@ -34360,11 +34648,11 @@ async function displayVersion() {
34360
34648
  let localKitVersion = null;
34361
34649
  let isGlobalOnlyKit = false;
34362
34650
  const globalKitDir = PathResolver2.getGlobalKitDir();
34363
- const globalMetadataPath = join35(globalKitDir, "metadata.json");
34651
+ const globalMetadataPath = join36(globalKitDir, "metadata.json");
34364
34652
  const prefix = PathResolver2.getPathPrefix(false);
34365
- const localMetadataPath = prefix ? join35(process.cwd(), prefix, "metadata.json") : join35(process.cwd(), "metadata.json");
34653
+ const localMetadataPath = prefix ? join36(process.cwd(), prefix, "metadata.json") : join36(process.cwd(), "metadata.json");
34366
34654
  const isLocalSameAsGlobal = localMetadataPath === globalMetadataPath;
34367
- if (!isLocalSameAsGlobal && existsSync8(localMetadataPath)) {
34655
+ if (!isLocalSameAsGlobal && existsSync9(localMetadataPath)) {
34368
34656
  try {
34369
34657
  const rawMetadata = JSON.parse(readFileSync5(localMetadataPath, "utf-8"));
34370
34658
  const metadata = MetadataSchema.parse(rawMetadata);
@@ -34378,7 +34666,7 @@ async function displayVersion() {
34378
34666
  logger2.verbose("Failed to parse local metadata.json", { error });
34379
34667
  }
34380
34668
  }
34381
- if (existsSync8(globalMetadataPath)) {
34669
+ if (existsSync9(globalMetadataPath)) {
34382
34670
  try {
34383
34671
  const rawMetadata = JSON.parse(readFileSync5(globalMetadataPath, "utf-8"));
34384
34672
  const metadata = MetadataSchema.parse(rawMetadata);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudekit-cli",
3
- "version": "3.9.2",
3
+ "version": "3.10.1",
4
4
  "description": "CLI tool for bootstrapping and updating ClaudeKit projects",
5
5
  "type": "module",
6
6
  "repository": {