claudekit-cli 4.0.0-dev.2 → 4.0.0-dev.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -324,6 +324,7 @@ Remove ClaudeKit installations from your system:
324
324
  ck uninstall # Interactive mode - prompts for scope and confirmation
325
325
  ck uninstall --local # Uninstall only local installation (current project)
326
326
  ck uninstall --global # Uninstall only global installation (~/.claude/)
327
+ ck uninstall -g --kit marketing # Remove only global Marketing kit
327
328
  ck uninstall -l -y # Local only, skip confirmation
328
329
  ck uninstall -g -y # Global only, skip confirmation
329
330
  ck uninstall --yes # Non-interactive - skip confirmation (for scripts)
@@ -334,7 +335,11 @@ ck uninstall --yes # Non-interactive - skip confirmation (for scripts)
334
335
  - **Local only**: Remove from current project (`.claude/`)
335
336
  - **Global only**: Remove from user directory (`~/.claude/`)
336
337
  - **Both**: Remove all ClaudeKit installations
338
+ - When multiple kits are installed in the selected scope, interactive uninstall prompts for:
339
+ - **Marketing kit only** or **Engineer kit only**: Remove one kit and preserve the other
340
+ - **All ClaudeKit kits**: Remove the full selected installation scope
337
341
  - Use `--local` or `--global` flags to skip the prompt
342
+ - Use `--kit engineer` or `--kit marketing` to skip the kit prompt
338
343
 
339
344
  **What it does:**
340
345
  - Detects local `.claude` directory in current project
package/cli-manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "4.0.0-dev.2",
3
- "generatedAt": "2026-05-07T18:05:52.152Z",
2
+ "version": "4.0.0-dev.4",
3
+ "generatedAt": "2026-05-09T14:28:30.285Z",
4
4
  "commands": {
5
5
  "agents": {
6
6
  "name": "agents",
@@ -1922,6 +1922,10 @@
1922
1922
  "description": "Remove ClaudeKit installations (ownership-aware)",
1923
1923
  "usage": "ck uninstall [options]",
1924
1924
  "examples": [
1925
+ {
1926
+ "command": "ck uninstall --global --kit marketing",
1927
+ "description": "Remove only the global Marketing kit and preserve Engineer"
1928
+ },
1925
1929
  {
1926
1930
  "command": "ck uninstall --local --yes",
1927
1931
  "description": "Remove local installation without confirmation"
package/dist/index.js CHANGED
@@ -12552,6 +12552,100 @@ async function migrateRegistryV2ToV3(v2Registry) {
12552
12552
  appliedManifestVersion: undefined
12553
12553
  };
12554
12554
  }
12555
+ function getStringField(item, field) {
12556
+ const record = item;
12557
+ if (!(field in record) || record[field] === undefined) {
12558
+ return;
12559
+ }
12560
+ const value = record[field];
12561
+ if (typeof value !== "string") {
12562
+ throw new Error("portable-registry.json has unsupported schema/version");
12563
+ }
12564
+ return value;
12565
+ }
12566
+ function getOwnedSections(item) {
12567
+ const record = item;
12568
+ if (!("ownedSections" in record) || record.ownedSections === undefined) {
12569
+ return;
12570
+ }
12571
+ const value = record.ownedSections;
12572
+ if (!Array.isArray(value) || !value.every((section) => typeof section === "string")) {
12573
+ throw new Error("portable-registry.json has unsupported schema/version");
12574
+ }
12575
+ return value;
12576
+ }
12577
+ function getInstallSource(item) {
12578
+ const installSource = getStringField(item, "installSource");
12579
+ if (installSource === undefined) {
12580
+ return "kit";
12581
+ }
12582
+ if (installSource !== "kit" && installSource !== "manual") {
12583
+ throw new Error("portable-registry.json has unsupported schema/version");
12584
+ }
12585
+ return installSource;
12586
+ }
12587
+ async function repairStaleRegistryV3(registry) {
12588
+ const installations = [];
12589
+ for (const item of registry.installations) {
12590
+ let targetChecksum = normalizeChecksum(getStringField(item, "targetChecksum")) || UNKNOWN_CHECKSUM;
12591
+ if (targetChecksum === UNKNOWN_CHECKSUM && existsSync2(item.path)) {
12592
+ try {
12593
+ targetChecksum = await computeFileChecksum(item.path);
12594
+ } catch {
12595
+ targetChecksum = UNKNOWN_CHECKSUM;
12596
+ }
12597
+ }
12598
+ installations.push({
12599
+ ...item,
12600
+ sourceChecksum: normalizeChecksum(getStringField(item, "sourceChecksum")) || UNKNOWN_CHECKSUM,
12601
+ targetChecksum,
12602
+ installSource: getInstallSource(item),
12603
+ ownedSections: getOwnedSections(item)
12604
+ });
12605
+ }
12606
+ return normalizePortableRegistryChecksums({
12607
+ ...registry,
12608
+ version: "3.0",
12609
+ installations,
12610
+ lastReconciled: registry.lastReconciled,
12611
+ appliedManifestVersion: registry.appliedManifestVersion
12612
+ });
12613
+ }
12614
+ async function persistCurrentStaleRegistryV3Repair(preparedRepair) {
12615
+ return withRegistryLock(async () => {
12616
+ let content;
12617
+ try {
12618
+ content = await readFile2(REGISTRY_PATH, "utf-8");
12619
+ } catch (error) {
12620
+ if (isErrnoCode(error, "ENOENT")) {
12621
+ return readPortableRegistryInternal({ persistStaleV3Repair: false });
12622
+ }
12623
+ throw error;
12624
+ }
12625
+ let data;
12626
+ try {
12627
+ data = JSON.parse(content);
12628
+ } catch (error) {
12629
+ throw new Error(`portable-registry.json is not valid JSON: ${error instanceof Error ? error.message : "Unknown parse error"}`);
12630
+ }
12631
+ const v3Result = PortableRegistrySchemaV3.safeParse(data);
12632
+ if (v3Result.success) {
12633
+ return normalizePortableRegistryChecksums(v3Result.data);
12634
+ }
12635
+ const repairableV3Result = RepairablePortableRegistrySchemaV3.safeParse(data);
12636
+ if (!repairableV3Result.success) {
12637
+ return readPortableRegistryInternal({ persistStaleV3Repair: false });
12638
+ }
12639
+ const repairedRegistry = preparedRepair && preparedRepair.sourceContent === content ? preparedRepair.repairedRegistry : await repairStaleRegistryV3(repairableV3Result.data);
12640
+ if (await isMigrationLocked()) {
12641
+ logger.verbose("Migration in progress by another process, using repaired v3 view");
12642
+ return repairedRegistry;
12643
+ }
12644
+ logger.verbose("Repairing stale portable registry v3.0 fields");
12645
+ await writePortableRegistry(repairedRegistry);
12646
+ return repairedRegistry;
12647
+ });
12648
+ }
12555
12649
  async function isMigrationLocked() {
12556
12650
  try {
12557
12651
  const lockContent = await readFile2(MIGRATION_LOCK_PATH, "utf-8");
@@ -12589,6 +12683,9 @@ async function removeMigrationLock() {
12589
12683
  } catch {}
12590
12684
  }
12591
12685
  async function readPortableRegistry() {
12686
+ return readPortableRegistryInternal({ persistStaleV3Repair: true });
12687
+ }
12688
+ async function readPortableRegistryInternal(options2) {
12592
12689
  try {
12593
12690
  const content = await readFile2(REGISTRY_PATH, "utf-8");
12594
12691
  let data;
@@ -12601,6 +12698,21 @@ async function readPortableRegistry() {
12601
12698
  if (v3Result.success) {
12602
12699
  return normalizePortableRegistryChecksums(v3Result.data);
12603
12700
  }
12701
+ const repairableV3Result = RepairablePortableRegistrySchemaV3.safeParse(data);
12702
+ if (repairableV3Result.success) {
12703
+ const repairedRegistry = await repairStaleRegistryV3(repairableV3Result.data);
12704
+ if (!options2.persistStaleV3Repair) {
12705
+ return repairedRegistry;
12706
+ }
12707
+ if (await isMigrationLocked()) {
12708
+ logger.verbose("Migration in progress by another process, using repaired v3 view");
12709
+ return repairedRegistry;
12710
+ }
12711
+ return persistCurrentStaleRegistryV3Repair({
12712
+ sourceContent: content,
12713
+ repairedRegistry
12714
+ });
12715
+ }
12604
12716
  const v2Result = PortableRegistrySchema.safeParse(data);
12605
12717
  if (!v2Result.success) {
12606
12718
  throw new Error("portable-registry.json has unsupported schema/version");
@@ -12640,6 +12752,9 @@ async function readPortableRegistry() {
12640
12752
  }
12641
12753
  return { version: "3.0", installations: [] };
12642
12754
  }
12755
+ async function readPortableRegistryWithinRegistryLock() {
12756
+ return readPortableRegistryInternal({ persistStaleV3Repair: false });
12757
+ }
12643
12758
  async function writePortableRegistry(registry) {
12644
12759
  const dir = dirname(REGISTRY_PATH);
12645
12760
  if (!existsSync2(dir)) {
@@ -12682,7 +12797,7 @@ async function withRegistryLock(operation) {
12682
12797
  }
12683
12798
  async function addPortableInstallation(item, type, provider, global2, path, sourcePath, options2) {
12684
12799
  await withRegistryLock(async () => {
12685
- const registry = await readPortableRegistry();
12800
+ const registry = await readPortableRegistryWithinRegistryLock();
12686
12801
  registry.installations = registry.installations.filter((i) => !(i.item === item && i.type === type && i.provider === provider && i.global === global2));
12687
12802
  registry.installations.push({
12688
12803
  item,
@@ -12703,7 +12818,7 @@ async function addPortableInstallation(item, type, provider, global2, path, sour
12703
12818
  }
12704
12819
  async function removePortableInstallation(item, type, provider, global2) {
12705
12820
  return withRegistryLock(async () => {
12706
- const registry = await readPortableRegistry();
12821
+ const registry = await readPortableRegistryWithinRegistryLock();
12707
12822
  const index = registry.installations.findIndex((i) => i.item === item && i.type === type && i.provider === provider && i.global === global2);
12708
12823
  if (index === -1)
12709
12824
  return null;
@@ -12727,14 +12842,14 @@ function findPortableInstallations(registry, item, type, provider, global2) {
12727
12842
  }
12728
12843
  async function updateAppliedManifestVersion(version) {
12729
12844
  await withRegistryLock(async () => {
12730
- const registry = await readPortableRegistry();
12845
+ const registry = await readPortableRegistryWithinRegistryLock();
12731
12846
  registry.appliedManifestVersion = version;
12732
12847
  await writePortableRegistry(registry);
12733
12848
  });
12734
12849
  }
12735
12850
  async function removeInstallationsByFilter(predicate) {
12736
12851
  return withRegistryLock(async () => {
12737
- const registry = await readPortableRegistry();
12852
+ const registry = await readPortableRegistryWithinRegistryLock();
12738
12853
  const removed = [];
12739
12854
  registry.installations = registry.installations.filter((entry) => {
12740
12855
  if (predicate(entry)) {
@@ -12751,7 +12866,7 @@ async function removeInstallationsByFilter(predicate) {
12751
12866
  }
12752
12867
  async function syncPortableRegistry() {
12753
12868
  return withRegistryLock(async () => {
12754
- const registry = await readPortableRegistry();
12869
+ const registry = await readPortableRegistryWithinRegistryLock();
12755
12870
  const removed = [];
12756
12871
  registry.installations = registry.installations.filter((i) => {
12757
12872
  if (!existsSync2(i.path)) {
@@ -12766,7 +12881,7 @@ async function syncPortableRegistry() {
12766
12881
  return { removed };
12767
12882
  });
12768
12883
  }
12769
- var import_proper_lockfile, home2, REGISTRY_PATH, REGISTRY_LOCK_PATH, LEGACY_REGISTRY_PATH, MIGRATION_LOCK_PATH, PortableInstallationSchema, PortableRegistrySchema, PortableInstallationSchemaV3, PortableRegistrySchemaV3, LegacyInstallationSchema, LegacyRegistrySchema;
12884
+ var import_proper_lockfile, home2, REGISTRY_PATH, REGISTRY_LOCK_PATH, LEGACY_REGISTRY_PATH, MIGRATION_LOCK_PATH, PortableInstallationSchema, PortableRegistrySchema, PortableInstallationSchemaV3, PortableRegistrySchemaV3, RepairablePortableRegistrySchemaV3, LegacyInstallationSchema, LegacyRegistrySchema;
12770
12885
  var init_portable_registry = __esm(() => {
12771
12886
  init_zod();
12772
12887
  init_logger();
@@ -12811,6 +12926,12 @@ var init_portable_registry = __esm(() => {
12811
12926
  lastReconciled: exports_external.string().optional(),
12812
12927
  appliedManifestVersion: exports_external.string().optional()
12813
12928
  });
12929
+ RepairablePortableRegistrySchemaV3 = exports_external.object({
12930
+ version: exports_external.literal("3.0"),
12931
+ installations: exports_external.array(PortableInstallationSchema),
12932
+ lastReconciled: exports_external.string().optional(),
12933
+ appliedManifestVersion: exports_external.string().optional()
12934
+ }).passthrough();
12814
12935
  LegacyInstallationSchema = exports_external.object({
12815
12936
  skill: exports_external.string(),
12816
12937
  agent: exports_external.string(),
@@ -62359,7 +62480,7 @@ var package_default;
62359
62480
  var init_package = __esm(() => {
62360
62481
  package_default = {
62361
62482
  name: "claudekit-cli",
62362
- version: "4.0.0-dev.2",
62483
+ version: "4.0.0-dev.4",
62363
62484
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
62364
62485
  type: "module",
62365
62486
  repository: {
@@ -78178,6 +78299,10 @@ var init_uninstall_command_help = __esm(() => {
78178
78299
  description: "Remove ClaudeKit installations (ownership-aware)",
78179
78300
  usage: "ck uninstall [options]",
78180
78301
  examples: [
78302
+ {
78303
+ command: "ck uninstall --global --kit marketing",
78304
+ description: "Remove only the global Marketing kit and preserve Engineer"
78305
+ },
78181
78306
  {
78182
78307
  command: "ck uninstall --local --yes",
78183
78308
  description: "Remove local installation without confirmation"
@@ -105192,6 +105317,21 @@ function isSyncContext(ctx) {
105192
105317
  }
105193
105318
 
105194
105319
  // src/commands/init/phases/selection-handler.ts
105320
+ function buildKitScopedUninstallCommands(kits, resolvedDir, isGlobal) {
105321
+ const scopeFlag = isGlobal || PathResolver.isLocalSameAsGlobal(resolvedDir) ? "--global" : "--local";
105322
+ return kits.map((kit) => `ck uninstall ${scopeFlag} --kit ${kit}`);
105323
+ }
105324
+ function buildRerunInitCommand(kitType, resolvedDir, isGlobal) {
105325
+ const args = ["ck init"];
105326
+ const targetsGlobal = isGlobal || PathResolver.isLocalSameAsGlobal(resolvedDir);
105327
+ if (targetsGlobal) {
105328
+ args.push("--global");
105329
+ } else {
105330
+ args.push(`--dir ${JSON.stringify(resolvedDir)}`);
105331
+ }
105332
+ args.push(`--kit ${kitType}`);
105333
+ return args.join(" ");
105334
+ }
105195
105335
  async function handleSelection(ctx) {
105196
105336
  if (ctx.cancelled)
105197
105337
  return ctx;
@@ -105407,6 +105547,11 @@ async function handleSelection(ctx) {
105407
105547
  try {
105408
105548
  const confirmAdd = await ctx.prompts.confirm(`${existingKitsDisplay} already installed. Add ${kit.name} alongside?`);
105409
105549
  if (!confirmAdd) {
105550
+ logger.info("To remove one installed kit first, run:");
105551
+ for (const command of buildKitScopedUninstallCommands(otherKits, resolvedDir, ctx.options.global)) {
105552
+ logger.info(` ${command}`);
105553
+ }
105554
+ logger.info(`Then rerun: ${buildRerunInitCommand(kitType, resolvedDir, ctx.options.global)}`);
105410
105555
  logger.warning("Multi-kit installation cancelled by user");
105411
105556
  return { ...ctx, cancelled: true };
105412
105557
  }
@@ -111280,6 +111425,9 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
111280
111425
  remainingKits: []
111281
111426
  };
111282
111427
  const metadata = await ManifestWriter.readManifest(installation.path);
111428
+ if (kit && (!metadata || !getInstalledKits(metadata).includes(kit))) {
111429
+ return result;
111430
+ }
111283
111431
  const uninstallManifest = await ManifestWriter.getUninstallManifest(installation.path, kit);
111284
111432
  result.remainingKits = uninstallManifest.remainingKits;
111285
111433
  if (uninstallManifest.isMultiKit && kit && metadata?.kits?.[kit]) {
@@ -111590,6 +111738,56 @@ async function promptScope(installations) {
111590
111738
  }
111591
111739
  return selected;
111592
111740
  }
111741
+ async function getInstallationKits(installation) {
111742
+ const metadata = await ManifestWriter.readManifest(installation.path);
111743
+ return metadata ? getInstalledKits(metadata) : [];
111744
+ }
111745
+ async function getInstalledKitSet(installations) {
111746
+ const installedKitSet = new Set;
111747
+ for (const installation of installations) {
111748
+ for (const kit of await getInstallationKits(installation)) {
111749
+ installedKitSet.add(kit);
111750
+ }
111751
+ }
111752
+ return installedKitSet;
111753
+ }
111754
+ async function filterInstallationsByKit(installations, kit) {
111755
+ const filtered = [];
111756
+ for (const installation of installations) {
111757
+ const installedKits = await getInstallationKits(installation);
111758
+ if (installedKits.includes(kit)) {
111759
+ filtered.push(installation);
111760
+ }
111761
+ }
111762
+ return filtered;
111763
+ }
111764
+ async function promptKitSelection(installedKitSet) {
111765
+ if (installedKitSet.size < 2) {
111766
+ return "all";
111767
+ }
111768
+ const kitOrder = ["marketing", "engineer"];
111769
+ const kitOptions = kitOrder.filter((kit) => installedKitSet.has(kit)).map((kit) => ({
111770
+ value: kit,
111771
+ label: `${kit[0].toUpperCase()}${kit.slice(1)} kit only`,
111772
+ hint: "Preserve other installed kit(s) and customizations"
111773
+ }));
111774
+ const options2 = [
111775
+ ...kitOptions,
111776
+ {
111777
+ value: "all",
111778
+ label: "All ClaudeKit kits",
111779
+ hint: "Remove the full selected installation scope"
111780
+ }
111781
+ ];
111782
+ const selected = await ie({
111783
+ message: "Multiple kits are installed. What do you want to uninstall?",
111784
+ options: options2
111785
+ });
111786
+ if (lD(selected)) {
111787
+ return null;
111788
+ }
111789
+ return selected;
111790
+ }
111593
111791
  async function confirmUninstall(scope, kitLabel = "") {
111594
111792
  const scopeText = scope === "all" ? "all ClaudeKit installations" : scope === "local" ? "local ClaudeKit installation" : "global ClaudeKit installation";
111595
111793
  const confirmed = await se({
@@ -111648,7 +111846,7 @@ async function uninstallCommand(options2) {
111648
111846
  }
111649
111847
  scope = promptedScope;
111650
111848
  }
111651
- const installations = allInstallations.filter((i) => {
111849
+ let installations = allInstallations.filter((i) => {
111652
111850
  if (scope === "all")
111653
111851
  return true;
111654
111852
  return i.type === scope;
@@ -111658,16 +111856,41 @@ async function uninstallCommand(options2) {
111658
111856
  logger.info(`No ${scopeLabel} ClaudeKit installation found.`);
111659
111857
  return;
111660
111858
  }
111859
+ let kitToRemove = validOptions.kit;
111860
+ if (!kitToRemove) {
111861
+ const installedKitSet = await getInstalledKitSet(installations);
111862
+ if (validOptions.yes) {
111863
+ if (installedKitSet.size > 1) {
111864
+ logger.info("Removing all installed kits (--yes flag bypasses kit prompt; use --kit to scope).");
111865
+ }
111866
+ } else {
111867
+ const selectedKit = await promptKitSelection(installedKitSet);
111868
+ if (!selectedKit) {
111869
+ logger.info("Uninstall cancelled.");
111870
+ return;
111871
+ }
111872
+ if (selectedKit !== "all") {
111873
+ kitToRemove = selectedKit;
111874
+ }
111875
+ }
111876
+ }
111877
+ if (kitToRemove) {
111878
+ installations = await filterInstallationsByKit(installations, kitToRemove);
111879
+ if (installations.length === 0) {
111880
+ logger.info(`Kit "${kitToRemove}" is not installed in selected scope.`);
111881
+ return;
111882
+ }
111883
+ }
111661
111884
  displayInstallations(installations, scope);
111662
- if (validOptions.kit) {
111663
- log.info(import_picocolors39.default.cyan(`Kit-scoped uninstall: ${validOptions.kit} kit only`));
111885
+ if (kitToRemove) {
111886
+ log.info(import_picocolors39.default.cyan(`Kit-scoped uninstall: ${kitToRemove} kit only`));
111664
111887
  }
111665
111888
  if (validOptions.dryRun) {
111666
111889
  log.info(import_picocolors39.default.yellow("DRY RUN MODE - No files will be deleted"));
111667
111890
  await removeInstallations(installations, {
111668
111891
  dryRun: true,
111669
111892
  forceOverwrite: validOptions.forceOverwrite,
111670
- kit: validOptions.kit
111893
+ kit: kitToRemove
111671
111894
  });
111672
111895
  prompts.outro("Dry-run complete. No changes were made.");
111673
111896
  return;
@@ -111677,7 +111900,7 @@ async function uninstallCommand(options2) {
111677
111900
  ${import_picocolors39.default.yellow("User modifications will be permanently deleted!")}`);
111678
111901
  }
111679
111902
  if (!validOptions.yes) {
111680
- const kitLabel = validOptions.kit ? ` (${validOptions.kit} kit only)` : "";
111903
+ const kitLabel = kitToRemove ? ` (${kitToRemove} kit only)` : "";
111681
111904
  const confirmed = await confirmUninstall(scope, kitLabel);
111682
111905
  if (!confirmed) {
111683
111906
  logger.info("Uninstall cancelled.");
@@ -111687,10 +111910,10 @@ ${import_picocolors39.default.yellow("User modifications will be permanently del
111687
111910
  const results = await removeInstallations(installations, {
111688
111911
  dryRun: false,
111689
111912
  forceOverwrite: validOptions.forceOverwrite,
111690
- kit: validOptions.kit
111913
+ kit: kitToRemove
111691
111914
  });
111692
111915
  const hasProtectedFiles = results.some((result) => result.protectedTrackedPaths.length > 0);
111693
- const kitMsg = validOptions.kit ? ` (${validOptions.kit} kit)` : "";
111916
+ const kitMsg = kitToRemove ? ` (${kitToRemove} kit)` : "";
111694
111917
  if (hasProtectedFiles) {
111695
111918
  prompts.outro(`ClaudeKit${kitMsg} uninstall completed with preserved customizations. Use --force-overwrite for full removal.`);
111696
111919
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudekit-cli",
3
- "version": "4.0.0-dev.2",
3
+ "version": "4.0.0-dev.4",
4
4
  "description": "CLI tool for bootstrapping and updating ClaudeKit projects",
5
5
  "type": "module",
6
6
  "repository": {