claudekit-cli 3.35.1-dev.1 → 3.36.0-dev.2

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 +1773 -734
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -38479,7 +38479,10 @@ var init_metadata = __esm(() => {
38479
38479
  files: exports_external.array(TrackedFileSchema).optional(),
38480
38480
  lastUpdateCheck: exports_external.string().optional(),
38481
38481
  dismissedVersion: exports_external.string().optional(),
38482
- installedSettings: InstalledSettingsSchema.optional()
38482
+ installedSettings: InstalledSettingsSchema.optional(),
38483
+ pluginInstalled: exports_external.boolean().optional(),
38484
+ pluginInstalledAt: exports_external.string().optional(),
38485
+ pluginVersion: exports_external.string().optional()
38483
38486
  });
38484
38487
  MultiKitMetadataSchema = exports_external.object({
38485
38488
  kits: exports_external.record(KitType, KitMetadataSchema).optional(),
@@ -53920,6 +53923,17 @@ var init_package_manager_detector = __esm(() => {
53920
53923
  });
53921
53924
 
53922
53925
  // src/domains/migration/metadata-migration.ts
53926
+ var exports_metadata_migration = {};
53927
+ __export(exports_metadata_migration, {
53928
+ updateKitPluginState: () => updateKitPluginState,
53929
+ needsMigration: () => needsMigration,
53930
+ migrateToMultiKit: () => migrateToMultiKit,
53931
+ getTrackedFilesForKit: () => getTrackedFilesForKit,
53932
+ getKitMetadata: () => getKitMetadata,
53933
+ getInstalledKits: () => getInstalledKits,
53934
+ getAllTrackedFiles: () => getAllTrackedFiles,
53935
+ detectMetadataFormat: () => detectMetadataFormat
53936
+ });
53923
53937
  import { join as join35 } from "node:path";
53924
53938
  async function detectMetadataFormat(claudeDir2) {
53925
53939
  const metadataPath = join35(claudeDir2, "metadata.json");
@@ -53956,6 +53970,9 @@ async function detectMetadataFormat(claudeDir2) {
53956
53970
  return { format: "none", metadata: null, detectedKit: null };
53957
53971
  }
53958
53972
  }
53973
+ function needsMigration(detection) {
53974
+ return detection.format === "legacy";
53975
+ }
53959
53976
  async function migrateToMultiKit(claudeDir2) {
53960
53977
  const detection = await detectMetadataFormat(claudeDir2);
53961
53978
  if (detection.format === "multi-kit") {
@@ -54079,6 +54096,41 @@ function getInstalledKits(metadata) {
54079
54096
  }
54080
54097
  return [];
54081
54098
  }
54099
+ async function updateKitPluginState(claudeDir2, kit, state) {
54100
+ const metadataPath = join35(claudeDir2, "metadata.json");
54101
+ if (!await import_fs_extra3.pathExists(metadataPath)) {
54102
+ logger.debug("No metadata.json found — skipping plugin state update");
54103
+ return;
54104
+ }
54105
+ try {
54106
+ let metadata = {};
54107
+ try {
54108
+ const raw = await import_fs_extra3.readFile(metadataPath, "utf-8");
54109
+ const parsed = JSON.parse(raw);
54110
+ if (parsed && typeof parsed === "object") {
54111
+ metadata = parsed;
54112
+ } else {
54113
+ logger.warning("metadata.json is not an object; rebuilding minimal plugin metadata structure");
54114
+ }
54115
+ } catch (error) {
54116
+ logger.warning(`metadata.json unreadable during plugin state update; rebuilding: ${error}`);
54117
+ }
54118
+ if (!metadata.kits || typeof metadata.kits !== "object") {
54119
+ metadata.kits = {};
54120
+ }
54121
+ const existingKitState = metadata.kits[kit];
54122
+ metadata.kits[kit] = {
54123
+ ...existingKitState || {},
54124
+ version: existingKitState?.version ?? "",
54125
+ installedAt: existingKitState?.installedAt ?? state.pluginInstalledAt,
54126
+ ...state
54127
+ };
54128
+ await import_fs_extra3.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
54129
+ logger.debug(`Plugin state saved: ${state.pluginInstalled ? "installed" : "failed"}`);
54130
+ } catch (error) {
54131
+ logger.debug(`Failed to update plugin state: ${error}`);
54132
+ }
54133
+ }
54082
54134
  var import_fs_extra3;
54083
54135
  var init_metadata_migration = __esm(() => {
54084
54136
  init_logger();
@@ -54314,7 +54366,7 @@ var package_default;
54314
54366
  var init_package = __esm(() => {
54315
54367
  package_default = {
54316
54368
  name: "claudekit-cli",
54317
- version: "3.35.1-dev.1",
54369
+ version: "3.36.0-dev.2",
54318
54370
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
54319
54371
  type: "module",
54320
54372
  repository: {
@@ -61503,7 +61555,7 @@ var require_picomatch2 = __commonJS((exports, module) => {
61503
61555
  import { exec as exec7, execFile as execFile7, spawn as spawn3 } from "node:child_process";
61504
61556
  import { promisify as promisify13 } from "node:util";
61505
61557
  function executeInteractiveScript(command, args, options2) {
61506
- return new Promise((resolve13, reject) => {
61558
+ return new Promise((resolve14, reject) => {
61507
61559
  const child = spawn3(command, args, {
61508
61560
  stdio: ["ignore", "inherit", "inherit"],
61509
61561
  cwd: options2?.cwd,
@@ -61524,7 +61576,7 @@ function executeInteractiveScript(command, args, options2) {
61524
61576
  } else if (code !== 0) {
61525
61577
  reject(new Error(`Command exited with code ${code}`));
61526
61578
  } else {
61527
- resolve13();
61579
+ resolve14();
61528
61580
  }
61529
61581
  });
61530
61582
  child.on("error", (error) => {
@@ -61545,7 +61597,7 @@ var init_process_executor = __esm(() => {
61545
61597
  });
61546
61598
 
61547
61599
  // src/services/package-installer/validators.ts
61548
- import { resolve as resolve13 } from "node:path";
61600
+ import { resolve as resolve14 } from "node:path";
61549
61601
  function validatePackageName(packageName) {
61550
61602
  if (!packageName || typeof packageName !== "string") {
61551
61603
  throw new Error("Package name must be a non-empty string");
@@ -61558,8 +61610,8 @@ function validatePackageName(packageName) {
61558
61610
  }
61559
61611
  }
61560
61612
  function validateScriptPath(skillsDir2, scriptPath) {
61561
- const skillsDirResolved = resolve13(skillsDir2);
61562
- const scriptPathResolved = resolve13(scriptPath);
61613
+ const skillsDirResolved = resolve14(skillsDir2);
61614
+ const scriptPathResolved = resolve14(scriptPath);
61563
61615
  const skillsDirNormalized = isWindows() ? skillsDirResolved.toLowerCase() : skillsDirResolved;
61564
61616
  const scriptPathNormalized = isWindows() ? scriptPathResolved.toLowerCase() : scriptPathResolved;
61565
61617
  if (!scriptPathNormalized.startsWith(skillsDirNormalized)) {
@@ -61746,7 +61798,7 @@ var init_gemini_installer = __esm(() => {
61746
61798
  });
61747
61799
 
61748
61800
  // src/services/package-installer/opencode-installer.ts
61749
- import { join as join59 } from "node:path";
61801
+ import { join as join58 } from "node:path";
61750
61802
  async function isOpenCodeInstalled() {
61751
61803
  try {
61752
61804
  await execAsync7("opencode --version", { timeout: 5000 });
@@ -61769,7 +61821,7 @@ async function installOpenCode() {
61769
61821
  logger.info(`Installing ${displayName}...`);
61770
61822
  const { unlink: unlink11 } = await import("node:fs/promises");
61771
61823
  const { tmpdir: tmpdir4 } = await import("node:os");
61772
- const tempScriptPath = join59(tmpdir4(), "opencode-install.sh");
61824
+ const tempScriptPath = join58(tmpdir4(), "opencode-install.sh");
61773
61825
  try {
61774
61826
  logger.info("Downloading OpenCode installation script...");
61775
61827
  await execFileAsync5("curl", ["-fsSL", "https://opencode.ai/install", "-o", tempScriptPath], {
@@ -61821,7 +61873,7 @@ var PARTIAL_INSTALL_VERSION = "partial", EXIT_CODE_CRITICAL_FAILURE = 1, EXIT_CO
61821
61873
 
61822
61874
  // src/services/package-installer/install-error-handler.ts
61823
61875
  import { existsSync as existsSync44, readFileSync as readFileSync9, unlinkSync as unlinkSync2 } from "node:fs";
61824
- import { join as join60 } from "node:path";
61876
+ import { join as join59 } from "node:path";
61825
61877
  function parseNameReason(str2) {
61826
61878
  const colonIndex = str2.indexOf(":");
61827
61879
  if (colonIndex === -1) {
@@ -61830,7 +61882,7 @@ function parseNameReason(str2) {
61830
61882
  return [str2.slice(0, colonIndex).trim(), str2.slice(colonIndex + 1).trim()];
61831
61883
  }
61832
61884
  function displayInstallErrors(skillsDir2) {
61833
- const summaryPath = join60(skillsDir2, ".install-error-summary.json");
61885
+ const summaryPath = join59(skillsDir2, ".install-error-summary.json");
61834
61886
  if (!existsSync44(summaryPath)) {
61835
61887
  logger.error("Skills installation failed. Run with --verbose for details.");
61836
61888
  return;
@@ -61921,7 +61973,7 @@ async function checkNeedsSudoPackages() {
61921
61973
  }
61922
61974
  }
61923
61975
  function hasInstallState(skillsDir2) {
61924
- const stateFilePath = join60(skillsDir2, ".install-state.json");
61976
+ const stateFilePath = join59(skillsDir2, ".install-state.json");
61925
61977
  return existsSync44(stateFilePath);
61926
61978
  }
61927
61979
  var WHICH_COMMAND_TIMEOUT_MS = 5000;
@@ -61930,7 +61982,7 @@ var init_install_error_handler = __esm(() => {
61930
61982
  });
61931
61983
 
61932
61984
  // src/services/package-installer/skills-installer.ts
61933
- import { join as join61 } from "node:path";
61985
+ import { join as join60 } from "node:path";
61934
61986
  async function installSkillsDependencies(skillsDir2, options2 = {}) {
61935
61987
  const { skipConfirm = false, withSudo = false } = options2;
61936
61988
  const displayName = "Skills Dependencies";
@@ -61956,7 +62008,7 @@ async function installSkillsDependencies(skillsDir2, options2 = {}) {
61956
62008
  const clack = await Promise.resolve().then(() => (init_dist2(), exports_dist));
61957
62009
  const platform7 = process.platform;
61958
62010
  const scriptName = platform7 === "win32" ? "install.ps1" : "install.sh";
61959
- const scriptPath = join61(skillsDir2, scriptName);
62011
+ const scriptPath = join60(skillsDir2, scriptName);
61960
62012
  try {
61961
62013
  validateScriptPath(skillsDir2, scriptPath);
61962
62014
  } catch (error) {
@@ -61972,7 +62024,7 @@ async function installSkillsDependencies(skillsDir2, options2 = {}) {
61972
62024
  logger.warning(`Skills installation script not found: ${scriptPath}`);
61973
62025
  logger.info("");
61974
62026
  logger.info("\uD83D\uDCD6 Manual Installation Instructions:");
61975
- logger.info(` See: ${join61(skillsDir2, "INSTALLATION.md")}`);
62027
+ logger.info(` See: ${join60(skillsDir2, "INSTALLATION.md")}`);
61976
62028
  logger.info("");
61977
62029
  logger.info("Quick start:");
61978
62030
  logger.info(" cd .claude/skills/ai-multimodal/scripts");
@@ -62019,7 +62071,7 @@ async function installSkillsDependencies(skillsDir2, options2 = {}) {
62019
62071
  logger.info(` ${platform7 === "win32" ? `powershell -File "${scriptPath}"` : `bash ${scriptPath}`}`);
62020
62072
  logger.info("");
62021
62073
  logger.info("Or see complete guide:");
62022
- logger.info(` ${join61(skillsDir2, "INSTALLATION.md")}`);
62074
+ logger.info(` ${join60(skillsDir2, "INSTALLATION.md")}`);
62023
62075
  return {
62024
62076
  success: false,
62025
62077
  package: displayName,
@@ -62140,7 +62192,7 @@ async function installSkillsDependencies(skillsDir2, options2 = {}) {
62140
62192
  logger.info("\uD83D\uDCD6 Manual Installation Instructions:");
62141
62193
  logger.info("");
62142
62194
  logger.info("See complete guide:");
62143
- logger.info(` cat ${join61(skillsDir2, "INSTALLATION.md")}`);
62195
+ logger.info(` cat ${join60(skillsDir2, "INSTALLATION.md")}`);
62144
62196
  logger.info("");
62145
62197
  logger.info("Quick start:");
62146
62198
  logger.info(" cd .claude/skills/ai-multimodal/scripts");
@@ -62186,7 +62238,7 @@ var init_skills_installer2 = __esm(() => {
62186
62238
  // src/services/package-installer/gemini-mcp/config-manager.ts
62187
62239
  import { existsSync as existsSync45 } from "node:fs";
62188
62240
  import { mkdir as mkdir17, readFile as readFile35, writeFile as writeFile20 } from "node:fs/promises";
62189
- import { dirname as dirname15, join as join62 } from "node:path";
62241
+ import { dirname as dirname16, join as join61 } from "node:path";
62190
62242
  async function readJsonFile(filePath) {
62191
62243
  try {
62192
62244
  const content = await readFile35(filePath, "utf-8");
@@ -62198,7 +62250,7 @@ async function readJsonFile(filePath) {
62198
62250
  }
62199
62251
  }
62200
62252
  async function addGeminiToGitignore(projectDir) {
62201
- const gitignorePath = join62(projectDir, ".gitignore");
62253
+ const gitignorePath = join61(projectDir, ".gitignore");
62202
62254
  const geminiPattern = ".gemini/";
62203
62255
  try {
62204
62256
  let content = "";
@@ -62226,7 +62278,7 @@ ${geminiPattern}
62226
62278
  }
62227
62279
  }
62228
62280
  async function createNewSettingsWithMerge(geminiSettingsPath, mcpConfigPath) {
62229
- const linkDir = dirname15(geminiSettingsPath);
62281
+ const linkDir = dirname16(geminiSettingsPath);
62230
62282
  if (!existsSync45(linkDir)) {
62231
62283
  await mkdir17(linkDir, { recursive: true });
62232
62284
  logger.debug(`Created directory: ${linkDir}`);
@@ -62290,12 +62342,12 @@ var init_config_manager2 = __esm(() => {
62290
62342
  // src/services/package-installer/gemini-mcp/validation.ts
62291
62343
  import { existsSync as existsSync46, lstatSync, readlinkSync } from "node:fs";
62292
62344
  import { homedir as homedir27 } from "node:os";
62293
- import { join as join63 } from "node:path";
62345
+ import { join as join62 } from "node:path";
62294
62346
  function getGlobalMcpConfigPath() {
62295
- return join63(homedir27(), ".claude", ".mcp.json");
62347
+ return join62(homedir27(), ".claude", ".mcp.json");
62296
62348
  }
62297
62349
  function getLocalMcpConfigPath(projectDir) {
62298
- return join63(projectDir, ".mcp.json");
62350
+ return join62(projectDir, ".mcp.json");
62299
62351
  }
62300
62352
  function findMcpConfigPath(projectDir) {
62301
62353
  const localPath = getLocalMcpConfigPath(projectDir);
@@ -62313,9 +62365,9 @@ function findMcpConfigPath(projectDir) {
62313
62365
  }
62314
62366
  function getGeminiSettingsPath(projectDir, isGlobal) {
62315
62367
  if (isGlobal) {
62316
- return join63(homedir27(), ".gemini", "settings.json");
62368
+ return join62(homedir27(), ".gemini", "settings.json");
62317
62369
  }
62318
- return join63(projectDir, ".gemini", "settings.json");
62370
+ return join62(projectDir, ".gemini", "settings.json");
62319
62371
  }
62320
62372
  function checkExistingGeminiConfig(projectDir, isGlobal = false) {
62321
62373
  const geminiSettingsPath = getGeminiSettingsPath(projectDir, isGlobal);
@@ -62345,9 +62397,9 @@ var init_validation = __esm(() => {
62345
62397
  // src/services/package-installer/gemini-mcp/linker-core.ts
62346
62398
  import { existsSync as existsSync47 } from "node:fs";
62347
62399
  import { mkdir as mkdir18, symlink as symlink2 } from "node:fs/promises";
62348
- import { dirname as dirname16, join as join64 } from "node:path";
62400
+ import { dirname as dirname17, join as join63 } from "node:path";
62349
62401
  async function createSymlink(targetPath, linkPath, projectDir, isGlobal) {
62350
- const linkDir = dirname16(linkPath);
62402
+ const linkDir = dirname17(linkPath);
62351
62403
  if (!existsSync47(linkDir)) {
62352
62404
  await mkdir18(linkDir, { recursive: true });
62353
62405
  logger.debug(`Created directory: ${linkDir}`);
@@ -62356,7 +62408,7 @@ async function createSymlink(targetPath, linkPath, projectDir, isGlobal) {
62356
62408
  if (isGlobal) {
62357
62409
  symlinkTarget = getGlobalMcpConfigPath();
62358
62410
  } else {
62359
- const localMcpPath = join64(projectDir, ".mcp.json");
62411
+ const localMcpPath = join63(projectDir, ".mcp.json");
62360
62412
  const isLocalConfig = targetPath === localMcpPath;
62361
62413
  symlinkTarget = isLocalConfig ? "../.mcp.json" : targetPath;
62362
62414
  }
@@ -62389,10 +62441,10 @@ __export(exports_gemini_mcp_linker, {
62389
62441
  checkExistingGeminiConfig: () => checkExistingGeminiConfig,
62390
62442
  addGeminiToGitignore: () => addGeminiToGitignore
62391
62443
  });
62392
- import { resolve as resolve14 } from "node:path";
62444
+ import { resolve as resolve15 } from "node:path";
62393
62445
  async function linkGeminiMcpConfig(projectDir, options2 = {}) {
62394
62446
  const { skipGitignore = false, isGlobal = false } = options2;
62395
- const resolvedProjectDir = resolve14(projectDir);
62447
+ const resolvedProjectDir = resolve15(projectDir);
62396
62448
  const geminiSettingsPath = getGeminiSettingsPath(resolvedProjectDir, isGlobal);
62397
62449
  const mcpConfigPath = findMcpConfigPath(resolvedProjectDir);
62398
62450
  if (!mcpConfigPath) {
@@ -63040,7 +63092,7 @@ var require_get_stream = __commonJS((exports, module) => {
63040
63092
  };
63041
63093
  const { maxBuffer } = options2;
63042
63094
  let stream;
63043
- await new Promise((resolve16, reject) => {
63095
+ await new Promise((resolve17, reject) => {
63044
63096
  const rejectPromise = (error) => {
63045
63097
  if (error && stream.getBufferedLength() <= BufferConstants.MAX_LENGTH) {
63046
63098
  error.bufferedData = stream.getBufferedValue();
@@ -63052,7 +63104,7 @@ var require_get_stream = __commonJS((exports, module) => {
63052
63104
  rejectPromise(error);
63053
63105
  return;
63054
63106
  }
63055
- resolve16();
63107
+ resolve17();
63056
63108
  });
63057
63109
  stream.on("data", () => {
63058
63110
  if (stream.getBufferedLength() > maxBuffer) {
@@ -64413,7 +64465,7 @@ var require_extract_zip = __commonJS((exports, module) => {
64413
64465
  debug("opening", this.zipPath, "with opts", this.opts);
64414
64466
  this.zipfile = await openZip(this.zipPath, { lazyEntries: true });
64415
64467
  this.canceled = false;
64416
- return new Promise((resolve16, reject) => {
64468
+ return new Promise((resolve17, reject) => {
64417
64469
  this.zipfile.on("error", (err) => {
64418
64470
  this.canceled = true;
64419
64471
  reject(err);
@@ -64422,7 +64474,7 @@ var require_extract_zip = __commonJS((exports, module) => {
64422
64474
  this.zipfile.on("close", () => {
64423
64475
  if (!this.canceled) {
64424
64476
  debug("zip extraction complete");
64425
- resolve16();
64477
+ resolve17();
64426
64478
  }
64427
64479
  });
64428
64480
  this.zipfile.on("entry", async (entry) => {
@@ -64529,6 +64581,412 @@ var require_extract_zip = __commonJS((exports, module) => {
64529
64581
  };
64530
64582
  });
64531
64583
 
64584
+ // src/services/file-operations/manifest/manifest-reader.ts
64585
+ import { join as join74 } from "node:path";
64586
+ async function readManifest(claudeDir2) {
64587
+ const metadataPath = join74(claudeDir2, "metadata.json");
64588
+ if (!await import_fs_extra8.pathExists(metadataPath)) {
64589
+ return null;
64590
+ }
64591
+ try {
64592
+ const content = await import_fs_extra8.readFile(metadataPath, "utf-8");
64593
+ const parsed = JSON.parse(content);
64594
+ return MetadataSchema.parse(parsed);
64595
+ } catch (error) {
64596
+ logger.debug(`Failed to read manifest: ${error}`);
64597
+ return null;
64598
+ }
64599
+ }
64600
+ async function readKitManifest(claudeDir2, kit) {
64601
+ const metadata = await readManifest(claudeDir2);
64602
+ if (!metadata)
64603
+ return null;
64604
+ return getKitMetadata(metadata, kit);
64605
+ }
64606
+ async function findFileInInstalledKits(claudeDir2, relativePath, excludeKit) {
64607
+ const metadata = await readManifest(claudeDir2);
64608
+ if (!metadata?.kits) {
64609
+ return {
64610
+ exists: false,
64611
+ ownerKit: null,
64612
+ checksum: null,
64613
+ version: null,
64614
+ sourceTimestamp: null,
64615
+ installedAt: null
64616
+ };
64617
+ }
64618
+ for (const [kitName, kitMeta] of Object.entries(metadata.kits)) {
64619
+ const kit = kitName;
64620
+ if (kit === excludeKit)
64621
+ continue;
64622
+ if (!kitMeta.files)
64623
+ continue;
64624
+ const file = kitMeta.files.find((f3) => f3.path === relativePath);
64625
+ if (file) {
64626
+ return {
64627
+ exists: true,
64628
+ ownerKit: kit,
64629
+ checksum: file.checksum,
64630
+ version: kitMeta.version,
64631
+ sourceTimestamp: file.sourceTimestamp ?? null,
64632
+ installedAt: file.installedAt ?? null
64633
+ };
64634
+ }
64635
+ }
64636
+ return {
64637
+ exists: false,
64638
+ ownerKit: null,
64639
+ checksum: null,
64640
+ version: null,
64641
+ sourceTimestamp: null,
64642
+ installedAt: null
64643
+ };
64644
+ }
64645
+ async function getUninstallManifest(claudeDir2, kit) {
64646
+ const detection = await detectMetadataFormat(claudeDir2);
64647
+ if (detection.format === "multi-kit" && detection.metadata?.kits) {
64648
+ const installedKits = Object.keys(detection.metadata.kits);
64649
+ if (kit) {
64650
+ const kitMeta = detection.metadata.kits[kit];
64651
+ if (!kitMeta?.files) {
64652
+ return {
64653
+ filesToRemove: [],
64654
+ filesToPreserve: USER_CONFIG_PATTERNS,
64655
+ hasManifest: true,
64656
+ isMultiKit: true,
64657
+ remainingKits: installedKits.filter((k2) => k2 !== kit)
64658
+ };
64659
+ }
64660
+ const kitFiles = kitMeta.files.map((f3) => f3.path);
64661
+ const sharedFiles = new Set;
64662
+ for (const otherKit of installedKits) {
64663
+ if (otherKit !== kit) {
64664
+ const otherMeta = detection.metadata.kits[otherKit];
64665
+ if (otherMeta?.files) {
64666
+ for (const f3 of otherMeta.files) {
64667
+ sharedFiles.add(f3.path);
64668
+ }
64669
+ }
64670
+ }
64671
+ }
64672
+ const filesToRemove = kitFiles.filter((f3) => !sharedFiles.has(f3));
64673
+ const filesToPreserve = [
64674
+ ...USER_CONFIG_PATTERNS,
64675
+ ...kitFiles.filter((f3) => sharedFiles.has(f3))
64676
+ ];
64677
+ return {
64678
+ filesToRemove,
64679
+ filesToPreserve,
64680
+ hasManifest: true,
64681
+ isMultiKit: true,
64682
+ remainingKits: installedKits.filter((k2) => k2 !== kit)
64683
+ };
64684
+ }
64685
+ const allFiles = getAllTrackedFiles(detection.metadata);
64686
+ return {
64687
+ filesToRemove: allFiles.map((f3) => f3.path),
64688
+ filesToPreserve: USER_CONFIG_PATTERNS,
64689
+ hasManifest: true,
64690
+ isMultiKit: true,
64691
+ remainingKits: []
64692
+ };
64693
+ }
64694
+ if (detection.format === "legacy" && detection.metadata) {
64695
+ const legacyFiles2 = detection.metadata.files?.map((f3) => f3.path) || [];
64696
+ const installedFiles = detection.metadata.installedFiles || [];
64697
+ const hasFiles = legacyFiles2.length > 0 || installedFiles.length > 0;
64698
+ if (!hasFiles) {
64699
+ const legacyDirs2 = ["commands", "agents", "skills", "rules", "workflows", "hooks", "scripts"];
64700
+ const legacyFileList = ["metadata.json"];
64701
+ return {
64702
+ filesToRemove: [...legacyDirs2, ...legacyFileList],
64703
+ filesToPreserve: USER_CONFIG_PATTERNS,
64704
+ hasManifest: false,
64705
+ isMultiKit: false,
64706
+ remainingKits: []
64707
+ };
64708
+ }
64709
+ return {
64710
+ filesToRemove: legacyFiles2.length > 0 ? legacyFiles2 : installedFiles,
64711
+ filesToPreserve: detection.metadata.userConfigFiles || USER_CONFIG_PATTERNS,
64712
+ hasManifest: true,
64713
+ isMultiKit: false,
64714
+ remainingKits: []
64715
+ };
64716
+ }
64717
+ const legacyDirs = ["commands", "agents", "skills", "rules", "workflows", "hooks", "scripts"];
64718
+ const legacyFiles = ["metadata.json"];
64719
+ return {
64720
+ filesToRemove: [...legacyDirs, ...legacyFiles],
64721
+ filesToPreserve: USER_CONFIG_PATTERNS,
64722
+ hasManifest: false,
64723
+ isMultiKit: false,
64724
+ remainingKits: []
64725
+ };
64726
+ }
64727
+ var import_fs_extra8;
64728
+ var init_manifest_reader = __esm(() => {
64729
+ init_metadata_migration();
64730
+ init_logger();
64731
+ init_types3();
64732
+ import_fs_extra8 = __toESM(require_lib3(), 1);
64733
+ });
64734
+
64735
+ // src/domains/installation/deletion-handler.ts
64736
+ var exports_deletion_handler = {};
64737
+ __export(exports_deletion_handler, {
64738
+ handleDeletions: () => handleDeletions,
64739
+ categorizeDeletions: () => categorizeDeletions
64740
+ });
64741
+ import {
64742
+ existsSync as existsSync48,
64743
+ lstatSync as lstatSync3,
64744
+ readdirSync as readdirSync3,
64745
+ realpathSync as realpathSync3,
64746
+ rmSync as rmSync2,
64747
+ rmdirSync,
64748
+ unlinkSync as unlinkSync3
64749
+ } from "node:fs";
64750
+ import { dirname as dirname19, join as join75, relative as relative10, resolve as resolve18, sep as sep3 } from "node:path";
64751
+ function findFileInMetadata(metadata, path13) {
64752
+ if (!metadata)
64753
+ return null;
64754
+ const normalizedPath = normalizeRelativePath(path13);
64755
+ if (metadata.kits) {
64756
+ for (const kitMeta of Object.values(metadata.kits)) {
64757
+ if (kitMeta?.files) {
64758
+ const found = kitMeta.files.find((f3) => normalizeRelativePath(f3.path) === normalizedPath);
64759
+ if (found)
64760
+ return found;
64761
+ }
64762
+ }
64763
+ }
64764
+ if (metadata.files) {
64765
+ const found = metadata.files.find((f3) => normalizeRelativePath(f3.path) === normalizedPath);
64766
+ if (found)
64767
+ return found;
64768
+ }
64769
+ return null;
64770
+ }
64771
+ function shouldDeletePath(path13, metadata) {
64772
+ const tracked = findFileInMetadata(metadata, path13);
64773
+ if (!tracked)
64774
+ return true;
64775
+ return tracked.ownership !== "user";
64776
+ }
64777
+ function collectFilesRecursively(dir, baseDir) {
64778
+ const results = [];
64779
+ if (!existsSync48(dir))
64780
+ return results;
64781
+ try {
64782
+ const entries = readdirSync3(dir, { withFileTypes: true });
64783
+ for (const entry of entries) {
64784
+ const fullPath = join75(dir, entry.name);
64785
+ const relativePath = relative10(baseDir, fullPath);
64786
+ if (entry.isDirectory()) {
64787
+ results.push(...collectFilesRecursively(fullPath, baseDir));
64788
+ } else {
64789
+ results.push(normalizeRelativePath(relativePath));
64790
+ }
64791
+ }
64792
+ } catch {}
64793
+ return results;
64794
+ }
64795
+ function expandGlobPatterns(patterns, claudeDir2) {
64796
+ const expanded = [];
64797
+ const allFiles = collectFilesRecursively(claudeDir2, claudeDir2);
64798
+ for (const pattern of patterns) {
64799
+ const normalizedPattern = normalizeRelativePath(pattern);
64800
+ if (PathResolver.isGlobPattern(normalizedPattern)) {
64801
+ const matcher = import_picomatch2.default(normalizedPattern);
64802
+ const matches = allFiles.filter((file) => matcher(file));
64803
+ expanded.push(...matches);
64804
+ if (matches.length > 0) {
64805
+ logger.debug(`Pattern "${normalizedPattern}" matched ${matches.length} files`);
64806
+ }
64807
+ } else {
64808
+ expanded.push(normalizedPattern);
64809
+ }
64810
+ }
64811
+ return [...new Set(expanded)];
64812
+ }
64813
+ function cleanupEmptyDirectories(filePath, claudeDir2) {
64814
+ const normalizedClaudeDir = resolve18(claudeDir2);
64815
+ let currentDir = resolve18(dirname19(filePath));
64816
+ let iterations = 0;
64817
+ while (currentDir !== normalizedClaudeDir && currentDir.startsWith(normalizedClaudeDir) && iterations < MAX_CLEANUP_ITERATIONS) {
64818
+ iterations++;
64819
+ try {
64820
+ const entries = readdirSync3(currentDir);
64821
+ if (entries.length === 0) {
64822
+ rmdirSync(currentDir);
64823
+ logger.debug(`Removed empty directory: ${currentDir}`);
64824
+ currentDir = resolve18(dirname19(currentDir));
64825
+ } else {
64826
+ break;
64827
+ }
64828
+ } catch {
64829
+ break;
64830
+ }
64831
+ }
64832
+ }
64833
+ function normalizeRelativePath(path13) {
64834
+ return path13.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
64835
+ }
64836
+ function isWithinBase(candidatePath, basePath) {
64837
+ return candidatePath === basePath || candidatePath.startsWith(`${basePath}${sep3}`);
64838
+ }
64839
+ function validateExistingDeletionTarget(fullPath, claudeDir2) {
64840
+ const normalizedPath = resolve18(fullPath);
64841
+ const normalizedClaudeDir = resolve18(claudeDir2);
64842
+ if (!isWithinBase(normalizedPath, normalizedClaudeDir)) {
64843
+ throw new Error(`Path traversal detected: ${fullPath}`);
64844
+ }
64845
+ let realBase = normalizedClaudeDir;
64846
+ try {
64847
+ realBase = realpathSync3(normalizedClaudeDir);
64848
+ } catch {}
64849
+ try {
64850
+ const realParent = realpathSync3(dirname19(fullPath));
64851
+ if (!isWithinBase(realParent, realBase)) {
64852
+ throw new Error(`Path escapes base via symlink parent: ${fullPath}`);
64853
+ }
64854
+ } catch (error) {
64855
+ throw new Error(`Failed to validate deletion parent for ${fullPath}: ${String(error)}`);
64856
+ }
64857
+ try {
64858
+ const stat13 = lstatSync3(fullPath);
64859
+ if (!stat13.isSymbolicLink()) {
64860
+ const realTarget = realpathSync3(fullPath);
64861
+ if (!isWithinBase(realTarget, realBase)) {
64862
+ throw new Error(`Path escapes base via symlink target: ${fullPath}`);
64863
+ }
64864
+ }
64865
+ } catch (error) {
64866
+ throw new Error(`Failed to validate deletion target ${fullPath}: ${String(error)}`);
64867
+ }
64868
+ }
64869
+ function deletePath(fullPath, claudeDir2) {
64870
+ validateExistingDeletionTarget(fullPath, claudeDir2);
64871
+ try {
64872
+ const stat13 = lstatSync3(fullPath);
64873
+ if (stat13.isDirectory()) {
64874
+ rmSync2(fullPath, { recursive: true, force: true });
64875
+ } else {
64876
+ unlinkSync3(fullPath);
64877
+ cleanupEmptyDirectories(fullPath, claudeDir2);
64878
+ }
64879
+ } catch (error) {
64880
+ throw new Error(`Failed to delete ${fullPath}: ${error instanceof Error ? error.message : String(error)}`);
64881
+ }
64882
+ }
64883
+ async function updateMetadataAfterDeletion(claudeDir2, deletedPaths) {
64884
+ const metadataPath = join75(claudeDir2, "metadata.json");
64885
+ if (!await import_fs_extra9.pathExists(metadataPath)) {
64886
+ return;
64887
+ }
64888
+ let content;
64889
+ try {
64890
+ content = await import_fs_extra9.readFile(metadataPath, "utf-8");
64891
+ } catch {
64892
+ logger.debug("Failed to read metadata.json for cleanup");
64893
+ return;
64894
+ }
64895
+ let metadata;
64896
+ try {
64897
+ metadata = JSON.parse(content);
64898
+ } catch {
64899
+ logger.debug("Failed to parse metadata.json for cleanup");
64900
+ return;
64901
+ }
64902
+ const deletedSet = new Set(deletedPaths);
64903
+ const isDeletedOrInDeletedDir = (path13) => {
64904
+ if (deletedSet.has(path13))
64905
+ return true;
64906
+ for (const deleted of deletedPaths) {
64907
+ if (path13.startsWith(`${deleted}/`))
64908
+ return true;
64909
+ }
64910
+ return false;
64911
+ };
64912
+ if (metadata.kits) {
64913
+ for (const kitName of Object.keys(metadata.kits)) {
64914
+ const kit = metadata.kits[kitName];
64915
+ if (kit?.files) {
64916
+ kit.files = kit.files.filter((f3) => !isDeletedOrInDeletedDir(f3.path));
64917
+ }
64918
+ }
64919
+ }
64920
+ if (metadata.files) {
64921
+ metadata.files = metadata.files.filter((f3) => !isDeletedOrInDeletedDir(f3.path));
64922
+ }
64923
+ try {
64924
+ await import_fs_extra9.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
64925
+ logger.debug(`Updated metadata.json, removed ${deletedPaths.length} entries`);
64926
+ } catch {
64927
+ logger.debug("Failed to write updated metadata.json");
64928
+ }
64929
+ }
64930
+ function categorizeDeletions(deletions) {
64931
+ const immediate = [];
64932
+ const deferred = [];
64933
+ for (const path13 of deletions) {
64934
+ if (path13.startsWith("skills/") || path13.startsWith("skills\\")) {
64935
+ deferred.push(path13);
64936
+ } else {
64937
+ immediate.push(path13);
64938
+ }
64939
+ }
64940
+ return { immediate, deferred };
64941
+ }
64942
+ async function handleDeletions(sourceMetadata, claudeDir2) {
64943
+ const deletionPatterns = sourceMetadata.deletions || [];
64944
+ if (deletionPatterns.length === 0) {
64945
+ return { deletedPaths: [], preservedPaths: [], errors: [] };
64946
+ }
64947
+ const deletions = expandGlobPatterns(deletionPatterns, claudeDir2);
64948
+ const userMetadata = await readManifest(claudeDir2);
64949
+ const result = { deletedPaths: [], preservedPaths: [], errors: [] };
64950
+ for (const path13 of deletions) {
64951
+ const normalizedRelativePath = normalizeRelativePath(path13);
64952
+ const fullPath = join75(claudeDir2, normalizedRelativePath);
64953
+ const normalizedResolvedPath = resolve18(fullPath);
64954
+ const normalizedClaudeDir = resolve18(claudeDir2);
64955
+ if (!isWithinBase(normalizedResolvedPath, normalizedClaudeDir)) {
64956
+ logger.warning(`Skipping invalid path: ${normalizedRelativePath}`);
64957
+ result.errors.push(normalizedRelativePath);
64958
+ continue;
64959
+ }
64960
+ if (!shouldDeletePath(normalizedRelativePath, userMetadata)) {
64961
+ result.preservedPaths.push(normalizedRelativePath);
64962
+ logger.verbose(`Preserved user file: ${normalizedRelativePath}`);
64963
+ continue;
64964
+ }
64965
+ if (existsSync48(fullPath)) {
64966
+ try {
64967
+ deletePath(fullPath, claudeDir2);
64968
+ result.deletedPaths.push(normalizedRelativePath);
64969
+ logger.verbose(`Deleted: ${normalizedRelativePath}`);
64970
+ } catch (error) {
64971
+ result.errors.push(normalizedRelativePath);
64972
+ logger.debug(`Failed to delete ${normalizedRelativePath}: ${error}`);
64973
+ }
64974
+ }
64975
+ }
64976
+ if (result.deletedPaths.length > 0) {
64977
+ await updateMetadataAfterDeletion(claudeDir2, result.deletedPaths);
64978
+ }
64979
+ return result;
64980
+ }
64981
+ var import_fs_extra9, import_picomatch2, MAX_CLEANUP_ITERATIONS = 50;
64982
+ var init_deletion_handler = __esm(() => {
64983
+ init_manifest_reader();
64984
+ init_logger();
64985
+ init_path_resolver();
64986
+ import_fs_extra9 = __toESM(require_lib3(), 1);
64987
+ import_picomatch2 = __toESM(require_picomatch2(), 1);
64988
+ });
64989
+
64532
64990
  // src/domains/ui/ownership-display.ts
64533
64991
  var exports_ownership_display = {};
64534
64992
  __export(exports_ownership_display, {
@@ -64665,6 +65123,596 @@ var init_ownership_display = __esm(() => {
64665
65123
  import_picocolors22 = __toESM(require_picocolors(), 1);
64666
65124
  });
64667
65125
 
65126
+ // src/shared/claude-exec-options.ts
65127
+ function buildExecOptions(timeout2) {
65128
+ const env2 = { ...process.env };
65129
+ for (const key of Object.keys(env2)) {
65130
+ if (key.startsWith("CLAUDE") && key !== "CLAUDE_CONFIG_DIR") {
65131
+ delete env2[key];
65132
+ }
65133
+ }
65134
+ return {
65135
+ timeout: timeout2,
65136
+ env: env2,
65137
+ shell: process.platform === "win32"
65138
+ };
65139
+ }
65140
+
65141
+ // src/services/cc-version-checker.ts
65142
+ var exports_cc_version_checker = {};
65143
+ __export(exports_cc_version_checker, {
65144
+ requireCCPluginSupport: () => requireCCPluginSupport,
65145
+ parseVersion: () => parseVersion,
65146
+ isCCPluginSupportError: () => isCCPluginSupportError,
65147
+ getCCVersion: () => getCCVersion,
65148
+ compareVersions: () => compareVersions9,
65149
+ MIN_PLUGIN_VERSION: () => MIN_PLUGIN_VERSION,
65150
+ CCPluginSupportError: () => CCPluginSupportError
65151
+ });
65152
+ import { execFile as execFile9 } from "node:child_process";
65153
+ import { promisify as promisify14 } from "node:util";
65154
+ function parseVersion(version) {
65155
+ const match2 = version.match(/^(\d+)\.(\d+)\.(\d+)/);
65156
+ if (!match2)
65157
+ return null;
65158
+ return [Number(match2[1]), Number(match2[2]), Number(match2[3])];
65159
+ }
65160
+ function compareVersions9(a3, b3) {
65161
+ const parsedA = parseVersion(a3);
65162
+ const parsedB = parseVersion(b3);
65163
+ if (!parsedA || !parsedB)
65164
+ return -1;
65165
+ for (let i = 0;i < 3; i++) {
65166
+ if (parsedA[i] !== parsedB[i])
65167
+ return parsedA[i] - parsedB[i];
65168
+ }
65169
+ return 0;
65170
+ }
65171
+ async function getCCVersionResult() {
65172
+ try {
65173
+ const { stdout: stdout2, stderr } = await execFileAsync6("claude", ["--version"], buildExecOptions(5000));
65174
+ const output2 = stdout2.trim();
65175
+ const match2 = output2.match(/(\d+\.\d+\.\d+)/);
65176
+ if (!match2) {
65177
+ return {
65178
+ version: null,
65179
+ error: new CCPluginSupportError("cc_version_unparseable", "Failed to parse Claude Code version output", output2 || stderr?.trim() || "empty output")
65180
+ };
65181
+ }
65182
+ return { version: match2[1] };
65183
+ } catch (error) {
65184
+ const err = error;
65185
+ if (err.code === "ENOENT") {
65186
+ return {
65187
+ version: null,
65188
+ error: new CCPluginSupportError("cc_not_found", "Claude Code CLI not found on PATH")
65189
+ };
65190
+ }
65191
+ return {
65192
+ version: null,
65193
+ error: new CCPluginSupportError("cc_exec_failed", "Failed to run 'claude --version'", (err.stderr ?? err.message ?? "Unknown error").trim())
65194
+ };
65195
+ }
65196
+ }
65197
+ async function getCCVersion() {
65198
+ const result = await getCCVersionResult();
65199
+ return result.version;
65200
+ }
65201
+ async function requireCCPluginSupport() {
65202
+ const result = await getCCVersionResult();
65203
+ const version = result.version;
65204
+ if (!version) {
65205
+ throw result.error ?? new CCPluginSupportError("cc_exec_failed", "Failed to determine Claude Code version");
65206
+ }
65207
+ if (compareVersions9(version, MIN_PLUGIN_VERSION) < 0) {
65208
+ throw new CCPluginSupportError("cc_version_too_old", `Claude Code ${version} does not support plugins (requires >= ${MIN_PLUGIN_VERSION})`);
65209
+ }
65210
+ logger.debug(`CC version ${version} supports plugins (>= ${MIN_PLUGIN_VERSION})`);
65211
+ }
65212
+ function isCCPluginSupportError(error) {
65213
+ return error instanceof CCPluginSupportError;
65214
+ }
65215
+ var execFileAsync6, MIN_PLUGIN_VERSION = "1.0.33", CCPluginSupportError;
65216
+ var init_cc_version_checker = __esm(() => {
65217
+ init_logger();
65218
+ execFileAsync6 = promisify14(execFile9);
65219
+ CCPluginSupportError = class CCPluginSupportError extends Error {
65220
+ code;
65221
+ details;
65222
+ constructor(code2, message, details) {
65223
+ super(message);
65224
+ this.code = code2;
65225
+ this.details = details;
65226
+ this.name = "CCPluginSupportError";
65227
+ }
65228
+ };
65229
+ });
65230
+
65231
+ // src/services/plugin-installer.ts
65232
+ var exports_plugin_installer = {};
65233
+ __export(exports_plugin_installer, {
65234
+ handlePluginUninstall: () => handlePluginUninstall,
65235
+ handlePluginInstall: () => handlePluginInstall
65236
+ });
65237
+ import { execFile as execFile10 } from "node:child_process";
65238
+ import { rename as rename6 } from "node:fs/promises";
65239
+ import { join as join99 } from "node:path";
65240
+ import { promisify as promisify15 } from "node:util";
65241
+ function getMarketplacePath() {
65242
+ const dataDir = PathResolver.getClaudeKitDir();
65243
+ return join99(dataDir, MARKETPLACE_DIR_NAME);
65244
+ }
65245
+ function escapeRegex2(value) {
65246
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
65247
+ }
65248
+ function outputContainsToken(output2, token) {
65249
+ const tokenPattern = new RegExp(`(^|\\s)${escapeRegex2(token)}(?=\\s|$)`, "i");
65250
+ return output2.split(/\r?\n/).map((line) => line.trim()).some((line) => tokenPattern.test(line));
65251
+ }
65252
+ async function runClaudePlugin(args) {
65253
+ try {
65254
+ const { stdout: stdout2, stderr } = await execFileAsync7("claude", ["plugin", ...args], buildExecOptions(30000));
65255
+ return { success: true, stdout: stdout2.trim(), stderr: stderr.trim() };
65256
+ } catch (error) {
65257
+ const err = error;
65258
+ return {
65259
+ success: false,
65260
+ stdout: (err.stdout ?? "").trim(),
65261
+ stderr: (err.stderr ?? err.message ?? "Unknown error").trim()
65262
+ };
65263
+ }
65264
+ }
65265
+ async function isClaudeAvailable() {
65266
+ try {
65267
+ await execFileAsync7("claude", ["--version"], buildExecOptions(5000));
65268
+ return true;
65269
+ } catch {
65270
+ return false;
65271
+ }
65272
+ }
65273
+ async function copyPluginToMarketplace(extractDir) {
65274
+ const marketplacePath = getMarketplacePath();
65275
+ const sourceMarketplaceJson = join99(extractDir, ".claude-plugin", "marketplace.json");
65276
+ const sourcePluginDir = join99(extractDir, "plugins", PLUGIN_NAME);
65277
+ if (!await import_fs_extra30.pathExists(sourceMarketplaceJson) || !await import_fs_extra30.pathExists(sourcePluginDir)) {
65278
+ logger.debug("Kit release does not contain plugin structure — skipping plugin install");
65279
+ return false;
65280
+ }
65281
+ await import_fs_extra30.ensureDir(join99(marketplacePath, ".claude-plugin"));
65282
+ await import_fs_extra30.ensureDir(join99(marketplacePath, "plugins"));
65283
+ const suffix = `${process.pid}-${Date.now()}`;
65284
+ const destMarketplaceJson = join99(marketplacePath, ".claude-plugin", "marketplace.json");
65285
+ const destPluginDir = join99(marketplacePath, "plugins", PLUGIN_NAME);
65286
+ const tempMarketplaceJson = `${destMarketplaceJson}.tmp-${suffix}`;
65287
+ const tempPluginDir = `${destPluginDir}.tmp-${suffix}`;
65288
+ const backupMarketplaceJson = `${destMarketplaceJson}.bak-${suffix}`;
65289
+ const backupPluginDir = `${destPluginDir}.bak-${suffix}`;
65290
+ await import_fs_extra30.remove(tempMarketplaceJson).catch(() => {});
65291
+ await import_fs_extra30.remove(tempPluginDir).catch(() => {});
65292
+ await import_fs_extra30.remove(backupMarketplaceJson).catch(() => {});
65293
+ await import_fs_extra30.remove(backupPluginDir).catch(() => {});
65294
+ await import_fs_extra30.copy(sourceMarketplaceJson, tempMarketplaceJson, { overwrite: true });
65295
+ await import_fs_extra30.copy(sourcePluginDir, tempPluginDir, { overwrite: true });
65296
+ let backupMarketplaceCreated = false;
65297
+ let backupPluginCreated = false;
65298
+ let marketplaceReplaced = false;
65299
+ let pluginReplaced = false;
65300
+ try {
65301
+ if (await import_fs_extra30.pathExists(destMarketplaceJson)) {
65302
+ await rename6(destMarketplaceJson, backupMarketplaceJson);
65303
+ backupMarketplaceCreated = true;
65304
+ }
65305
+ await rename6(tempMarketplaceJson, destMarketplaceJson);
65306
+ marketplaceReplaced = true;
65307
+ if (await import_fs_extra30.pathExists(destPluginDir)) {
65308
+ await rename6(destPluginDir, backupPluginDir);
65309
+ backupPluginCreated = true;
65310
+ }
65311
+ await rename6(tempPluginDir, destPluginDir);
65312
+ pluginReplaced = true;
65313
+ } catch (error) {
65314
+ if (marketplaceReplaced) {
65315
+ await import_fs_extra30.remove(destMarketplaceJson).catch(() => {});
65316
+ }
65317
+ if (pluginReplaced) {
65318
+ await import_fs_extra30.remove(destPluginDir).catch(() => {});
65319
+ }
65320
+ if (backupMarketplaceCreated) {
65321
+ await rename6(backupMarketplaceJson, destMarketplaceJson).catch(() => {});
65322
+ }
65323
+ if (backupPluginCreated) {
65324
+ await rename6(backupPluginDir, destPluginDir).catch(() => {});
65325
+ }
65326
+ await import_fs_extra30.remove(tempMarketplaceJson).catch(() => {});
65327
+ await import_fs_extra30.remove(tempPluginDir).catch(() => {});
65328
+ throw error;
65329
+ }
65330
+ await import_fs_extra30.remove(backupMarketplaceJson).catch(() => {});
65331
+ await import_fs_extra30.remove(backupPluginDir).catch(() => {});
65332
+ await import_fs_extra30.remove(tempMarketplaceJson).catch(() => {});
65333
+ await import_fs_extra30.remove(tempPluginDir).catch(() => {});
65334
+ logger.debug("Plugin copied to marketplace");
65335
+ return true;
65336
+ }
65337
+ async function registerMarketplace() {
65338
+ const marketplacePath = getMarketplacePath();
65339
+ const listResult = await runClaudePlugin(["marketplace", "list"]);
65340
+ const alreadyRegistered = listResult.success && outputContainsToken(listResult.stdout, MARKETPLACE_NAME);
65341
+ if (alreadyRegistered) {
65342
+ const addResult = await runClaudePlugin(["marketplace", "add", marketplacePath]);
65343
+ if (addResult.success) {
65344
+ logger.debug("Marketplace re-registered successfully");
65345
+ return true;
65346
+ }
65347
+ logger.debug("Marketplace add failed while registered; removing stale entry and retrying");
65348
+ const removeResult = await runClaudePlugin(["marketplace", "remove", MARKETPLACE_NAME]);
65349
+ if (!removeResult.success) {
65350
+ logger.debug(`Marketplace remove failed: ${removeResult.stderr}`);
65351
+ return false;
65352
+ }
65353
+ const retryResult = await runClaudePlugin(["marketplace", "add", marketplacePath]);
65354
+ if (!retryResult.success) {
65355
+ logger.warning(`Marketplace remove succeeded but retry-add failed: ${retryResult.stderr}`);
65356
+ return false;
65357
+ }
65358
+ logger.debug("Marketplace re-registered after remove+retry");
65359
+ return true;
65360
+ }
65361
+ const result = await runClaudePlugin(["marketplace", "add", marketplacePath]);
65362
+ if (!result.success) {
65363
+ logger.debug(`Marketplace registration failed: ${result.stderr}`);
65364
+ return false;
65365
+ }
65366
+ logger.debug("Marketplace registered successfully");
65367
+ return true;
65368
+ }
65369
+ async function installOrUpdatePlugin() {
65370
+ const pluginRef = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
65371
+ const listResult = await runClaudePlugin(["list"]);
65372
+ const isInstalled = listResult.success && outputContainsToken(listResult.stdout, pluginRef);
65373
+ if (isInstalled) {
65374
+ const result2 = await runClaudePlugin(["update", pluginRef]);
65375
+ if (result2.success) {
65376
+ logger.debug("Plugin updated successfully");
65377
+ return true;
65378
+ }
65379
+ logger.debug(`Plugin update failed (${result2.stderr}); re-verifying install state`);
65380
+ const stillInstalled = await verifyPluginInstalled();
65381
+ if (stillInstalled) {
65382
+ logger.debug("Plugin update failed but plugin is still installed — treating as success");
65383
+ return true;
65384
+ }
65385
+ logger.debug("Plugin update failed and plugin is no longer listed");
65386
+ return false;
65387
+ }
65388
+ const result = await runClaudePlugin(["install", pluginRef]);
65389
+ if (!result.success) {
65390
+ logger.debug(`Plugin install failed: ${result.stderr}`);
65391
+ return false;
65392
+ }
65393
+ logger.debug("Plugin installed successfully");
65394
+ return true;
65395
+ }
65396
+ async function verifyPluginInstalled() {
65397
+ const pluginRef = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
65398
+ const result = await runClaudePlugin(["list"]);
65399
+ if (!result.success)
65400
+ return false;
65401
+ return outputContainsToken(result.stdout, pluginRef);
65402
+ }
65403
+ async function handlePluginInstall(extractDir) {
65404
+ if (!await isClaudeAvailable()) {
65405
+ return {
65406
+ installed: false,
65407
+ marketplaceRegistered: false,
65408
+ verified: false,
65409
+ error: "Claude Code CLI not found on PATH"
65410
+ };
65411
+ }
65412
+ logger.debug("Registering CK plugin with Claude Code...");
65413
+ let copied = false;
65414
+ try {
65415
+ copied = await copyPluginToMarketplace(extractDir);
65416
+ } catch (error) {
65417
+ return {
65418
+ installed: false,
65419
+ marketplaceRegistered: false,
65420
+ verified: false,
65421
+ error: `Plugin copy failed: ${error instanceof Error ? error.message : "Unknown error"}`
65422
+ };
65423
+ }
65424
+ if (!copied) {
65425
+ return {
65426
+ installed: false,
65427
+ marketplaceRegistered: false,
65428
+ verified: false,
65429
+ error: "No plugin found in kit release"
65430
+ };
65431
+ }
65432
+ const registered = await registerMarketplace();
65433
+ if (!registered) {
65434
+ return {
65435
+ installed: false,
65436
+ marketplaceRegistered: false,
65437
+ verified: false,
65438
+ error: "Marketplace registration failed"
65439
+ };
65440
+ }
65441
+ const installOk = await installOrUpdatePlugin();
65442
+ if (!installOk) {
65443
+ return {
65444
+ installed: false,
65445
+ marketplaceRegistered: true,
65446
+ verified: false,
65447
+ error: "Plugin install/update failed"
65448
+ };
65449
+ }
65450
+ const verified = await verifyPluginInstalled();
65451
+ if (verified) {
65452
+ logger.success("CK plugin installed — skills available as /ck:* commands");
65453
+ } else {
65454
+ logger.warning("Plugin installed but verification failed — skills may not be available");
65455
+ }
65456
+ return {
65457
+ installed: true,
65458
+ marketplaceRegistered: true,
65459
+ verified,
65460
+ error: verified ? undefined : "Post-install verification failed"
65461
+ };
65462
+ }
65463
+ async function handlePluginUninstall() {
65464
+ if (!await isClaudeAvailable()) {
65465
+ logger.debug("Claude Code CLI not found — skipping plugin cleanup");
65466
+ return;
65467
+ }
65468
+ const isInstalled = await verifyPluginInstalled();
65469
+ if (isInstalled) {
65470
+ const pluginRef = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
65471
+ const uninstallResult = await runClaudePlugin(["uninstall", pluginRef]);
65472
+ if (uninstallResult.success) {
65473
+ logger.debug("CK plugin uninstalled from Claude Code");
65474
+ } else {
65475
+ logger.debug(`Plugin uninstall failed: ${uninstallResult.stderr}`);
65476
+ }
65477
+ }
65478
+ const marketplaceRemoveResult = await runClaudePlugin([
65479
+ "marketplace",
65480
+ "remove",
65481
+ MARKETPLACE_NAME
65482
+ ]);
65483
+ if (!marketplaceRemoveResult.success) {
65484
+ logger.debug(`Marketplace remove failed: ${marketplaceRemoveResult.stderr}`);
65485
+ }
65486
+ const marketplacePath = getMarketplacePath();
65487
+ if (await import_fs_extra30.pathExists(marketplacePath)) {
65488
+ try {
65489
+ await import_fs_extra30.remove(marketplacePath);
65490
+ logger.debug("Marketplace directory cleaned up");
65491
+ } catch (error) {
65492
+ logger.warning(`Failed to clean marketplace directory: ${error instanceof Error ? error.message : String(error)}`);
65493
+ }
65494
+ }
65495
+ }
65496
+ var import_fs_extra30, execFileAsync7, MARKETPLACE_DIR_NAME = "marketplace", PLUGIN_NAME = "ck", MARKETPLACE_NAME = "claudekit";
65497
+ var init_plugin_installer = __esm(() => {
65498
+ init_logger();
65499
+ init_path_resolver();
65500
+ import_fs_extra30 = __toESM(require_lib3(), 1);
65501
+ execFileAsync7 = promisify15(execFile10);
65502
+ });
65503
+
65504
+ // src/services/skill-migration-merger.ts
65505
+ var exports_skill_migration_merger = {};
65506
+ __export(exports_skill_migration_merger, {
65507
+ migrateUserSkills: () => migrateUserSkills
65508
+ });
65509
+ import { readFile as readFile47 } from "node:fs/promises";
65510
+ import { join as join100 } from "node:path";
65511
+ async function migrateUserSkills(claudeDir2, pluginVerified) {
65512
+ const result = {
65513
+ preserved: [],
65514
+ deleted: [],
65515
+ userOwned: [],
65516
+ canDelete: false
65517
+ };
65518
+ if (!pluginVerified) {
65519
+ return result;
65520
+ }
65521
+ const metadataPath = join100(claudeDir2, "metadata.json");
65522
+ if (!await import_fs_extra31.pathExists(metadataPath)) {
65523
+ return result;
65524
+ }
65525
+ let trackedFiles;
65526
+ try {
65527
+ const content = await readFile47(metadataPath, "utf-8");
65528
+ const metadata = JSON.parse(content);
65529
+ trackedFiles = extractTrackedSkillFiles(metadata);
65530
+ } catch {
65531
+ logger.debug("Could not read metadata for skill migration");
65532
+ return result;
65533
+ }
65534
+ if (trackedFiles.length === 0) {
65535
+ return result;
65536
+ }
65537
+ result.canDelete = true;
65538
+ const preservedSet = new Set;
65539
+ const deletedSet = new Set;
65540
+ const userOwnedSet = new Set;
65541
+ for (const file of trackedFiles) {
65542
+ const skillDir = extractSkillDir(file.path);
65543
+ if (!skillDir)
65544
+ continue;
65545
+ switch (file.ownership) {
65546
+ case "user":
65547
+ userOwnedSet.add(skillDir);
65548
+ break;
65549
+ case "ck-modified":
65550
+ preservedSet.add(skillDir);
65551
+ break;
65552
+ case "ck":
65553
+ deletedSet.add(skillDir);
65554
+ break;
65555
+ }
65556
+ }
65557
+ for (const dir of preservedSet) {
65558
+ deletedSet.delete(dir);
65559
+ }
65560
+ result.preserved = [...preservedSet];
65561
+ result.deleted = [...deletedSet];
65562
+ result.userOwned = [...userOwnedSet];
65563
+ for (const dir of result.preserved) {
65564
+ const skillName = dir.split("/")[1];
65565
+ logger.info(`Preserved customizations: /${skillName} (standalone alongside /ck:${skillName})`);
65566
+ }
65567
+ return result;
65568
+ }
65569
+ function extractTrackedSkillFiles(metadata) {
65570
+ const files = [];
65571
+ if (metadata.kits && typeof metadata.kits === "object") {
65572
+ for (const kit of Object.values(metadata.kits)) {
65573
+ if (kit.files) {
65574
+ files.push(...kit.files.filter((f3) => isSkillPath(f3.path)));
65575
+ }
65576
+ }
65577
+ }
65578
+ if (Array.isArray(metadata.files)) {
65579
+ files.push(...metadata.files.filter((f3) => isSkillPath(f3.path)));
65580
+ }
65581
+ return files;
65582
+ }
65583
+ function isSkillPath(path14) {
65584
+ const normalized = path14.replace(/\\/g, "/");
65585
+ return normalized.startsWith("skills/");
65586
+ }
65587
+ function extractSkillDir(path14) {
65588
+ const normalized = path14.replace(/\\/g, "/");
65589
+ const parts = normalized.split("/").filter(Boolean);
65590
+ if (parts.length < 2 || parts[0] !== "skills") {
65591
+ return null;
65592
+ }
65593
+ return `skills/${parts[1]}`;
65594
+ }
65595
+ var import_fs_extra31;
65596
+ var init_skill_migration_merger = __esm(() => {
65597
+ init_logger();
65598
+ import_fs_extra31 = __toESM(require_lib3(), 1);
65599
+ });
65600
+
65601
+ // src/services/standalone-skill-cleanup.ts
65602
+ var exports_standalone_skill_cleanup = {};
65603
+ __export(exports_standalone_skill_cleanup, {
65604
+ cleanupOverlappingStandaloneSkills: () => cleanupOverlappingStandaloneSkills
65605
+ });
65606
+ import { readFile as readFile48, readdir as readdir32 } from "node:fs/promises";
65607
+ import { join as join101 } from "node:path";
65608
+ async function listSkillDirs(dir) {
65609
+ if (!await import_fs_extra32.pathExists(dir))
65610
+ return new Set;
65611
+ try {
65612
+ const entries = await readdir32(dir, { withFileTypes: true });
65613
+ const dirs = entries.filter((e2) => e2.isDirectory());
65614
+ const results = await Promise.all(dirs.map(async (e2) => {
65615
+ const exists = await import_fs_extra32.pathExists(join101(dir, e2.name, "SKILL.md"));
65616
+ return exists ? e2.name : null;
65617
+ }));
65618
+ return new Set(results.filter(Boolean));
65619
+ } catch {
65620
+ return new Set;
65621
+ }
65622
+ }
65623
+ async function getSkillOwnershipMap(claudeDir2) {
65624
+ const ownershipMap = new Map;
65625
+ const metadataPath = join101(claudeDir2, "metadata.json");
65626
+ if (!await import_fs_extra32.pathExists(metadataPath))
65627
+ return ownershipMap;
65628
+ let trackedFiles;
65629
+ try {
65630
+ const content = await readFile48(metadataPath, "utf-8");
65631
+ const metadata = JSON.parse(content);
65632
+ trackedFiles = extractTrackedSkillFiles2(metadata);
65633
+ } catch {
65634
+ logger.debug("standalone-skill-cleanup: could not read metadata");
65635
+ return ownershipMap;
65636
+ }
65637
+ for (const file of trackedFiles) {
65638
+ const skillDirName = extractSkillDirName(file.path);
65639
+ if (!skillDirName)
65640
+ continue;
65641
+ const existing = ownershipMap.get(skillDirName);
65642
+ if (file.ownership === "user" || file.ownership === "ck-modified") {
65643
+ ownershipMap.set(skillDirName, "user");
65644
+ } else if (!existing) {
65645
+ ownershipMap.set(skillDirName, "ck");
65646
+ }
65647
+ }
65648
+ return ownershipMap;
65649
+ }
65650
+ function extractTrackedSkillFiles2(metadata) {
65651
+ const files = [];
65652
+ if (metadata.kits && typeof metadata.kits === "object") {
65653
+ for (const kit of Object.values(metadata.kits)) {
65654
+ if (kit.files) {
65655
+ files.push(...kit.files.filter((f3) => f3.path?.startsWith("skills/")));
65656
+ }
65657
+ }
65658
+ }
65659
+ if (Array.isArray(metadata.files)) {
65660
+ files.push(...metadata.files.filter((f3) => f3.path?.startsWith("skills/")));
65661
+ }
65662
+ return files;
65663
+ }
65664
+ function extractSkillDirName(filePath) {
65665
+ const normalized = filePath.replace(/\\/g, "/");
65666
+ const parts = normalized.split("/").filter(Boolean);
65667
+ if (parts.length < 2 || parts[0] !== "skills")
65668
+ return null;
65669
+ return parts[1];
65670
+ }
65671
+ async function cleanupOverlappingStandaloneSkills(claudeDir2, pluginSkillsDir) {
65672
+ const standaloneSkillsDir = join101(claudeDir2, "skills");
65673
+ const result = {
65674
+ removed: [],
65675
+ preserved: [],
65676
+ pluginSkillsDir
65677
+ };
65678
+ const [pluginSkills, standaloneSkills] = await Promise.all([
65679
+ listSkillDirs(pluginSkillsDir),
65680
+ listSkillDirs(standaloneSkillsDir)
65681
+ ]);
65682
+ if (pluginSkills.size === 0 || standaloneSkills.size === 0) {
65683
+ logger.debug(`standalone-skill-cleanup: nothing to clean (plugin=${pluginSkills.size}, standalone=${standaloneSkills.size})`);
65684
+ return result;
65685
+ }
65686
+ const overlaps = [...standaloneSkills].filter((name2) => pluginSkills.has(name2));
65687
+ if (overlaps.length === 0)
65688
+ return result;
65689
+ const ownershipMap = await getSkillOwnershipMap(claudeDir2);
65690
+ for (const skillName of overlaps) {
65691
+ const ownership = ownershipMap.get(skillName);
65692
+ const skillPath = join101(standaloneSkillsDir, skillName);
65693
+ if (ownership === "user" || ownership === undefined) {
65694
+ result.preserved.push(skillName);
65695
+ const reason = ownership === "user" ? "user-owned/modified" : "untracked";
65696
+ logger.debug(`standalone-skill-cleanup: preserved ${skillName} (${reason})`);
65697
+ continue;
65698
+ }
65699
+ try {
65700
+ await import_fs_extra32.remove(skillPath);
65701
+ result.removed.push(skillName);
65702
+ logger.debug(`standalone-skill-cleanup: removed standalone ${skillName} (plugin has it)`);
65703
+ } catch (error) {
65704
+ result.preserved.push(skillName);
65705
+ logger.debug(`standalone-skill-cleanup: could not remove ${skillName}: ${error}`);
65706
+ }
65707
+ }
65708
+ return result;
65709
+ }
65710
+ var import_fs_extra32;
65711
+ var init_standalone_skill_cleanup = __esm(() => {
65712
+ init_logger();
65713
+ import_fs_extra32 = __toESM(require_lib3(), 1);
65714
+ });
65715
+
64668
65716
  // src/domains/help/commands/common-options.ts
64669
65717
  var filterOptionsGroup, folderOptionsGroup;
64670
65718
  var init_common_options = __esm(() => {
@@ -65782,7 +66830,7 @@ function getPagerArgs(pagerCmd) {
65782
66830
  return [];
65783
66831
  }
65784
66832
  async function trySystemPager(content) {
65785
- return new Promise((resolve26) => {
66833
+ return new Promise((resolve27) => {
65786
66834
  const pagerCmd = process.env.PAGER || "less";
65787
66835
  const pagerArgs = getPagerArgs(pagerCmd);
65788
66836
  try {
@@ -65792,20 +66840,20 @@ async function trySystemPager(content) {
65792
66840
  });
65793
66841
  const timeout2 = setTimeout(() => {
65794
66842
  pager.kill();
65795
- resolve26(false);
66843
+ resolve27(false);
65796
66844
  }, 30000);
65797
66845
  pager.stdin.write(content);
65798
66846
  pager.stdin.end();
65799
66847
  pager.on("close", (code2) => {
65800
66848
  clearTimeout(timeout2);
65801
- resolve26(code2 === 0);
66849
+ resolve27(code2 === 0);
65802
66850
  });
65803
66851
  pager.on("error", () => {
65804
66852
  clearTimeout(timeout2);
65805
- resolve26(false);
66853
+ resolve27(false);
65806
66854
  });
65807
66855
  } catch {
65808
- resolve26(false);
66856
+ resolve27(false);
65809
66857
  }
65810
66858
  });
65811
66859
  }
@@ -65832,16 +66880,16 @@ async function basicPager(content) {
65832
66880
  break;
65833
66881
  }
65834
66882
  const remaining = lines.length - currentLine;
65835
- await new Promise((resolve26) => {
66883
+ await new Promise((resolve27) => {
65836
66884
  rl.question(`-- More (${remaining} lines) [Enter/q] --`, (answer) => {
65837
66885
  if (answer.toLowerCase() === "q") {
65838
66886
  rl.close();
65839
66887
  process.exitCode = 0;
65840
- resolve26();
66888
+ resolve27();
65841
66889
  return;
65842
66890
  }
65843
66891
  process.stdout.write("\x1B[1A\x1B[2K");
65844
- resolve26();
66892
+ resolve27();
65845
66893
  });
65846
66894
  });
65847
66895
  }
@@ -72514,7 +73562,7 @@ class ConfigVersionChecker {
72514
73562
  }
72515
73563
  // src/domains/sync/sync-engine.ts
72516
73564
  import { lstat as lstat3, readFile as readFile34, readlink, realpath as realpath3, stat as stat9 } from "node:fs/promises";
72517
- import { isAbsolute as isAbsolute3, join as join58, normalize as normalize7, relative as relative7 } from "node:path";
73565
+ import { dirname as dirname15, isAbsolute as isAbsolute3, normalize as normalize7, relative as relative7, resolve as resolve13 } from "node:path";
72518
73566
 
72519
73567
  // src/services/file-operations/ownership-checker.ts
72520
73568
  init_metadata_migration();
@@ -73875,7 +74923,7 @@ async function validateSymlinkChain(path5, basePath, maxDepth = MAX_SYMLINK_DEPT
73875
74923
  if (!stats.isSymbolicLink())
73876
74924
  break;
73877
74925
  const target = await readlink(current);
73878
- const resolvedTarget = isAbsolute3(target) ? target : join58(current, "..", target);
74926
+ const resolvedTarget = isAbsolute3(target) ? target : resolve13(dirname15(current), target);
73879
74927
  const normalizedTarget = normalize7(resolvedTarget);
73880
74928
  const rel = relative7(basePath, normalizedTarget);
73881
74929
  if (rel.startsWith("..") || isAbsolute3(rel)) {
@@ -73894,6 +74942,10 @@ async function validateSymlinkChain(path5, basePath, maxDepth = MAX_SYMLINK_DEPT
73894
74942
  throw new Error(`Symlink chain too deep (>${maxDepth}): ${path5}`);
73895
74943
  }
73896
74944
  }
74945
+ function isOutsideBase(candidatePath, basePath) {
74946
+ const rel = relative7(basePath, candidatePath);
74947
+ return rel.startsWith("..") || isAbsolute3(rel);
74948
+ }
73897
74949
  async function validateSyncPath(basePath, filePath) {
73898
74950
  if (!filePath || filePath.trim() === "") {
73899
74951
  throw new Error("Empty file path not allowed");
@@ -73908,37 +74960,44 @@ async function validateSyncPath(basePath, filePath) {
73908
74960
  if (isAbsolute3(normalized)) {
73909
74961
  throw new Error(`Absolute paths not allowed: ${filePath}`);
73910
74962
  }
73911
- if (normalized.startsWith("..") || normalized.includes("/../")) {
74963
+ const pathParts = normalized.split(/[\\/]+/).filter(Boolean);
74964
+ if (pathParts.includes("..")) {
73912
74965
  throw new Error(`Path traversal not allowed: ${filePath}`);
73913
74966
  }
73914
- const fullPath = join58(basePath, normalized);
73915
- const rel = relative7(basePath, fullPath);
73916
- if (rel.startsWith("..") || isAbsolute3(rel)) {
74967
+ const lexicalBase = resolve13(basePath);
74968
+ const fullPath = resolve13(basePath, normalized);
74969
+ const resolvedBase = await realpath3(basePath).catch(() => lexicalBase);
74970
+ if (isOutsideBase(fullPath, lexicalBase)) {
73917
74971
  throw new Error(`Path escapes base directory: ${filePath}`);
73918
74972
  }
73919
- await validateSymlinkChain(fullPath, basePath);
74973
+ await validateSymlinkChain(fullPath, resolvedBase);
73920
74974
  try {
73921
- const resolvedBase = await realpath3(basePath);
73922
74975
  const resolvedFull = await realpath3(fullPath);
73923
- const resolvedRel = relative7(resolvedBase, resolvedFull);
73924
- if (resolvedRel.startsWith("..") || isAbsolute3(resolvedRel)) {
74976
+ if (isOutsideBase(resolvedFull, resolvedBase)) {
73925
74977
  throw new Error(`Symlink escapes base directory: ${filePath}`);
73926
74978
  }
73927
74979
  } catch (error) {
73928
74980
  if (error.code === "ENOENT") {
73929
- const parentPath = join58(fullPath, "..");
73930
- try {
73931
- const resolvedBase = await realpath3(basePath);
73932
- const resolvedParent = await realpath3(parentPath);
73933
- const resolvedRel = relative7(resolvedBase, resolvedParent);
73934
- if (resolvedRel.startsWith("..") || isAbsolute3(resolvedRel)) {
73935
- throw new Error(`Parent symlink escapes base directory: ${filePath}`);
73936
- }
73937
- } catch (parentError) {
73938
- if (parentError.code !== "ENOENT") {
73939
- throw parentError;
74981
+ let ancestor = dirname15(fullPath);
74982
+ while (ancestor !== lexicalBase) {
74983
+ try {
74984
+ await lstat3(ancestor);
74985
+ break;
74986
+ } catch (ancestorError) {
74987
+ if (ancestorError.code !== "ENOENT") {
74988
+ throw ancestorError;
74989
+ }
74990
+ const nextAncestor = dirname15(ancestor);
74991
+ if (nextAncestor === ancestor) {
74992
+ break;
74993
+ }
74994
+ ancestor = nextAncestor;
73940
74995
  }
73941
74996
  }
74997
+ const resolvedAncestor = await realpath3(ancestor).catch(() => null);
74998
+ if (!resolvedAncestor || isOutsideBase(resolvedAncestor, resolvedBase)) {
74999
+ throw new Error(`Parent symlink escapes base directory: ${filePath}`);
75000
+ }
73942
75001
  } else {
73943
75002
  throw error;
73944
75003
  }
@@ -74090,14 +75149,24 @@ class SyncEngine {
74090
75149
  }
74091
75150
  static async loadFileContent(filePath) {
74092
75151
  try {
74093
- const lstats = await lstat3(filePath);
74094
- if (lstats.isSymbolicLink()) {
75152
+ const beforeStats = await lstat3(filePath);
75153
+ if (beforeStats.isSymbolicLink()) {
74095
75154
  throw new Error(`Symlink not allowed for sync: ${filePath}`);
74096
75155
  }
74097
- if (lstats.size > MAX_SYNC_FILE_SIZE) {
74098
- throw new Error(`File too large for sync (${Math.round(lstats.size / 1024 / 1024)}MB > ${MAX_SYNC_FILE_SIZE / 1024 / 1024}MB limit)`);
75156
+ if (beforeStats.size > MAX_SYNC_FILE_SIZE) {
75157
+ throw new Error(`File too large for sync (${Math.round(beforeStats.size / 1024 / 1024)}MB > ${MAX_SYNC_FILE_SIZE / 1024 / 1024}MB limit)`);
74099
75158
  }
74100
75159
  const buffer = await readFile34(filePath);
75160
+ const afterStats = await lstat3(filePath);
75161
+ if (afterStats.isSymbolicLink()) {
75162
+ throw new Error(`File became symlink during read: ${filePath}`);
75163
+ }
75164
+ if (beforeStats.dev !== afterStats.dev || beforeStats.ino !== afterStats.ino) {
75165
+ throw new Error(`File changed identity during read: ${filePath}`);
75166
+ }
75167
+ if (beforeStats.mtimeMs !== afterStats.mtimeMs || beforeStats.size !== afterStats.size) {
75168
+ throw new Error(`File changed during read: ${filePath}`);
75169
+ }
74101
75170
  if (buffer.includes(0)) {
74102
75171
  return { content: "", isBinary: true };
74103
75172
  }
@@ -75108,7 +76177,7 @@ init_logger();
75108
76177
  var import_proper_lockfile4 = __toESM(require_proper_lockfile(), 1);
75109
76178
  import { mkdir as mkdir19 } from "node:fs/promises";
75110
76179
  import os5 from "node:os";
75111
- import { join as join65 } from "node:path";
76180
+ import { join as join64 } from "node:path";
75112
76181
  var LOCK_CONFIG = {
75113
76182
  stale: 60000,
75114
76183
  retries: 0
@@ -75116,12 +76185,12 @@ var LOCK_CONFIG = {
75116
76185
  var activeLocks = new Set;
75117
76186
  var cleanupRegistered = false;
75118
76187
  function getLocksDir() {
75119
- return join65(os5.homedir(), ".claudekit", "locks");
76188
+ return join64(os5.homedir(), ".claudekit", "locks");
75120
76189
  }
75121
76190
  function cleanupLocks() {
75122
76191
  for (const name of activeLocks) {
75123
76192
  try {
75124
- const lockPath = join65(getLocksDir(), `${name}.lock`);
76193
+ const lockPath = join64(getLocksDir(), `${name}.lock`);
75125
76194
  import_proper_lockfile4.default.unlockSync(lockPath, { realpath: false });
75126
76195
  } catch {
75127
76196
  try {
@@ -75144,7 +76213,7 @@ async function ensureLocksDir() {
75144
76213
  async function withProcessLock(lockName, fn) {
75145
76214
  registerCleanupHandlers();
75146
76215
  await ensureLocksDir();
75147
- const lockPath = join65(getLocksDir(), `${lockName}.lock`);
76216
+ const lockPath = join64(getLocksDir(), `${lockName}.lock`);
75148
76217
  let release;
75149
76218
  try {
75150
76219
  release = await import_proper_lockfile4.default.lock(lockPath, { ...LOCK_CONFIG, realpath: false });
@@ -75175,7 +76244,7 @@ init_logger();
75175
76244
  init_logger();
75176
76245
  init_path_resolver();
75177
76246
  var import_fs_extra7 = __toESM(require_lib3(), 1);
75178
- import { join as join66 } from "node:path";
76247
+ import { join as join65 } from "node:path";
75179
76248
  async function handleConflicts(ctx) {
75180
76249
  if (ctx.cancelled)
75181
76250
  return ctx;
@@ -75184,7 +76253,7 @@ async function handleConflicts(ctx) {
75184
76253
  if (PathResolver.isLocalSameAsGlobal()) {
75185
76254
  return ctx;
75186
76255
  }
75187
- const localSettingsPath = join66(process.cwd(), ".claude", "settings.json");
76256
+ const localSettingsPath = join65(process.cwd(), ".claude", "settings.json");
75188
76257
  if (!await import_fs_extra7.pathExists(localSettingsPath)) {
75189
76258
  return ctx;
75190
76259
  }
@@ -75199,7 +76268,7 @@ async function handleConflicts(ctx) {
75199
76268
  return { ...ctx, cancelled: true };
75200
76269
  }
75201
76270
  if (choice === "remove") {
75202
- const localClaudeDir = join66(process.cwd(), ".claude");
76271
+ const localClaudeDir = join65(process.cwd(), ".claude");
75203
76272
  try {
75204
76273
  await import_fs_extra7.remove(localClaudeDir);
75205
76274
  logger.success("Removed local .claude/ directory");
@@ -75296,7 +76365,7 @@ init_logger();
75296
76365
  init_safe_spinner();
75297
76366
  import { mkdir as mkdir25, stat as stat12 } from "node:fs/promises";
75298
76367
  import { tmpdir as tmpdir4 } from "node:os";
75299
- import { join as join73 } from "node:path";
76368
+ import { join as join72 } from "node:path";
75300
76369
 
75301
76370
  // src/shared/temp-cleanup.ts
75302
76371
  init_logger();
@@ -75315,7 +76384,7 @@ init_logger();
75315
76384
  init_output_manager();
75316
76385
  import { createWriteStream as createWriteStream2, rmSync } from "node:fs";
75317
76386
  import { mkdir as mkdir20 } from "node:fs/promises";
75318
- import { join as join67 } from "node:path";
76387
+ import { join as join66 } from "node:path";
75319
76388
 
75320
76389
  // src/shared/progress-bar.ts
75321
76390
  init_output_manager();
@@ -75480,10 +76549,10 @@ init_types3();
75480
76549
  // src/domains/installation/utils/path-security.ts
75481
76550
  init_types3();
75482
76551
  import { lstatSync as lstatSync2, realpathSync as realpathSync2 } from "node:fs";
75483
- import { relative as relative8, resolve as resolve15 } from "node:path";
76552
+ import { relative as relative8, resolve as resolve16 } from "node:path";
75484
76553
  var MAX_EXTRACTION_SIZE = 500 * 1024 * 1024;
75485
76554
  function isPathSafe(basePath, targetPath) {
75486
- const resolvedBase = resolve15(basePath);
76555
+ const resolvedBase = resolve16(basePath);
75487
76556
  try {
75488
76557
  const stat10 = lstatSync2(targetPath);
75489
76558
  if (stat10.isSymbolicLink()) {
@@ -75493,7 +76562,7 @@ function isPathSafe(basePath, targetPath) {
75493
76562
  }
75494
76563
  }
75495
76564
  } catch {}
75496
- const resolvedTarget = resolve15(targetPath);
76565
+ const resolvedTarget = resolve16(targetPath);
75497
76566
  const relativePath = relative8(resolvedBase, resolvedTarget);
75498
76567
  return !relativePath.startsWith("..") && !relativePath.startsWith("/") && resolvedTarget.startsWith(resolvedBase);
75499
76568
  }
@@ -75525,7 +76594,7 @@ var MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
75525
76594
  class FileDownloader {
75526
76595
  async downloadAsset(asset, destDir) {
75527
76596
  try {
75528
- const destPath = join67(destDir, asset.name);
76597
+ const destPath = join66(destDir, asset.name);
75529
76598
  await mkdir20(destDir, { recursive: true });
75530
76599
  output.info(`Downloading ${asset.name} (${formatBytes(asset.size)})...`);
75531
76600
  logger.verbose("Download details", {
@@ -75581,7 +76650,7 @@ class FileDownloader {
75581
76650
  }
75582
76651
  if (downloadedSize !== totalSize) {
75583
76652
  fileStream.end();
75584
- await new Promise((resolve16) => fileStream.once("close", resolve16));
76653
+ await new Promise((resolve17) => fileStream.once("close", resolve17));
75585
76654
  try {
75586
76655
  rmSync(destPath, { force: true });
75587
76656
  } catch (cleanupError) {
@@ -75595,7 +76664,7 @@ class FileDownloader {
75595
76664
  return destPath;
75596
76665
  } catch (error) {
75597
76666
  fileStream.end();
75598
- await new Promise((resolve16) => fileStream.once("close", resolve16));
76667
+ await new Promise((resolve17) => fileStream.once("close", resolve17));
75599
76668
  try {
75600
76669
  rmSync(destPath, { force: true });
75601
76670
  } catch (cleanupError) {
@@ -75610,7 +76679,7 @@ class FileDownloader {
75610
76679
  }
75611
76680
  async downloadFile(params) {
75612
76681
  const { url, name, size, destDir, token } = params;
75613
- const destPath = join67(destDir, name);
76682
+ const destPath = join66(destDir, name);
75614
76683
  await mkdir20(destDir, { recursive: true });
75615
76684
  output.info(`Downloading ${name}${size ? ` (${formatBytes(size)})` : ""}...`);
75616
76685
  const headers = {};
@@ -75661,7 +76730,7 @@ class FileDownloader {
75661
76730
  const expectedSize = Number(response.headers.get("content-length"));
75662
76731
  if (expectedSize > 0 && downloadedSize !== expectedSize) {
75663
76732
  fileStream.end();
75664
- await new Promise((resolve16) => fileStream.once("close", resolve16));
76733
+ await new Promise((resolve17) => fileStream.once("close", resolve17));
75665
76734
  try {
75666
76735
  rmSync(destPath, { force: true });
75667
76736
  } catch (cleanupError) {
@@ -75679,7 +76748,7 @@ class FileDownloader {
75679
76748
  return destPath;
75680
76749
  } catch (error) {
75681
76750
  fileStream.end();
75682
- await new Promise((resolve16) => fileStream.once("close", resolve16));
76751
+ await new Promise((resolve17) => fileStream.once("close", resolve17));
75683
76752
  try {
75684
76753
  rmSync(destPath, { force: true });
75685
76754
  } catch (cleanupError) {
@@ -75696,7 +76765,7 @@ init_logger();
75696
76765
  init_types3();
75697
76766
  import { constants as constants4 } from "node:fs";
75698
76767
  import { access as access4, readdir as readdir13 } from "node:fs/promises";
75699
- import { join as join68 } from "node:path";
76768
+ import { join as join67 } from "node:path";
75700
76769
  async function validateExtraction(extractDir) {
75701
76770
  try {
75702
76771
  const entries = await readdir13(extractDir, { encoding: "utf8" });
@@ -75708,7 +76777,7 @@ async function validateExtraction(extractDir) {
75708
76777
  const missingPaths = [];
75709
76778
  for (const path5 of criticalPaths) {
75710
76779
  try {
75711
- await access4(join68(extractDir, path5), constants4.F_OK);
76780
+ await access4(join67(extractDir, path5), constants4.F_OK);
75712
76781
  logger.debug(`Found: ${path5}`);
75713
76782
  } catch {
75714
76783
  logger.warning(`Expected path not found: ${path5}`);
@@ -75730,7 +76799,7 @@ async function validateExtraction(extractDir) {
75730
76799
  // src/domains/installation/extraction/tar-extractor.ts
75731
76800
  init_logger();
75732
76801
  import { copyFile as copyFile4, mkdir as mkdir23, readdir as readdir15, rm as rm8, stat as stat10 } from "node:fs/promises";
75733
- import { join as join71 } from "node:path";
76802
+ import { join as join70 } from "node:path";
75734
76803
 
75735
76804
  // node_modules/@isaacs/fs-minipass/dist/esm/index.js
75736
76805
  import EE from "events";
@@ -76278,10 +77347,10 @@ class Minipass extends EventEmitter3 {
76278
77347
  return this[ENCODING] ? buf.join("") : Buffer.concat(buf, buf.dataLength);
76279
77348
  }
76280
77349
  async promise() {
76281
- return new Promise((resolve16, reject) => {
77350
+ return new Promise((resolve17, reject) => {
76282
77351
  this.on(DESTROYED, () => reject(new Error("stream destroyed")));
76283
77352
  this.on("error", (er) => reject(er));
76284
- this.on("end", () => resolve16());
77353
+ this.on("end", () => resolve17());
76285
77354
  });
76286
77355
  }
76287
77356
  [Symbol.asyncIterator]() {
@@ -76300,7 +77369,7 @@ class Minipass extends EventEmitter3 {
76300
77369
  return Promise.resolve({ done: false, value: res });
76301
77370
  if (this[EOF])
76302
77371
  return stop();
76303
- let resolve16;
77372
+ let resolve17;
76304
77373
  let reject;
76305
77374
  const onerr = (er) => {
76306
77375
  this.off("data", ondata);
@@ -76314,19 +77383,19 @@ class Minipass extends EventEmitter3 {
76314
77383
  this.off("end", onend);
76315
77384
  this.off(DESTROYED, ondestroy);
76316
77385
  this.pause();
76317
- resolve16({ value, done: !!this[EOF] });
77386
+ resolve17({ value, done: !!this[EOF] });
76318
77387
  };
76319
77388
  const onend = () => {
76320
77389
  this.off("error", onerr);
76321
77390
  this.off("data", ondata);
76322
77391
  this.off(DESTROYED, ondestroy);
76323
77392
  stop();
76324
- resolve16({ done: true, value: undefined });
77393
+ resolve17({ done: true, value: undefined });
76325
77394
  };
76326
77395
  const ondestroy = () => onerr(new Error("stream destroyed"));
76327
77396
  return new Promise((res2, rej) => {
76328
77397
  reject = rej;
76329
- resolve16 = res2;
77398
+ resolve17 = res2;
76330
77399
  this.once(DESTROYED, ondestroy);
76331
77400
  this.once("error", onerr);
76332
77401
  this.once("end", onend);
@@ -76783,7 +77852,7 @@ import path7 from "node:path";
76783
77852
 
76784
77853
  // node_modules/tar/dist/esm/list.js
76785
77854
  import fs9 from "node:fs";
76786
- import { dirname as dirname17, parse as parse3 } from "path";
77855
+ import { dirname as dirname18, parse as parse3 } from "path";
76787
77856
 
76788
77857
  // node_modules/tar/dist/esm/options.js
76789
77858
  var argmap = new Map([
@@ -77432,10 +78501,10 @@ class Minipass2 extends EventEmitter4 {
77432
78501
  return this[ENCODING2] ? buf.join("") : Buffer.concat(buf, buf.dataLength);
77433
78502
  }
77434
78503
  async promise() {
77435
- return new Promise((resolve16, reject) => {
78504
+ return new Promise((resolve17, reject) => {
77436
78505
  this.on(DESTROYED2, () => reject(new Error("stream destroyed")));
77437
78506
  this.on("error", (er) => reject(er));
77438
- this.on("end", () => resolve16());
78507
+ this.on("end", () => resolve17());
77439
78508
  });
77440
78509
  }
77441
78510
  [Symbol.asyncIterator]() {
@@ -77454,7 +78523,7 @@ class Minipass2 extends EventEmitter4 {
77454
78523
  return Promise.resolve({ done: false, value: res });
77455
78524
  if (this[EOF2])
77456
78525
  return stop();
77457
- let resolve16;
78526
+ let resolve17;
77458
78527
  let reject;
77459
78528
  const onerr = (er) => {
77460
78529
  this.off("data", ondata);
@@ -77468,19 +78537,19 @@ class Minipass2 extends EventEmitter4 {
77468
78537
  this.off("end", onend);
77469
78538
  this.off(DESTROYED2, ondestroy);
77470
78539
  this.pause();
77471
- resolve16({ value, done: !!this[EOF2] });
78540
+ resolve17({ value, done: !!this[EOF2] });
77472
78541
  };
77473
78542
  const onend = () => {
77474
78543
  this.off("error", onerr);
77475
78544
  this.off("data", ondata);
77476
78545
  this.off(DESTROYED2, ondestroy);
77477
78546
  stop();
77478
- resolve16({ done: true, value: undefined });
78547
+ resolve17({ done: true, value: undefined });
77479
78548
  };
77480
78549
  const ondestroy = () => onerr(new Error("stream destroyed"));
77481
78550
  return new Promise((res2, rej) => {
77482
78551
  reject = rej;
77483
- resolve16 = res2;
78552
+ resolve17 = res2;
77484
78553
  this.once(DESTROYED2, ondestroy);
77485
78554
  this.once("error", onerr);
77486
78555
  this.once("end", onend);
@@ -78908,10 +79977,10 @@ class Minipass3 extends EventEmitter5 {
78908
79977
  return this[ENCODING3] ? buf.join("") : Buffer.concat(buf, buf.dataLength);
78909
79978
  }
78910
79979
  async promise() {
78911
- return new Promise((resolve16, reject) => {
79980
+ return new Promise((resolve17, reject) => {
78912
79981
  this.on(DESTROYED3, () => reject(new Error("stream destroyed")));
78913
79982
  this.on("error", (er) => reject(er));
78914
- this.on("end", () => resolve16());
79983
+ this.on("end", () => resolve17());
78915
79984
  });
78916
79985
  }
78917
79986
  [Symbol.asyncIterator]() {
@@ -78930,7 +79999,7 @@ class Minipass3 extends EventEmitter5 {
78930
79999
  return Promise.resolve({ done: false, value: res });
78931
80000
  if (this[EOF3])
78932
80001
  return stop();
78933
- let resolve16;
80002
+ let resolve17;
78934
80003
  let reject;
78935
80004
  const onerr = (er) => {
78936
80005
  this.off("data", ondata);
@@ -78944,19 +80013,19 @@ class Minipass3 extends EventEmitter5 {
78944
80013
  this.off("end", onend);
78945
80014
  this.off(DESTROYED3, ondestroy);
78946
80015
  this.pause();
78947
- resolve16({ value, done: !!this[EOF3] });
80016
+ resolve17({ value, done: !!this[EOF3] });
78948
80017
  };
78949
80018
  const onend = () => {
78950
80019
  this.off("error", onerr);
78951
80020
  this.off("data", ondata);
78952
80021
  this.off(DESTROYED3, ondestroy);
78953
80022
  stop();
78954
- resolve16({ done: true, value: undefined });
80023
+ resolve17({ done: true, value: undefined });
78955
80024
  };
78956
80025
  const ondestroy = () => onerr(new Error("stream destroyed"));
78957
80026
  return new Promise((res2, rej) => {
78958
80027
  reject = rej;
78959
- resolve16 = res2;
80028
+ resolve17 = res2;
78960
80029
  this.once(DESTROYED3, ondestroy);
78961
80030
  this.once("error", onerr);
78962
80031
  this.once("end", onend);
@@ -79683,7 +80752,7 @@ var filesFilter = (opt, files) => {
79683
80752
  if (m2 !== undefined) {
79684
80753
  ret = m2;
79685
80754
  } else {
79686
- ret = mapHas(dirname17(file), root);
80755
+ ret = mapHas(dirname18(file), root);
79687
80756
  }
79688
80757
  }
79689
80758
  map.set(file, ret);
@@ -79727,9 +80796,9 @@ var listFile = (opt, _files) => {
79727
80796
  const parse4 = new Parser(opt);
79728
80797
  const readSize = opt.maxReadSize || 16 * 1024 * 1024;
79729
80798
  const file = opt.file;
79730
- const p = new Promise((resolve16, reject) => {
80799
+ const p = new Promise((resolve17, reject) => {
79731
80800
  parse4.on("error", reject);
79732
- parse4.on("end", resolve16);
80801
+ parse4.on("end", resolve17);
79733
80802
  fs9.stat(file, (er, stat10) => {
79734
80803
  if (er) {
79735
80804
  reject(er);
@@ -81492,7 +82561,7 @@ var mkdirSync = (dir, opt) => {
81492
82561
  };
81493
82562
 
81494
82563
  // node_modules/tar/dist/esm/path-reservations.js
81495
- import { join as join69 } from "node:path";
82564
+ import { join as join68 } from "node:path";
81496
82565
 
81497
82566
  // node_modules/tar/dist/esm/normalize-unicode.js
81498
82567
  var normalizeCache = Object.create(null);
@@ -81525,7 +82594,7 @@ var getDirs = (path10) => {
81525
82594
  const dirs = path10.split("/").slice(0, -1).reduce((set, path11) => {
81526
82595
  const s = set[set.length - 1];
81527
82596
  if (s !== undefined) {
81528
- path11 = join69(s, path11);
82597
+ path11 = join68(s, path11);
81529
82598
  }
81530
82599
  set.push(path11 || "/");
81531
82600
  return set;
@@ -81539,7 +82608,7 @@ class PathReservations {
81539
82608
  #running = new Set;
81540
82609
  reserve(paths, fn) {
81541
82610
  paths = isWindows4 ? ["win32 parallelization disabled"] : paths.map((p) => {
81542
- return stripTrailingSlashes(join69(normalizeUnicode(p))).toLowerCase();
82611
+ return stripTrailingSlashes(join68(normalizeUnicode(p))).toLowerCase();
81543
82612
  });
81544
82613
  const dirs = new Set(paths.map((path10) => getDirs(path10)).reduce((a3, b3) => a3.concat(b3)));
81545
82614
  this.#reservations.set(fn, { dirs, paths });
@@ -82309,9 +83378,9 @@ var extractFile = (opt, _3) => {
82309
83378
  const u = new Unpack(opt);
82310
83379
  const readSize = opt.maxReadSize || 16 * 1024 * 1024;
82311
83380
  const file = opt.file;
82312
- const p = new Promise((resolve16, reject) => {
83381
+ const p = new Promise((resolve17, reject) => {
82313
83382
  u.on("error", reject);
82314
- u.on("close", resolve16);
83383
+ u.on("close", resolve17);
82315
83384
  fs16.stat(file, (er, stat10) => {
82316
83385
  if (er) {
82317
83386
  reject(er);
@@ -82444,7 +83513,7 @@ var replaceAsync = (opt, files) => {
82444
83513
  };
82445
83514
  fs17.read(fd, headBuf, 0, 512, position, onread);
82446
83515
  };
82447
- const promise = new Promise((resolve16, reject) => {
83516
+ const promise = new Promise((resolve17, reject) => {
82448
83517
  p.on("error", reject);
82449
83518
  let flag = "r+";
82450
83519
  const onopen = (er, fd) => {
@@ -82469,7 +83538,7 @@ var replaceAsync = (opt, files) => {
82469
83538
  });
82470
83539
  p.pipe(stream);
82471
83540
  stream.on("error", reject);
82472
- stream.on("close", resolve16);
83541
+ stream.on("close", resolve17);
82473
83542
  addFilesAsync2(p, files);
82474
83543
  });
82475
83544
  });
@@ -82599,7 +83668,7 @@ function decodeFilePath(path12) {
82599
83668
  init_logger();
82600
83669
  init_types3();
82601
83670
  import { copyFile as copyFile3, lstat as lstat4, mkdir as mkdir22, readdir as readdir14 } from "node:fs/promises";
82602
- import { join as join70, relative as relative9 } from "node:path";
83671
+ import { join as join69, relative as relative9 } from "node:path";
82603
83672
  async function withRetry(fn, retries = 3) {
82604
83673
  for (let i = 0;i < retries; i++) {
82605
83674
  try {
@@ -82621,8 +83690,8 @@ async function moveDirectoryContents(sourceDir, destDir, shouldExclude, sizeTrac
82621
83690
  await mkdir22(destDir, { recursive: true });
82622
83691
  const entries = await readdir14(sourceDir, { encoding: "utf8" });
82623
83692
  for (const entry of entries) {
82624
- const sourcePath = join70(sourceDir, entry);
82625
- const destPath = join70(destDir, entry);
83693
+ const sourcePath = join69(sourceDir, entry);
83694
+ const destPath = join69(destDir, entry);
82626
83695
  const relativePath = relative9(sourceDir, sourcePath);
82627
83696
  if (!isPathSafe(destDir, destPath)) {
82628
83697
  logger.warning(`Skipping unsafe path: ${relativePath}`);
@@ -82649,8 +83718,8 @@ async function copyDirectory(sourceDir, destDir, shouldExclude, sizeTracker) {
82649
83718
  await mkdir22(destDir, { recursive: true });
82650
83719
  const entries = await readdir14(sourceDir, { encoding: "utf8" });
82651
83720
  for (const entry of entries) {
82652
- const sourcePath = join70(sourceDir, entry);
82653
- const destPath = join70(destDir, entry);
83721
+ const sourcePath = join69(sourceDir, entry);
83722
+ const destPath = join69(destDir, entry);
82654
83723
  const relativePath = relative9(sourceDir, sourcePath);
82655
83724
  if (!isPathSafe(destDir, destPath)) {
82656
83725
  logger.warning(`Skipping unsafe path: ${relativePath}`);
@@ -82698,7 +83767,7 @@ class TarExtractor {
82698
83767
  logger.debug(`Root entries: ${entries.join(", ")}`);
82699
83768
  if (entries.length === 1) {
82700
83769
  const rootEntry = entries[0];
82701
- const rootPath = join71(tempExtractDir, rootEntry);
83770
+ const rootPath = join70(tempExtractDir, rootEntry);
82702
83771
  const rootStat = await stat10(rootPath);
82703
83772
  if (rootStat.isDirectory()) {
82704
83773
  const rootContents = await readdir15(rootPath, { encoding: "utf8" });
@@ -82714,7 +83783,7 @@ class TarExtractor {
82714
83783
  }
82715
83784
  } else {
82716
83785
  await mkdir23(destDir, { recursive: true });
82717
- await copyFile4(rootPath, join71(destDir, rootEntry));
83786
+ await copyFile4(rootPath, join70(destDir, rootEntry));
82718
83787
  }
82719
83788
  } else {
82720
83789
  logger.debug("Multiple root entries - moving all");
@@ -82737,26 +83806,26 @@ init_logger();
82737
83806
  var import_extract_zip = __toESM(require_extract_zip(), 1);
82738
83807
  import { execFile as execFile8 } from "node:child_process";
82739
83808
  import { copyFile as copyFile5, mkdir as mkdir24, readdir as readdir16, rm as rm9, stat as stat11 } from "node:fs/promises";
82740
- import { join as join72 } from "node:path";
83809
+ import { join as join71 } from "node:path";
82741
83810
  class ZipExtractor {
82742
83811
  async tryNativeUnzip(archivePath, destDir) {
82743
83812
  if (!isMacOS()) {
82744
83813
  return false;
82745
83814
  }
82746
- return new Promise((resolve16) => {
83815
+ return new Promise((resolve17) => {
82747
83816
  mkdir24(destDir, { recursive: true }).then(() => {
82748
83817
  execFile8("unzip", ["-o", "-q", archivePath, "-d", destDir], (error, _stdout, stderr) => {
82749
83818
  if (error) {
82750
83819
  logger.debug(`Native unzip failed: ${stderr || error.message}`);
82751
- resolve16(false);
83820
+ resolve17(false);
82752
83821
  return;
82753
83822
  }
82754
83823
  logger.debug("Native unzip succeeded");
82755
- resolve16(true);
83824
+ resolve17(true);
82756
83825
  });
82757
83826
  }).catch((err) => {
82758
83827
  logger.debug(`Failed to create directory for native unzip: ${err.message}`);
82759
- resolve16(false);
83828
+ resolve17(false);
82760
83829
  });
82761
83830
  });
82762
83831
  }
@@ -82785,7 +83854,7 @@ class ZipExtractor {
82785
83854
  logger.debug(`Root entries: ${entries.join(", ")}`);
82786
83855
  if (entries.length === 1) {
82787
83856
  const rootEntry = entries[0];
82788
- const rootPath = join72(tempExtractDir, rootEntry);
83857
+ const rootPath = join71(tempExtractDir, rootEntry);
82789
83858
  const rootStat = await stat11(rootPath);
82790
83859
  if (rootStat.isDirectory()) {
82791
83860
  const rootContents = await readdir16(rootPath, { encoding: "utf8" });
@@ -82801,7 +83870,7 @@ class ZipExtractor {
82801
83870
  }
82802
83871
  } else {
82803
83872
  await mkdir24(destDir, { recursive: true });
82804
- await copyFile5(rootPath, join72(destDir, rootEntry));
83873
+ await copyFile5(rootPath, join71(destDir, rootEntry));
82805
83874
  }
82806
83875
  } else {
82807
83876
  logger.debug("Multiple root entries - moving all");
@@ -82900,7 +83969,7 @@ class DownloadManager {
82900
83969
  async createTempDir() {
82901
83970
  const timestamp = Date.now();
82902
83971
  const counter = DownloadManager.tempDirCounter++;
82903
- const primaryTempDir = join73(tmpdir4(), `claudekit-${timestamp}-${counter}`);
83972
+ const primaryTempDir = join72(tmpdir4(), `claudekit-${timestamp}-${counter}`);
82904
83973
  try {
82905
83974
  await mkdir25(primaryTempDir, { recursive: true });
82906
83975
  logger.debug(`Created temp directory: ${primaryTempDir}`);
@@ -82917,7 +83986,7 @@ Solutions:
82917
83986
  2. Set HOME environment variable
82918
83987
  3. Try running from a different directory`);
82919
83988
  }
82920
- const fallbackTempDir = join73(homeDir, ".claudekit", "tmp", `claudekit-${timestamp}-${counter}`);
83989
+ const fallbackTempDir = join72(homeDir, ".claudekit", "tmp", `claudekit-${timestamp}-${counter}`);
82921
83990
  try {
82922
83991
  await mkdir25(fallbackTempDir, { recursive: true });
82923
83992
  logger.debug(`Created temp directory (fallback): ${fallbackTempDir}`);
@@ -83266,349 +84335,8 @@ async function handleDownload(ctx) {
83266
84335
  };
83267
84336
  }
83268
84337
  // src/commands/init/phases/merge-handler.ts
83269
- import { join as join89 } from "node:path";
83270
-
83271
- // src/domains/installation/deletion-handler.ts
83272
- import { existsSync as existsSync48, lstatSync as lstatSync3, readdirSync as readdirSync3, rmSync as rmSync2, rmdirSync, unlinkSync as unlinkSync3 } from "node:fs";
83273
- import { dirname as dirname18, join as join76, relative as relative10, resolve as resolve17, sep as sep3 } from "node:path";
83274
-
83275
- // src/services/file-operations/manifest/manifest-reader.ts
83276
- init_metadata_migration();
83277
- init_logger();
83278
- init_types3();
83279
- var import_fs_extra8 = __toESM(require_lib3(), 1);
83280
- import { join as join75 } from "node:path";
83281
- async function readManifest(claudeDir2) {
83282
- const metadataPath = join75(claudeDir2, "metadata.json");
83283
- if (!await import_fs_extra8.pathExists(metadataPath)) {
83284
- return null;
83285
- }
83286
- try {
83287
- const content = await import_fs_extra8.readFile(metadataPath, "utf-8");
83288
- const parsed = JSON.parse(content);
83289
- return MetadataSchema.parse(parsed);
83290
- } catch (error) {
83291
- logger.debug(`Failed to read manifest: ${error}`);
83292
- return null;
83293
- }
83294
- }
83295
- async function readKitManifest(claudeDir2, kit) {
83296
- const metadata = await readManifest(claudeDir2);
83297
- if (!metadata)
83298
- return null;
83299
- return getKitMetadata(metadata, kit);
83300
- }
83301
- async function findFileInInstalledKits(claudeDir2, relativePath, excludeKit) {
83302
- const metadata = await readManifest(claudeDir2);
83303
- if (!metadata?.kits) {
83304
- return {
83305
- exists: false,
83306
- ownerKit: null,
83307
- checksum: null,
83308
- version: null,
83309
- sourceTimestamp: null,
83310
- installedAt: null
83311
- };
83312
- }
83313
- for (const [kitName, kitMeta] of Object.entries(metadata.kits)) {
83314
- const kit = kitName;
83315
- if (kit === excludeKit)
83316
- continue;
83317
- if (!kitMeta.files)
83318
- continue;
83319
- const file = kitMeta.files.find((f3) => f3.path === relativePath);
83320
- if (file) {
83321
- return {
83322
- exists: true,
83323
- ownerKit: kit,
83324
- checksum: file.checksum,
83325
- version: kitMeta.version,
83326
- sourceTimestamp: file.sourceTimestamp ?? null,
83327
- installedAt: file.installedAt ?? null
83328
- };
83329
- }
83330
- }
83331
- return {
83332
- exists: false,
83333
- ownerKit: null,
83334
- checksum: null,
83335
- version: null,
83336
- sourceTimestamp: null,
83337
- installedAt: null
83338
- };
83339
- }
83340
- async function getUninstallManifest(claudeDir2, kit) {
83341
- const detection = await detectMetadataFormat(claudeDir2);
83342
- if (detection.format === "multi-kit" && detection.metadata?.kits) {
83343
- const installedKits = Object.keys(detection.metadata.kits);
83344
- if (kit) {
83345
- const kitMeta = detection.metadata.kits[kit];
83346
- if (!kitMeta?.files) {
83347
- return {
83348
- filesToRemove: [],
83349
- filesToPreserve: USER_CONFIG_PATTERNS,
83350
- hasManifest: true,
83351
- isMultiKit: true,
83352
- remainingKits: installedKits.filter((k2) => k2 !== kit)
83353
- };
83354
- }
83355
- const kitFiles = kitMeta.files.map((f3) => f3.path);
83356
- const sharedFiles = new Set;
83357
- for (const otherKit of installedKits) {
83358
- if (otherKit !== kit) {
83359
- const otherMeta = detection.metadata.kits[otherKit];
83360
- if (otherMeta?.files) {
83361
- for (const f3 of otherMeta.files) {
83362
- sharedFiles.add(f3.path);
83363
- }
83364
- }
83365
- }
83366
- }
83367
- const filesToRemove = kitFiles.filter((f3) => !sharedFiles.has(f3));
83368
- const filesToPreserve = [
83369
- ...USER_CONFIG_PATTERNS,
83370
- ...kitFiles.filter((f3) => sharedFiles.has(f3))
83371
- ];
83372
- return {
83373
- filesToRemove,
83374
- filesToPreserve,
83375
- hasManifest: true,
83376
- isMultiKit: true,
83377
- remainingKits: installedKits.filter((k2) => k2 !== kit)
83378
- };
83379
- }
83380
- const allFiles = getAllTrackedFiles(detection.metadata);
83381
- return {
83382
- filesToRemove: allFiles.map((f3) => f3.path),
83383
- filesToPreserve: USER_CONFIG_PATTERNS,
83384
- hasManifest: true,
83385
- isMultiKit: true,
83386
- remainingKits: []
83387
- };
83388
- }
83389
- if (detection.format === "legacy" && detection.metadata) {
83390
- const legacyFiles2 = detection.metadata.files?.map((f3) => f3.path) || [];
83391
- const installedFiles = detection.metadata.installedFiles || [];
83392
- const hasFiles = legacyFiles2.length > 0 || installedFiles.length > 0;
83393
- if (!hasFiles) {
83394
- const legacyDirs2 = ["commands", "agents", "skills", "rules", "workflows", "hooks", "scripts"];
83395
- const legacyFileList = ["metadata.json"];
83396
- return {
83397
- filesToRemove: [...legacyDirs2, ...legacyFileList],
83398
- filesToPreserve: USER_CONFIG_PATTERNS,
83399
- hasManifest: false,
83400
- isMultiKit: false,
83401
- remainingKits: []
83402
- };
83403
- }
83404
- return {
83405
- filesToRemove: legacyFiles2.length > 0 ? legacyFiles2 : installedFiles,
83406
- filesToPreserve: detection.metadata.userConfigFiles || USER_CONFIG_PATTERNS,
83407
- hasManifest: true,
83408
- isMultiKit: false,
83409
- remainingKits: []
83410
- };
83411
- }
83412
- const legacyDirs = ["commands", "agents", "skills", "rules", "workflows", "hooks", "scripts"];
83413
- const legacyFiles = ["metadata.json"];
83414
- return {
83415
- filesToRemove: [...legacyDirs, ...legacyFiles],
83416
- filesToPreserve: USER_CONFIG_PATTERNS,
83417
- hasManifest: false,
83418
- isMultiKit: false,
83419
- remainingKits: []
83420
- };
83421
- }
83422
-
83423
- // src/domains/installation/deletion-handler.ts
83424
- init_logger();
83425
- init_path_resolver();
83426
- var import_fs_extra9 = __toESM(require_lib3(), 1);
83427
- var import_picomatch2 = __toESM(require_picomatch2(), 1);
83428
- function findFileInMetadata(metadata, path13) {
83429
- if (!metadata)
83430
- return null;
83431
- if (metadata.kits) {
83432
- for (const kitMeta of Object.values(metadata.kits)) {
83433
- if (kitMeta?.files) {
83434
- const found = kitMeta.files.find((f3) => f3.path === path13);
83435
- if (found)
83436
- return found;
83437
- }
83438
- }
83439
- }
83440
- if (metadata.files) {
83441
- const found = metadata.files.find((f3) => f3.path === path13);
83442
- if (found)
83443
- return found;
83444
- }
83445
- return null;
83446
- }
83447
- function shouldDeletePath(path13, metadata) {
83448
- const tracked = findFileInMetadata(metadata, path13);
83449
- if (!tracked)
83450
- return true;
83451
- return tracked.ownership !== "user";
83452
- }
83453
- function collectFilesRecursively(dir, baseDir) {
83454
- const results = [];
83455
- if (!existsSync48(dir))
83456
- return results;
83457
- try {
83458
- const entries = readdirSync3(dir, { withFileTypes: true });
83459
- for (const entry of entries) {
83460
- const fullPath = join76(dir, entry.name);
83461
- const relativePath = relative10(baseDir, fullPath);
83462
- if (entry.isDirectory()) {
83463
- results.push(...collectFilesRecursively(fullPath, baseDir));
83464
- } else {
83465
- results.push(relativePath);
83466
- }
83467
- }
83468
- } catch {}
83469
- return results;
83470
- }
83471
- function expandGlobPatterns(patterns, claudeDir2) {
83472
- const expanded = [];
83473
- const allFiles = collectFilesRecursively(claudeDir2, claudeDir2);
83474
- for (const pattern of patterns) {
83475
- if (PathResolver.isGlobPattern(pattern)) {
83476
- const matcher = import_picomatch2.default(pattern);
83477
- const matches = allFiles.filter((file) => matcher(file));
83478
- expanded.push(...matches);
83479
- if (matches.length > 0) {
83480
- logger.debug(`Pattern "${pattern}" matched ${matches.length} files`);
83481
- }
83482
- } else {
83483
- expanded.push(pattern);
83484
- }
83485
- }
83486
- return [...new Set(expanded)];
83487
- }
83488
- var MAX_CLEANUP_ITERATIONS = 50;
83489
- function cleanupEmptyDirectories(filePath, claudeDir2) {
83490
- const normalizedClaudeDir = resolve17(claudeDir2);
83491
- let currentDir = resolve17(dirname18(filePath));
83492
- let iterations = 0;
83493
- while (currentDir !== normalizedClaudeDir && currentDir.startsWith(normalizedClaudeDir) && iterations < MAX_CLEANUP_ITERATIONS) {
83494
- iterations++;
83495
- try {
83496
- const entries = readdirSync3(currentDir);
83497
- if (entries.length === 0) {
83498
- rmdirSync(currentDir);
83499
- logger.debug(`Removed empty directory: ${currentDir}`);
83500
- currentDir = resolve17(dirname18(currentDir));
83501
- } else {
83502
- break;
83503
- }
83504
- } catch {
83505
- break;
83506
- }
83507
- }
83508
- }
83509
- function deletePath(fullPath, claudeDir2) {
83510
- const normalizedPath = resolve17(fullPath);
83511
- const normalizedClaudeDir = resolve17(claudeDir2);
83512
- if (!normalizedPath.startsWith(`${normalizedClaudeDir}${sep3}`) && normalizedPath !== normalizedClaudeDir) {
83513
- throw new Error(`Path traversal detected: ${fullPath}`);
83514
- }
83515
- try {
83516
- const stat13 = lstatSync3(fullPath);
83517
- if (stat13.isDirectory()) {
83518
- rmSync2(fullPath, { recursive: true, force: true });
83519
- } else {
83520
- unlinkSync3(fullPath);
83521
- cleanupEmptyDirectories(fullPath, claudeDir2);
83522
- }
83523
- } catch (error) {
83524
- throw new Error(`Failed to delete ${fullPath}: ${error instanceof Error ? error.message : String(error)}`);
83525
- }
83526
- }
83527
- async function updateMetadataAfterDeletion(claudeDir2, deletedPaths) {
83528
- const metadataPath = join76(claudeDir2, "metadata.json");
83529
- if (!await import_fs_extra9.pathExists(metadataPath)) {
83530
- return;
83531
- }
83532
- let content;
83533
- try {
83534
- content = await import_fs_extra9.readFile(metadataPath, "utf-8");
83535
- } catch {
83536
- logger.debug("Failed to read metadata.json for cleanup");
83537
- return;
83538
- }
83539
- let metadata;
83540
- try {
83541
- metadata = JSON.parse(content);
83542
- } catch {
83543
- logger.debug("Failed to parse metadata.json for cleanup");
83544
- return;
83545
- }
83546
- const deletedSet = new Set(deletedPaths);
83547
- const isDeletedOrInDeletedDir = (path13) => {
83548
- if (deletedSet.has(path13))
83549
- return true;
83550
- for (const deleted of deletedPaths) {
83551
- if (path13.startsWith(`${deleted}/`))
83552
- return true;
83553
- }
83554
- return false;
83555
- };
83556
- if (metadata.kits) {
83557
- for (const kitName of Object.keys(metadata.kits)) {
83558
- const kit = metadata.kits[kitName];
83559
- if (kit?.files) {
83560
- kit.files = kit.files.filter((f3) => !isDeletedOrInDeletedDir(f3.path));
83561
- }
83562
- }
83563
- }
83564
- if (metadata.files) {
83565
- metadata.files = metadata.files.filter((f3) => !isDeletedOrInDeletedDir(f3.path));
83566
- }
83567
- try {
83568
- await import_fs_extra9.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
83569
- logger.debug(`Updated metadata.json, removed ${deletedPaths.length} entries`);
83570
- } catch {
83571
- logger.debug("Failed to write updated metadata.json");
83572
- }
83573
- }
83574
- async function handleDeletions(sourceMetadata, claudeDir2) {
83575
- const deletionPatterns = sourceMetadata.deletions || [];
83576
- if (deletionPatterns.length === 0) {
83577
- return { deletedPaths: [], preservedPaths: [], errors: [] };
83578
- }
83579
- const deletions = expandGlobPatterns(deletionPatterns, claudeDir2);
83580
- const userMetadata = await readManifest(claudeDir2);
83581
- const result = { deletedPaths: [], preservedPaths: [], errors: [] };
83582
- for (const path13 of deletions) {
83583
- const fullPath = join76(claudeDir2, path13);
83584
- const normalizedPath = resolve17(fullPath);
83585
- const normalizedClaudeDir = resolve17(claudeDir2);
83586
- if (!normalizedPath.startsWith(`${normalizedClaudeDir}${sep3}`)) {
83587
- logger.warning(`Skipping invalid path: ${path13}`);
83588
- result.errors.push(path13);
83589
- continue;
83590
- }
83591
- if (!shouldDeletePath(path13, userMetadata)) {
83592
- result.preservedPaths.push(path13);
83593
- logger.verbose(`Preserved user file: ${path13}`);
83594
- continue;
83595
- }
83596
- if (existsSync48(fullPath)) {
83597
- try {
83598
- deletePath(fullPath, claudeDir2);
83599
- result.deletedPaths.push(path13);
83600
- logger.verbose(`Deleted: ${path13}`);
83601
- } catch (error) {
83602
- result.errors.push(path13);
83603
- logger.debug(`Failed to delete ${path13}: ${error}`);
83604
- }
83605
- }
83606
- }
83607
- if (result.deletedPaths.length > 0) {
83608
- await updateMetadataAfterDeletion(claudeDir2, result.deletedPaths);
83609
- }
83610
- return result;
83611
- }
84338
+ init_deletion_handler();
84339
+ import { join as join88 } from "node:path";
83612
84340
 
83613
84341
  // src/domains/installation/file-merger.ts
83614
84342
  init_logger();
@@ -83620,9 +84348,10 @@ init_logger();
83620
84348
  init_types3();
83621
84349
  var import_fs_extra12 = __toESM(require_lib3(), 1);
83622
84350
  var import_ignore3 = __toESM(require_ignore(), 1);
83623
- import { dirname as dirname20, join as join79, relative as relative12 } from "node:path";
84351
+ import { dirname as dirname21, join as join78, relative as relative12 } from "node:path";
83624
84352
 
83625
84353
  // src/domains/installation/selective-merger.ts
84354
+ init_manifest_reader();
83626
84355
  import { stat as stat13 } from "node:fs/promises";
83627
84356
  init_logger();
83628
84357
  var import_semver2 = __toESM(require_semver2(), 1);
@@ -83799,7 +84528,7 @@ init_logger();
83799
84528
  var import_fs_extra10 = __toESM(require_lib3(), 1);
83800
84529
  var import_ignore2 = __toESM(require_ignore(), 1);
83801
84530
  import { relative as relative11 } from "node:path";
83802
- import { join as join77 } from "node:path";
84531
+ import { join as join76 } from "node:path";
83803
84532
 
83804
84533
  // node_modules/@isaacs/balanced-match/dist/esm/index.js
83805
84534
  var balanced = (a3, b3, str2) => {
@@ -85255,7 +85984,7 @@ class FileScanner {
85255
85984
  const files = [];
85256
85985
  const entries = await import_fs_extra10.readdir(dir, { encoding: "utf8" });
85257
85986
  for (const entry of entries) {
85258
- const fullPath = join77(dir, entry);
85987
+ const fullPath = join76(dir, entry);
85259
85988
  const relativePath = relative11(baseDir, fullPath);
85260
85989
  const normalizedRelativePath = relativePath.replace(/\\/g, "/");
85261
85990
  const stats = await import_fs_extra10.lstat(fullPath);
@@ -85295,7 +86024,7 @@ import { execSync as execSync4 } from "node:child_process";
85295
86024
  init_shared();
85296
86025
  import { existsSync as existsSync49 } from "node:fs";
85297
86026
  import { mkdir as mkdir26, readFile as readFile38, writeFile as writeFile22 } from "node:fs/promises";
85298
- import { dirname as dirname19, join as join78 } from "node:path";
86027
+ import { dirname as dirname20, join as join77 } from "node:path";
85299
86028
  var CK_JSON_FILE = ".ck.json";
85300
86029
 
85301
86030
  class InstalledSettingsTracker {
@@ -85309,9 +86038,9 @@ class InstalledSettingsTracker {
85309
86038
  }
85310
86039
  getCkJsonPath() {
85311
86040
  if (this.isGlobal) {
85312
- return join78(this.projectDir, CK_JSON_FILE);
86041
+ return join77(this.projectDir, CK_JSON_FILE);
85313
86042
  }
85314
- return join78(this.projectDir, ".claude", CK_JSON_FILE);
86043
+ return join77(this.projectDir, ".claude", CK_JSON_FILE);
85315
86044
  }
85316
86045
  async loadInstalledSettings() {
85317
86046
  const ckJsonPath = this.getCkJsonPath();
@@ -85346,7 +86075,7 @@ class InstalledSettingsTracker {
85346
86075
  data.kits[this.kitName] = {};
85347
86076
  }
85348
86077
  data.kits[this.kitName].installedSettings = settings;
85349
- await mkdir26(dirname19(ckJsonPath), { recursive: true });
86078
+ await mkdir26(dirname20(ckJsonPath), { recursive: true });
85350
86079
  await writeFile22(ckJsonPath, JSON.stringify(data, null, 2), "utf-8");
85351
86080
  logger.debug(`Saved installed settings to ${ckJsonPath}`);
85352
86081
  } catch (error) {
@@ -85864,7 +86593,7 @@ class CopyExecutor {
85864
86593
  for (const file of files) {
85865
86594
  const relativePath = relative12(sourceDir, file);
85866
86595
  const normalizedRelativePath = relativePath.replace(/\\/g, "/");
85867
- const destPath = join79(destDir, relativePath);
86596
+ const destPath = join78(destDir, relativePath);
85868
86597
  if (await import_fs_extra12.pathExists(destPath)) {
85869
86598
  if (this.fileScanner.shouldNeverCopy(normalizedRelativePath)) {
85870
86599
  logger.debug(`Security-sensitive file exists but won't be overwritten: ${normalizedRelativePath}`);
@@ -85886,7 +86615,7 @@ class CopyExecutor {
85886
86615
  for (const file of files) {
85887
86616
  const relativePath = relative12(sourceDir, file);
85888
86617
  const normalizedRelativePath = relativePath.replace(/\\/g, "/");
85889
- const destPath = join79(destDir, relativePath);
86618
+ const destPath = join78(destDir, relativePath);
85890
86619
  if (this.fileScanner.shouldNeverCopy(normalizedRelativePath)) {
85891
86620
  logger.debug(`Skipping security-sensitive file: ${normalizedRelativePath}`);
85892
86621
  skippedCount++;
@@ -85963,10 +86692,10 @@ class CopyExecutor {
85963
86692
  }
85964
86693
  trackInstalledFile(relativePath) {
85965
86694
  this.installedFiles.add(relativePath);
85966
- let dir = dirname20(relativePath);
86695
+ let dir = dirname21(relativePath);
85967
86696
  while (dir && dir !== "." && dir !== "/") {
85968
86697
  this.installedDirectories.add(`${dir}/`);
85969
- dir = dirname20(dir);
86698
+ dir = dirname21(dir);
85970
86699
  }
85971
86700
  }
85972
86701
  }
@@ -86056,15 +86785,19 @@ class FileMerger {
86056
86785
 
86057
86786
  // src/domains/migration/legacy-migration.ts
86058
86787
  import { readdir as readdir18, stat as stat14 } from "node:fs/promises";
86059
- import { join as join83, relative as relative13 } from "node:path";
86788
+ import { join as join82, relative as relative13 } from "node:path";
86789
+
86790
+ // src/services/file-operations/manifest/index.ts
86791
+ init_manifest_reader();
86792
+
86060
86793
  // src/services/file-operations/manifest/manifest-tracker.ts
86061
- import { join as join82 } from "node:path";
86794
+ import { join as join81 } from "node:path";
86062
86795
 
86063
86796
  // src/domains/migration/release-manifest.ts
86064
86797
  init_logger();
86065
86798
  init_zod();
86066
86799
  var import_fs_extra13 = __toESM(require_lib3(), 1);
86067
- import { join as join80 } from "node:path";
86800
+ import { join as join79 } from "node:path";
86068
86801
  var ReleaseManifestFileSchema = exports_external.object({
86069
86802
  path: exports_external.string(),
86070
86803
  checksum: exports_external.string().regex(/^[a-f0-9]{64}$/),
@@ -86079,7 +86812,7 @@ var ReleaseManifestSchema = exports_external.object({
86079
86812
 
86080
86813
  class ReleaseManifestLoader {
86081
86814
  static async load(extractDir) {
86082
- const manifestPath = join80(extractDir, "release-manifest.json");
86815
+ const manifestPath = join79(extractDir, "release-manifest.json");
86083
86816
  try {
86084
86817
  const content = await import_fs_extra13.readFile(manifestPath, "utf-8");
86085
86818
  const parsed = JSON.parse(content);
@@ -86103,11 +86836,12 @@ init_safe_spinner();
86103
86836
  init_metadata_migration();
86104
86837
  init_logger();
86105
86838
  init_types3();
86839
+ init_manifest_reader();
86106
86840
  var import_fs_extra14 = __toESM(require_lib3(), 1);
86107
86841
  var import_proper_lockfile5 = __toESM(require_proper_lockfile(), 1);
86108
- import { join as join81 } from "node:path";
86842
+ import { join as join80 } from "node:path";
86109
86843
  async function writeManifest(claudeDir2, kitName, version, scope, kitType, trackedFiles, userConfigFiles) {
86110
- const metadataPath = join81(claudeDir2, "metadata.json");
86844
+ const metadataPath = join80(claudeDir2, "metadata.json");
86111
86845
  const kit = kitType || (/\bmarketing\b/i.test(kitName) ? "marketing" : "engineer");
86112
86846
  await import_fs_extra14.ensureFile(metadataPath);
86113
86847
  let release = null;
@@ -86127,20 +86861,54 @@ async function writeManifest(claudeDir2, kitName, version, scope, kitType, track
86127
86861
  const content = await import_fs_extra14.readFile(metadataPath, "utf-8");
86128
86862
  const parsed = JSON.parse(content);
86129
86863
  if (parsed && typeof parsed === "object" && Object.keys(parsed).length > 0) {
86130
- existingMetadata = parsed;
86864
+ const validatedExisting = MetadataSchema.safeParse(parsed);
86865
+ if (validatedExisting.success) {
86866
+ existingMetadata = validatedExisting.data;
86867
+ } else {
86868
+ logger.warning("Existing metadata.json is invalid; preserving recoverable fields and rebuilding the rest");
86869
+ const raw2 = parsed;
86870
+ const recoveredKits = {};
86871
+ if (raw2.kits && typeof raw2.kits === "object") {
86872
+ for (const [rawKitName, rawKitValue] of Object.entries(raw2.kits)) {
86873
+ if ((rawKitName === "engineer" || rawKitName === "marketing") && rawKitValue && typeof rawKitValue === "object") {
86874
+ const recoveredKit = rawKitValue;
86875
+ if (typeof recoveredKit.version === "string" && typeof recoveredKit.installedAt === "string") {
86876
+ recoveredKits[rawKitName] = recoveredKit;
86877
+ }
86878
+ }
86879
+ }
86880
+ }
86881
+ existingMetadata = {
86882
+ kits: recoveredKits,
86883
+ scope: raw2.scope === "local" || raw2.scope === "global" ? raw2.scope : undefined,
86884
+ name: typeof raw2.name === "string" ? raw2.name : undefined,
86885
+ version: typeof raw2.version === "string" ? raw2.version : undefined,
86886
+ installedAt: typeof raw2.installedAt === "string" ? raw2.installedAt : undefined,
86887
+ userConfigFiles: Array.isArray(raw2.userConfigFiles) ? raw2.userConfigFiles.filter((entry) => typeof entry === "string") : undefined
86888
+ };
86889
+ }
86131
86890
  }
86132
86891
  } catch (error) {
86133
86892
  logger.debug(`Could not read existing metadata: ${error}`);
86134
86893
  }
86135
86894
  }
86136
86895
  const installedAt = new Date().toISOString();
86896
+ const existingKits = existingMetadata.kits || {};
86897
+ const existingKitMetadata = existingKits[kit];
86137
86898
  const kitMetadata = {
86899
+ ...existingKitMetadata,
86138
86900
  version,
86139
86901
  installedAt,
86140
86902
  files: trackedFiles.length > 0 ? trackedFiles : undefined
86141
86903
  };
86142
- const existingKits = existingMetadata.kits || {};
86143
86904
  const otherKitsExist = Object.keys(existingKits).some((k2) => k2 !== kit);
86905
+ const mergedUserConfigFiles = [
86906
+ ...new Set([
86907
+ ...existingMetadata.userConfigFiles || [],
86908
+ ...USER_CONFIG_PATTERNS,
86909
+ ...userConfigFiles
86910
+ ])
86911
+ ];
86144
86912
  const metadata = {
86145
86913
  kits: {
86146
86914
  ...existingKits,
@@ -86150,7 +86918,7 @@ async function writeManifest(claudeDir2, kitName, version, scope, kitType, track
86150
86918
  name: otherKitsExist ? existingMetadata.name ?? kitName : kitName,
86151
86919
  version: otherKitsExist ? existingMetadata.version ?? version : version,
86152
86920
  installedAt: otherKitsExist ? existingMetadata.installedAt ?? installedAt : installedAt,
86153
- userConfigFiles: [...USER_CONFIG_PATTERNS, ...userConfigFiles]
86921
+ userConfigFiles: mergedUserConfigFiles
86154
86922
  };
86155
86923
  const validated = MetadataSchema.parse(metadata);
86156
86924
  await import_fs_extra14.writeFile(metadataPath, JSON.stringify(validated, null, 2), "utf-8");
@@ -86163,7 +86931,7 @@ async function writeManifest(claudeDir2, kitName, version, scope, kitType, track
86163
86931
  }
86164
86932
  }
86165
86933
  async function removeKitFromManifest(claudeDir2, kit) {
86166
- const metadataPath = join81(claudeDir2, "metadata.json");
86934
+ const metadataPath = join80(claudeDir2, "metadata.json");
86167
86935
  if (!await import_fs_extra14.pathExists(metadataPath))
86168
86936
  return false;
86169
86937
  let release = null;
@@ -86293,7 +87061,7 @@ function buildFileTrackingList(options2) {
86293
87061
  if (!isGlobal && !installedPath.startsWith(".claude/"))
86294
87062
  continue;
86295
87063
  const relativePath = isGlobal ? installedPath : installedPath.replace(/^\.claude\//, "");
86296
- const filePath = join82(claudeDir2, relativePath);
87064
+ const filePath = join81(claudeDir2, relativePath);
86297
87065
  const manifestEntry = releaseManifest ? ReleaseManifestLoader.findFile(releaseManifest, installedPath) : null;
86298
87066
  const ownership = manifestEntry ? "ck" : "user";
86299
87067
  filesToTrack.push({
@@ -86400,7 +87168,7 @@ class LegacyMigration {
86400
87168
  continue;
86401
87169
  if (SKIP_DIRS_ALL.includes(entry))
86402
87170
  continue;
86403
- const fullPath = join83(dir, entry);
87171
+ const fullPath = join82(dir, entry);
86404
87172
  let stats;
86405
87173
  try {
86406
87174
  stats = await stat14(fullPath);
@@ -86502,7 +87270,7 @@ User-created files (sample):`);
86502
87270
  ];
86503
87271
  if (filesToChecksum.length > 0) {
86504
87272
  const checksumResults = await mapWithLimit(filesToChecksum, async ({ relativePath, ownership }) => {
86505
- const fullPath = join83(claudeDir2, relativePath);
87273
+ const fullPath = join82(claudeDir2, relativePath);
86506
87274
  const checksum = await OwnershipChecker.calculateChecksum(fullPath);
86507
87275
  return { relativePath, checksum, ownership };
86508
87276
  });
@@ -86523,7 +87291,7 @@ User-created files (sample):`);
86523
87291
  installedAt: new Date().toISOString(),
86524
87292
  files: trackedFiles
86525
87293
  };
86526
- const metadataPath = join83(claudeDir2, "metadata.json");
87294
+ const metadataPath = join82(claudeDir2, "metadata.json");
86527
87295
  await import_fs_extra15.writeFile(metadataPath, JSON.stringify(updatedMetadata, null, 2));
86528
87296
  logger.success(`Migration complete: tracked ${trackedFiles.length} files`);
86529
87297
  return true;
@@ -86629,7 +87397,7 @@ function buildConflictSummary(fileConflicts, hookConflicts, mcpConflicts) {
86629
87397
  init_logger();
86630
87398
  init_skip_directories();
86631
87399
  var import_fs_extra16 = __toESM(require_lib3(), 1);
86632
- import { join as join84, relative as relative14, resolve as resolve18 } from "node:path";
87400
+ import { join as join83, relative as relative14, resolve as resolve19 } from "node:path";
86633
87401
 
86634
87402
  class FileScanner2 {
86635
87403
  static async getFiles(dirPath, relativeTo) {
@@ -86645,7 +87413,7 @@ class FileScanner2 {
86645
87413
  logger.debug(`Skipping directory: ${entry}`);
86646
87414
  continue;
86647
87415
  }
86648
- const fullPath = join84(dirPath, entry);
87416
+ const fullPath = join83(dirPath, entry);
86649
87417
  if (!FileScanner2.isSafePath(basePath, fullPath)) {
86650
87418
  logger.warning(`Skipping potentially unsafe path: ${entry}`);
86651
87419
  continue;
@@ -86680,8 +87448,8 @@ class FileScanner2 {
86680
87448
  return files;
86681
87449
  }
86682
87450
  static async findCustomFiles(destDir, sourceDir, subPath) {
86683
- const destSubDir = join84(destDir, subPath);
86684
- const sourceSubDir = join84(sourceDir, subPath);
87451
+ const destSubDir = join83(destDir, subPath);
87452
+ const sourceSubDir = join83(sourceDir, subPath);
86685
87453
  logger.debug(`findCustomFiles - destDir: ${destDir}`);
86686
87454
  logger.debug(`findCustomFiles - sourceDir: ${sourceDir}`);
86687
87455
  logger.debug(`findCustomFiles - subPath: "${subPath}"`);
@@ -86709,8 +87477,8 @@ class FileScanner2 {
86709
87477
  return customFiles;
86710
87478
  }
86711
87479
  static isSafePath(basePath, targetPath) {
86712
- const resolvedBase = resolve18(basePath);
86713
- const resolvedTarget = resolve18(targetPath);
87480
+ const resolvedBase = resolve19(basePath);
87481
+ const resolvedTarget = resolve19(targetPath);
86714
87482
  return resolvedTarget.startsWith(resolvedBase);
86715
87483
  }
86716
87484
  static toPosixPath(path14) {
@@ -86722,12 +87490,12 @@ class FileScanner2 {
86722
87490
  init_logger();
86723
87491
  var import_fs_extra17 = __toESM(require_lib3(), 1);
86724
87492
  import { lstat as lstat7, mkdir as mkdir27, readdir as readdir21, stat as stat15 } from "node:fs/promises";
86725
- import { join as join86 } from "node:path";
87493
+ import { join as join85 } from "node:path";
86726
87494
 
86727
87495
  // src/services/transformers/commands-prefix/content-transformer.ts
86728
87496
  init_logger();
86729
87497
  import { readFile as readFile42, readdir as readdir20, writeFile as writeFile26 } from "node:fs/promises";
86730
- import { join as join85 } from "node:path";
87498
+ import { join as join84 } from "node:path";
86731
87499
  var TRANSFORMABLE_EXTENSIONS = new Set([
86732
87500
  ".md",
86733
87501
  ".txt",
@@ -86788,7 +87556,7 @@ async function transformCommandReferences(directory, options2 = {}) {
86788
87556
  async function processDirectory(dir) {
86789
87557
  const entries = await readdir20(dir, { withFileTypes: true });
86790
87558
  for (const entry of entries) {
86791
- const fullPath = join85(dir, entry.name);
87559
+ const fullPath = join84(dir, entry.name);
86792
87560
  if (entry.isDirectory()) {
86793
87561
  if (entry.name === "node_modules" || entry.name.startsWith(".") && entry.name !== ".claude") {
86794
87562
  continue;
@@ -86863,14 +87631,14 @@ function shouldApplyPrefix(options2) {
86863
87631
  // src/services/transformers/commands-prefix/prefix-applier.ts
86864
87632
  async function applyPrefix(extractDir) {
86865
87633
  validatePath(extractDir, "extractDir");
86866
- const commandsDir = join86(extractDir, ".claude", "commands");
87634
+ const commandsDir = join85(extractDir, ".claude", "commands");
86867
87635
  if (!await import_fs_extra17.pathExists(commandsDir)) {
86868
87636
  logger.verbose("No commands directory found, skipping prefix application");
86869
87637
  return;
86870
87638
  }
86871
87639
  logger.info("Applying /ck: prefix to slash commands...");
86872
- const backupDir = join86(extractDir, ".commands-backup");
86873
- const tempDir = join86(extractDir, ".commands-prefix-temp");
87640
+ const backupDir = join85(extractDir, ".commands-backup");
87641
+ const tempDir = join85(extractDir, ".commands-prefix-temp");
86874
87642
  try {
86875
87643
  const entries = await readdir21(commandsDir);
86876
87644
  if (entries.length === 0) {
@@ -86878,7 +87646,7 @@ async function applyPrefix(extractDir) {
86878
87646
  return;
86879
87647
  }
86880
87648
  if (entries.length === 1 && entries[0] === "ck") {
86881
- const ckDir2 = join86(commandsDir, "ck");
87649
+ const ckDir2 = join85(commandsDir, "ck");
86882
87650
  const ckStat = await stat15(ckDir2);
86883
87651
  if (ckStat.isDirectory()) {
86884
87652
  logger.verbose("Commands already have /ck: prefix, skipping");
@@ -86888,17 +87656,17 @@ async function applyPrefix(extractDir) {
86888
87656
  await import_fs_extra17.copy(commandsDir, backupDir);
86889
87657
  logger.verbose("Created backup of commands directory");
86890
87658
  await mkdir27(tempDir, { recursive: true });
86891
- const ckDir = join86(tempDir, "ck");
87659
+ const ckDir = join85(tempDir, "ck");
86892
87660
  await mkdir27(ckDir, { recursive: true });
86893
87661
  let processedCount = 0;
86894
87662
  for (const entry of entries) {
86895
- const sourcePath = join86(commandsDir, entry);
87663
+ const sourcePath = join85(commandsDir, entry);
86896
87664
  const stats = await lstat7(sourcePath);
86897
87665
  if (stats.isSymbolicLink()) {
86898
87666
  logger.warning(`Skipping symlink for security: ${entry}`);
86899
87667
  continue;
86900
87668
  }
86901
- const destPath = join86(ckDir, entry);
87669
+ const destPath = join85(ckDir, entry);
86902
87670
  await import_fs_extra17.copy(sourcePath, destPath, {
86903
87671
  overwrite: false,
86904
87672
  errorOnExist: true
@@ -86916,7 +87684,7 @@ async function applyPrefix(extractDir) {
86916
87684
  await import_fs_extra17.move(tempDir, commandsDir);
86917
87685
  await import_fs_extra17.remove(backupDir);
86918
87686
  logger.success("Successfully reorganized commands to /ck: prefix");
86919
- const claudeDir2 = join86(extractDir, ".claude");
87687
+ const claudeDir2 = join85(extractDir, ".claude");
86920
87688
  logger.info("Transforming command references in file contents...");
86921
87689
  const transformResult = await transformCommandReferences(claudeDir2, {
86922
87690
  verbose: logger.isVerbose()
@@ -86954,20 +87722,20 @@ async function applyPrefix(extractDir) {
86954
87722
  // src/services/transformers/commands-prefix/prefix-cleaner.ts
86955
87723
  init_metadata_migration();
86956
87724
  import { lstat as lstat9, readdir as readdir23 } from "node:fs/promises";
86957
- import { join as join88 } from "node:path";
87725
+ import { join as join87 } from "node:path";
86958
87726
  init_logger();
86959
87727
  var import_fs_extra19 = __toESM(require_lib3(), 1);
86960
87728
 
86961
87729
  // src/services/transformers/commands-prefix/file-processor.ts
86962
87730
  import { lstat as lstat8, readdir as readdir22 } from "node:fs/promises";
86963
- import { join as join87 } from "node:path";
87731
+ import { join as join86 } from "node:path";
86964
87732
  init_logger();
86965
87733
  var import_fs_extra18 = __toESM(require_lib3(), 1);
86966
87734
  async function scanDirectoryFiles(dir) {
86967
87735
  const files = [];
86968
87736
  const entries = await readdir22(dir);
86969
87737
  for (const entry of entries) {
86970
- const fullPath = join87(dir, entry);
87738
+ const fullPath = join86(dir, entry);
86971
87739
  const stats = await lstat8(fullPath);
86972
87740
  if (stats.isSymbolicLink()) {
86973
87741
  continue;
@@ -87095,8 +87863,8 @@ function isDifferentKitDirectory(dirName, currentKit) {
87095
87863
  async function cleanupCommandsDirectory(targetDir, isGlobal, options2 = {}) {
87096
87864
  const { dryRun = false } = options2;
87097
87865
  validatePath(targetDir, "targetDir");
87098
- const claudeDir2 = isGlobal ? targetDir : join88(targetDir, ".claude");
87099
- const commandsDir = join88(claudeDir2, "commands");
87866
+ const claudeDir2 = isGlobal ? targetDir : join87(targetDir, ".claude");
87867
+ const commandsDir = join87(claudeDir2, "commands");
87100
87868
  const accumulator = {
87101
87869
  results: [],
87102
87870
  deletedCount: 0,
@@ -87138,7 +87906,7 @@ async function cleanupCommandsDirectory(targetDir, isGlobal, options2 = {}) {
87138
87906
  }
87139
87907
  const metadataForChecks = options2.kitType ? createKitSpecificMetadata(metadata, options2.kitType) : metadata;
87140
87908
  for (const entry of entries) {
87141
- const entryPath = join88(commandsDir, entry);
87909
+ const entryPath = join87(commandsDir, entry);
87142
87910
  const stats = await lstat9(entryPath);
87143
87911
  if (stats.isSymbolicLink()) {
87144
87912
  addSymlinkSkip(entry, accumulator);
@@ -87195,7 +87963,7 @@ async function handleMerge(ctx) {
87195
87963
  let customClaudeFiles = [];
87196
87964
  if (!ctx.options.fresh) {
87197
87965
  logger.info("Scanning for custom .claude files...");
87198
- const scanSourceDir = ctx.options.global ? join89(ctx.extractDir, ".claude") : ctx.extractDir;
87966
+ const scanSourceDir = ctx.options.global ? join88(ctx.extractDir, ".claude") : ctx.extractDir;
87199
87967
  const scanTargetSubdir = ctx.options.global ? "" : ".claude";
87200
87968
  customClaudeFiles = await FileScanner2.findCustomFiles(ctx.resolvedDir, scanSourceDir, scanTargetSubdir);
87201
87969
  } else {
@@ -87260,28 +88028,38 @@ async function handleMerge(ctx) {
87260
88028
  return { ...ctx, cancelled: true };
87261
88029
  }
87262
88030
  }
87263
- const sourceDir = ctx.options.global ? join89(ctx.extractDir, ".claude") : ctx.extractDir;
88031
+ const sourceDir = ctx.options.global ? join88(ctx.extractDir, ".claude") : ctx.extractDir;
87264
88032
  await merger.merge(sourceDir, ctx.resolvedDir, ctx.isNonInteractive);
87265
88033
  const fileConflicts = merger.getFileConflicts();
87266
88034
  if (fileConflicts.length > 0 && !ctx.isNonInteractive) {
87267
88035
  const summary = buildConflictSummary(fileConflicts, [], []);
87268
88036
  displayConflictSummary(summary);
87269
88037
  }
88038
+ let deferredDeletions = [];
87270
88039
  try {
87271
- const sourceMetadataPath = ctx.options.global ? join89(sourceDir, "metadata.json") : join89(sourceDir, ".claude", "metadata.json");
88040
+ const sourceMetadataPath = ctx.options.global ? join88(sourceDir, "metadata.json") : join88(sourceDir, ".claude", "metadata.json");
87272
88041
  if (await import_fs_extra20.pathExists(sourceMetadataPath)) {
87273
88042
  const metadataContent = await import_fs_extra20.readFile(sourceMetadataPath, "utf-8");
87274
88043
  const sourceMetadata = JSON.parse(metadataContent);
87275
88044
  if (sourceMetadata.deletions && sourceMetadata.deletions.length > 0) {
87276
- const deletionResult = await handleDeletions(sourceMetadata, ctx.claudeDir);
87277
- if (deletionResult.deletedPaths.length > 0) {
87278
- logger.info(`Removed ${deletionResult.deletedPaths.length} deprecated file(s)`);
87279
- for (const path14 of deletionResult.deletedPaths) {
87280
- logger.verbose(` - ${path14}`);
88045
+ const { categorizeDeletions: categorizeDeletions2 } = await Promise.resolve().then(() => (init_deletion_handler(), exports_deletion_handler));
88046
+ const categorized = categorizeDeletions2(sourceMetadata.deletions);
88047
+ if (categorized.immediate.length > 0) {
88048
+ const immediateMetadata = { ...sourceMetadata, deletions: categorized.immediate };
88049
+ const deletionResult = await handleDeletions(immediateMetadata, ctx.claudeDir);
88050
+ if (deletionResult.deletedPaths.length > 0) {
88051
+ logger.info(`Removed ${deletionResult.deletedPaths.length} deprecated file(s)`);
88052
+ for (const path14 of deletionResult.deletedPaths) {
88053
+ logger.verbose(` - ${path14}`);
88054
+ }
88055
+ }
88056
+ if (deletionResult.preservedPaths.length > 0) {
88057
+ logger.verbose(`Preserved ${deletionResult.preservedPaths.length} user-owned file(s)`);
87281
88058
  }
87282
88059
  }
87283
- if (deletionResult.preservedPaths.length > 0) {
87284
- logger.verbose(`Preserved ${deletionResult.preservedPaths.length} user-owned file(s)`);
88060
+ if (categorized.deferred.length > 0) {
88061
+ deferredDeletions = categorized.deferred;
88062
+ logger.debug(`Deferred ${categorized.deferred.length} skill deletion(s) to post-install phase`);
87285
88063
  }
87286
88064
  }
87287
88065
  } else {
@@ -87308,11 +88086,12 @@ async function handleMerge(ctx) {
87308
88086
  return {
87309
88087
  ...ctx,
87310
88088
  customClaudeFiles,
87311
- includePatterns
88089
+ includePatterns,
88090
+ deferredDeletions
87312
88091
  };
87313
88092
  }
87314
88093
  // src/commands/init/phases/migration-handler.ts
87315
- import { join as join97 } from "node:path";
88094
+ import { join as join96 } from "node:path";
87316
88095
 
87317
88096
  // src/domains/skills/skills-detector.ts
87318
88097
  init_logger();
@@ -87328,7 +88107,7 @@ init_types3();
87328
88107
  var import_fs_extra21 = __toESM(require_lib3(), 1);
87329
88108
  import { createHash as createHash4 } from "node:crypto";
87330
88109
  import { readFile as readFile44, readdir as readdir24, writeFile as writeFile27 } from "node:fs/promises";
87331
- import { join as join90, relative as relative15 } from "node:path";
88110
+ import { join as join89, relative as relative15 } from "node:path";
87332
88111
 
87333
88112
  class SkillsManifestManager {
87334
88113
  static MANIFEST_FILENAME = ".skills-manifest.json";
@@ -87350,12 +88129,12 @@ class SkillsManifestManager {
87350
88129
  return manifest;
87351
88130
  }
87352
88131
  static async writeManifest(skillsDir2, manifest) {
87353
- const manifestPath = join90(skillsDir2, SkillsManifestManager.MANIFEST_FILENAME);
88132
+ const manifestPath = join89(skillsDir2, SkillsManifestManager.MANIFEST_FILENAME);
87354
88133
  await writeFile27(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
87355
88134
  logger.debug(`Wrote manifest to: ${manifestPath}`);
87356
88135
  }
87357
88136
  static async readManifest(skillsDir2) {
87358
- const manifestPath = join90(skillsDir2, SkillsManifestManager.MANIFEST_FILENAME);
88137
+ const manifestPath = join89(skillsDir2, SkillsManifestManager.MANIFEST_FILENAME);
87359
88138
  if (!await import_fs_extra21.pathExists(manifestPath)) {
87360
88139
  logger.debug(`No manifest found at: ${manifestPath}`);
87361
88140
  return null;
@@ -87378,7 +88157,7 @@ class SkillsManifestManager {
87378
88157
  return "flat";
87379
88158
  }
87380
88159
  for (const dir of dirs.slice(0, 3)) {
87381
- const dirPath = join90(skillsDir2, dir.name);
88160
+ const dirPath = join89(skillsDir2, dir.name);
87382
88161
  const subEntries = await readdir24(dirPath, { withFileTypes: true });
87383
88162
  const hasSubdirs = subEntries.some((entry) => entry.isDirectory());
87384
88163
  if (hasSubdirs) {
@@ -87397,7 +88176,7 @@ class SkillsManifestManager {
87397
88176
  const entries = await readdir24(skillsDir2, { withFileTypes: true });
87398
88177
  for (const entry of entries) {
87399
88178
  if (entry.isDirectory() && !BUILD_ARTIFACT_DIRS.includes(entry.name) && !entry.name.startsWith(".")) {
87400
- const skillPath = join90(skillsDir2, entry.name);
88179
+ const skillPath = join89(skillsDir2, entry.name);
87401
88180
  const hash = await SkillsManifestManager.hashDirectory(skillPath);
87402
88181
  skills.push({
87403
88182
  name: entry.name,
@@ -87409,11 +88188,11 @@ class SkillsManifestManager {
87409
88188
  const categories = await readdir24(skillsDir2, { withFileTypes: true });
87410
88189
  for (const category of categories) {
87411
88190
  if (category.isDirectory() && !BUILD_ARTIFACT_DIRS.includes(category.name) && !category.name.startsWith(".")) {
87412
- const categoryPath = join90(skillsDir2, category.name);
88191
+ const categoryPath = join89(skillsDir2, category.name);
87413
88192
  const skillEntries = await readdir24(categoryPath, { withFileTypes: true });
87414
88193
  for (const skillEntry of skillEntries) {
87415
88194
  if (skillEntry.isDirectory() && !skillEntry.name.startsWith(".")) {
87416
- const skillPath = join90(categoryPath, skillEntry.name);
88195
+ const skillPath = join89(categoryPath, skillEntry.name);
87417
88196
  const hash = await SkillsManifestManager.hashDirectory(skillPath);
87418
88197
  skills.push({
87419
88198
  name: skillEntry.name,
@@ -87443,7 +88222,7 @@ class SkillsManifestManager {
87443
88222
  const files = [];
87444
88223
  const entries = await readdir24(dirPath, { withFileTypes: true });
87445
88224
  for (const entry of entries) {
87446
- const fullPath = join90(dirPath, entry.name);
88225
+ const fullPath = join89(dirPath, entry.name);
87447
88226
  if (entry.name.startsWith(".") || BUILD_ARTIFACT_DIRS.includes(entry.name)) {
87448
88227
  continue;
87449
88228
  }
@@ -87565,7 +88344,7 @@ function getPathMapping(skillName, oldBasePath, newBasePath) {
87565
88344
  // src/domains/skills/detection/script-detector.ts
87566
88345
  var import_fs_extra22 = __toESM(require_lib3(), 1);
87567
88346
  import { readdir as readdir25 } from "node:fs/promises";
87568
- import { join as join91 } from "node:path";
88347
+ import { join as join90 } from "node:path";
87569
88348
  async function scanDirectory(skillsDir2) {
87570
88349
  if (!await import_fs_extra22.pathExists(skillsDir2)) {
87571
88350
  return ["flat", []];
@@ -87578,12 +88357,12 @@ async function scanDirectory(skillsDir2) {
87578
88357
  let totalSkillLikeCount = 0;
87579
88358
  const allSkills = [];
87580
88359
  for (const dir of dirs) {
87581
- const dirPath = join91(skillsDir2, dir.name);
88360
+ const dirPath = join90(skillsDir2, dir.name);
87582
88361
  const subEntries = await readdir25(dirPath, { withFileTypes: true });
87583
88362
  const subdirs = subEntries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
87584
88363
  if (subdirs.length > 0) {
87585
88364
  for (const subdir of subdirs.slice(0, 3)) {
87586
- const subdirPath = join91(dirPath, subdir.name);
88365
+ const subdirPath = join90(dirPath, subdir.name);
87587
88366
  const subdirFiles = await readdir25(subdirPath, { withFileTypes: true });
87588
88367
  const hasSkillMarker = subdirFiles.some((file) => file.isFile() && (file.name === "skill.md" || file.name === "README.md" || file.name === "readme.md" || file.name === "config.json" || file.name === "package.json"));
87589
88368
  if (hasSkillMarker) {
@@ -87740,12 +88519,12 @@ class SkillsMigrationDetector {
87740
88519
  // src/domains/skills/skills-migrator.ts
87741
88520
  init_logger();
87742
88521
  init_types3();
87743
- import { join as join96 } from "node:path";
88522
+ import { join as join95 } from "node:path";
87744
88523
 
87745
88524
  // src/domains/skills/migrator/migration-executor.ts
87746
88525
  init_logger();
87747
88526
  import { copyFile as copyFile6, mkdir as mkdir28, readdir as readdir26, rm as rm10 } from "node:fs/promises";
87748
- import { join as join92 } from "node:path";
88527
+ import { join as join91 } from "node:path";
87749
88528
  var import_fs_extra24 = __toESM(require_lib3(), 1);
87750
88529
 
87751
88530
  // src/domains/skills/skills-migration-prompts.ts
@@ -87910,8 +88689,8 @@ async function copySkillDirectory(sourceDir, destDir) {
87910
88689
  await mkdir28(destDir, { recursive: true });
87911
88690
  const entries = await readdir26(sourceDir, { withFileTypes: true });
87912
88691
  for (const entry of entries) {
87913
- const sourcePath = join92(sourceDir, entry.name);
87914
- const destPath = join92(destDir, entry.name);
88692
+ const sourcePath = join91(sourceDir, entry.name);
88693
+ const destPath = join91(destDir, entry.name);
87915
88694
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.isSymbolicLink()) {
87916
88695
  continue;
87917
88696
  }
@@ -87926,7 +88705,7 @@ async function executeInternal(mappings, customizations, currentSkillsDir, inter
87926
88705
  const migrated = [];
87927
88706
  const preserved = [];
87928
88707
  const errors2 = [];
87929
- const tempDir = join92(currentSkillsDir, "..", ".skills-migration-temp");
88708
+ const tempDir = join91(currentSkillsDir, "..", ".skills-migration-temp");
87930
88709
  await mkdir28(tempDir, { recursive: true });
87931
88710
  try {
87932
88711
  for (const mapping of mappings) {
@@ -87947,9 +88726,9 @@ async function executeInternal(mappings, customizations, currentSkillsDir, inter
87947
88726
  }
87948
88727
  }
87949
88728
  const category = mapping.category;
87950
- const targetPath = category ? join92(tempDir, category, skillName) : join92(tempDir, skillName);
88729
+ const targetPath = category ? join91(tempDir, category, skillName) : join91(tempDir, skillName);
87951
88730
  if (category) {
87952
- await mkdir28(join92(tempDir, category), { recursive: true });
88731
+ await mkdir28(join91(tempDir, category), { recursive: true });
87953
88732
  }
87954
88733
  await copySkillDirectory(currentSkillPath, targetPath);
87955
88734
  migrated.push(skillName);
@@ -88016,7 +88795,7 @@ init_logger();
88016
88795
  init_types3();
88017
88796
  var import_fs_extra25 = __toESM(require_lib3(), 1);
88018
88797
  import { copyFile as copyFile7, mkdir as mkdir29, readdir as readdir27, rm as rm11, stat as stat16 } from "node:fs/promises";
88019
- import { basename as basename10, join as join93, normalize as normalize8 } from "node:path";
88798
+ import { basename as basename10, join as join92, normalize as normalize8 } from "node:path";
88020
88799
  function validatePath2(path14, paramName) {
88021
88800
  if (!path14 || typeof path14 !== "string") {
88022
88801
  throw new SkillsMigrationError(`${paramName} must be a non-empty string`);
@@ -88042,7 +88821,7 @@ class SkillsBackupManager {
88042
88821
  const timestamp = Date.now();
88043
88822
  const randomSuffix = Math.random().toString(36).substring(2, 8);
88044
88823
  const backupDirName = `${SkillsBackupManager.BACKUP_PREFIX}${timestamp}-${randomSuffix}`;
88045
- const backupDir = parentDir ? join93(parentDir, backupDirName) : join93(skillsDir2, "..", backupDirName);
88824
+ const backupDir = parentDir ? join92(parentDir, backupDirName) : join92(skillsDir2, "..", backupDirName);
88046
88825
  logger.info(`Creating backup at: ${backupDir}`);
88047
88826
  try {
88048
88827
  await mkdir29(backupDir, { recursive: true });
@@ -88093,7 +88872,7 @@ class SkillsBackupManager {
88093
88872
  }
88094
88873
  try {
88095
88874
  const entries = await readdir27(parentDir, { withFileTypes: true });
88096
- const backups = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(SkillsBackupManager.BACKUP_PREFIX)).map((entry) => join93(parentDir, entry.name));
88875
+ const backups = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(SkillsBackupManager.BACKUP_PREFIX)).map((entry) => join92(parentDir, entry.name));
88097
88876
  backups.sort().reverse();
88098
88877
  return backups;
88099
88878
  } catch (error) {
@@ -88121,8 +88900,8 @@ class SkillsBackupManager {
88121
88900
  static async copyDirectory(sourceDir, destDir) {
88122
88901
  const entries = await readdir27(sourceDir, { withFileTypes: true });
88123
88902
  for (const entry of entries) {
88124
- const sourcePath = join93(sourceDir, entry.name);
88125
- const destPath = join93(destDir, entry.name);
88903
+ const sourcePath = join92(sourceDir, entry.name);
88904
+ const destPath = join92(destDir, entry.name);
88126
88905
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.isSymbolicLink()) {
88127
88906
  continue;
88128
88907
  }
@@ -88138,7 +88917,7 @@ class SkillsBackupManager {
88138
88917
  let size = 0;
88139
88918
  const entries = await readdir27(dirPath, { withFileTypes: true });
88140
88919
  for (const entry of entries) {
88141
- const fullPath = join93(dirPath, entry.name);
88920
+ const fullPath = join92(dirPath, entry.name);
88142
88921
  if (entry.isSymbolicLink()) {
88143
88922
  continue;
88144
88923
  }
@@ -88174,12 +88953,12 @@ init_skip_directories();
88174
88953
  import { createHash as createHash5 } from "node:crypto";
88175
88954
  import { createReadStream as createReadStream3 } from "node:fs";
88176
88955
  import { readFile as readFile45, readdir as readdir28 } from "node:fs/promises";
88177
- import { join as join94, relative as relative16 } from "node:path";
88956
+ import { join as join93, relative as relative16 } from "node:path";
88178
88957
  async function getAllFiles(dirPath) {
88179
88958
  const files = [];
88180
88959
  const entries = await readdir28(dirPath, { withFileTypes: true });
88181
88960
  for (const entry of entries) {
88182
- const fullPath = join94(dirPath, entry.name);
88961
+ const fullPath = join93(dirPath, entry.name);
88183
88962
  if (entry.name.startsWith(".") || BUILD_ARTIFACT_DIRS.includes(entry.name) || entry.isSymbolicLink()) {
88184
88963
  continue;
88185
88964
  }
@@ -88193,12 +88972,12 @@ async function getAllFiles(dirPath) {
88193
88972
  return files;
88194
88973
  }
88195
88974
  async function hashFile(filePath) {
88196
- return new Promise((resolve19, reject) => {
88975
+ return new Promise((resolve20, reject) => {
88197
88976
  const hash = createHash5("sha256");
88198
88977
  const stream = createReadStream3(filePath);
88199
88978
  stream.on("data", (chunk) => hash.update(chunk));
88200
88979
  stream.on("end", () => {
88201
- resolve19(hash.digest("hex"));
88980
+ resolve20(hash.digest("hex"));
88202
88981
  });
88203
88982
  stream.on("error", (error) => {
88204
88983
  stream.destroy();
@@ -88306,7 +89085,7 @@ async function detectFileChanges(currentSkillPath, baselineSkillPath) {
88306
89085
  init_types3();
88307
89086
  var import_fs_extra27 = __toESM(require_lib3(), 1);
88308
89087
  import { readdir as readdir29 } from "node:fs/promises";
88309
- import { join as join95, normalize as normalize9 } from "node:path";
89088
+ import { join as join94, normalize as normalize9 } from "node:path";
88310
89089
  function validatePath3(path14, paramName) {
88311
89090
  if (!path14 || typeof path14 !== "string") {
88312
89091
  throw new SkillsMigrationError(`${paramName} must be a non-empty string`);
@@ -88327,13 +89106,13 @@ async function scanSkillsDirectory(skillsDir2) {
88327
89106
  if (dirs.length === 0) {
88328
89107
  return ["flat", []];
88329
89108
  }
88330
- const firstDirPath = join95(skillsDir2, dirs[0].name);
89109
+ const firstDirPath = join94(skillsDir2, dirs[0].name);
88331
89110
  const subEntries = await readdir29(firstDirPath, { withFileTypes: true });
88332
89111
  const subdirs = subEntries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
88333
89112
  if (subdirs.length > 0) {
88334
89113
  let skillLikeCount = 0;
88335
89114
  for (const subdir of subdirs.slice(0, 3)) {
88336
- const subdirPath = join95(firstDirPath, subdir.name);
89115
+ const subdirPath = join94(firstDirPath, subdir.name);
88337
89116
  const subdirFiles = await readdir29(subdirPath, { withFileTypes: true });
88338
89117
  const hasSkillMarker = subdirFiles.some((file) => file.isFile() && (file.name === "skill.md" || file.name === "README.md" || file.name === "readme.md" || file.name === "config.json" || file.name === "package.json"));
88339
89118
  if (hasSkillMarker) {
@@ -88343,7 +89122,7 @@ async function scanSkillsDirectory(skillsDir2) {
88343
89122
  if (skillLikeCount > 0) {
88344
89123
  const skills = [];
88345
89124
  for (const dir of dirs) {
88346
- const categoryPath = join95(skillsDir2, dir.name);
89125
+ const categoryPath = join94(skillsDir2, dir.name);
88347
89126
  const skillDirs = await readdir29(categoryPath, { withFileTypes: true });
88348
89127
  skills.push(...skillDirs.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name));
88349
89128
  }
@@ -88353,7 +89132,7 @@ async function scanSkillsDirectory(skillsDir2) {
88353
89132
  return ["flat", dirs.map((dir) => dir.name)];
88354
89133
  }
88355
89134
  async function findSkillPath(skillsDir2, skillName) {
88356
- const flatPath = join95(skillsDir2, skillName);
89135
+ const flatPath = join94(skillsDir2, skillName);
88357
89136
  if (await import_fs_extra27.pathExists(flatPath)) {
88358
89137
  return { path: flatPath, category: undefined };
88359
89138
  }
@@ -88362,8 +89141,8 @@ async function findSkillPath(skillsDir2, skillName) {
88362
89141
  if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "node_modules") {
88363
89142
  continue;
88364
89143
  }
88365
- const categoryPath = join95(skillsDir2, entry.name);
88366
- const skillPath = join95(categoryPath, skillName);
89144
+ const categoryPath = join94(skillsDir2, entry.name);
89145
+ const skillPath = join94(categoryPath, skillName);
88367
89146
  if (await import_fs_extra27.pathExists(skillPath)) {
88368
89147
  return { path: skillPath, category: entry.name };
88369
89148
  }
@@ -88457,7 +89236,7 @@ class SkillsMigrator {
88457
89236
  }
88458
89237
  }
88459
89238
  if (options2.backup && !options2.dryRun) {
88460
- const claudeDir2 = join96(currentSkillsDir, "..");
89239
+ const claudeDir2 = join95(currentSkillsDir, "..");
88461
89240
  result.backupPath = await SkillsBackupManager.createBackup(currentSkillsDir, claudeDir2);
88462
89241
  logger.success(`Backup created at: ${result.backupPath}`);
88463
89242
  }
@@ -88518,7 +89297,7 @@ async function handleMigration(ctx) {
88518
89297
  logger.debug("Skipping skills migration (fresh installation)");
88519
89298
  return ctx;
88520
89299
  }
88521
- const newSkillsDir = join97(ctx.extractDir, ".claude", "skills");
89300
+ const newSkillsDir = join96(ctx.extractDir, ".claude", "skills");
88522
89301
  const currentSkillsDir = PathResolver.buildSkillsPath(ctx.resolvedDir, ctx.options.global);
88523
89302
  if (!await import_fs_extra28.pathExists(newSkillsDir) || !await import_fs_extra28.pathExists(currentSkillsDir)) {
88524
89303
  return ctx;
@@ -88542,13 +89321,13 @@ async function handleMigration(ctx) {
88542
89321
  }
88543
89322
  // src/commands/init/phases/opencode-handler.ts
88544
89323
  import { cp as cp3, readdir as readdir31, rm as rm12 } from "node:fs/promises";
88545
- import { join as join99 } from "node:path";
89324
+ import { join as join98 } from "node:path";
88546
89325
 
88547
89326
  // src/services/transformers/opencode-path-transformer.ts
88548
89327
  init_logger();
88549
89328
  import { readFile as readFile46, readdir as readdir30, writeFile as writeFile28 } from "node:fs/promises";
88550
89329
  import { platform as platform12 } from "node:os";
88551
- import { extname as extname5, join as join98 } from "node:path";
89330
+ import { extname as extname5, join as join97 } from "node:path";
88552
89331
  var IS_WINDOWS2 = platform12() === "win32";
88553
89332
  function getOpenCodeGlobalPath() {
88554
89333
  return "$HOME/.config/opencode/";
@@ -88609,7 +89388,7 @@ async function transformPathsForGlobalOpenCode(directory, options2 = {}) {
88609
89388
  async function processDirectory2(dir) {
88610
89389
  const entries = await readdir30(dir, { withFileTypes: true });
88611
89390
  for (const entry of entries) {
88612
- const fullPath = join98(dir, entry.name);
89391
+ const fullPath = join97(dir, entry.name);
88613
89392
  if (entry.isDirectory()) {
88614
89393
  if (entry.name === "node_modules" || entry.name.startsWith(".")) {
88615
89394
  continue;
@@ -88648,7 +89427,7 @@ async function handleOpenCode(ctx) {
88648
89427
  if (ctx.cancelled || !ctx.extractDir || !ctx.resolvedDir) {
88649
89428
  return ctx;
88650
89429
  }
88651
- const openCodeSource = join99(ctx.extractDir, ".opencode");
89430
+ const openCodeSource = join98(ctx.extractDir, ".opencode");
88652
89431
  if (!await import_fs_extra29.pathExists(openCodeSource)) {
88653
89432
  logger.debug("No .opencode directory in archive, skipping");
88654
89433
  return ctx;
@@ -88666,8 +89445,8 @@ async function handleOpenCode(ctx) {
88666
89445
  await import_fs_extra29.ensureDir(targetDir);
88667
89446
  const entries = await readdir31(openCodeSource, { withFileTypes: true });
88668
89447
  for (const entry of entries) {
88669
- const sourcePath = join99(openCodeSource, entry.name);
88670
- const targetPath = join99(targetDir, entry.name);
89448
+ const sourcePath = join98(openCodeSource, entry.name);
89449
+ const targetPath = join98(targetDir, entry.name);
88671
89450
  if (await import_fs_extra29.pathExists(targetPath)) {
88672
89451
  if (!ctx.options.forceOverwrite) {
88673
89452
  logger.verbose(`Skipping existing: ${entry.name}`);
@@ -88764,20 +89543,21 @@ Please use only one download method.`);
88764
89543
  }
88765
89544
  // src/commands/init/phases/post-install-handler.ts
88766
89545
  init_projects_registry();
88767
- import { join as join100 } from "node:path";
89546
+ import { join as join102 } from "node:path";
89547
+ init_cc_version_checker();
88768
89548
  init_logger();
88769
89549
  init_path_resolver();
88770
- var import_fs_extra30 = __toESM(require_lib3(), 1);
89550
+ var import_fs_extra33 = __toESM(require_lib3(), 1);
88771
89551
  async function handlePostInstall(ctx) {
88772
89552
  if (ctx.cancelled || !ctx.extractDir || !ctx.resolvedDir || !ctx.claudeDir) {
88773
89553
  return ctx;
88774
89554
  }
88775
89555
  if (ctx.options.global) {
88776
- const claudeMdSource = join100(ctx.extractDir, "CLAUDE.md");
88777
- const claudeMdDest = join100(ctx.resolvedDir, "CLAUDE.md");
88778
- if (await import_fs_extra30.pathExists(claudeMdSource)) {
88779
- if (ctx.options.fresh || !await import_fs_extra30.pathExists(claudeMdDest)) {
88780
- await import_fs_extra30.copy(claudeMdSource, claudeMdDest);
89556
+ const claudeMdSource = join102(ctx.extractDir, "CLAUDE.md");
89557
+ const claudeMdDest = join102(ctx.resolvedDir, "CLAUDE.md");
89558
+ if (await import_fs_extra33.pathExists(claudeMdSource)) {
89559
+ if (ctx.options.fresh || !await import_fs_extra33.pathExists(claudeMdDest)) {
89560
+ await import_fs_extra33.copy(claudeMdSource, claudeMdDest);
88781
89561
  logger.success(ctx.options.fresh ? "Replaced CLAUDE.md in global directory (fresh install)" : "Copied CLAUDE.md to global directory");
88782
89562
  } else {
88783
89563
  logger.debug("CLAUDE.md already exists in global directory (preserved)");
@@ -88796,6 +89576,90 @@ async function handlePostInstall(ctx) {
88796
89576
  withSudo: ctx.options.withSudo
88797
89577
  });
88798
89578
  }
89579
+ let pluginSupported = false;
89580
+ try {
89581
+ const { requireCCPluginSupport: requireCCPluginSupport2 } = await Promise.resolve().then(() => (init_cc_version_checker(), exports_cc_version_checker));
89582
+ await requireCCPluginSupport2();
89583
+ pluginSupported = true;
89584
+ } catch (error) {
89585
+ if (error instanceof CCPluginSupportError) {
89586
+ logger.info(`Plugin install skipped: ${error.message}`);
89587
+ if (error.code === "cc_version_too_old") {
89588
+ logger.info("Upgrade: brew upgrade claude-code (or npm i -g @anthropic-ai/claude-code)");
89589
+ }
89590
+ if (error.code === "cc_not_found") {
89591
+ logger.info("Install Claude Code CLI, then re-run ck init to enable /ck:* plugin skills");
89592
+ }
89593
+ } else {
89594
+ logger.info(`Plugin install skipped: ${error instanceof Error ? error.message : "Unknown error"}`);
89595
+ }
89596
+ }
89597
+ let pluginVerified = false;
89598
+ if (pluginSupported) {
89599
+ try {
89600
+ const { handlePluginInstall: handlePluginInstall2 } = await Promise.resolve().then(() => (init_plugin_installer(), exports_plugin_installer));
89601
+ const pluginResult = await handlePluginInstall2(ctx.extractDir);
89602
+ pluginVerified = pluginResult.verified;
89603
+ if (pluginResult.error) {
89604
+ logger.info(`Plugin install issue: ${pluginResult.error}`);
89605
+ }
89606
+ } catch (error) {
89607
+ logger.debug(`Plugin install skipped: ${error instanceof Error ? error.message : "Unknown error"}`);
89608
+ }
89609
+ }
89610
+ if (ctx.claudeDir && ctx.kitType) {
89611
+ try {
89612
+ const { updateKitPluginState: updateKitPluginState2 } = await Promise.resolve().then(() => (init_metadata_migration(), exports_metadata_migration));
89613
+ await updateKitPluginState2(ctx.claudeDir, ctx.kitType, {
89614
+ pluginInstalled: pluginVerified,
89615
+ pluginInstalledAt: new Date().toISOString(),
89616
+ pluginVersion: ctx.selectedVersion || "unknown"
89617
+ });
89618
+ } catch {}
89619
+ }
89620
+ if (ctx.deferredDeletions?.length && ctx.claudeDir) {
89621
+ try {
89622
+ const { migrateUserSkills: migrateUserSkills2 } = await Promise.resolve().then(() => (init_skill_migration_merger(), exports_skill_migration_merger));
89623
+ const migration = await migrateUserSkills2(ctx.claudeDir, pluginVerified);
89624
+ if (pluginVerified) {
89625
+ if (!migration.canDelete) {
89626
+ logger.warning("Skill migration metadata unavailable — preserving existing skills (fail-safe)");
89627
+ return { ...ctx, installSkills, pluginSupported };
89628
+ }
89629
+ const preservedDirs = new Set(migration.preserved.map(normalizeSkillDir));
89630
+ const safeDeletions = ctx.deferredDeletions.filter((d3) => {
89631
+ const dirPath = extractSkillDirFromDeletionPath(d3);
89632
+ if (!dirPath)
89633
+ return true;
89634
+ return !preservedDirs.has(normalizeSkillDir(dirPath));
89635
+ });
89636
+ if (safeDeletions.length > 0) {
89637
+ const { handleDeletions: handleDeletions2 } = await Promise.resolve().then(() => (init_deletion_handler(), exports_deletion_handler));
89638
+ const deferredResult = await handleDeletions2({ deletions: safeDeletions }, ctx.claudeDir);
89639
+ if (deferredResult.deletedPaths.length > 0) {
89640
+ logger.info(`Removed ${deferredResult.deletedPaths.length} old skill file(s) (replaced by plugin)`);
89641
+ }
89642
+ }
89643
+ } else {
89644
+ logger.info("Plugin not verified — keeping existing skills as fallback");
89645
+ }
89646
+ } catch (error) {
89647
+ logger.debug(`Deferred skill deletion failed: ${error}`);
89648
+ }
89649
+ }
89650
+ if (pluginVerified) {
89651
+ try {
89652
+ const { cleanupOverlappingStandaloneSkills: cleanupOverlappingStandaloneSkills2 } = await Promise.resolve().then(() => (init_standalone_skill_cleanup(), exports_standalone_skill_cleanup));
89653
+ const globalClaudeDir = PathResolver.getGlobalKitDir();
89654
+ const pluginSkillsDir = join102(PathResolver.getClaudeKitDir(), "marketplace", "plugins", "ck", "skills");
89655
+ const overlap = await cleanupOverlappingStandaloneSkills2(globalClaudeDir, pluginSkillsDir);
89656
+ if (overlap.removed.length > 0) {
89657
+ logger.info(`Cleaned up ${overlap.removed.length} standalone skill(s) now provided by /ck:* plugin`);
89658
+ }
89659
+ } catch (error) {
89660
+ logger.info(`Standalone skill cleanup failed: ${error}`);
89661
+ }
89662
+ }
88799
89663
  if (!ctx.isNonInteractive) {
88800
89664
  const { isGeminiInstalled: isGeminiInstalled2 } = await Promise.resolve().then(() => (init_package_installer(), exports_package_installer));
88801
89665
  const { checkExistingGeminiConfig: checkExistingGeminiConfig2, findMcpConfigPath: findMcpConfigPath2, processGeminiMcpLinking: processGeminiMcpLinking2 } = await Promise.resolve().then(() => (init_gemini_mcp_linker(), exports_gemini_mcp_linker));
@@ -88822,7 +89686,7 @@ async function handlePostInstall(ctx) {
88822
89686
  }
88823
89687
  if (!ctx.options.skipSetup) {
88824
89688
  await promptSetupWizardIfNeeded({
88825
- envPath: join100(ctx.claudeDir, ".env"),
89689
+ envPath: join102(ctx.claudeDir, ".env"),
88826
89690
  claudeDir: ctx.claudeDir,
88827
89691
  isGlobal: ctx.options.global,
88828
89692
  isNonInteractive: ctx.isNonInteractive,
@@ -88839,14 +89703,26 @@ async function handlePostInstall(ctx) {
88839
89703
  }
88840
89704
  return {
88841
89705
  ...ctx,
88842
- installSkills
89706
+ installSkills,
89707
+ pluginSupported
88843
89708
  };
88844
89709
  }
89710
+ function normalizeSkillDir(path14) {
89711
+ return path14.replace(/\\/g, "/").replace(/\/+/g, "/");
89712
+ }
89713
+ function extractSkillDirFromDeletionPath(path14) {
89714
+ const normalized = normalizeSkillDir(path14);
89715
+ const parts = normalized.split("/").filter(Boolean);
89716
+ if (parts.length < 2 || parts[0] !== "skills") {
89717
+ return null;
89718
+ }
89719
+ return `skills/${parts[1]}`;
89720
+ }
88845
89721
  // src/commands/init/phases/selection-handler.ts
88846
89722
  init_config_manager();
88847
89723
  init_github_client();
88848
89724
  import { mkdir as mkdir30 } from "node:fs/promises";
88849
- import { join as join102, resolve as resolve20 } from "node:path";
89725
+ import { join as join104, resolve as resolve21 } from "node:path";
88850
89726
 
88851
89727
  // src/domains/github/kit-access-checker.ts
88852
89728
  init_logger();
@@ -88878,8 +89754,8 @@ async function detectAccessibleKits() {
88878
89754
  // src/domains/github/preflight-checker.ts
88879
89755
  init_logger();
88880
89756
  import { exec as exec8 } from "node:child_process";
88881
- import { promisify as promisify14 } from "node:util";
88882
- var execAsync8 = promisify14(exec8);
89757
+ import { promisify as promisify16 } from "node:util";
89758
+ var execAsync8 = promisify16(exec8);
88883
89759
  function createSuccessfulPreflightResult() {
88884
89760
  return {
88885
89761
  success: true,
@@ -88976,11 +89852,12 @@ async function runPreflightChecks() {
88976
89852
 
88977
89853
  // src/domains/installation/fresh-installer.ts
88978
89854
  init_metadata_migration();
88979
- import { existsSync as existsSync50, readdirSync as readdirSync4, rmSync as rmSync3, rmdirSync as rmdirSync2, unlinkSync as unlinkSync4 } from "node:fs";
88980
- import { dirname as dirname21, join as join101, resolve as resolve19 } from "node:path";
89855
+ init_manifest_reader();
88981
89856
  init_logger();
88982
89857
  init_safe_spinner();
88983
- var import_fs_extra31 = __toESM(require_lib3(), 1);
89858
+ var import_fs_extra34 = __toESM(require_lib3(), 1);
89859
+ import { existsSync as existsSync50, readdirSync as readdirSync4, rmSync as rmSync3, rmdirSync as rmdirSync2, unlinkSync as unlinkSync4 } from "node:fs";
89860
+ import { dirname as dirname22, join as join103, resolve as resolve20 } from "node:path";
88984
89861
  var CLAUDEKIT_SUBDIRECTORIES = ["commands", "agents", "skills", "rules", "hooks"];
88985
89862
  async function analyzeFreshInstallation(claudeDir2) {
88986
89863
  const metadata = await readManifest(claudeDir2);
@@ -89025,15 +89902,15 @@ async function analyzeFreshInstallation(claudeDir2) {
89025
89902
  };
89026
89903
  }
89027
89904
  function cleanupEmptyDirectories2(filePath, claudeDir2) {
89028
- const normalizedClaudeDir = resolve19(claudeDir2);
89029
- let currentDir = resolve19(dirname21(filePath));
89905
+ const normalizedClaudeDir = resolve20(claudeDir2);
89906
+ let currentDir = resolve20(dirname22(filePath));
89030
89907
  while (currentDir !== normalizedClaudeDir && currentDir.startsWith(normalizedClaudeDir)) {
89031
89908
  try {
89032
89909
  const entries = readdirSync4(currentDir);
89033
89910
  if (entries.length === 0) {
89034
89911
  rmdirSync2(currentDir);
89035
89912
  logger.debug(`Removed empty directory: ${currentDir}`);
89036
- currentDir = resolve19(dirname21(currentDir));
89913
+ currentDir = resolve20(dirname22(currentDir));
89037
89914
  } else {
89038
89915
  break;
89039
89916
  }
@@ -89050,7 +89927,7 @@ async function removeFilesByOwnership(claudeDir2, analysis, includeModified) {
89050
89927
  const filesToRemove = includeModified ? [...analysis.ckFiles, ...analysis.ckModifiedFiles] : analysis.ckFiles;
89051
89928
  const filesToPreserve = includeModified ? analysis.userFiles : [...analysis.ckModifiedFiles, ...analysis.userFiles];
89052
89929
  for (const file of filesToRemove) {
89053
- const fullPath = join101(claudeDir2, file.path);
89930
+ const fullPath = join103(claudeDir2, file.path);
89054
89931
  try {
89055
89932
  if (existsSync50(fullPath)) {
89056
89933
  unlinkSync4(fullPath);
@@ -89075,13 +89952,13 @@ async function removeFilesByOwnership(claudeDir2, analysis, includeModified) {
89075
89952
  };
89076
89953
  }
89077
89954
  async function updateMetadataAfterFresh(claudeDir2, removedFiles) {
89078
- const metadataPath = join101(claudeDir2, "metadata.json");
89079
- if (!await import_fs_extra31.pathExists(metadataPath)) {
89955
+ const metadataPath = join103(claudeDir2, "metadata.json");
89956
+ if (!await import_fs_extra34.pathExists(metadataPath)) {
89080
89957
  return;
89081
89958
  }
89082
89959
  let content;
89083
89960
  try {
89084
- content = await import_fs_extra31.readFile(metadataPath, "utf-8");
89961
+ content = await import_fs_extra34.readFile(metadataPath, "utf-8");
89085
89962
  } catch (readError) {
89086
89963
  logger.warning(`Failed to read metadata.json: ${readError instanceof Error ? readError.message : String(readError)}`);
89087
89964
  return;
@@ -89107,7 +89984,7 @@ async function updateMetadataAfterFresh(claudeDir2, removedFiles) {
89107
89984
  metadata.files = metadata.files.filter((f3) => !removedSet.has(f3.path));
89108
89985
  }
89109
89986
  try {
89110
- await import_fs_extra31.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
89987
+ await import_fs_extra34.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
89111
89988
  logger.debug(`Updated metadata.json, removed ${removedFiles.length} file entries`);
89112
89989
  } catch (writeError) {
89113
89990
  logger.warning(`Failed to write metadata.json: ${writeError instanceof Error ? writeError.message : String(writeError)}`);
@@ -89118,16 +89995,16 @@ async function removeSubdirectoriesFallback(claudeDir2) {
89118
89995
  const removedFiles = [];
89119
89996
  let removedDirCount = 0;
89120
89997
  for (const subdir of CLAUDEKIT_SUBDIRECTORIES) {
89121
- const subdirPath = join101(claudeDir2, subdir);
89122
- if (await import_fs_extra31.pathExists(subdirPath)) {
89998
+ const subdirPath = join103(claudeDir2, subdir);
89999
+ if (await import_fs_extra34.pathExists(subdirPath)) {
89123
90000
  rmSync3(subdirPath, { recursive: true, force: true });
89124
90001
  removedDirCount++;
89125
90002
  removedFiles.push(`${subdir}/ (entire directory)`);
89126
90003
  logger.debug(`Removed subdirectory: ${subdir}/`);
89127
90004
  }
89128
90005
  }
89129
- const metadataPath = join101(claudeDir2, "metadata.json");
89130
- if (await import_fs_extra31.pathExists(metadataPath)) {
90006
+ const metadataPath = join103(claudeDir2, "metadata.json");
90007
+ if (await import_fs_extra34.pathExists(metadataPath)) {
89131
90008
  unlinkSync4(metadataPath);
89132
90009
  removedFiles.push("metadata.json");
89133
90010
  }
@@ -89140,7 +90017,7 @@ async function removeSubdirectoriesFallback(claudeDir2) {
89140
90017
  };
89141
90018
  }
89142
90019
  async function handleFreshInstallation(claudeDir2, prompts) {
89143
- if (!await import_fs_extra31.pathExists(claudeDir2)) {
90020
+ if (!await import_fs_extra34.pathExists(claudeDir2)) {
89144
90021
  logger.info(".claude directory does not exist, proceeding with fresh installation");
89145
90022
  return true;
89146
90023
  }
@@ -89172,10 +90049,11 @@ async function handleFreshInstallation(claudeDir2, prompts) {
89172
90049
 
89173
90050
  // src/commands/init/phases/selection-handler.ts
89174
90051
  init_claudekit_scanner();
90052
+ init_manifest_reader();
89175
90053
  init_logger();
89176
90054
  init_path_resolver();
89177
90055
  init_types3();
89178
- var import_fs_extra32 = __toESM(require_lib3(), 1);
90056
+ var import_fs_extra35 = __toESM(require_lib3(), 1);
89179
90057
 
89180
90058
  // src/commands/init/types.ts
89181
90059
  function isSyncContext(ctx) {
@@ -89344,7 +90222,7 @@ async function handleSelection(ctx) {
89344
90222
  }
89345
90223
  }
89346
90224
  }
89347
- const resolvedDir = resolve20(targetDir);
90225
+ const resolvedDir = resolve21(targetDir);
89348
90226
  logger.info(`Target directory: ${resolvedDir}`);
89349
90227
  if (!ctx.options.global && PathResolver.isLocalSameAsGlobal(resolvedDir)) {
89350
90228
  logger.warning("You're at HOME directory. Installing here modifies your GLOBAL ClaudeKit.");
@@ -89366,7 +90244,7 @@ async function handleSelection(ctx) {
89366
90244
  return { ...ctx, cancelled: true };
89367
90245
  }
89368
90246
  }
89369
- if (!await import_fs_extra32.pathExists(resolvedDir)) {
90247
+ if (!await import_fs_extra35.pathExists(resolvedDir)) {
89370
90248
  if (ctx.options.global) {
89371
90249
  await mkdir30(resolvedDir, { recursive: true });
89372
90250
  logger.info(`Created global directory: ${resolvedDir}`);
@@ -89378,7 +90256,7 @@ async function handleSelection(ctx) {
89378
90256
  }
89379
90257
  if (!ctx.options.fresh) {
89380
90258
  const prefix = PathResolver.getPathPrefix(ctx.options.global);
89381
- const claudeDir2 = prefix ? join102(resolvedDir, prefix) : resolvedDir;
90259
+ const claudeDir2 = prefix ? join104(resolvedDir, prefix) : resolvedDir;
89382
90260
  try {
89383
90261
  const existingMetadata = await readManifest(claudeDir2);
89384
90262
  if (existingMetadata?.kits) {
@@ -89410,7 +90288,7 @@ async function handleSelection(ctx) {
89410
90288
  }
89411
90289
  if (ctx.options.fresh) {
89412
90290
  const prefix = PathResolver.getPathPrefix(ctx.options.global);
89413
- const claudeDir2 = prefix ? join102(resolvedDir, prefix) : resolvedDir;
90291
+ const claudeDir2 = prefix ? join104(resolvedDir, prefix) : resolvedDir;
89414
90292
  const canProceed = await handleFreshInstallation(claudeDir2, ctx.prompts);
89415
90293
  if (!canProceed) {
89416
90294
  return { ...ctx, cancelled: true };
@@ -89429,7 +90307,7 @@ async function handleSelection(ctx) {
89429
90307
  logger.info("Fetching available versions...");
89430
90308
  let currentVersion = null;
89431
90309
  try {
89432
- const metadataPath = ctx.options.global ? join102(PathResolver.getGlobalKitDir(), "metadata.json") : join102(resolvedDir, ".claude", "metadata.json");
90310
+ const metadataPath = ctx.options.global ? join104(PathResolver.getGlobalKitDir(), "metadata.json") : join104(resolvedDir, ".claude", "metadata.json");
89433
90311
  const metadata = await readClaudeKitMetadata(metadataPath);
89434
90312
  currentVersion = metadata?.version || null;
89435
90313
  if (currentVersion) {
@@ -89503,25 +90381,27 @@ async function handleSelection(ctx) {
89503
90381
  };
89504
90382
  }
89505
90383
  // src/commands/init/phases/sync-handler.ts
89506
- import { copyFile as copyFile8, mkdir as mkdir31, open as open4, readFile as readFile48, rename as rename6, stat as stat17, unlink as unlink11, writeFile as writeFile30 } from "node:fs/promises";
89507
- import { dirname as dirname22, join as join103, resolve as resolve21 } from "node:path";
90384
+ import { copyFile as copyFile8, mkdir as mkdir31, open as open4, readFile as readFile50, rename as rename7, stat as stat17, unlink as unlink11, writeFile as writeFile30 } from "node:fs/promises";
90385
+ import { dirname as dirname23, join as join105, resolve as resolve22 } from "node:path";
90386
+ init_cc_version_checker();
90387
+ init_manifest_reader();
89508
90388
  init_logger();
89509
90389
  init_path_resolver();
89510
- var import_fs_extra33 = __toESM(require_lib3(), 1);
90390
+ var import_fs_extra36 = __toESM(require_lib3(), 1);
89511
90391
  var import_picocolors23 = __toESM(require_picocolors(), 1);
89512
90392
  async function handleSync(ctx) {
89513
90393
  if (!ctx.options.sync) {
89514
90394
  return ctx;
89515
90395
  }
89516
- const resolvedDir = ctx.options.global ? PathResolver.getGlobalKitDir() : resolve21(ctx.options.dir || ".");
89517
- const claudeDir2 = ctx.options.global ? resolvedDir : join103(resolvedDir, ".claude");
89518
- if (!await import_fs_extra33.pathExists(claudeDir2)) {
90396
+ const resolvedDir = ctx.options.global ? PathResolver.getGlobalKitDir() : resolve22(ctx.options.dir || ".");
90397
+ const claudeDir2 = ctx.options.global ? resolvedDir : join105(resolvedDir, ".claude");
90398
+ if (!await import_fs_extra36.pathExists(claudeDir2)) {
89519
90399
  logger.error("Cannot sync: no .claude directory found");
89520
90400
  ctx.prompts.note("Run 'ck init' without --sync to install first.", "No Installation Found");
89521
90401
  return { ...ctx, cancelled: true };
89522
90402
  }
89523
- const metadataPath = join103(claudeDir2, "metadata.json");
89524
- if (!await import_fs_extra33.pathExists(metadataPath)) {
90403
+ const metadataPath = join105(claudeDir2, "metadata.json");
90404
+ if (!await import_fs_extra36.pathExists(metadataPath)) {
89525
90405
  logger.error("Cannot sync: no metadata.json found");
89526
90406
  ctx.prompts.note(`Your installation may be from an older version.
89527
90407
  Run 'ck init' to update.`, "Legacy Installation");
@@ -89618,17 +90498,42 @@ function getLockTimeout() {
89618
90498
  return timeoutMs;
89619
90499
  }
89620
90500
  var STALE_LOCK_THRESHOLD_MS = 5 * 60 * 1000;
90501
+ function isProcessAlive(pid) {
90502
+ try {
90503
+ process.kill(pid, 0);
90504
+ return true;
90505
+ } catch (error) {
90506
+ const code2 = error.code;
90507
+ return code2 === "EPERM";
90508
+ }
90509
+ }
90510
+ async function readLockPayload(lockPath) {
90511
+ try {
90512
+ const raw2 = await readFile50(lockPath, "utf-8");
90513
+ const parsed = JSON.parse(raw2);
90514
+ if (typeof parsed.pid === "number" && Number.isInteger(parsed.pid) && parsed.pid > 0) {
90515
+ return {
90516
+ pid: parsed.pid,
90517
+ startedAt: typeof parsed.startedAt === "string" && parsed.startedAt.length > 0 ? parsed.startedAt : "unknown"
90518
+ };
90519
+ }
90520
+ } catch {}
90521
+ return null;
90522
+ }
89621
90523
  async function acquireSyncLock(global3) {
89622
90524
  const cacheDir = PathResolver.getCacheDir(global3);
89623
- const lockPath = join103(cacheDir, ".sync-lock");
90525
+ const lockPath = join105(cacheDir, ".sync-lock");
89624
90526
  const startTime = Date.now();
89625
90527
  const lockTimeout = getLockTimeout();
89626
- await mkdir31(dirname22(lockPath), { recursive: true });
90528
+ await mkdir31(dirname23(lockPath), { recursive: true });
89627
90529
  while (Date.now() - startTime < lockTimeout) {
89628
90530
  try {
89629
90531
  const handle = await open4(lockPath, "wx");
90532
+ await handle.writeFile(JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }), "utf-8");
89630
90533
  return async () => {
89631
- await handle.close();
90534
+ try {
90535
+ await handle.close();
90536
+ } catch {}
89632
90537
  await unlink11(lockPath).catch(() => {});
89633
90538
  };
89634
90539
  } catch (err) {
@@ -89636,18 +90541,29 @@ async function acquireSyncLock(global3) {
89636
90541
  try {
89637
90542
  const lockStat = await stat17(lockPath);
89638
90543
  const lockAge = Math.abs(Date.now() - lockStat.mtimeMs);
89639
- if (lockAge > STALE_LOCK_THRESHOLD_MS) {
89640
- logger.warning(`Removing stale sync lock (age: ${Math.round(lockAge / 1000)}s)`);
90544
+ const lockOwner = await readLockPayload(lockPath);
90545
+ if (lockOwner?.pid === process.pid) {
90546
+ throw new Error("Sync lock is already held by current process");
90547
+ }
90548
+ const ownerAlive = lockOwner?.pid ? isProcessAlive(lockOwner.pid) : null;
90549
+ if (lockAge > STALE_LOCK_THRESHOLD_MS && ownerAlive !== true) {
90550
+ logger.warning(`Removing stale sync lock (age: ${Math.round(lockAge / 1000)}s${lockOwner?.pid ? `, pid=${lockOwner.pid}` : ""})`);
89641
90551
  await unlink11(lockPath).catch(() => {});
89642
90552
  continue;
89643
90553
  }
90554
+ if (lockAge > STALE_LOCK_THRESHOLD_MS && ownerAlive === true) {
90555
+ logger.debug(`Sync lock older than threshold but owner pid=${lockOwner?.pid} still alive; waiting`);
90556
+ }
89644
90557
  } catch (statError) {
90558
+ if (statError instanceof Error && statError.message.includes("already held by current process")) {
90559
+ throw statError;
90560
+ }
89645
90561
  if (statError.code === "ENOENT") {
89646
90562
  continue;
89647
90563
  }
89648
90564
  logger.debug(`Lock stat failed: ${statError}`);
89649
90565
  }
89650
- await new Promise((resolve22) => setTimeout(resolve22, 100));
90566
+ await new Promise((resolve23) => setTimeout(resolve23, 100));
89651
90567
  continue;
89652
90568
  }
89653
90569
  throw err;
@@ -89666,18 +90582,20 @@ async function executeSyncMerge(ctx) {
89666
90582
  const releaseLock = await acquireSyncLock(ctx.options.global);
89667
90583
  try {
89668
90584
  const trackedFiles = ctx.syncTrackedFiles;
89669
- const upstreamDir = ctx.options.global ? join103(ctx.extractDir, ".claude") : ctx.extractDir;
90585
+ const upstreamDir = ctx.options.global ? join105(ctx.extractDir, ".claude") : ctx.extractDir;
89670
90586
  let deletions = [];
89671
90587
  try {
89672
- const sourceMetadataPath = join103(upstreamDir, "metadata.json");
89673
- if (await import_fs_extra33.pathExists(sourceMetadataPath)) {
89674
- const content = await readFile48(sourceMetadataPath, "utf-8");
90588
+ const sourceMetadataPath = join105(upstreamDir, "metadata.json");
90589
+ if (await import_fs_extra36.pathExists(sourceMetadataPath)) {
90590
+ const content = await readFile50(sourceMetadataPath, "utf-8");
89675
90591
  const sourceMetadata = JSON.parse(content);
89676
90592
  deletions = sourceMetadata.deletions || [];
89677
90593
  }
89678
90594
  } catch (error) {
89679
90595
  logger.debug(`Failed to load source metadata for deletion filtering: ${error}`);
89680
90596
  }
90597
+ const { categorizeDeletions: categorizeDeletions2, handleDeletions: handleDeletions2 } = await Promise.resolve().then(() => (init_deletion_handler(), exports_deletion_handler));
90598
+ const categorizedDeletions = categorizeDeletions2(deletions);
89681
90599
  const filteredTrackedFiles = filterDeletionPaths(trackedFiles, deletions);
89682
90600
  if (deletions.length > 0) {
89683
90601
  const filtered = trackedFiles.length - filteredTrackedFiles.length;
@@ -89685,23 +90603,44 @@ async function executeSyncMerge(ctx) {
89685
90603
  }
89686
90604
  logger.info("Analyzing file changes...");
89687
90605
  const plan = await SyncEngine.createSyncPlan(filteredTrackedFiles, ctx.claudeDir, upstreamDir);
90606
+ const forceOverwriteNonInteractive = ctx.isNonInteractive && ctx.options.forceOverwrite;
90607
+ const autoUpdateQueue = forceOverwriteNonInteractive ? dedupeTrackedFiles([...plan.autoUpdate, ...plan.needsReview]) : plan.autoUpdate;
90608
+ const reviewQueue = forceOverwriteNonInteractive ? [] : plan.needsReview;
89688
90609
  displaySyncPlan(plan);
89689
- if (plan.autoUpdate.length === 0 && plan.needsReview.length === 0) {
90610
+ if (autoUpdateQueue.length === 0 && reviewQueue.length === 0 && categorizedDeletions.immediate.length === 0 && categorizedDeletions.deferred.length === 0) {
89690
90611
  ctx.prompts.note("All files are up to date or user-owned.", "No Changes Needed");
90612
+ }
90613
+ if (reviewQueue.length > 0 && ctx.isNonInteractive) {
90614
+ logger.error(`Cannot complete sync: ${reviewQueue.length} file(s) require interactive review`);
90615
+ ctx.prompts.note(`The following files have local modifications:
90616
+ ${reviewQueue.slice(0, 5).map((f3) => ` • ${f3.path}`).join(`
90617
+ `)}${reviewQueue.length > 5 ? `
90618
+ ... and ${reviewQueue.length - 5} more` : ""}
90619
+
90620
+ Options:
90621
+ 1. Run 'ck init --sync' without --yes for interactive merge
90622
+ 2. Use --force-overwrite to accept all upstream changes
90623
+ 3. Manually resolve conflicts before syncing`, "Sync Blocked");
89691
90624
  return { ...ctx, cancelled: true };
89692
90625
  }
89693
- const backupDir = PathResolver.getBackupDir();
89694
- await createBackup(ctx.claudeDir, trackedFiles, backupDir);
89695
- logger.success(`Backup created at ${import_picocolors23.default.dim(backupDir)}`);
89696
- if (plan.autoUpdate.length > 0) {
89697
- logger.info(`Auto-updating ${plan.autoUpdate.length} file(s)...`);
90626
+ if (forceOverwriteNonInteractive && plan.needsReview.length > 0) {
90627
+ logger.info(`--force-overwrite enabled: auto-updating ${plan.needsReview.length} locally modified file(s)`);
90628
+ }
90629
+ const willModifyFiles = autoUpdateQueue.length > 0 || reviewQueue.length > 0 || categorizedDeletions.immediate.length > 0 || categorizedDeletions.deferred.length > 0;
90630
+ if (willModifyFiles) {
90631
+ const backupDir = PathResolver.getBackupDir();
90632
+ await createBackup(ctx.claudeDir, trackedFiles, backupDir);
90633
+ logger.success(`Backup created at ${import_picocolors23.default.dim(backupDir)}`);
90634
+ }
90635
+ if (autoUpdateQueue.length > 0) {
90636
+ logger.info(`Auto-updating ${autoUpdateQueue.length} file(s)...`);
89698
90637
  let updateSuccess = 0;
89699
90638
  let updateFailed = 0;
89700
- for (const file of plan.autoUpdate) {
90639
+ for (const file of autoUpdateQueue) {
89701
90640
  try {
89702
90641
  const sourcePath = await validateSyncPath(upstreamDir, file.path);
89703
90642
  const targetPath = await validateSyncPath(ctx.claudeDir, file.path);
89704
- const targetDir = join103(targetPath, "..");
90643
+ const targetDir = dirname23(targetPath);
89705
90644
  try {
89706
90645
  await mkdir31(targetDir, { recursive: true });
89707
90646
  } catch (mkdirError) {
@@ -89711,7 +90650,7 @@ async function executeSyncMerge(ctx) {
89711
90650
  ctx.prompts.note("Your disk is full. Free up space and try again.", "Sync Failed");
89712
90651
  return { ...ctx, cancelled: true };
89713
90652
  }
89714
- if (errCode === "EROFS" || errCode === "EACCES") {
90653
+ if (errCode === "EROFS" || errCode === "EACCES" || errCode === "EPERM") {
89715
90654
  logger.warning(`Cannot create directory ${file.path}: ${errCode}`);
89716
90655
  updateFailed++;
89717
90656
  continue;
@@ -89729,7 +90668,7 @@ async function executeSyncMerge(ctx) {
89729
90668
  ctx.prompts.note("Your disk is full. Free up space and try again.", "Sync Failed");
89730
90669
  return { ...ctx, cancelled: true };
89731
90670
  }
89732
- if (errCode === "EACCES" || errCode === "EPERM") {
90671
+ if (errCode === "EACCES" || errCode === "EPERM" || errCode === "EROFS") {
89733
90672
  logger.warning(`Permission denied: ${file.path} - check file permissions`);
89734
90673
  updateFailed++;
89735
90674
  } else if (errMsg.includes("Symlink") || errMsg.includes("Path")) {
@@ -89744,19 +90683,22 @@ async function executeSyncMerge(ctx) {
89744
90683
  if (updateSuccess > 0) {
89745
90684
  logger.success(`Auto-updated ${updateSuccess} file(s)${updateFailed > 0 ? ` (${updateFailed} failed)` : ""}`);
89746
90685
  }
90686
+ if (updateSuccess === 0 && updateFailed > 0) {
90687
+ logger.warning("No files were updated due to write errors");
90688
+ }
89747
90689
  }
89748
- if (plan.needsReview.length > 0 && !ctx.isNonInteractive) {
89749
- logger.info(`${plan.needsReview.length} file(s) need interactive review...`);
90690
+ if (reviewQueue.length > 0 && !ctx.isNonInteractive) {
90691
+ logger.info(`${reviewQueue.length} file(s) need interactive review...`);
89750
90692
  let totalApplied = 0;
89751
90693
  let totalRejected = 0;
89752
90694
  let skippedFiles = 0;
89753
- for (const file of plan.needsReview) {
90695
+ for (const file of reviewQueue) {
89754
90696
  let currentPath;
89755
90697
  let upstreamPath;
89756
90698
  try {
89757
90699
  currentPath = await validateSyncPath(ctx.claudeDir, file.path);
89758
90700
  upstreamPath = await validateSyncPath(upstreamDir, file.path);
89759
- } catch (error) {
90701
+ } catch {
89760
90702
  logger.warning(`Skipping invalid path during review: ${file.path}`);
89761
90703
  skippedFiles++;
89762
90704
  continue;
@@ -89783,7 +90725,7 @@ async function executeSyncMerge(ctx) {
89783
90725
  const tempPath = `${currentPath}.tmp.${Date.now()}`;
89784
90726
  try {
89785
90727
  await writeFile30(tempPath, result.result, "utf-8");
89786
- await rename6(tempPath, currentPath);
90728
+ await rename7(tempPath, currentPath);
89787
90729
  } catch (atomicError) {
89788
90730
  await unlink11(tempPath).catch(() => {});
89789
90731
  throw atomicError;
@@ -89804,8 +90746,8 @@ async function executeSyncMerge(ctx) {
89804
90746
  console.log("");
89805
90747
  console.log(import_picocolors23.default.bold("Sync Summary:"));
89806
90748
  console.log(import_picocolors23.default.dim("─".repeat(40)));
89807
- if (plan.autoUpdate.length > 0) {
89808
- console.log(import_picocolors23.default.green(` ✓ ${plan.autoUpdate.length} file(s) auto-updated`));
90749
+ if (autoUpdateQueue.length > 0) {
90750
+ console.log(import_picocolors23.default.green(` ✓ ${autoUpdateQueue.length} file(s) auto-updated`));
89809
90751
  }
89810
90752
  if (totalApplied > 0) {
89811
90753
  console.log(import_picocolors23.default.green(` ✓ ${totalApplied} hunk(s) applied`));
@@ -89819,23 +90761,96 @@ async function executeSyncMerge(ctx) {
89819
90761
  if (plan.skipped.length > 0) {
89820
90762
  console.log(import_picocolors23.default.dim(` ─ ${plan.skipped.length} user-owned file(s) unchanged`));
89821
90763
  }
89822
- } else if (plan.needsReview.length > 0 && ctx.isNonInteractive) {
89823
- logger.error(`Cannot complete sync: ${plan.needsReview.length} file(s) require interactive review`);
89824
- ctx.prompts.note(`The following files have local modifications:
89825
- ${plan.needsReview.slice(0, 5).map((f3) => ` • ${f3.path}`).join(`
89826
- `)}${plan.needsReview.length > 5 ? `
89827
- ... and ${plan.needsReview.length - 5} more` : ""}
89828
-
89829
- Options:
89830
- 1. Run 'ck init --sync' without --yes for interactive merge
89831
- 2. Use --force-overwrite to accept all upstream changes
89832
- 3. Manually resolve conflicts before syncing`, "Sync Blocked");
89833
- return { ...ctx, cancelled: true };
90764
+ }
90765
+ if (categorizedDeletions.immediate.length > 0) {
90766
+ try {
90767
+ const deletionResult = await handleDeletions2({ deletions: categorizedDeletions.immediate }, ctx.claudeDir);
90768
+ if (deletionResult.deletedPaths.length > 0) {
90769
+ logger.info(`Removed ${deletionResult.deletedPaths.length} deprecated file(s)`);
90770
+ }
90771
+ if (deletionResult.preservedPaths.length > 0) {
90772
+ logger.verbose(`Preserved ${deletionResult.preservedPaths.length} user-owned file(s)`);
90773
+ }
90774
+ } catch (error) {
90775
+ logger.debug(`Immediate deletion cleanup failed during sync: ${error}`);
90776
+ }
90777
+ }
90778
+ let pluginSupported = false;
90779
+ let pluginVerified = false;
90780
+ try {
90781
+ const { requireCCPluginSupport: requireCCPluginSupport2 } = await Promise.resolve().then(() => (init_cc_version_checker(), exports_cc_version_checker));
90782
+ await requireCCPluginSupport2();
90783
+ pluginSupported = true;
90784
+ } catch (error) {
90785
+ if (error instanceof CCPluginSupportError) {
90786
+ logger.info(`Plugin install skipped during sync: ${error.message}`);
90787
+ if (error.code === "cc_version_too_old") {
90788
+ logger.info("Upgrade Claude Code, then re-run: ck init --sync (plugin migration requires >= 1.0.33)");
90789
+ }
90790
+ if (error.code === "cc_not_found") {
90791
+ logger.info("Install Claude Code CLI to enable /ck:* plugin skills");
90792
+ }
90793
+ } else {
90794
+ logger.debug(`Plugin version check failed during sync: ${error instanceof Error ? error.message : "Unknown error"}`);
90795
+ }
90796
+ }
90797
+ if (pluginSupported && ctx.extractDir) {
90798
+ try {
90799
+ const { handlePluginInstall: handlePluginInstall2 } = await Promise.resolve().then(() => (init_plugin_installer(), exports_plugin_installer));
90800
+ const pluginResult = await handlePluginInstall2(ctx.extractDir);
90801
+ pluginVerified = pluginResult.verified;
90802
+ if (pluginResult.error) {
90803
+ logger.debug(`Plugin install issue: ${pluginResult.error}`);
90804
+ }
90805
+ } catch (error) {
90806
+ logger.debug(`Plugin install skipped: ${error instanceof Error ? error.message : "Unknown error"}`);
90807
+ }
90808
+ }
90809
+ if (ctx.claudeDir && ctx.kitType) {
90810
+ try {
90811
+ const { updateKitPluginState: updateKitPluginState2 } = await Promise.resolve().then(() => (init_metadata_migration(), exports_metadata_migration));
90812
+ await updateKitPluginState2(ctx.claudeDir, ctx.kitType, {
90813
+ pluginInstalled: pluginVerified,
90814
+ pluginInstalledAt: new Date().toISOString(),
90815
+ pluginVersion: ctx.selectedVersion || ctx.syncLatestVersion || "unknown"
90816
+ });
90817
+ } catch {}
90818
+ }
90819
+ if (categorizedDeletions.deferred.length > 0) {
90820
+ if (pluginVerified) {
90821
+ try {
90822
+ const { migrateUserSkills: migrateUserSkills2 } = await Promise.resolve().then(() => (init_skill_migration_merger(), exports_skill_migration_merger));
90823
+ const migration = await migrateUserSkills2(ctx.claudeDir, pluginVerified);
90824
+ if (!migration.canDelete) {
90825
+ logger.warning("Skill migration metadata unavailable during sync — preserving existing skills");
90826
+ } else {
90827
+ const preservedDirs = new Set(migration.preserved.map(normalizeSkillDir2));
90828
+ const safeDeletions = categorizedDeletions.deferred.filter((path14) => {
90829
+ const skillDir = extractSkillDirFromDeletionPath2(path14);
90830
+ return !skillDir || !preservedDirs.has(normalizeSkillDir2(skillDir));
90831
+ });
90832
+ if (safeDeletions.length > 0) {
90833
+ const deferredResult = await handleDeletions2({ deletions: safeDeletions }, ctx.claudeDir);
90834
+ if (deferredResult.deletedPaths.length > 0) {
90835
+ logger.info(`Removed ${deferredResult.deletedPaths.length} old skill file(s) during sync`);
90836
+ }
90837
+ }
90838
+ }
90839
+ } catch (error) {
90840
+ logger.debug(`Deferred skill cleanup failed during sync: ${error}`);
90841
+ }
90842
+ } else {
90843
+ logger.info("Plugin not verified during sync — keeping existing skills as fallback");
90844
+ }
89834
90845
  }
89835
90846
  ctx.prompts.outro("Config sync completed successfully");
89836
90847
  return { ...ctx, cancelled: true };
89837
90848
  } finally {
89838
- await releaseLock();
90849
+ try {
90850
+ await releaseLock();
90851
+ } catch (error) {
90852
+ logger.debug(`Failed to release sync lock: ${error}`);
90853
+ }
89839
90854
  }
89840
90855
  }
89841
90856
  function displaySyncPlan(plan) {
@@ -89870,9 +90885,9 @@ async function createBackup(claudeDir2, files, backupDir) {
89870
90885
  for (const file of files) {
89871
90886
  try {
89872
90887
  const sourcePath = await validateSyncPath(claudeDir2, file.path);
89873
- if (await import_fs_extra33.pathExists(sourcePath)) {
90888
+ if (await import_fs_extra36.pathExists(sourcePath)) {
89874
90889
  const targetPath = await validateSyncPath(backupDir, file.path);
89875
- const targetDir = join103(targetPath, "..");
90890
+ const targetDir = dirname23(targetPath);
89876
90891
  await mkdir31(targetDir, { recursive: true });
89877
90892
  await copyFile8(sourcePath, targetPath);
89878
90893
  }
@@ -89885,9 +90900,27 @@ async function createBackup(claudeDir2, files, backupDir) {
89885
90900
  }
89886
90901
  }
89887
90902
  }
90903
+ function dedupeTrackedFiles(files) {
90904
+ const deduped = new Map;
90905
+ for (const file of files) {
90906
+ deduped.set(file.path, file);
90907
+ }
90908
+ return [...deduped.values()];
90909
+ }
90910
+ function normalizeSkillDir2(path14) {
90911
+ return path14.replace(/\\/g, "/").replace(/\/+/g, "/");
90912
+ }
90913
+ function extractSkillDirFromDeletionPath2(path14) {
90914
+ const normalized = normalizeSkillDir2(path14);
90915
+ const parts = normalized.split("/").filter(Boolean);
90916
+ if (parts.length < 2 || parts[0] !== "skills") {
90917
+ return null;
90918
+ }
90919
+ return `skills/${parts[1]}`;
90920
+ }
89888
90921
  // src/commands/init/phases/transform-handler.ts
89889
90922
  init_config_manager();
89890
- import { join as join107 } from "node:path";
90923
+ import { join as join109 } from "node:path";
89891
90924
 
89892
90925
  // src/services/transformers/folder-path-transformer.ts
89893
90926
  init_logger();
@@ -89896,40 +90929,40 @@ init_types3();
89896
90929
  // src/services/transformers/folder-transform/folder-renamer.ts
89897
90930
  init_logger();
89898
90931
  init_types3();
89899
- var import_fs_extra34 = __toESM(require_lib3(), 1);
89900
- import { rename as rename7, rm as rm13 } from "node:fs/promises";
89901
- import { join as join104, relative as relative18 } from "node:path";
90932
+ var import_fs_extra37 = __toESM(require_lib3(), 1);
90933
+ import { rename as rename8, rm as rm13 } from "node:fs/promises";
90934
+ import { join as join106, relative as relative18 } from "node:path";
89902
90935
  async function collectDirsToRename(extractDir, folders) {
89903
90936
  const dirsToRename = [];
89904
90937
  if (folders.docs !== DEFAULT_FOLDERS.docs) {
89905
- const docsPath = join104(extractDir, DEFAULT_FOLDERS.docs);
89906
- if (await import_fs_extra34.pathExists(docsPath)) {
90938
+ const docsPath = join106(extractDir, DEFAULT_FOLDERS.docs);
90939
+ if (await import_fs_extra37.pathExists(docsPath)) {
89907
90940
  dirsToRename.push({
89908
90941
  from: docsPath,
89909
- to: join104(extractDir, folders.docs)
90942
+ to: join106(extractDir, folders.docs)
89910
90943
  });
89911
90944
  }
89912
- const claudeDocsPath = join104(extractDir, ".claude", DEFAULT_FOLDERS.docs);
89913
- if (await import_fs_extra34.pathExists(claudeDocsPath)) {
90945
+ const claudeDocsPath = join106(extractDir, ".claude", DEFAULT_FOLDERS.docs);
90946
+ if (await import_fs_extra37.pathExists(claudeDocsPath)) {
89914
90947
  dirsToRename.push({
89915
90948
  from: claudeDocsPath,
89916
- to: join104(extractDir, ".claude", folders.docs)
90949
+ to: join106(extractDir, ".claude", folders.docs)
89917
90950
  });
89918
90951
  }
89919
90952
  }
89920
90953
  if (folders.plans !== DEFAULT_FOLDERS.plans) {
89921
- const plansPath = join104(extractDir, DEFAULT_FOLDERS.plans);
89922
- if (await import_fs_extra34.pathExists(plansPath)) {
90954
+ const plansPath = join106(extractDir, DEFAULT_FOLDERS.plans);
90955
+ if (await import_fs_extra37.pathExists(plansPath)) {
89923
90956
  dirsToRename.push({
89924
90957
  from: plansPath,
89925
- to: join104(extractDir, folders.plans)
90958
+ to: join106(extractDir, folders.plans)
89926
90959
  });
89927
90960
  }
89928
- const claudePlansPath = join104(extractDir, ".claude", DEFAULT_FOLDERS.plans);
89929
- if (await import_fs_extra34.pathExists(claudePlansPath)) {
90961
+ const claudePlansPath = join106(extractDir, ".claude", DEFAULT_FOLDERS.plans);
90962
+ if (await import_fs_extra37.pathExists(claudePlansPath)) {
89930
90963
  dirsToRename.push({
89931
90964
  from: claudePlansPath,
89932
- to: join104(extractDir, ".claude", folders.plans)
90965
+ to: join106(extractDir, ".claude", folders.plans)
89933
90966
  });
89934
90967
  }
89935
90968
  }
@@ -89937,11 +90970,11 @@ async function collectDirsToRename(extractDir, folders) {
89937
90970
  }
89938
90971
  async function moveAcrossDevices(src, dest) {
89939
90972
  try {
89940
- await rename7(src, dest);
90973
+ await rename8(src, dest);
89941
90974
  } catch (e2) {
89942
90975
  if (e2.code === "EXDEV") {
89943
90976
  logger.debug(`Cross-device move detected, using copy+delete: ${src} -> ${dest}`);
89944
- await import_fs_extra34.copy(src, dest, { overwrite: true });
90977
+ await import_fs_extra37.copy(src, dest, { overwrite: true });
89945
90978
  await rm13(src, { recursive: true, force: true });
89946
90979
  } else {
89947
90980
  throw e2;
@@ -89969,8 +91002,8 @@ async function renameFolders(dirsToRename, extractDir, options2) {
89969
91002
  // src/services/transformers/folder-transform/path-replacer.ts
89970
91003
  init_logger();
89971
91004
  init_types3();
89972
- import { readFile as readFile49, readdir as readdir32, writeFile as writeFile31 } from "node:fs/promises";
89973
- import { join as join105, relative as relative19 } from "node:path";
91005
+ import { readFile as readFile51, readdir as readdir33, writeFile as writeFile31 } from "node:fs/promises";
91006
+ import { join as join107, relative as relative19 } from "node:path";
89974
91007
  var TRANSFORMABLE_FILE_PATTERNS = [
89975
91008
  ".md",
89976
91009
  ".txt",
@@ -90021,9 +91054,9 @@ function compileReplacements(replacements) {
90021
91054
  async function transformFileContents(dir, compiledReplacements, options2) {
90022
91055
  let filesChanged = 0;
90023
91056
  let replacementsCount = 0;
90024
- const entries = await readdir32(dir, { withFileTypes: true });
91057
+ const entries = await readdir33(dir, { withFileTypes: true });
90025
91058
  for (const entry of entries) {
90026
- const fullPath = join105(dir, entry.name);
91059
+ const fullPath = join107(dir, entry.name);
90027
91060
  if (entry.isDirectory()) {
90028
91061
  if (entry.name === "node_modules" || entry.name === ".git") {
90029
91062
  continue;
@@ -90036,7 +91069,7 @@ async function transformFileContents(dir, compiledReplacements, options2) {
90036
91069
  if (!shouldTransform)
90037
91070
  continue;
90038
91071
  try {
90039
- const content = await readFile49(fullPath, "utf-8");
91072
+ const content = await readFile51(fullPath, "utf-8");
90040
91073
  let newContent = content;
90041
91074
  let changeCount = 0;
90042
91075
  for (const { regex: regex2, replacement } of compiledReplacements) {
@@ -90158,9 +91191,9 @@ async function transformFolderPaths(extractDir, folders, options2 = {}) {
90158
91191
 
90159
91192
  // src/services/transformers/global-path-transformer.ts
90160
91193
  init_logger();
90161
- import { readFile as readFile50, readdir as readdir33, writeFile as writeFile32 } from "node:fs/promises";
91194
+ import { readFile as readFile52, readdir as readdir34, writeFile as writeFile32 } from "node:fs/promises";
90162
91195
  import { platform as platform13 } from "node:os";
90163
- import { extname as extname6, join as join106 } from "node:path";
91196
+ import { extname as extname6, join as join108 } from "node:path";
90164
91197
  var IS_WINDOWS3 = platform13() === "win32";
90165
91198
  var HOME_PREFIX = IS_WINDOWS3 ? "%USERPROFILE%" : "$HOME";
90166
91199
  function getHomeDirPrefix() {
@@ -90268,9 +91301,9 @@ async function transformPathsForGlobalInstall(directory, options2 = {}) {
90268
91301
  let filesSkipped = 0;
90269
91302
  const skippedFiles = [];
90270
91303
  async function processDirectory2(dir) {
90271
- const entries = await readdir33(dir, { withFileTypes: true });
91304
+ const entries = await readdir34(dir, { withFileTypes: true });
90272
91305
  for (const entry of entries) {
90273
- const fullPath = join106(dir, entry.name);
91306
+ const fullPath = join108(dir, entry.name);
90274
91307
  if (entry.isDirectory()) {
90275
91308
  if (entry.name === "node_modules" || entry.name.startsWith(".") && entry.name !== ".claude") {
90276
91309
  continue;
@@ -90278,7 +91311,7 @@ async function transformPathsForGlobalInstall(directory, options2 = {}) {
90278
91311
  await processDirectory2(fullPath);
90279
91312
  } else if (entry.isFile() && shouldTransformFile3(entry.name)) {
90280
91313
  try {
90281
- const content = await readFile50(fullPath, "utf-8");
91314
+ const content = await readFile52(fullPath, "utf-8");
90282
91315
  const { transformed, changes } = transformContent(content);
90283
91316
  if (changes > 0) {
90284
91317
  await writeFile32(fullPath, transformed, "utf-8");
@@ -90346,7 +91379,7 @@ async function handleTransforms(ctx) {
90346
91379
  logger.debug(ctx.options.global ? "Saved folder configuration to ~/.claude/.ck.json" : "Saved folder configuration to .claude/.ck.json");
90347
91380
  }
90348
91381
  }
90349
- const claudeDir2 = ctx.options.global ? ctx.resolvedDir : join107(ctx.resolvedDir, ".claude");
91382
+ const claudeDir2 = ctx.options.global ? ctx.resolvedDir : join109(ctx.resolvedDir, ".claude");
90350
91383
  return {
90351
91384
  ...ctx,
90352
91385
  foldersConfig,
@@ -90540,8 +91573,8 @@ init_checksum_utils();
90540
91573
  init_config_discovery();
90541
91574
  var import_picocolors25 = __toESM(require_picocolors(), 1);
90542
91575
  import { existsSync as existsSync51 } from "node:fs";
90543
- import { readFile as readFile51, rm as rm14, unlink as unlink12 } from "node:fs/promises";
90544
- import { resolve as resolve22 } from "node:path";
91576
+ import { readFile as readFile53, rm as rm14, unlink as unlink12 } from "node:fs/promises";
91577
+ import { resolve as resolve23 } from "node:path";
90545
91578
 
90546
91579
  // src/commands/portable/conflict-resolver.ts
90547
91580
  init_dist2();
@@ -90888,7 +91921,7 @@ function shouldExecuteAction2(action) {
90888
91921
  }
90889
91922
  async function executeDeleteAction(action, options2) {
90890
91923
  const preservePaths = options2?.preservePaths ?? new Set;
90891
- const shouldPreserveTarget = action.targetPath.length > 0 && preservePaths.has(resolve22(action.targetPath));
91924
+ const shouldPreserveTarget = action.targetPath.length > 0 && preservePaths.has(resolve23(action.targetPath));
90892
91925
  try {
90893
91926
  if (!shouldPreserveTarget && action.targetPath && existsSync51(action.targetPath)) {
90894
91927
  await rm14(action.targetPath, { recursive: true, force: true });
@@ -91113,7 +92146,7 @@ async function migrateCommand(options2) {
91113
92146
  for (const action of conflictActions) {
91114
92147
  if (!action.diff && action.targetPath && existsSync51(action.targetPath)) {
91115
92148
  try {
91116
- const targetContent = await readFile51(action.targetPath, "utf-8");
92149
+ const targetContent = await readFile53(action.targetPath, "utf-8");
91117
92150
  const sourceItem = agents2.find((a3) => a3.name === action.item) || commands.find((c2) => c2.name === action.item) || (configItem?.name === action.item ? configItem : null) || ruleItems.find((r2) => r2.name === action.item);
91118
92151
  if (sourceItem) {
91119
92152
  const providerConfig = providers[action.provider];
@@ -91204,7 +92237,7 @@ async function migrateCommand(options2) {
91204
92237
  allResults.push(...await installSkillDirectories(skills, skillProviders, installOpts));
91205
92238
  }
91206
92239
  }
91207
- const writtenPaths = new Set(allResults.filter((result) => result.success && !result.skipped && result.path.length > 0).map((result) => resolve22(result.path)));
92240
+ const writtenPaths = new Set(allResults.filter((result) => result.success && !result.skipped && result.path.length > 0).map((result) => resolve23(result.path)));
91208
92241
  for (const deleteAction of plannedDeleteActions) {
91209
92242
  allResults.push(await executeDeleteAction(deleteAction, {
91210
92243
  preservePaths: writtenPaths
@@ -91320,7 +92353,7 @@ async function computeTargetStates(selectedProviders, global3) {
91320
92353
  exists: true
91321
92354
  };
91322
92355
  try {
91323
- const content = await readFile51(entry.path, "utf-8");
92356
+ const content = await readFile53(entry.path, "utf-8");
91324
92357
  state.currentChecksum = computeContentChecksum(content);
91325
92358
  } catch (error) {
91326
92359
  logger.debug(`[migrate] Failed to read target for checksum: ${entry.path} (${String(error)})`);
@@ -91379,11 +92412,11 @@ var import_picocolors26 = __toESM(require_picocolors(), 1);
91379
92412
 
91380
92413
  // src/commands/new/phases/directory-setup.ts
91381
92414
  init_config_manager();
91382
- import { resolve as resolve23 } from "node:path";
92415
+ import { resolve as resolve24 } from "node:path";
91383
92416
  init_logger();
91384
92417
  init_path_resolver();
91385
92418
  init_types3();
91386
- var import_fs_extra35 = __toESM(require_lib3(), 1);
92419
+ var import_fs_extra38 = __toESM(require_lib3(), 1);
91387
92420
  async function directorySetup(validOptions, prompts) {
91388
92421
  const isNonInteractive2 = !process.stdin.isTTY || process.env.CI === "true" || process.env.NON_INTERACTIVE === "true";
91389
92422
  const config = await ConfigManager.get();
@@ -91464,7 +92497,7 @@ async function directorySetup(validOptions, prompts) {
91464
92497
  targetDir = await prompts.getDirectory(targetDir);
91465
92498
  }
91466
92499
  }
91467
- const resolvedDir = resolve23(targetDir);
92500
+ const resolvedDir = resolve24(targetDir);
91468
92501
  logger.info(`Target directory: ${resolvedDir}`);
91469
92502
  if (PathResolver.isLocalSameAsGlobal(resolvedDir)) {
91470
92503
  logger.warning("You're creating a project at HOME directory.");
@@ -91482,8 +92515,8 @@ async function directorySetup(validOptions, prompts) {
91482
92515
  return null;
91483
92516
  }
91484
92517
  }
91485
- if (await import_fs_extra35.pathExists(resolvedDir)) {
91486
- const files = await import_fs_extra35.readdir(resolvedDir);
92518
+ if (await import_fs_extra38.pathExists(resolvedDir)) {
92519
+ const files = await import_fs_extra38.readdir(resolvedDir);
91487
92520
  const isEmpty = files.length === 0;
91488
92521
  if (!isEmpty) {
91489
92522
  if (isNonInteractive2) {
@@ -91521,7 +92554,7 @@ async function handleDirectorySetup(ctx) {
91521
92554
  // src/commands/new/phases/project-creation.ts
91522
92555
  init_config_manager();
91523
92556
  init_github_client();
91524
- import { join as join108 } from "node:path";
92557
+ import { join as join110 } from "node:path";
91525
92558
  init_logger();
91526
92559
  init_output_manager();
91527
92560
  init_types3();
@@ -91647,7 +92680,7 @@ async function projectCreation(kit, resolvedDir, validOptions, isNonInteractive2
91647
92680
  output.section("Installing");
91648
92681
  logger.verbose("Installation target", { directory: resolvedDir });
91649
92682
  const merger = new FileMerger;
91650
- const claudeDir2 = join108(resolvedDir, ".claude");
92683
+ const claudeDir2 = join110(resolvedDir, ".claude");
91651
92684
  merger.setMultiKitContext(claudeDir2, kit);
91652
92685
  if (validOptions.exclude && validOptions.exclude.length > 0) {
91653
92686
  merger.addIgnorePatterns(validOptions.exclude);
@@ -91694,7 +92727,7 @@ async function handleProjectCreation(ctx) {
91694
92727
  }
91695
92728
  // src/commands/new/phases/post-setup.ts
91696
92729
  init_projects_registry();
91697
- import { join as join109 } from "node:path";
92730
+ import { join as join111 } from "node:path";
91698
92731
  init_package_installer();
91699
92732
  init_logger();
91700
92733
  init_path_resolver();
@@ -91726,9 +92759,9 @@ async function postSetup(resolvedDir, validOptions, isNonInteractive2, prompts)
91726
92759
  withSudo: validOptions.withSudo
91727
92760
  });
91728
92761
  }
91729
- const claudeDir2 = join109(resolvedDir, ".claude");
92762
+ const claudeDir2 = join111(resolvedDir, ".claude");
91730
92763
  await promptSetupWizardIfNeeded({
91731
- envPath: join109(claudeDir2, ".env"),
92764
+ envPath: join111(claudeDir2, ".env"),
91732
92765
  claudeDir: claudeDir2,
91733
92766
  isGlobal: false,
91734
92767
  isNonInteractive: isNonInteractive2,
@@ -91801,11 +92834,11 @@ init_logger();
91801
92834
  init_safe_prompts();
91802
92835
  var import_picocolors27 = __toESM(require_picocolors(), 1);
91803
92836
  import { existsSync as existsSync52 } from "node:fs";
91804
- import { resolve as resolve24 } from "node:path";
92837
+ import { resolve as resolve25 } from "node:path";
91805
92838
  async function handleAdd(projectPath, options2) {
91806
92839
  logger.debug(`Adding project: ${projectPath}, options: ${JSON.stringify(options2)}`);
91807
92840
  intro("Add Project");
91808
- const absolutePath = resolve24(projectPath);
92841
+ const absolutePath = resolve25(projectPath);
91809
92842
  if (!existsSync52(absolutePath)) {
91810
92843
  log.error(`Path does not exist: ${absolutePath}`);
91811
92844
  process.exitCode = 1;
@@ -92695,7 +93728,7 @@ var import_picocolors32 = __toESM(require_picocolors(), 1);
92695
93728
  // src/commands/uninstall/installation-detector.ts
92696
93729
  init_claudekit_scanner();
92697
93730
  init_path_resolver();
92698
- var import_fs_extra36 = __toESM(require_lib3(), 1);
93731
+ var import_fs_extra39 = __toESM(require_lib3(), 1);
92699
93732
  function hasClaudeKitComponents(components) {
92700
93733
  return components.agents > 0 || components.commands > 0 || components.rules > 0 || components.skills > 0;
92701
93734
  }
@@ -92710,7 +93743,7 @@ async function detectInstallations() {
92710
93743
  installations.push({
92711
93744
  type: "local",
92712
93745
  path: setup.project.path,
92713
- exists: await import_fs_extra36.pathExists(setup.project.path),
93746
+ exists: await import_fs_extra39.pathExists(setup.project.path),
92714
93747
  hasMetadata,
92715
93748
  components: setup.project.components
92716
93749
  });
@@ -92723,7 +93756,7 @@ async function detectInstallations() {
92723
93756
  installations.push({
92724
93757
  type: "global",
92725
93758
  path: setup.global.path,
92726
- exists: await import_fs_extra36.pathExists(setup.global.path),
93759
+ exists: await import_fs_extra39.pathExists(setup.global.path),
92727
93760
  hasMetadata,
92728
93761
  components: setup.global.components
92729
93762
  });
@@ -92734,16 +93767,16 @@ async function detectInstallations() {
92734
93767
 
92735
93768
  // src/commands/uninstall/removal-handler.ts
92736
93769
  import { readdirSync as readdirSync6, rmSync as rmSync5 } from "node:fs";
92737
- import { join as join111, resolve as resolve25, sep as sep5 } from "node:path";
93770
+ import { join as join113, resolve as resolve26, sep as sep5 } from "node:path";
92738
93771
  init_logger();
92739
93772
  init_safe_prompts();
92740
93773
  init_safe_spinner();
92741
- var import_fs_extra37 = __toESM(require_lib3(), 1);
93774
+ var import_fs_extra40 = __toESM(require_lib3(), 1);
92742
93775
 
92743
93776
  // src/commands/uninstall/analysis-handler.ts
92744
93777
  init_metadata_migration();
92745
93778
  import { readdirSync as readdirSync5, rmSync as rmSync4 } from "node:fs";
92746
- import { dirname as dirname23, join as join110 } from "node:path";
93779
+ import { dirname as dirname24, join as join112 } from "node:path";
92747
93780
  init_logger();
92748
93781
  init_safe_prompts();
92749
93782
  var import_picocolors31 = __toESM(require_picocolors(), 1);
@@ -92761,7 +93794,7 @@ function classifyFileByOwnership(ownership, forceOverwrite, deleteReason) {
92761
93794
  }
92762
93795
  async function cleanupEmptyDirectories3(filePath, installationRoot) {
92763
93796
  let cleaned = 0;
92764
- let currentDir = dirname23(filePath);
93797
+ let currentDir = dirname24(filePath);
92765
93798
  while (currentDir !== installationRoot && currentDir.startsWith(installationRoot)) {
92766
93799
  try {
92767
93800
  const entries = readdirSync5(currentDir);
@@ -92769,7 +93802,7 @@ async function cleanupEmptyDirectories3(filePath, installationRoot) {
92769
93802
  rmSync4(currentDir, { recursive: true });
92770
93803
  cleaned++;
92771
93804
  logger.debug(`Removed empty directory: ${currentDir}`);
92772
- currentDir = dirname23(currentDir);
93805
+ currentDir = dirname24(currentDir);
92773
93806
  } else {
92774
93807
  break;
92775
93808
  }
@@ -92791,7 +93824,7 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
92791
93824
  if (uninstallManifest.isMultiKit && kit && metadata?.kits?.[kit]) {
92792
93825
  const kitFiles = metadata.kits[kit].files || [];
92793
93826
  for (const trackedFile of kitFiles) {
92794
- const filePath = join110(installation.path, trackedFile.path);
93827
+ const filePath = join112(installation.path, trackedFile.path);
92795
93828
  if (uninstallManifest.filesToPreserve.includes(trackedFile.path)) {
92796
93829
  result.toPreserve.push({ path: trackedFile.path, reason: "shared with other kit" });
92797
93830
  continue;
@@ -92821,7 +93854,7 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
92821
93854
  return result;
92822
93855
  }
92823
93856
  for (const trackedFile of allTrackedFiles) {
92824
- const filePath = join110(installation.path, trackedFile.path);
93857
+ const filePath = join112(installation.path, trackedFile.path);
92825
93858
  const ownershipResult = await OwnershipChecker.checkOwnership(filePath, metadata, installation.path);
92826
93859
  if (!ownershipResult.exists)
92827
93860
  continue;
@@ -92866,7 +93899,7 @@ function displayDryRunPreview(analysis, installationType) {
92866
93899
  // src/commands/uninstall/removal-handler.ts
92867
93900
  async function isDirectory(filePath) {
92868
93901
  try {
92869
- const stats = await import_fs_extra37.lstat(filePath);
93902
+ const stats = await import_fs_extra40.lstat(filePath);
92870
93903
  return stats.isDirectory();
92871
93904
  } catch {
92872
93905
  logger.debug(`Failed to check if path is directory: ${filePath}`);
@@ -92875,16 +93908,16 @@ async function isDirectory(filePath) {
92875
93908
  }
92876
93909
  async function isPathSafeToRemove(filePath, baseDir) {
92877
93910
  try {
92878
- const resolvedPath = resolve25(filePath);
92879
- const resolvedBase = resolve25(baseDir);
93911
+ const resolvedPath = resolve26(filePath);
93912
+ const resolvedBase = resolve26(baseDir);
92880
93913
  if (!resolvedPath.startsWith(resolvedBase + sep5) && resolvedPath !== resolvedBase) {
92881
93914
  logger.debug(`Path outside installation directory: ${filePath}`);
92882
93915
  return false;
92883
93916
  }
92884
- const stats = await import_fs_extra37.lstat(filePath);
93917
+ const stats = await import_fs_extra40.lstat(filePath);
92885
93918
  if (stats.isSymbolicLink()) {
92886
- const realPath = await import_fs_extra37.realpath(filePath);
92887
- const resolvedReal = resolve25(realPath);
93919
+ const realPath = await import_fs_extra40.realpath(filePath);
93920
+ const resolvedReal = resolve26(realPath);
92888
93921
  if (!resolvedReal.startsWith(resolvedBase + sep5) && resolvedReal !== resolvedBase) {
92889
93922
  logger.debug(`Symlink points outside installation directory: ${filePath} -> ${realPath}`);
92890
93923
  return false;
@@ -92918,15 +93951,15 @@ async function removeInstallations(installations, options2) {
92918
93951
  let removedCount = 0;
92919
93952
  let cleanedDirs = 0;
92920
93953
  for (const item of analysis.toDelete) {
92921
- const filePath = join111(installation.path, item.path);
92922
- if (!await import_fs_extra37.pathExists(filePath))
93954
+ const filePath = join113(installation.path, item.path);
93955
+ if (!await import_fs_extra40.pathExists(filePath))
92923
93956
  continue;
92924
93957
  if (!await isPathSafeToRemove(filePath, installation.path)) {
92925
93958
  logger.debug(`Skipping unsafe path: ${item.path}`);
92926
93959
  continue;
92927
93960
  }
92928
93961
  const isDir = await isDirectory(filePath);
92929
- await import_fs_extra37.remove(filePath);
93962
+ await import_fs_extra40.remove(filePath);
92930
93963
  removedCount++;
92931
93964
  logger.debug(`Removed ${isDir ? "directory" : "file"}: ${item.path}`);
92932
93965
  if (!isDir) {
@@ -93110,6 +94143,12 @@ ${import_picocolors32.default.yellow("User modifications will be permanently del
93110
94143
  forceOverwrite: validOptions.forceOverwrite,
93111
94144
  kit: validOptions.kit
93112
94145
  });
94146
+ if (!validOptions.kit || validOptions.kit === "engineer") {
94147
+ try {
94148
+ const { handlePluginUninstall: handlePluginUninstall2 } = await Promise.resolve().then(() => (init_plugin_installer(), exports_plugin_installer));
94149
+ await handlePluginUninstall2();
94150
+ } catch {}
94151
+ }
93113
94152
  const kitMsg = validOptions.kit ? ` (${validOptions.kit} kit)` : "";
93114
94153
  prompts.outro(`ClaudeKit${kitMsg} uninstalled successfully!`);
93115
94154
  } catch (error) {
@@ -93311,7 +94350,7 @@ init_logger();
93311
94350
  init_path_resolver();
93312
94351
  init_types3();
93313
94352
  import { existsSync as existsSync53, readFileSync as readFileSync10 } from "node:fs";
93314
- import { join as join112 } from "node:path";
94353
+ import { join as join114 } from "node:path";
93315
94354
  var packageVersion = package_default.version;
93316
94355
  function formatInstalledKits(metadata) {
93317
94356
  if (!metadata.kits || Object.keys(metadata.kits).length === 0) {
@@ -93343,9 +94382,9 @@ async function displayVersion() {
93343
94382
  let localKitVersion = null;
93344
94383
  let isGlobalOnlyKit = false;
93345
94384
  const globalKitDir = PathResolver.getGlobalKitDir();
93346
- const globalMetadataPath = join112(globalKitDir, "metadata.json");
94385
+ const globalMetadataPath = join114(globalKitDir, "metadata.json");
93347
94386
  const prefix = PathResolver.getPathPrefix(false);
93348
- const localMetadataPath = prefix ? join112(process.cwd(), prefix, "metadata.json") : join112(process.cwd(), "metadata.json");
94387
+ const localMetadataPath = prefix ? join114(process.cwd(), prefix, "metadata.json") : join114(process.cwd(), "metadata.json");
93349
94388
  const isLocalSameAsGlobal = localMetadataPath === globalMetadataPath;
93350
94389
  if (!isLocalSameAsGlobal && existsSync53(localMetadataPath)) {
93351
94390
  try {
@@ -93738,7 +94777,7 @@ var output2 = new OutputManager2;
93738
94777
 
93739
94778
  // src/shared/temp-cleanup.ts
93740
94779
  init_logger();
93741
- var import_fs_extra38 = __toESM(require_lib3(), 1);
94780
+ var import_fs_extra41 = __toESM(require_lib3(), 1);
93742
94781
  import { rmSync as rmSync6 } from "node:fs";
93743
94782
  var tempDirs2 = new Set;
93744
94783
  async function cleanup() {
@@ -93747,7 +94786,7 @@ async function cleanup() {
93747
94786
  logger.debug(`Cleaning up ${tempDirs2.size} temporary director(ies)...`);
93748
94787
  for (const dir of tempDirs2) {
93749
94788
  try {
93750
- await import_fs_extra38.remove(dir);
94789
+ await import_fs_extra41.remove(dir);
93751
94790
  logger.debug(`Cleaned up temp directory: ${dir}`);
93752
94791
  } catch (error) {
93753
94792
  logger.debug(`Failed to clean temp directory ${dir}: ${error}`);