claudekit-cli 3.26.1 → 3.27.0

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 +303 -80
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -16194,7 +16194,7 @@ var init_init_command_help = __esm(() => {
16194
16194
  },
16195
16195
  {
16196
16196
  flags: "--fresh",
16197
- description: "Completely remove .claude directory before downloading (requires confirmation)"
16197
+ description: "Remove ClaudeKit directories (commands/, agents/, skills/, rules/, hooks/) and reinstall"
16198
16198
  }
16199
16199
  ]
16200
16200
  },
@@ -16592,7 +16592,7 @@ function getPagerArgs(pagerCmd) {
16592
16592
  return [];
16593
16593
  }
16594
16594
  async function trySystemPager(content) {
16595
- return new Promise((resolve10) => {
16595
+ return new Promise((resolve11) => {
16596
16596
  const pagerCmd = process.env.PAGER || "less";
16597
16597
  const pagerArgs = getPagerArgs(pagerCmd);
16598
16598
  try {
@@ -16602,20 +16602,20 @@ async function trySystemPager(content) {
16602
16602
  });
16603
16603
  const timeout = setTimeout(() => {
16604
16604
  pager.kill();
16605
- resolve10(false);
16605
+ resolve11(false);
16606
16606
  }, 30000);
16607
16607
  pager.stdin.write(content);
16608
16608
  pager.stdin.end();
16609
16609
  pager.on("close", (code2) => {
16610
16610
  clearTimeout(timeout);
16611
- resolve10(code2 === 0);
16611
+ resolve11(code2 === 0);
16612
16612
  });
16613
16613
  pager.on("error", () => {
16614
16614
  clearTimeout(timeout);
16615
- resolve10(false);
16615
+ resolve11(false);
16616
16616
  });
16617
16617
  } catch {
16618
- resolve10(false);
16618
+ resolve11(false);
16619
16619
  }
16620
16620
  });
16621
16621
  }
@@ -16642,16 +16642,16 @@ async function basicPager(content) {
16642
16642
  break;
16643
16643
  }
16644
16644
  const remaining = lines.length - currentLine;
16645
- await new Promise((resolve10) => {
16645
+ await new Promise((resolve11) => {
16646
16646
  rl.question(`-- More (${remaining} lines) [Enter/q] --`, (answer) => {
16647
16647
  if (answer.toLowerCase() === "q") {
16648
16648
  rl.close();
16649
16649
  process.exitCode = 0;
16650
- resolve10();
16650
+ resolve11();
16651
16651
  return;
16652
16652
  }
16653
16653
  process.stdout.write("\x1B[1A\x1B[2K");
16654
- resolve10();
16654
+ resolve11();
16655
16655
  });
16656
16656
  });
16657
16657
  }
@@ -24690,12 +24690,25 @@ async function promptDirectorySelection(global2 = false) {
24690
24690
  }
24691
24691
  return selectedCategories;
24692
24692
  }
24693
- async function promptFreshConfirmation(targetPath) {
24694
- logger.warning("[!] WARNING: Fresh installation will completely remove the .claude directory!");
24693
+ async function promptFreshConfirmation(targetPath, analysis) {
24694
+ logger.warning("[!] Fresh installation will remove ClaudeKit files:");
24695
24695
  logger.info(`Path: ${targetPath}`);
24696
- logger.info("All custom files, configurations, and modifications will be permanently deleted.");
24696
+ if (analysis?.hasMetadata) {
24697
+ const ckCount = analysis.ckFiles.length + analysis.ckModifiedFiles.length;
24698
+ const userCount = analysis.userFiles.length;
24699
+ logger.info(` Remove: ${ckCount} CK-owned files`);
24700
+ logger.info(` Preserve: ${userCount} user-created files`);
24701
+ if (userCount > 0) {
24702
+ const samples = analysis.userFiles.slice(0, 3).map((f3) => f3.path);
24703
+ const remaining = userCount - samples.length;
24704
+ logger.info(` Examples preserved: ${samples.join(", ")}${remaining > 0 ? ` (+${remaining} more)` : ""}`);
24705
+ }
24706
+ } else {
24707
+ logger.info(" Remove: commands/, agents/, skills/, rules/, hooks/");
24708
+ logger.info(" Preserve: settings.json, Claude Code data");
24709
+ }
24697
24710
  const confirmation = await te({
24698
- message: "Type 'yes' to confirm complete removal:",
24711
+ message: "Type 'yes' to confirm:",
24699
24712
  placeholder: "yes",
24700
24713
  validate: (value) => {
24701
24714
  if (value.toLowerCase() !== "yes") {
@@ -24893,8 +24906,8 @@ class PromptsManager {
24893
24906
  logger.info("You can install these manually later using npm install -g <package>");
24894
24907
  }
24895
24908
  }
24896
- async promptFreshConfirmation(targetPath) {
24897
- return promptFreshConfirmation(targetPath);
24909
+ async promptFreshConfirmation(targetPath, analysis) {
24910
+ return promptFreshConfirmation(targetPath, analysis);
24898
24911
  }
24899
24912
  async promptUpdateMode() {
24900
24913
  return promptUpdateMode();
@@ -26449,7 +26462,7 @@ var import_ignore = __toESM(require_ignore(), 1);
26449
26462
  // src/domains/installation/download/file-downloader.ts
26450
26463
  init_logger();
26451
26464
  init_output_manager();
26452
- import { createWriteStream as createWriteStream2 } from "node:fs";
26465
+ import { createWriteStream as createWriteStream2, rmSync } from "node:fs";
26453
26466
  import { mkdir as mkdir9 } from "node:fs/promises";
26454
26467
  import { join as join28 } from "node:path";
26455
26468
 
@@ -26697,11 +26710,29 @@ class FileDownloader {
26697
26710
  downloadedSize += value.length;
26698
26711
  progressBar.update(downloadedSize);
26699
26712
  }
26713
+ if (downloadedSize !== totalSize) {
26714
+ fileStream.end();
26715
+ await new Promise((resolve5) => fileStream.once("close", resolve5));
26716
+ try {
26717
+ rmSync(destPath, { force: true });
26718
+ } catch (cleanupError) {
26719
+ const errorMsg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
26720
+ logger.debug(`Failed to clean up partial download ${destPath}: ${errorMsg}`);
26721
+ }
26722
+ throw new DownloadError(`Incomplete download: received ${formatBytes(downloadedSize)} of ${formatBytes(totalSize)}`);
26723
+ }
26700
26724
  fileStream.end();
26701
26725
  progressBar.complete(`Downloaded ${asset.name}`);
26702
26726
  return destPath;
26703
26727
  } catch (error) {
26704
- fileStream.close();
26728
+ fileStream.end();
26729
+ await new Promise((resolve5) => fileStream.once("close", resolve5));
26730
+ try {
26731
+ rmSync(destPath, { force: true });
26732
+ } catch (cleanupError) {
26733
+ const errorMsg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
26734
+ logger.debug(`Failed to clean up partial download ${destPath}: ${errorMsg}`);
26735
+ }
26705
26736
  throw error;
26706
26737
  }
26707
26738
  } catch (error) {
@@ -26751,11 +26782,19 @@ class FileDownloader {
26751
26782
  progressBar.update(downloadedSize);
26752
26783
  }
26753
26784
  }
26754
- fileStream.end();
26755
26785
  const expectedSize = Number(response.headers.get("content-length"));
26756
26786
  if (expectedSize > 0 && downloadedSize !== expectedSize) {
26787
+ fileStream.end();
26788
+ await new Promise((resolve5) => fileStream.once("close", resolve5));
26789
+ try {
26790
+ rmSync(destPath, { force: true });
26791
+ } catch (cleanupError) {
26792
+ const errorMsg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
26793
+ logger.debug(`Failed to clean up partial download ${destPath}: ${errorMsg}`);
26794
+ }
26757
26795
  throw new DownloadError(`Incomplete download: received ${formatBytes(downloadedSize)} of ${formatBytes(expectedSize)}`);
26758
26796
  }
26797
+ fileStream.end();
26759
26798
  if (progressBar) {
26760
26799
  progressBar.complete(`Downloaded ${name}`);
26761
26800
  } else {
@@ -26763,7 +26802,14 @@ class FileDownloader {
26763
26802
  }
26764
26803
  return destPath;
26765
26804
  } catch (error) {
26766
- fileStream.close();
26805
+ fileStream.end();
26806
+ await new Promise((resolve5) => fileStream.once("close", resolve5));
26807
+ try {
26808
+ rmSync(destPath, { force: true });
26809
+ } catch (cleanupError) {
26810
+ const errorMsg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
26811
+ logger.debug(`Failed to clean up partial download ${destPath}: ${errorMsg}`);
26812
+ }
26767
26813
  throw error;
26768
26814
  }
26769
26815
  }
@@ -40339,7 +40385,7 @@ Optional: DISCORD_WEBHOOK_URL, TELEGRAM_BOT_TOKEN`, "Configuration skipped");
40339
40385
  }
40340
40386
  // src/commands/init/phases/selection-handler.ts
40341
40387
  import { mkdir as mkdir20 } from "node:fs/promises";
40342
- import { join as join66, resolve as resolve7 } from "node:path";
40388
+ import { join as join66, resolve as resolve8 } from "node:path";
40343
40389
 
40344
40390
  // src/domains/github/kit-access-checker.ts
40345
40391
  init_logger();
@@ -40466,38 +40512,196 @@ async function runPreflightChecks() {
40466
40512
  }
40467
40513
 
40468
40514
  // src/domains/installation/fresh-installer.ts
40515
+ import { existsSync as existsSync19, readdirSync, rmSync as rmSync2, rmdirSync, unlinkSync as unlinkSync3 } from "node:fs";
40516
+ import { dirname as dirname9, join as join65, resolve as resolve7 } from "node:path";
40469
40517
  init_logger();
40470
- import { join as join65 } from "node:path";
40471
40518
  var import_fs_extra29 = __toESM(require_lib(), 1);
40472
40519
  var CLAUDEKIT_SUBDIRECTORIES = ["commands", "agents", "skills", "rules", "hooks"];
40520
+ async function analyzeFreshInstallation(claudeDir) {
40521
+ const metadata = await readManifest(claudeDir);
40522
+ if (!metadata) {
40523
+ return {
40524
+ ckFiles: [],
40525
+ ckModifiedFiles: [],
40526
+ userFiles: [],
40527
+ hasMetadata: false
40528
+ };
40529
+ }
40530
+ const allFiles = getAllTrackedFiles(metadata);
40531
+ if (allFiles.length === 0) {
40532
+ return {
40533
+ ckFiles: [],
40534
+ ckModifiedFiles: [],
40535
+ userFiles: [],
40536
+ hasMetadata: false
40537
+ };
40538
+ }
40539
+ const ckFiles = [];
40540
+ const ckModifiedFiles = [];
40541
+ const userFiles = [];
40542
+ for (const file of allFiles) {
40543
+ switch (file.ownership) {
40544
+ case "ck":
40545
+ ckFiles.push(file);
40546
+ break;
40547
+ case "ck-modified":
40548
+ ckModifiedFiles.push(file);
40549
+ break;
40550
+ case "user":
40551
+ userFiles.push(file);
40552
+ break;
40553
+ }
40554
+ }
40555
+ return {
40556
+ ckFiles,
40557
+ ckModifiedFiles,
40558
+ userFiles,
40559
+ hasMetadata: true
40560
+ };
40561
+ }
40562
+ function cleanupEmptyDirectories(filePath, claudeDir) {
40563
+ const normalizedClaudeDir = resolve7(claudeDir);
40564
+ let currentDir = resolve7(dirname9(filePath));
40565
+ while (currentDir !== normalizedClaudeDir && currentDir.startsWith(normalizedClaudeDir)) {
40566
+ try {
40567
+ const entries = readdirSync(currentDir);
40568
+ if (entries.length === 0) {
40569
+ rmdirSync(currentDir);
40570
+ logger.debug(`Removed empty directory: ${currentDir}`);
40571
+ currentDir = resolve7(dirname9(currentDir));
40572
+ } else {
40573
+ break;
40574
+ }
40575
+ } catch (error) {
40576
+ const errorMsg = error instanceof Error ? error.message : String(error);
40577
+ logger.debug(`Could not remove directory ${currentDir}: ${errorMsg}`);
40578
+ break;
40579
+ }
40580
+ }
40581
+ }
40582
+ async function removeFilesByOwnership(claudeDir, analysis, includeModified) {
40583
+ const removedFiles = [];
40584
+ const preservedFiles = [];
40585
+ const filesToRemove = includeModified ? [...analysis.ckFiles, ...analysis.ckModifiedFiles] : analysis.ckFiles;
40586
+ const filesToPreserve = includeModified ? analysis.userFiles : [...analysis.ckModifiedFiles, ...analysis.userFiles];
40587
+ for (const file of filesToRemove) {
40588
+ const fullPath = join65(claudeDir, file.path);
40589
+ try {
40590
+ if (existsSync19(fullPath)) {
40591
+ unlinkSync3(fullPath);
40592
+ removedFiles.push(file.path);
40593
+ logger.debug(`Removed: ${file.path}`);
40594
+ cleanupEmptyDirectories(fullPath, claudeDir);
40595
+ }
40596
+ } catch (error) {
40597
+ logger.debug(`Failed to remove ${file.path}: ${error}`);
40598
+ }
40599
+ }
40600
+ for (const file of filesToPreserve) {
40601
+ preservedFiles.push(file.path);
40602
+ }
40603
+ await updateMetadataAfterFresh(claudeDir, removedFiles);
40604
+ return {
40605
+ success: true,
40606
+ removedCount: removedFiles.length,
40607
+ preservedCount: preservedFiles.length,
40608
+ removedFiles,
40609
+ preservedFiles
40610
+ };
40611
+ }
40612
+ async function updateMetadataAfterFresh(claudeDir, removedFiles) {
40613
+ const metadataPath = join65(claudeDir, "metadata.json");
40614
+ if (!await import_fs_extra29.pathExists(metadataPath)) {
40615
+ return;
40616
+ }
40617
+ let content;
40618
+ try {
40619
+ content = await import_fs_extra29.readFile(metadataPath, "utf-8");
40620
+ } catch (readError) {
40621
+ logger.warning(`Failed to read metadata.json: ${readError instanceof Error ? readError.message : String(readError)}`);
40622
+ return;
40623
+ }
40624
+ let metadata;
40625
+ try {
40626
+ metadata = JSON.parse(content);
40627
+ } catch (parseError) {
40628
+ logger.warning(`Failed to parse metadata.json: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
40629
+ logger.info("Recommendation: Run 'ck init' to rebuild metadata");
40630
+ return;
40631
+ }
40632
+ const removedSet = new Set(removedFiles);
40633
+ if (metadata.kits) {
40634
+ for (const kitName of Object.keys(metadata.kits)) {
40635
+ const kit = metadata.kits[kitName];
40636
+ if (kit?.files) {
40637
+ kit.files = kit.files.filter((f3) => !removedSet.has(f3.path));
40638
+ }
40639
+ }
40640
+ }
40641
+ if (metadata.files) {
40642
+ metadata.files = metadata.files.filter((f3) => !removedSet.has(f3.path));
40643
+ }
40644
+ try {
40645
+ await import_fs_extra29.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
40646
+ logger.debug(`Updated metadata.json, removed ${removedFiles.length} file entries`);
40647
+ } catch (writeError) {
40648
+ logger.warning(`Failed to write metadata.json: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
40649
+ logger.info("Recommendation: Check file permissions and run 'ck init' to rebuild metadata");
40650
+ }
40651
+ }
40652
+ async function removeSubdirectoriesFallback(claudeDir) {
40653
+ const removedFiles = [];
40654
+ let removedDirCount = 0;
40655
+ for (const subdir of CLAUDEKIT_SUBDIRECTORIES) {
40656
+ const subdirPath = join65(claudeDir, subdir);
40657
+ if (await import_fs_extra29.pathExists(subdirPath)) {
40658
+ rmSync2(subdirPath, { recursive: true, force: true });
40659
+ removedDirCount++;
40660
+ removedFiles.push(`${subdir}/ (entire directory)`);
40661
+ logger.debug(`Removed subdirectory: ${subdir}/`);
40662
+ }
40663
+ }
40664
+ const metadataPath = join65(claudeDir, "metadata.json");
40665
+ if (await import_fs_extra29.pathExists(metadataPath)) {
40666
+ unlinkSync3(metadataPath);
40667
+ removedFiles.push("metadata.json");
40668
+ }
40669
+ return {
40670
+ success: true,
40671
+ removedCount: removedDirCount,
40672
+ preservedCount: 0,
40673
+ removedFiles,
40674
+ preservedFiles: []
40675
+ };
40676
+ }
40473
40677
  async function handleFreshInstallation(claudeDir, prompts) {
40474
40678
  if (!await import_fs_extra29.pathExists(claudeDir)) {
40475
40679
  logger.info(".claude directory does not exist, proceeding with fresh installation");
40476
40680
  return true;
40477
40681
  }
40478
- const confirmed = await prompts.promptFreshConfirmation(claudeDir);
40682
+ const analysis = await analyzeFreshInstallation(claudeDir);
40683
+ const confirmed = await prompts.promptFreshConfirmation(claudeDir, analysis);
40479
40684
  if (!confirmed) {
40480
40685
  logger.info("Fresh installation cancelled");
40481
40686
  return false;
40482
40687
  }
40483
- logger.info("Removing ClaudeKit-managed subdirectories...");
40484
- const spinner = createSpinner("Removing ClaudeKit subdirectories...").start();
40688
+ const spinner = createSpinner("Removing ClaudeKit files...").start();
40485
40689
  try {
40486
- const { rmSync } = await import("node:fs");
40487
- let removedCount = 0;
40488
- for (const subdir of CLAUDEKIT_SUBDIRECTORIES) {
40489
- const subdirPath = join65(claudeDir, subdir);
40490
- if (await import_fs_extra29.pathExists(subdirPath)) {
40491
- rmSync(subdirPath, { recursive: true, force: true });
40492
- removedCount++;
40493
- logger.debug(`Removed subdirectory: ${subdir}/`);
40494
- }
40690
+ let result;
40691
+ if (analysis.hasMetadata && (analysis.ckFiles.length > 0 || analysis.ckModifiedFiles.length > 0)) {
40692
+ result = await removeFilesByOwnership(claudeDir, analysis, true);
40693
+ spinner.succeed(`Removed ${result.removedCount} CK files, preserved ${result.preservedCount} user files`);
40694
+ } else {
40695
+ result = await removeSubdirectoriesFallback(claudeDir);
40696
+ spinner.succeed(`Removed ${result.removedCount} ClaudeKit directories`);
40697
+ }
40698
+ if (result.preservedCount > 0) {
40699
+ logger.verbose(`Preserved user files: ${result.preservedFiles.slice(0, 5).join(", ")}${result.preservedFiles.length > 5 ? ` and ${result.preservedFiles.length - 5} more` : ""}`);
40495
40700
  }
40496
- spinner.succeed(`Successfully removed ${removedCount} ClaudeKit subdirectories (preserving user configs)`);
40497
40701
  return true;
40498
40702
  } catch (error) {
40499
- spinner.fail("Failed to remove ClaudeKit subdirectories");
40500
- throw new Error(`Failed to remove subdirectories: ${error instanceof Error ? error.message : "Unknown error"}`);
40703
+ spinner.fail("Failed to remove ClaudeKit files");
40704
+ throw new Error(`Failed to remove files: ${error instanceof Error ? error.message : "Unknown error"}`);
40501
40705
  }
40502
40706
  }
40503
40707
 
@@ -40526,6 +40730,18 @@ async function handleSelection(ctx) {
40526
40730
  release: release2
40527
40731
  };
40528
40732
  }
40733
+ const downloadMethods = [];
40734
+ if (ctx.options.useGit)
40735
+ downloadMethods.push("--use-git");
40736
+ if (ctx.options.archive)
40737
+ downloadMethods.push("--archive");
40738
+ if (ctx.options.kitPath)
40739
+ downloadMethods.push("--kit-path");
40740
+ if (downloadMethods.length > 1) {
40741
+ logger.error(`Mutually exclusive download methods: ${downloadMethods.join(", ")}`);
40742
+ logger.info("Use only one of: --use-git, --archive, or --kit-path");
40743
+ return { ...ctx, cancelled: true };
40744
+ }
40529
40745
  const config = await ConfigManager.get();
40530
40746
  let accessibleKits;
40531
40747
  if (!ctx.options.useGit && !ctx.options.kitPath && !ctx.options.archive) {
@@ -40661,7 +40877,7 @@ async function handleSelection(ctx) {
40661
40877
  }
40662
40878
  }
40663
40879
  }
40664
- const resolvedDir = resolve7(targetDir);
40880
+ const resolvedDir = resolve8(targetDir);
40665
40881
  logger.info(`Target directory: ${resolvedDir}`);
40666
40882
  if (!ctx.options.global && PathResolver.isLocalSameAsGlobal(resolvedDir)) {
40667
40883
  logger.warning("You're at HOME directory. Installing here modifies your GLOBAL ClaudeKit.");
@@ -40733,15 +40949,16 @@ async function handleSelection(ctx) {
40733
40949
  return { ...ctx, cancelled: true };
40734
40950
  }
40735
40951
  }
40736
- const github = new GitHubClient;
40952
+ const isOfflineMode = !!(ctx.options.kitPath || ctx.options.archive);
40953
+ const github = isOfflineMode ? null : new GitHubClient;
40737
40954
  let selectedVersion = ctx.options.release;
40738
- if (!selectedVersion && ctx.isNonInteractive && !ctx.options.yes) {
40955
+ if (!selectedVersion && ctx.isNonInteractive && !ctx.options.yes && !isOfflineMode) {
40739
40956
  throw new Error("Non-interactive mode requires either: --release <tag> OR --yes (uses latest)");
40740
40957
  }
40741
- if (!selectedVersion && ctx.options.yes) {
40958
+ if (!selectedVersion && ctx.options.yes && !isOfflineMode) {
40742
40959
  logger.info("Using latest stable version (--yes flag)");
40743
40960
  }
40744
- if (!selectedVersion && !ctx.isNonInteractive) {
40961
+ if (!selectedVersion && !ctx.isNonInteractive && !isOfflineMode) {
40745
40962
  logger.info("Fetching available versions...");
40746
40963
  let currentVersion = null;
40747
40964
  try {
@@ -40774,7 +40991,13 @@ async function handleSelection(ctx) {
40774
40991
  }
40775
40992
  }
40776
40993
  let release;
40777
- if (ctx.options.useGit && selectedVersion) {
40994
+ if (isOfflineMode) {
40995
+ release = undefined;
40996
+ logger.verbose("Offline mode - skipping release fetch", {
40997
+ kitPath: ctx.options.kitPath,
40998
+ archive: ctx.options.archive
40999
+ });
41000
+ } else if (ctx.options.useGit && selectedVersion) {
40778
41001
  release = {
40779
41002
  id: 0,
40780
41003
  tag_name: selectedVersion,
@@ -40786,9 +41009,9 @@ async function handleSelection(ctx) {
40786
41009
  assets: []
40787
41010
  };
40788
41011
  logger.verbose("Using git clone mode with tag", { tag: selectedVersion });
40789
- } else if (selectedVersion) {
41012
+ } else if (selectedVersion && github) {
40790
41013
  release = await github.getReleaseByTag(kit, selectedVersion);
40791
- } else {
41014
+ } else if (github) {
40792
41015
  if (ctx.options.beta) {
40793
41016
  logger.info("Fetching latest beta release...");
40794
41017
  } else {
@@ -40813,8 +41036,8 @@ async function handleSelection(ctx) {
40813
41036
  };
40814
41037
  }
40815
41038
  // src/commands/init/phases/sync-handler.ts
40816
- import { copyFile as copyFile6, mkdir as mkdir21, open, rename as rename3, stat as stat10, unlink as unlink7, writeFile as writeFile19 } from "node:fs/promises";
40817
- import { dirname as dirname9, join as join67, resolve as resolve8 } from "node:path";
41039
+ import { copyFile as copyFile6, mkdir as mkdir21, open, rename as rename3, stat as stat10, unlink as unlink7, writeFile as writeFile20 } from "node:fs/promises";
41040
+ import { dirname as dirname10, join as join67, resolve as resolve9 } from "node:path";
40818
41041
  init_logger();
40819
41042
  var import_fs_extra31 = __toESM(require_lib(), 1);
40820
41043
  var import_picocolors19 = __toESM(require_picocolors(), 1);
@@ -40822,7 +41045,7 @@ async function handleSync(ctx) {
40822
41045
  if (!ctx.options.sync) {
40823
41046
  return ctx;
40824
41047
  }
40825
- const resolvedDir = ctx.options.global ? PathResolver.getGlobalKitDir() : resolve8(ctx.options.dir || ".");
41048
+ const resolvedDir = ctx.options.global ? PathResolver.getGlobalKitDir() : resolve9(ctx.options.dir || ".");
40826
41049
  const claudeDir = ctx.options.global ? resolvedDir : join67(resolvedDir, ".claude");
40827
41050
  if (!await import_fs_extra31.pathExists(claudeDir)) {
40828
41051
  logger.error("Cannot sync: no .claude directory found");
@@ -40932,7 +41155,7 @@ async function acquireSyncLock(global3) {
40932
41155
  const lockPath = join67(cacheDir, ".sync-lock");
40933
41156
  const startTime = Date.now();
40934
41157
  const lockTimeout = getLockTimeout();
40935
- await mkdir21(dirname9(lockPath), { recursive: true });
41158
+ await mkdir21(dirname10(lockPath), { recursive: true });
40936
41159
  while (Date.now() - startTime < lockTimeout) {
40937
41160
  try {
40938
41161
  const handle = await open(lockPath, "wx");
@@ -40956,7 +41179,7 @@ async function acquireSyncLock(global3) {
40956
41179
  }
40957
41180
  logger.debug(`Lock stat failed: ${statError}`);
40958
41181
  }
40959
- await new Promise((resolve9) => setTimeout(resolve9, 100));
41182
+ await new Promise((resolve10) => setTimeout(resolve10, 100));
40960
41183
  continue;
40961
41184
  }
40962
41185
  throw err;
@@ -41075,7 +41298,7 @@ async function executeSyncMerge(ctx) {
41075
41298
  try {
41076
41299
  const tempPath = `${currentPath}.tmp.${Date.now()}`;
41077
41300
  try {
41078
- await writeFile19(tempPath, result.result, "utf-8");
41301
+ await writeFile20(tempPath, result.result, "utf-8");
41079
41302
  await rename3(tempPath, currentPath);
41080
41303
  } catch (atomicError) {
41081
41304
  await unlink7(tempPath).catch(() => {});
@@ -41261,7 +41484,7 @@ async function renameFolders(dirsToRename, extractDir, options) {
41261
41484
  // src/services/transformers/folder-transform/path-replacer.ts
41262
41485
  init_logger();
41263
41486
  init_types2();
41264
- import { readFile as readFile23, readdir as readdir23, writeFile as writeFile20 } from "node:fs/promises";
41487
+ import { readFile as readFile24, readdir as readdir23, writeFile as writeFile21 } from "node:fs/promises";
41265
41488
  import { join as join69, relative as relative13 } from "node:path";
41266
41489
  var TRANSFORMABLE_FILE_PATTERNS = [
41267
41490
  ".md",
@@ -41328,7 +41551,7 @@ async function transformFileContents(dir, compiledReplacements, options) {
41328
41551
  if (!shouldTransform)
41329
41552
  continue;
41330
41553
  try {
41331
- const content = await readFile23(fullPath, "utf-8");
41554
+ const content = await readFile24(fullPath, "utf-8");
41332
41555
  let newContent = content;
41333
41556
  let changeCount = 0;
41334
41557
  for (const { regex: regex2, replacement } of compiledReplacements) {
@@ -41344,7 +41567,7 @@ async function transformFileContents(dir, compiledReplacements, options) {
41344
41567
  if (options.dryRun) {
41345
41568
  logger.debug(`[dry-run] Would update ${relative13(dir, fullPath)}: ${changeCount} replacement(s)`);
41346
41569
  } else {
41347
- await writeFile20(fullPath, newContent, "utf-8");
41570
+ await writeFile21(fullPath, newContent, "utf-8");
41348
41571
  logger.debug(`Updated ${relative13(dir, fullPath)}: ${changeCount} replacement(s)`);
41349
41572
  }
41350
41573
  filesChanged++;
@@ -41450,7 +41673,7 @@ async function transformFolderPaths(extractDir, folders, options = {}) {
41450
41673
 
41451
41674
  // src/services/transformers/global-path-transformer.ts
41452
41675
  init_logger();
41453
- import { readFile as readFile24, readdir as readdir24, writeFile as writeFile21 } from "node:fs/promises";
41676
+ import { readFile as readFile25, readdir as readdir24, writeFile as writeFile22 } from "node:fs/promises";
41454
41677
  import { platform as platform12 } from "node:os";
41455
41678
  import { extname as extname3, join as join70 } from "node:path";
41456
41679
  var IS_WINDOWS4 = platform12() === "win32";
@@ -41570,10 +41793,10 @@ async function transformPathsForGlobalInstall(directory, options = {}) {
41570
41793
  await processDirectory2(fullPath);
41571
41794
  } else if (entry.isFile() && shouldTransformFile3(entry.name)) {
41572
41795
  try {
41573
- const content = await readFile24(fullPath, "utf-8");
41796
+ const content = await readFile25(fullPath, "utf-8");
41574
41797
  const { transformed, changes } = transformContent(content);
41575
41798
  if (changes > 0) {
41576
- await writeFile21(fullPath, transformed, "utf-8");
41799
+ await writeFile22(fullPath, transformed, "utf-8");
41577
41800
  filesTransformed++;
41578
41801
  totalChanges += changes;
41579
41802
  if (options.verbose) {
@@ -41828,7 +42051,7 @@ init_types2();
41828
42051
  var import_picocolors20 = __toESM(require_picocolors(), 1);
41829
42052
 
41830
42053
  // src/commands/new/phases/directory-setup.ts
41831
- import { resolve as resolve9 } from "node:path";
42054
+ import { resolve as resolve10 } from "node:path";
41832
42055
  init_logger();
41833
42056
  init_types2();
41834
42057
  var import_fs_extra33 = __toESM(require_lib(), 1);
@@ -41912,7 +42135,7 @@ async function directorySetup(validOptions, prompts) {
41912
42135
  targetDir = await prompts.getDirectory(targetDir);
41913
42136
  }
41914
42137
  }
41915
- const resolvedDir = resolve9(targetDir);
42138
+ const resolvedDir = resolve10(targetDir);
41916
42139
  logger.info(`Target directory: ${resolvedDir}`);
41917
42140
  if (PathResolver.isLocalSameAsGlobal(resolvedDir)) {
41918
42141
  logger.warning("You're creating a project at HOME directory.");
@@ -42253,14 +42476,14 @@ async function detectInstallations() {
42253
42476
  }
42254
42477
 
42255
42478
  // src/commands/uninstall/removal-handler.ts
42256
- import { readdirSync as readdirSync2, rmSync as rmSync2 } from "node:fs";
42479
+ import { readdirSync as readdirSync3, rmSync as rmSync4 } from "node:fs";
42257
42480
  import { join as join74 } from "node:path";
42258
42481
  init_logger();
42259
42482
  var import_fs_extra35 = __toESM(require_lib(), 1);
42260
42483
 
42261
42484
  // src/commands/uninstall/analysis-handler.ts
42262
- import { readdirSync, rmSync } from "node:fs";
42263
- import { dirname as dirname10, join as join73 } from "node:path";
42485
+ import { readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
42486
+ import { dirname as dirname11, join as join73 } from "node:path";
42264
42487
  init_logger();
42265
42488
  var import_picocolors21 = __toESM(require_picocolors(), 1);
42266
42489
  function classifyFileByOwnership(ownership, forceOverwrite, deleteReason) {
@@ -42275,17 +42498,17 @@ function classifyFileByOwnership(ownership, forceOverwrite, deleteReason) {
42275
42498
  }
42276
42499
  return { action: "preserve", reason: "user-created" };
42277
42500
  }
42278
- async function cleanupEmptyDirectories(filePath, installationRoot) {
42501
+ async function cleanupEmptyDirectories2(filePath, installationRoot) {
42279
42502
  let cleaned = 0;
42280
- let currentDir = dirname10(filePath);
42503
+ let currentDir = dirname11(filePath);
42281
42504
  while (currentDir !== installationRoot && currentDir.startsWith(installationRoot)) {
42282
42505
  try {
42283
- const entries = readdirSync(currentDir);
42506
+ const entries = readdirSync2(currentDir);
42284
42507
  if (entries.length === 0) {
42285
- rmSync(currentDir, { recursive: true });
42508
+ rmSync3(currentDir, { recursive: true });
42286
42509
  cleaned++;
42287
42510
  logger.debug(`Removed empty directory: ${currentDir}`);
42288
- currentDir = dirname10(currentDir);
42511
+ currentDir = dirname11(currentDir);
42289
42512
  } else {
42290
42513
  break;
42291
42514
  }
@@ -42402,16 +42625,16 @@ async function removeInstallations(installations, options) {
42402
42625
  await import_fs_extra35.remove(filePath);
42403
42626
  removedCount++;
42404
42627
  logger.debug(`Removed: ${item.path}`);
42405
- cleanedDirs += await cleanupEmptyDirectories(filePath, installation.path);
42628
+ cleanedDirs += await cleanupEmptyDirectories2(filePath, installation.path);
42406
42629
  }
42407
42630
  }
42408
42631
  if (options.kit && analysis.remainingKits.length > 0) {
42409
42632
  await ManifestWriter.removeKitFromManifest(installation.path, options.kit);
42410
42633
  }
42411
42634
  try {
42412
- const remaining = readdirSync2(installation.path);
42635
+ const remaining = readdirSync3(installation.path);
42413
42636
  if (remaining.length === 0) {
42414
- rmSync2(installation.path, { recursive: true });
42637
+ rmSync4(installation.path, { recursive: true });
42415
42638
  logger.debug(`Removed empty installation directory: ${installation.path}`);
42416
42639
  }
42417
42640
  } catch {}
@@ -42715,7 +42938,7 @@ var import_fs_extra36 = __toESM(require_lib(), 1);
42715
42938
  // package.json
42716
42939
  var package_default = {
42717
42940
  name: "claudekit-cli",
42718
- version: "3.26.1",
42941
+ version: "3.27.0",
42719
42942
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
42720
42943
  type: "module",
42721
42944
  repository: {
@@ -43193,7 +43416,7 @@ function registerCommands(cli) {
43193
43416
  }
43194
43417
 
43195
43418
  // src/cli/version-display.ts
43196
- import { existsSync as existsSync20, readFileSync as readFileSync7 } from "node:fs";
43419
+ import { existsSync as existsSync21, readFileSync as readFileSync7 } from "node:fs";
43197
43420
  import { join as join77 } from "node:path";
43198
43421
 
43199
43422
  // src/domains/versioning/checking/version-utils.ts
@@ -43219,8 +43442,8 @@ init_types2();
43219
43442
 
43220
43443
  // src/domains/versioning/version-cache.ts
43221
43444
  init_logger();
43222
- import { existsSync as existsSync19 } from "node:fs";
43223
- import { mkdir as mkdir22, readFile as readFile26, writeFile as writeFile22 } from "node:fs/promises";
43445
+ import { existsSync as existsSync20 } from "node:fs";
43446
+ import { mkdir as mkdir22, readFile as readFile27, writeFile as writeFile23 } from "node:fs/promises";
43224
43447
  import { join as join76 } from "node:path";
43225
43448
  class VersionCacheManager {
43226
43449
  static CACHE_FILENAME = "version-check.json";
@@ -43232,11 +43455,11 @@ class VersionCacheManager {
43232
43455
  static async load() {
43233
43456
  const cacheFile = VersionCacheManager.getCacheFile();
43234
43457
  try {
43235
- if (!existsSync19(cacheFile)) {
43458
+ if (!existsSync20(cacheFile)) {
43236
43459
  logger.debug("Version check cache not found");
43237
43460
  return null;
43238
43461
  }
43239
- const content = await readFile26(cacheFile, "utf-8");
43462
+ const content = await readFile27(cacheFile, "utf-8");
43240
43463
  const cache2 = JSON.parse(content);
43241
43464
  if (!cache2.lastCheck || !cache2.currentVersion || !cache2.latestVersion) {
43242
43465
  logger.debug("Invalid cache structure, ignoring");
@@ -43253,10 +43476,10 @@ class VersionCacheManager {
43253
43476
  const cacheFile = VersionCacheManager.getCacheFile();
43254
43477
  const cacheDir = PathResolver.getCacheDir(false);
43255
43478
  try {
43256
- if (!existsSync19(cacheDir)) {
43479
+ if (!existsSync20(cacheDir)) {
43257
43480
  await mkdir22(cacheDir, { recursive: true, mode: 448 });
43258
43481
  }
43259
- await writeFile22(cacheFile, JSON.stringify(cache2, null, 2), "utf-8");
43482
+ await writeFile23(cacheFile, JSON.stringify(cache2, null, 2), "utf-8");
43260
43483
  logger.debug(`Version check cache saved to ${cacheFile}`);
43261
43484
  } catch (error) {
43262
43485
  logger.debug(`Failed to save version check cache: ${error}`);
@@ -43275,7 +43498,7 @@ class VersionCacheManager {
43275
43498
  static async clear() {
43276
43499
  const cacheFile = VersionCacheManager.getCacheFile();
43277
43500
  try {
43278
- if (existsSync19(cacheFile)) {
43501
+ if (existsSync20(cacheFile)) {
43279
43502
  const fs14 = await import("node:fs/promises");
43280
43503
  await fs14.unlink(cacheFile);
43281
43504
  logger.debug("Version check cache cleared");
@@ -43498,7 +43721,7 @@ async function displayVersion() {
43498
43721
  const prefix = PathResolver.getPathPrefix(false);
43499
43722
  const localMetadataPath = prefix ? join77(process.cwd(), prefix, "metadata.json") : join77(process.cwd(), "metadata.json");
43500
43723
  const isLocalSameAsGlobal = localMetadataPath === globalMetadataPath;
43501
- if (!isLocalSameAsGlobal && existsSync20(localMetadataPath)) {
43724
+ if (!isLocalSameAsGlobal && existsSync21(localMetadataPath)) {
43502
43725
  try {
43503
43726
  const rawMetadata = JSON.parse(readFileSync7(localMetadataPath, "utf-8"));
43504
43727
  const metadata = MetadataSchema.parse(rawMetadata);
@@ -43512,7 +43735,7 @@ async function displayVersion() {
43512
43735
  logger.verbose("Failed to parse local metadata.json", { error });
43513
43736
  }
43514
43737
  }
43515
- if (existsSync20(globalMetadataPath)) {
43738
+ if (existsSync21(globalMetadataPath)) {
43516
43739
  try {
43517
43740
  const rawMetadata = JSON.parse(readFileSync7(globalMetadataPath, "utf-8"));
43518
43741
  const metadata = MetadataSchema.parse(rawMetadata);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudekit-cli",
3
- "version": "3.26.1",
3
+ "version": "3.27.0",
4
4
  "description": "CLI tool for bootstrapping and updating ClaudeKit projects",
5
5
  "type": "module",
6
6
  "repository": {