claudekit-cli 3.41.2 → 3.41.3

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 +161 -28
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -57632,7 +57632,7 @@ var package_default;
57632
57632
  var init_package = __esm(() => {
57633
57633
  package_default = {
57634
57634
  name: "claudekit-cli",
57635
- version: "3.41.2",
57635
+ version: "3.41.3",
57636
57636
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
57637
57637
  type: "module",
57638
57638
  repository: {
@@ -80380,7 +80380,7 @@ class OwnershipChecker {
80380
80380
  return { path: filePath, ownership: "user", exists: true };
80381
80381
  }
80382
80382
  const relativePath = relative7(claudeDir2, filePath).replace(/\\/g, "/");
80383
- const tracked = allTrackedFiles.find((f3) => f3.path === relativePath);
80383
+ const tracked = allTrackedFiles.find((f3) => f3.path.replace(/\\/g, "/") === relativePath);
80384
80384
  if (!tracked) {
80385
80385
  return { path: filePath, ownership: "user", exists: true };
80386
80386
  }
@@ -91130,6 +91130,7 @@ async function findFileInInstalledKits(claudeDir2, relativePath, excludeKit) {
91130
91130
  };
91131
91131
  }
91132
91132
  async function getUninstallManifest(claudeDir2, kit) {
91133
+ const normalizeTrackedPath = (relativePath) => relativePath.replace(/\\/g, "/");
91133
91134
  const detection = await detectMetadataFormat(claudeDir2);
91134
91135
  if (detection.format === "multi-kit" && detection.metadata?.kits) {
91135
91136
  const installedKits = Object.keys(detection.metadata.kits);
@@ -91144,14 +91145,14 @@ async function getUninstallManifest(claudeDir2, kit) {
91144
91145
  remainingKits: installedKits.filter((k2) => k2 !== kit)
91145
91146
  };
91146
91147
  }
91147
- const kitFiles = kitMeta.files.map((f3) => f3.path);
91148
+ const kitFiles = kitMeta.files.map((f3) => normalizeTrackedPath(f3.path));
91148
91149
  const sharedFiles = new Set;
91149
91150
  for (const otherKit of installedKits) {
91150
91151
  if (otherKit !== kit) {
91151
91152
  const otherMeta = detection.metadata.kits[otherKit];
91152
91153
  if (otherMeta?.files) {
91153
91154
  for (const f3 of otherMeta.files) {
91154
- sharedFiles.add(f3.path);
91155
+ sharedFiles.add(normalizeTrackedPath(f3.path));
91155
91156
  }
91156
91157
  }
91157
91158
  }
@@ -91171,7 +91172,7 @@ async function getUninstallManifest(claudeDir2, kit) {
91171
91172
  }
91172
91173
  const allFiles = getAllTrackedFiles(detection.metadata);
91173
91174
  return {
91174
- filesToRemove: allFiles.map((f3) => f3.path),
91175
+ filesToRemove: allFiles.map((f3) => normalizeTrackedPath(f3.path)),
91175
91176
  filesToPreserve: USER_CONFIG_PATTERNS,
91176
91177
  hasManifest: true,
91177
91178
  isMultiKit: true,
@@ -91179,8 +91180,8 @@ async function getUninstallManifest(claudeDir2, kit) {
91179
91180
  };
91180
91181
  }
91181
91182
  if (detection.format === "legacy" && detection.metadata) {
91182
- const legacyFiles2 = detection.metadata.files?.map((f3) => f3.path) || [];
91183
- const installedFiles = detection.metadata.installedFiles || [];
91183
+ const legacyFiles2 = detection.metadata.files?.map((f3) => normalizeTrackedPath(f3.path)) || [];
91184
+ const installedFiles = (detection.metadata.installedFiles || []).map((path14) => normalizeTrackedPath(path14));
91184
91185
  const hasFiles = legacyFiles2.length > 0 || installedFiles.length > 0;
91185
91186
  if (!hasFiles) {
91186
91187
  const legacyDirs2 = ["commands", "agents", "skills", "rules", "workflows", "hooks", "scripts"];
@@ -94073,6 +94074,92 @@ async function removeKitFromManifest(claudeDir2, kit) {
94073
94074
  }
94074
94075
  }
94075
94076
  }
94077
+ async function retainTrackedFilesInManifest(claudeDir2, retainedPaths, options2) {
94078
+ const metadataPath = join91(claudeDir2, "metadata.json");
94079
+ if (!await import_fs_extra14.pathExists(metadataPath))
94080
+ return false;
94081
+ const normalizedPaths = new Set(retainedPaths.map((path15) => path15.replace(/\\/g, "/")));
94082
+ if (normalizedPaths.size === 0)
94083
+ return false;
94084
+ let release = null;
94085
+ try {
94086
+ release = await import_proper_lockfile5.lock(metadataPath, {
94087
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 1000 },
94088
+ stale: 60000
94089
+ });
94090
+ logger.debug(`Acquired lock on ${metadataPath} for retained metadata update`);
94091
+ const metadata = await readManifest(claudeDir2);
94092
+ if (!metadata)
94093
+ return false;
94094
+ if (metadata.kits) {
94095
+ const retainedKits = Object.entries(metadata.kits).reduce((acc, [kitName, kitMeta]) => {
94096
+ if (kitName === options2?.excludeKit) {
94097
+ return acc;
94098
+ }
94099
+ const keptFiles = (kitMeta.files || []).flatMap((file) => {
94100
+ const normalizedPath = file.path.replace(/\\/g, "/");
94101
+ if (!normalizedPaths.has(normalizedPath)) {
94102
+ return [];
94103
+ }
94104
+ return [
94105
+ {
94106
+ ...file,
94107
+ path: normalizedPath
94108
+ }
94109
+ ];
94110
+ });
94111
+ if (keptFiles.length > 0) {
94112
+ acc[kitName] = {
94113
+ ...kitMeta,
94114
+ files: keptFiles
94115
+ };
94116
+ }
94117
+ return acc;
94118
+ }, {});
94119
+ if (Object.keys(retainedKits).length === 0) {
94120
+ return false;
94121
+ }
94122
+ const retainedFiles2 = Object.values(retainedKits).flatMap((kitMeta) => kitMeta.files || []);
94123
+ const retainedInstalledFiles2 = retainedFiles2.map((file) => file.path);
94124
+ const updated2 = MetadataSchema.parse({
94125
+ ...metadata,
94126
+ kits: retainedKits,
94127
+ files: retainedFiles2.length > 0 ? retainedFiles2 : undefined,
94128
+ installedFiles: retainedInstalledFiles2.length > 0 ? retainedInstalledFiles2 : undefined
94129
+ });
94130
+ await import_fs_extra14.writeFile(metadataPath, JSON.stringify(updated2, null, 2), "utf-8");
94131
+ return true;
94132
+ }
94133
+ const retainedFiles = (metadata.files || []).flatMap((file) => {
94134
+ const normalizedPath = file.path.replace(/\\/g, "/");
94135
+ if (!normalizedPaths.has(normalizedPath)) {
94136
+ return [];
94137
+ }
94138
+ return [
94139
+ {
94140
+ ...file,
94141
+ path: normalizedPath
94142
+ }
94143
+ ];
94144
+ });
94145
+ const retainedInstalledFiles = (metadata.installedFiles || []).filter((path15) => normalizedPaths.has(path15.replace(/\\/g, "/")));
94146
+ if (retainedFiles.length === 0 && retainedInstalledFiles.length === 0) {
94147
+ return false;
94148
+ }
94149
+ const updated = MetadataSchema.parse({
94150
+ ...metadata,
94151
+ files: retainedFiles.length > 0 ? retainedFiles : undefined,
94152
+ installedFiles: retainedInstalledFiles.length > 0 ? retainedInstalledFiles : undefined
94153
+ });
94154
+ await import_fs_extra14.writeFile(metadataPath, JSON.stringify(updated, null, 2), "utf-8");
94155
+ return true;
94156
+ } finally {
94157
+ if (release) {
94158
+ await release();
94159
+ logger.debug(`Released lock on ${metadataPath}`);
94160
+ }
94161
+ }
94162
+ }
94076
94163
 
94077
94164
  // src/services/file-operations/manifest/manifest-tracker.ts
94078
94165
  class ManifestTracker {
@@ -94240,6 +94327,9 @@ class ManifestWriter {
94240
94327
  static async removeKitFromManifest(claudeDir2, kit) {
94241
94328
  return removeKitFromManifest(claudeDir2, kit);
94242
94329
  }
94330
+ static async retainTrackedFilesInManifest(claudeDir2, retainedPaths, options2) {
94331
+ return retainTrackedFilesInManifest(claudeDir2, retainedPaths, options2);
94332
+ }
94243
94333
  }
94244
94334
 
94245
94335
  // src/domains/migration/legacy-migration.ts
@@ -101259,7 +101349,7 @@ import { join as join124, resolve as resolve31, sep as sep8 } from "node:path";
101259
101349
  init_logger();
101260
101350
  init_safe_prompts();
101261
101351
  init_safe_spinner();
101262
- var import_fs_extra37 = __toESM(require_lib3(), 1);
101352
+ var import_fs_extra38 = __toESM(require_lib3(), 1);
101263
101353
 
101264
101354
  // src/commands/uninstall/analysis-handler.ts
101265
101355
  init_metadata_migration();
@@ -101267,7 +101357,11 @@ import { readdirSync as readdirSync6, rmSync as rmSync5 } from "node:fs";
101267
101357
  import { dirname as dirname32, join as join123 } from "node:path";
101268
101358
  init_logger();
101269
101359
  init_safe_prompts();
101360
+ var import_fs_extra37 = __toESM(require_lib3(), 1);
101270
101361
  var import_picocolors36 = __toESM(require_picocolors(), 1);
101362
+ function normalizeTrackedPath(relativePath) {
101363
+ return relativePath.replace(/\\/g, "/");
101364
+ }
101271
101365
  function classifyFileByOwnership(ownership, forceOverwrite, deleteReason) {
101272
101366
  if (ownership === "ck") {
101273
101367
  return { action: "delete", reason: deleteReason };
@@ -101304,17 +101398,30 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
101304
101398
  const result = {
101305
101399
  toDelete: [],
101306
101400
  toPreserve: [],
101401
+ retainedManifestPaths: [],
101402
+ protectedTrackedPaths: [],
101307
101403
  remainingKits: []
101308
101404
  };
101309
101405
  const metadata = await ManifestWriter.readManifest(installation.path);
101310
101406
  const uninstallManifest = await ManifestWriter.getUninstallManifest(installation.path, kit);
101311
101407
  result.remainingKits = uninstallManifest.remainingKits;
101312
101408
  if (uninstallManifest.isMultiKit && kit && metadata?.kits?.[kit]) {
101409
+ const preservedPaths = new Set(uninstallManifest.filesToPreserve.map((filePath) => normalizeTrackedPath(filePath)));
101410
+ for (const remainingKit of result.remainingKits) {
101411
+ const remainingFiles = metadata.kits?.[remainingKit]?.files || [];
101412
+ for (const file of remainingFiles) {
101413
+ const relativePath = normalizeTrackedPath(file.path);
101414
+ if (await import_fs_extra37.pathExists(join123(installation.path, relativePath))) {
101415
+ result.retainedManifestPaths.push(relativePath);
101416
+ }
101417
+ }
101418
+ }
101313
101419
  const kitFiles = metadata.kits[kit].files || [];
101314
101420
  for (const trackedFile of kitFiles) {
101315
- const filePath = join123(installation.path, trackedFile.path);
101316
- if (uninstallManifest.filesToPreserve.includes(trackedFile.path)) {
101317
- result.toPreserve.push({ path: trackedFile.path, reason: "shared with other kit" });
101421
+ const relativePath = normalizeTrackedPath(trackedFile.path);
101422
+ const filePath = join123(installation.path, relativePath);
101423
+ if (preservedPaths.has(relativePath)) {
101424
+ result.toPreserve.push({ path: relativePath, reason: "shared with other kit" });
101318
101425
  continue;
101319
101426
  }
101320
101427
  const ownershipResult = await OwnershipChecker.checkOwnership(filePath, metadata, installation.path);
@@ -101322,12 +101429,14 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
101322
101429
  continue;
101323
101430
  const classification = classifyFileByOwnership(ownershipResult.ownership, forceOverwrite, `${kit} kit (pristine)`);
101324
101431
  if (classification.action === "delete") {
101325
- result.toDelete.push({ path: trackedFile.path, reason: classification.reason });
101432
+ result.toDelete.push({ path: relativePath, reason: classification.reason });
101326
101433
  } else {
101327
- result.toPreserve.push({ path: trackedFile.path, reason: classification.reason });
101434
+ result.toPreserve.push({ path: relativePath, reason: classification.reason });
101435
+ result.protectedTrackedPaths.push(relativePath);
101436
+ result.retainedManifestPaths.push(relativePath);
101328
101437
  }
101329
101438
  }
101330
- if (result.remainingKits.length === 0) {
101439
+ if (result.retainedManifestPaths.length === 0) {
101331
101440
  result.toDelete.push({ path: "metadata.json", reason: "metadata file" });
101332
101441
  }
101333
101442
  return result;
@@ -101342,18 +101451,23 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
101342
101451
  return result;
101343
101452
  }
101344
101453
  for (const trackedFile of allTrackedFiles) {
101345
- const filePath = join123(installation.path, trackedFile.path);
101454
+ const relativePath = normalizeTrackedPath(trackedFile.path);
101455
+ const filePath = join123(installation.path, relativePath);
101346
101456
  const ownershipResult = await OwnershipChecker.checkOwnership(filePath, metadata, installation.path);
101347
101457
  if (!ownershipResult.exists)
101348
101458
  continue;
101349
101459
  const classification = classifyFileByOwnership(ownershipResult.ownership, forceOverwrite, "CK-owned (pristine)");
101350
101460
  if (classification.action === "delete") {
101351
- result.toDelete.push({ path: trackedFile.path, reason: classification.reason });
101461
+ result.toDelete.push({ path: relativePath, reason: classification.reason });
101352
101462
  } else {
101353
- result.toPreserve.push({ path: trackedFile.path, reason: classification.reason });
101463
+ result.toPreserve.push({ path: relativePath, reason: classification.reason });
101464
+ result.protectedTrackedPaths.push(relativePath);
101465
+ result.retainedManifestPaths.push(relativePath);
101354
101466
  }
101355
101467
  }
101356
- result.toDelete.push({ path: "metadata.json", reason: "metadata file" });
101468
+ if (result.retainedManifestPaths.length === 0) {
101469
+ result.toDelete.push({ path: "metadata.json", reason: "metadata file" });
101470
+ }
101357
101471
  return result;
101358
101472
  }
101359
101473
  function displayDryRunPreview(analysis, installationType) {
@@ -101387,7 +101501,7 @@ function displayDryRunPreview(analysis, installationType) {
101387
101501
  // src/commands/uninstall/removal-handler.ts
101388
101502
  async function isDirectory(filePath) {
101389
101503
  try {
101390
- const stats = await import_fs_extra37.lstat(filePath);
101504
+ const stats = await import_fs_extra38.lstat(filePath);
101391
101505
  return stats.isDirectory();
101392
101506
  } catch {
101393
101507
  logger.debug(`Failed to check if path is directory: ${filePath}`);
@@ -101402,9 +101516,9 @@ async function isPathSafeToRemove(filePath, baseDir) {
101402
101516
  logger.debug(`Path outside installation directory: ${filePath}`);
101403
101517
  return false;
101404
101518
  }
101405
- const stats = await import_fs_extra37.lstat(filePath);
101519
+ const stats = await import_fs_extra38.lstat(filePath);
101406
101520
  if (stats.isSymbolicLink()) {
101407
- const realPath = await import_fs_extra37.realpath(filePath);
101521
+ const realPath = await import_fs_extra38.realpath(filePath);
101408
101522
  const resolvedReal = resolve31(realPath);
101409
101523
  if (!resolvedReal.startsWith(resolvedBase + sep8) && resolvedReal !== resolvedBase) {
101410
101524
  logger.debug(`Symlink points outside installation directory: ${filePath} -> ${realPath}`);
@@ -101418,6 +101532,7 @@ async function isPathSafeToRemove(filePath, baseDir) {
101418
101532
  }
101419
101533
  }
101420
101534
  async function removeInstallations(installations, options2) {
101535
+ const summaries = [];
101421
101536
  for (const installation of installations) {
101422
101537
  const analysis = await analyzeInstallation(installation, options2.forceOverwrite, options2.kit);
101423
101538
  if (options2.dryRun) {
@@ -101440,22 +101555,25 @@ async function removeInstallations(installations, options2) {
101440
101555
  let cleanedDirs = 0;
101441
101556
  for (const item of analysis.toDelete) {
101442
101557
  const filePath = join124(installation.path, item.path);
101443
- if (!await import_fs_extra37.pathExists(filePath))
101558
+ if (!await import_fs_extra38.pathExists(filePath))
101444
101559
  continue;
101445
101560
  if (!await isPathSafeToRemove(filePath, installation.path)) {
101446
101561
  logger.debug(`Skipping unsafe path: ${item.path}`);
101447
101562
  continue;
101448
101563
  }
101449
101564
  const isDir = await isDirectory(filePath);
101450
- await import_fs_extra37.remove(filePath);
101565
+ await import_fs_extra38.remove(filePath);
101451
101566
  removedCount++;
101452
101567
  logger.debug(`Removed ${isDir ? "directory" : "file"}: ${item.path}`);
101453
101568
  if (!isDir) {
101454
101569
  cleanedDirs += await cleanupEmptyDirectories3(filePath, installation.path);
101455
101570
  }
101456
101571
  }
101457
- if (options2.kit && analysis.remainingKits.length > 0) {
101458
- await ManifestWriter.removeKitFromManifest(installation.path, options2.kit);
101572
+ if (analysis.retainedManifestPaths.length > 0) {
101573
+ const retained = await ManifestWriter.retainTrackedFilesInManifest(installation.path, [...new Set(analysis.retainedManifestPaths)], options2.kit && analysis.protectedTrackedPaths.length === 0 ? { excludeKit: options2.kit } : undefined);
101574
+ if (!retained) {
101575
+ throw new Error("Failed to update metadata.json after partial uninstall");
101576
+ }
101459
101577
  }
101460
101578
  try {
101461
101579
  const remaining = readdirSync7(installation.path);
@@ -101473,11 +101591,21 @@ async function removeInstallations(installations, options2) {
101473
101591
  log.message(` ... and ${analysis.toPreserve.length - 5} more`);
101474
101592
  }
101475
101593
  }
101594
+ if (analysis.protectedTrackedPaths.length > 0) {
101595
+ log.warn("Protected ClaudeKit files were preserved. Metadata was retained so this installation does not fall back to legacy detection.");
101596
+ log.info("Use --force-overwrite to remove those files on the next uninstall run.");
101597
+ }
101598
+ summaries.push({
101599
+ path: installation.path,
101600
+ preservedCustomizations: analysis.toPreserve.length,
101601
+ protectedTrackedPaths: [...analysis.protectedTrackedPaths]
101602
+ });
101476
101603
  } catch (error) {
101477
101604
  spinner.fail(`Failed to remove ${installation.type} installation`);
101478
101605
  throw new Error(`Failed to remove files from ${installation.path}: ${error instanceof Error ? error.message : "Unknown error"}`);
101479
101606
  }
101480
101607
  }
101608
+ return summaries;
101481
101609
  }
101482
101610
 
101483
101611
  // src/commands/uninstall/uninstall-command.ts
@@ -101626,12 +101754,17 @@ ${import_picocolors37.default.yellow("User modifications will be permanently del
101626
101754
  return;
101627
101755
  }
101628
101756
  }
101629
- await removeInstallations(installations, {
101757
+ const results = await removeInstallations(installations, {
101630
101758
  dryRun: false,
101631
101759
  forceOverwrite: validOptions.forceOverwrite,
101632
101760
  kit: validOptions.kit
101633
101761
  });
101762
+ const hasProtectedFiles = results.some((result) => result.protectedTrackedPaths.length > 0);
101634
101763
  const kitMsg = validOptions.kit ? ` (${validOptions.kit} kit)` : "";
101764
+ if (hasProtectedFiles) {
101765
+ prompts.outro(`ClaudeKit${kitMsg} uninstall completed with preserved customizations. Use --force-overwrite for full removal.`);
101766
+ return;
101767
+ }
101635
101768
  prompts.outro(`ClaudeKit${kitMsg} uninstalled successfully!`);
101636
101769
  } catch (error) {
101637
101770
  logger.error(error instanceof Error ? error.message : "Unknown error");
@@ -104165,7 +104298,7 @@ var output2 = new OutputManager2;
104165
104298
 
104166
104299
  // src/shared/temp-cleanup.ts
104167
104300
  init_logger();
104168
- var import_fs_extra38 = __toESM(require_lib3(), 1);
104301
+ var import_fs_extra39 = __toESM(require_lib3(), 1);
104169
104302
  import { rmSync as rmSync7 } from "node:fs";
104170
104303
  var tempDirs2 = new Set;
104171
104304
  async function cleanup() {
@@ -104174,7 +104307,7 @@ async function cleanup() {
104174
104307
  logger.debug(`Cleaning up ${tempDirs2.size} temporary director(ies)...`);
104175
104308
  for (const dir of tempDirs2) {
104176
104309
  try {
104177
- await import_fs_extra38.remove(dir);
104310
+ await import_fs_extra39.remove(dir);
104178
104311
  logger.debug(`Cleaned up temp directory: ${dir}`);
104179
104312
  } catch (error) {
104180
104313
  logger.debug(`Failed to clean temp directory ${dir}: ${error}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudekit-cli",
3
- "version": "3.41.2",
3
+ "version": "3.41.3",
4
4
  "description": "CLI tool for bootstrapping and updating ClaudeKit projects",
5
5
  "type": "module",
6
6
  "repository": {