claudekit-cli 3.10.1 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1759 -411
  2. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -6600,7 +6600,8 @@ var init_commands = __esm(() => {
6600
6600
  global: exports_external.boolean().default(false),
6601
6601
  all: exports_external.boolean().default(false),
6602
6602
  dryRun: exports_external.boolean().default(false),
6603
- forceOverwrite: exports_external.boolean().default(false)
6603
+ forceOverwrite: exports_external.boolean().default(false),
6604
+ kit: KitType.optional()
6604
6605
  }).merge(GlobalOutputOptionsSchema);
6605
6606
  UpdateCliOptionsSchema = exports_external.object({
6606
6607
  release: exports_external.string().optional(),
@@ -6643,7 +6644,7 @@ var init_github = __esm(() => {
6643
6644
  });
6644
6645
 
6645
6646
  // src/types/metadata.ts
6646
- var TrackedFileSchema, MetadataSchema, ConfigSchema;
6647
+ var TrackedFileSchema, KitMetadataSchema, MultiKitMetadataSchema, LegacyMetadataSchema, MetadataSchema, ConfigSchema;
6647
6648
  var init_metadata = __esm(() => {
6648
6649
  init_zod();
6649
6650
  init_commands();
@@ -6654,7 +6655,22 @@ var init_metadata = __esm(() => {
6654
6655
  ownership: exports_external.enum(["ck", "user", "ck-modified"]),
6655
6656
  installedVersion: exports_external.string()
6656
6657
  });
6657
- MetadataSchema = exports_external.object({
6658
+ KitMetadataSchema = exports_external.object({
6659
+ version: exports_external.string(),
6660
+ installedAt: exports_external.string(),
6661
+ files: exports_external.array(TrackedFileSchema).optional()
6662
+ });
6663
+ MultiKitMetadataSchema = exports_external.object({
6664
+ kits: exports_external.record(KitType, KitMetadataSchema).optional(),
6665
+ scope: exports_external.enum(["local", "global"]).optional(),
6666
+ name: exports_external.string().optional(),
6667
+ version: exports_external.string().optional(),
6668
+ installedAt: exports_external.string().optional(),
6669
+ installedFiles: exports_external.array(exports_external.string()).optional(),
6670
+ userConfigFiles: exports_external.array(exports_external.string()).optional(),
6671
+ files: exports_external.array(TrackedFileSchema).optional()
6672
+ });
6673
+ LegacyMetadataSchema = exports_external.object({
6658
6674
  name: exports_external.string().optional(),
6659
6675
  version: exports_external.string().optional(),
6660
6676
  installedAt: exports_external.string().optional(),
@@ -6663,6 +6679,7 @@ var init_metadata = __esm(() => {
6663
6679
  userConfigFiles: exports_external.array(exports_external.string()).optional(),
6664
6680
  files: exports_external.array(TrackedFileSchema).optional()
6665
6681
  });
6682
+ MetadataSchema = MultiKitMetadataSchema;
6666
6683
  ConfigSchema = exports_external.object({
6667
6684
  defaults: exports_external.object({
6668
6685
  kit: KitType.optional(),
@@ -6747,8 +6764,11 @@ __export(exports_types, {
6747
6764
  PROTECTED_PATTERNS: () => PROTECTED_PATTERNS,
6748
6765
  NewCommandOptionsSchema: () => NewCommandOptionsSchema,
6749
6766
  NEVER_COPY_PATTERNS: () => NEVER_COPY_PATTERNS,
6767
+ MultiKitMetadataSchema: () => MultiKitMetadataSchema,
6750
6768
  MetadataSchema: () => MetadataSchema,
6769
+ LegacyMetadataSchema: () => LegacyMetadataSchema,
6751
6770
  KitType: () => KitType,
6771
+ KitMetadataSchema: () => KitMetadataSchema,
6752
6772
  KitConfigSchema: () => KitConfigSchema,
6753
6773
  GitHubReleaseSchema: () => GitHubReleaseSchema,
6754
6774
  GitHubReleaseAssetSchema: () => GitHubReleaseAssetSchema,
@@ -6895,7 +6915,7 @@ var require_umd = __commonJS((exports, module) => {
6895
6915
  });
6896
6916
 
6897
6917
  // src/shared/environment.ts
6898
- function isCIEnvironment3() {
6918
+ function isCIEnvironment() {
6899
6919
  return process.env.CI === "true" || process.env.CI_SAFE_MODE === "true";
6900
6920
  }
6901
6921
  function isNonInteractive() {
@@ -12253,9 +12273,751 @@ var require_ignore = __commonJS((exports, module) => {
12253
12273
  }
12254
12274
  });
12255
12275
 
12276
+ // node_modules/retry/lib/retry_operation.js
12277
+ var require_retry_operation = __commonJS((exports, module) => {
12278
+ function RetryOperation(timeouts, options) {
12279
+ if (typeof options === "boolean") {
12280
+ options = { forever: options };
12281
+ }
12282
+ this._originalTimeouts = JSON.parse(JSON.stringify(timeouts));
12283
+ this._timeouts = timeouts;
12284
+ this._options = options || {};
12285
+ this._maxRetryTime = options && options.maxRetryTime || Infinity;
12286
+ this._fn = null;
12287
+ this._errors = [];
12288
+ this._attempts = 1;
12289
+ this._operationTimeout = null;
12290
+ this._operationTimeoutCb = null;
12291
+ this._timeout = null;
12292
+ this._operationStart = null;
12293
+ if (this._options.forever) {
12294
+ this._cachedTimeouts = this._timeouts.slice(0);
12295
+ }
12296
+ }
12297
+ module.exports = RetryOperation;
12298
+ RetryOperation.prototype.reset = function() {
12299
+ this._attempts = 1;
12300
+ this._timeouts = this._originalTimeouts;
12301
+ };
12302
+ RetryOperation.prototype.stop = function() {
12303
+ if (this._timeout) {
12304
+ clearTimeout(this._timeout);
12305
+ }
12306
+ this._timeouts = [];
12307
+ this._cachedTimeouts = null;
12308
+ };
12309
+ RetryOperation.prototype.retry = function(err) {
12310
+ if (this._timeout) {
12311
+ clearTimeout(this._timeout);
12312
+ }
12313
+ if (!err) {
12314
+ return false;
12315
+ }
12316
+ var currentTime = new Date().getTime();
12317
+ if (err && currentTime - this._operationStart >= this._maxRetryTime) {
12318
+ this._errors.unshift(new Error("RetryOperation timeout occurred"));
12319
+ return false;
12320
+ }
12321
+ this._errors.push(err);
12322
+ var timeout = this._timeouts.shift();
12323
+ if (timeout === undefined) {
12324
+ if (this._cachedTimeouts) {
12325
+ this._errors.splice(this._errors.length - 1, this._errors.length);
12326
+ this._timeouts = this._cachedTimeouts.slice(0);
12327
+ timeout = this._timeouts.shift();
12328
+ } else {
12329
+ return false;
12330
+ }
12331
+ }
12332
+ var self2 = this;
12333
+ var timer = setTimeout(function() {
12334
+ self2._attempts++;
12335
+ if (self2._operationTimeoutCb) {
12336
+ self2._timeout = setTimeout(function() {
12337
+ self2._operationTimeoutCb(self2._attempts);
12338
+ }, self2._operationTimeout);
12339
+ if (self2._options.unref) {
12340
+ self2._timeout.unref();
12341
+ }
12342
+ }
12343
+ self2._fn(self2._attempts);
12344
+ }, timeout);
12345
+ if (this._options.unref) {
12346
+ timer.unref();
12347
+ }
12348
+ return true;
12349
+ };
12350
+ RetryOperation.prototype.attempt = function(fn, timeoutOps) {
12351
+ this._fn = fn;
12352
+ if (timeoutOps) {
12353
+ if (timeoutOps.timeout) {
12354
+ this._operationTimeout = timeoutOps.timeout;
12355
+ }
12356
+ if (timeoutOps.cb) {
12357
+ this._operationTimeoutCb = timeoutOps.cb;
12358
+ }
12359
+ }
12360
+ var self2 = this;
12361
+ if (this._operationTimeoutCb) {
12362
+ this._timeout = setTimeout(function() {
12363
+ self2._operationTimeoutCb();
12364
+ }, self2._operationTimeout);
12365
+ }
12366
+ this._operationStart = new Date().getTime();
12367
+ this._fn(this._attempts);
12368
+ };
12369
+ RetryOperation.prototype.try = function(fn) {
12370
+ console.log("Using RetryOperation.try() is deprecated");
12371
+ this.attempt(fn);
12372
+ };
12373
+ RetryOperation.prototype.start = function(fn) {
12374
+ console.log("Using RetryOperation.start() is deprecated");
12375
+ this.attempt(fn);
12376
+ };
12377
+ RetryOperation.prototype.start = RetryOperation.prototype.try;
12378
+ RetryOperation.prototype.errors = function() {
12379
+ return this._errors;
12380
+ };
12381
+ RetryOperation.prototype.attempts = function() {
12382
+ return this._attempts;
12383
+ };
12384
+ RetryOperation.prototype.mainError = function() {
12385
+ if (this._errors.length === 0) {
12386
+ return null;
12387
+ }
12388
+ var counts = {};
12389
+ var mainError = null;
12390
+ var mainErrorCount = 0;
12391
+ for (var i = 0;i < this._errors.length; i++) {
12392
+ var error = this._errors[i];
12393
+ var message = error.message;
12394
+ var count = (counts[message] || 0) + 1;
12395
+ counts[message] = count;
12396
+ if (count >= mainErrorCount) {
12397
+ mainError = error;
12398
+ mainErrorCount = count;
12399
+ }
12400
+ }
12401
+ return mainError;
12402
+ };
12403
+ });
12404
+
12405
+ // node_modules/retry/lib/retry.js
12406
+ var require_retry = __commonJS((exports) => {
12407
+ var RetryOperation = require_retry_operation();
12408
+ exports.operation = function(options) {
12409
+ var timeouts = exports.timeouts(options);
12410
+ return new RetryOperation(timeouts, {
12411
+ forever: options && options.forever,
12412
+ unref: options && options.unref,
12413
+ maxRetryTime: options && options.maxRetryTime
12414
+ });
12415
+ };
12416
+ exports.timeouts = function(options) {
12417
+ if (options instanceof Array) {
12418
+ return [].concat(options);
12419
+ }
12420
+ var opts = {
12421
+ retries: 10,
12422
+ factor: 2,
12423
+ minTimeout: 1 * 1000,
12424
+ maxTimeout: Infinity,
12425
+ randomize: false
12426
+ };
12427
+ for (var key in options) {
12428
+ opts[key] = options[key];
12429
+ }
12430
+ if (opts.minTimeout > opts.maxTimeout) {
12431
+ throw new Error("minTimeout is greater than maxTimeout");
12432
+ }
12433
+ var timeouts = [];
12434
+ for (var i = 0;i < opts.retries; i++) {
12435
+ timeouts.push(this.createTimeout(i, opts));
12436
+ }
12437
+ if (options && options.forever && !timeouts.length) {
12438
+ timeouts.push(this.createTimeout(i, opts));
12439
+ }
12440
+ timeouts.sort(function(a3, b3) {
12441
+ return a3 - b3;
12442
+ });
12443
+ return timeouts;
12444
+ };
12445
+ exports.createTimeout = function(attempt, opts) {
12446
+ var random = opts.randomize ? Math.random() + 1 : 1;
12447
+ var timeout = Math.round(random * opts.minTimeout * Math.pow(opts.factor, attempt));
12448
+ timeout = Math.min(timeout, opts.maxTimeout);
12449
+ return timeout;
12450
+ };
12451
+ exports.wrap = function(obj, options, methods) {
12452
+ if (options instanceof Array) {
12453
+ methods = options;
12454
+ options = null;
12455
+ }
12456
+ if (!methods) {
12457
+ methods = [];
12458
+ for (var key in obj) {
12459
+ if (typeof obj[key] === "function") {
12460
+ methods.push(key);
12461
+ }
12462
+ }
12463
+ }
12464
+ for (var i = 0;i < methods.length; i++) {
12465
+ var method = methods[i];
12466
+ var original = obj[method];
12467
+ obj[method] = function retryWrapper(original2) {
12468
+ var op = exports.operation(options);
12469
+ var args = Array.prototype.slice.call(arguments, 1);
12470
+ var callback = args.pop();
12471
+ args.push(function(err) {
12472
+ if (op.retry(err)) {
12473
+ return;
12474
+ }
12475
+ if (err) {
12476
+ arguments[0] = op.mainError();
12477
+ }
12478
+ callback.apply(this, arguments);
12479
+ });
12480
+ op.attempt(function() {
12481
+ original2.apply(obj, args);
12482
+ });
12483
+ }.bind(obj, original);
12484
+ obj[method].options = options;
12485
+ }
12486
+ };
12487
+ });
12488
+
12489
+ // node_modules/signal-exit/signals.js
12490
+ var require_signals = __commonJS((exports, module) => {
12491
+ module.exports = [
12492
+ "SIGABRT",
12493
+ "SIGALRM",
12494
+ "SIGHUP",
12495
+ "SIGINT",
12496
+ "SIGTERM"
12497
+ ];
12498
+ if (process.platform !== "win32") {
12499
+ module.exports.push("SIGVTALRM", "SIGXCPU", "SIGXFSZ", "SIGUSR2", "SIGTRAP", "SIGSYS", "SIGQUIT", "SIGIOT");
12500
+ }
12501
+ if (process.platform === "linux") {
12502
+ module.exports.push("SIGIO", "SIGPOLL", "SIGPWR", "SIGSTKFLT", "SIGUNUSED");
12503
+ }
12504
+ });
12505
+
12506
+ // node_modules/signal-exit/index.js
12507
+ var require_signal_exit = __commonJS((exports, module) => {
12508
+ var process10 = global.process;
12509
+ var processOk2 = function(process11) {
12510
+ return process11 && typeof process11 === "object" && typeof process11.removeListener === "function" && typeof process11.emit === "function" && typeof process11.reallyExit === "function" && typeof process11.listeners === "function" && typeof process11.kill === "function" && typeof process11.pid === "number" && typeof process11.on === "function";
12511
+ };
12512
+ if (!processOk2(process10)) {
12513
+ module.exports = function() {
12514
+ return function() {};
12515
+ };
12516
+ } else {
12517
+ assert3 = __require("assert");
12518
+ signals2 = require_signals();
12519
+ isWin = /^win/i.test(process10.platform);
12520
+ EE3 = __require("events");
12521
+ if (typeof EE3 !== "function") {
12522
+ EE3 = EE3.EventEmitter;
12523
+ }
12524
+ if (process10.__signal_exit_emitter__) {
12525
+ emitter = process10.__signal_exit_emitter__;
12526
+ } else {
12527
+ emitter = process10.__signal_exit_emitter__ = new EE3;
12528
+ emitter.count = 0;
12529
+ emitter.emitted = {};
12530
+ }
12531
+ if (!emitter.infinite) {
12532
+ emitter.setMaxListeners(Infinity);
12533
+ emitter.infinite = true;
12534
+ }
12535
+ module.exports = function(cb, opts) {
12536
+ if (!processOk2(global.process)) {
12537
+ return function() {};
12538
+ }
12539
+ assert3.equal(typeof cb, "function", "a callback must be provided for exit handler");
12540
+ if (loaded === false) {
12541
+ load2();
12542
+ }
12543
+ var ev = "exit";
12544
+ if (opts && opts.alwaysLast) {
12545
+ ev = "afterexit";
12546
+ }
12547
+ var remove = function() {
12548
+ emitter.removeListener(ev, cb);
12549
+ if (emitter.listeners("exit").length === 0 && emitter.listeners("afterexit").length === 0) {
12550
+ unload2();
12551
+ }
12552
+ };
12553
+ emitter.on(ev, cb);
12554
+ return remove;
12555
+ };
12556
+ unload2 = function unload() {
12557
+ if (!loaded || !processOk2(global.process)) {
12558
+ return;
12559
+ }
12560
+ loaded = false;
12561
+ signals2.forEach(function(sig) {
12562
+ try {
12563
+ process10.removeListener(sig, sigListeners[sig]);
12564
+ } catch (er) {}
12565
+ });
12566
+ process10.emit = originalProcessEmit;
12567
+ process10.reallyExit = originalProcessReallyExit;
12568
+ emitter.count -= 1;
12569
+ };
12570
+ module.exports.unload = unload2;
12571
+ emit = function emit(event, code2, signal) {
12572
+ if (emitter.emitted[event]) {
12573
+ return;
12574
+ }
12575
+ emitter.emitted[event] = true;
12576
+ emitter.emit(event, code2, signal);
12577
+ };
12578
+ sigListeners = {};
12579
+ signals2.forEach(function(sig) {
12580
+ sigListeners[sig] = function listener() {
12581
+ if (!processOk2(global.process)) {
12582
+ return;
12583
+ }
12584
+ var listeners = process10.listeners(sig);
12585
+ if (listeners.length === emitter.count) {
12586
+ unload2();
12587
+ emit("exit", null, sig);
12588
+ emit("afterexit", null, sig);
12589
+ if (isWin && sig === "SIGHUP") {
12590
+ sig = "SIGINT";
12591
+ }
12592
+ process10.kill(process10.pid, sig);
12593
+ }
12594
+ };
12595
+ });
12596
+ module.exports.signals = function() {
12597
+ return signals2;
12598
+ };
12599
+ loaded = false;
12600
+ load2 = function load() {
12601
+ if (loaded || !processOk2(global.process)) {
12602
+ return;
12603
+ }
12604
+ loaded = true;
12605
+ emitter.count += 1;
12606
+ signals2 = signals2.filter(function(sig) {
12607
+ try {
12608
+ process10.on(sig, sigListeners[sig]);
12609
+ return true;
12610
+ } catch (er) {
12611
+ return false;
12612
+ }
12613
+ });
12614
+ process10.emit = processEmit;
12615
+ process10.reallyExit = processReallyExit;
12616
+ };
12617
+ module.exports.load = load2;
12618
+ originalProcessReallyExit = process10.reallyExit;
12619
+ processReallyExit = function processReallyExit(code2) {
12620
+ if (!processOk2(global.process)) {
12621
+ return;
12622
+ }
12623
+ process10.exitCode = code2 || 0;
12624
+ emit("exit", process10.exitCode, null);
12625
+ emit("afterexit", process10.exitCode, null);
12626
+ originalProcessReallyExit.call(process10, process10.exitCode);
12627
+ };
12628
+ originalProcessEmit = process10.emit;
12629
+ processEmit = function processEmit(ev, arg) {
12630
+ if (ev === "exit" && processOk2(global.process)) {
12631
+ if (arg !== undefined) {
12632
+ process10.exitCode = arg;
12633
+ }
12634
+ var ret = originalProcessEmit.apply(this, arguments);
12635
+ emit("exit", process10.exitCode, null);
12636
+ emit("afterexit", process10.exitCode, null);
12637
+ return ret;
12638
+ } else {
12639
+ return originalProcessEmit.apply(this, arguments);
12640
+ }
12641
+ };
12642
+ }
12643
+ var assert3;
12644
+ var signals2;
12645
+ var isWin;
12646
+ var EE3;
12647
+ var emitter;
12648
+ var unload2;
12649
+ var emit;
12650
+ var sigListeners;
12651
+ var loaded;
12652
+ var load2;
12653
+ var originalProcessReallyExit;
12654
+ var processReallyExit;
12655
+ var originalProcessEmit;
12656
+ var processEmit;
12657
+ });
12658
+
12659
+ // node_modules/proper-lockfile/lib/mtime-precision.js
12660
+ var require_mtime_precision = __commonJS((exports, module) => {
12661
+ var cacheSymbol = Symbol();
12662
+ function probe(file, fs12, callback) {
12663
+ const cachedPrecision = fs12[cacheSymbol];
12664
+ if (cachedPrecision) {
12665
+ return fs12.stat(file, (err, stat3) => {
12666
+ if (err) {
12667
+ return callback(err);
12668
+ }
12669
+ callback(null, stat3.mtime, cachedPrecision);
12670
+ });
12671
+ }
12672
+ const mtime = new Date(Math.ceil(Date.now() / 1000) * 1000 + 5);
12673
+ fs12.utimes(file, mtime, mtime, (err) => {
12674
+ if (err) {
12675
+ return callback(err);
12676
+ }
12677
+ fs12.stat(file, (err2, stat3) => {
12678
+ if (err2) {
12679
+ return callback(err2);
12680
+ }
12681
+ const precision = stat3.mtime.getTime() % 1000 === 0 ? "s" : "ms";
12682
+ Object.defineProperty(fs12, cacheSymbol, { value: precision });
12683
+ callback(null, stat3.mtime, precision);
12684
+ });
12685
+ });
12686
+ }
12687
+ function getMtime(precision) {
12688
+ let now = Date.now();
12689
+ if (precision === "s") {
12690
+ now = Math.ceil(now / 1000) * 1000;
12691
+ }
12692
+ return new Date(now);
12693
+ }
12694
+ exports.probe = probe;
12695
+ exports.getMtime = getMtime;
12696
+ });
12697
+
12698
+ // node_modules/proper-lockfile/lib/lockfile.js
12699
+ var require_lockfile = __commonJS((exports, module) => {
12700
+ var path9 = __require("path");
12701
+ var fs12 = require_graceful_fs();
12702
+ var retry = require_retry();
12703
+ var onExit2 = require_signal_exit();
12704
+ var mtimePrecision = require_mtime_precision();
12705
+ var locks = {};
12706
+ function getLockFile(file, options) {
12707
+ return options.lockfilePath || `${file}.lock`;
12708
+ }
12709
+ function resolveCanonicalPath(file, options, callback) {
12710
+ if (!options.realpath) {
12711
+ return callback(null, path9.resolve(file));
12712
+ }
12713
+ options.fs.realpath(file, callback);
12714
+ }
12715
+ function acquireLock(file, options, callback) {
12716
+ const lockfilePath = getLockFile(file, options);
12717
+ options.fs.mkdir(lockfilePath, (err) => {
12718
+ if (!err) {
12719
+ return mtimePrecision.probe(lockfilePath, options.fs, (err2, mtime, mtimePrecision2) => {
12720
+ if (err2) {
12721
+ options.fs.rmdir(lockfilePath, () => {});
12722
+ return callback(err2);
12723
+ }
12724
+ callback(null, mtime, mtimePrecision2);
12725
+ });
12726
+ }
12727
+ if (err.code !== "EEXIST") {
12728
+ return callback(err);
12729
+ }
12730
+ if (options.stale <= 0) {
12731
+ return callback(Object.assign(new Error("Lock file is already being held"), { code: "ELOCKED", file }));
12732
+ }
12733
+ options.fs.stat(lockfilePath, (err2, stat3) => {
12734
+ if (err2) {
12735
+ if (err2.code === "ENOENT") {
12736
+ return acquireLock(file, { ...options, stale: 0 }, callback);
12737
+ }
12738
+ return callback(err2);
12739
+ }
12740
+ if (!isLockStale(stat3, options)) {
12741
+ return callback(Object.assign(new Error("Lock file is already being held"), { code: "ELOCKED", file }));
12742
+ }
12743
+ removeLock(file, options, (err3) => {
12744
+ if (err3) {
12745
+ return callback(err3);
12746
+ }
12747
+ acquireLock(file, { ...options, stale: 0 }, callback);
12748
+ });
12749
+ });
12750
+ });
12751
+ }
12752
+ function isLockStale(stat3, options) {
12753
+ return stat3.mtime.getTime() < Date.now() - options.stale;
12754
+ }
12755
+ function removeLock(file, options, callback) {
12756
+ options.fs.rmdir(getLockFile(file, options), (err) => {
12757
+ if (err && err.code !== "ENOENT") {
12758
+ return callback(err);
12759
+ }
12760
+ callback();
12761
+ });
12762
+ }
12763
+ function updateLock(file, options) {
12764
+ const lock2 = locks[file];
12765
+ if (lock2.updateTimeout) {
12766
+ return;
12767
+ }
12768
+ lock2.updateDelay = lock2.updateDelay || options.update;
12769
+ lock2.updateTimeout = setTimeout(() => {
12770
+ lock2.updateTimeout = null;
12771
+ options.fs.stat(lock2.lockfilePath, (err, stat3) => {
12772
+ const isOverThreshold = lock2.lastUpdate + options.stale < Date.now();
12773
+ if (err) {
12774
+ if (err.code === "ENOENT" || isOverThreshold) {
12775
+ return setLockAsCompromised(file, lock2, Object.assign(err, { code: "ECOMPROMISED" }));
12776
+ }
12777
+ lock2.updateDelay = 1000;
12778
+ return updateLock(file, options);
12779
+ }
12780
+ const isMtimeOurs = lock2.mtime.getTime() === stat3.mtime.getTime();
12781
+ if (!isMtimeOurs) {
12782
+ return setLockAsCompromised(file, lock2, Object.assign(new Error("Unable to update lock within the stale threshold"), { code: "ECOMPROMISED" }));
12783
+ }
12784
+ const mtime = mtimePrecision.getMtime(lock2.mtimePrecision);
12785
+ options.fs.utimes(lock2.lockfilePath, mtime, mtime, (err2) => {
12786
+ const isOverThreshold2 = lock2.lastUpdate + options.stale < Date.now();
12787
+ if (lock2.released) {
12788
+ return;
12789
+ }
12790
+ if (err2) {
12791
+ if (err2.code === "ENOENT" || isOverThreshold2) {
12792
+ return setLockAsCompromised(file, lock2, Object.assign(err2, { code: "ECOMPROMISED" }));
12793
+ }
12794
+ lock2.updateDelay = 1000;
12795
+ return updateLock(file, options);
12796
+ }
12797
+ lock2.mtime = mtime;
12798
+ lock2.lastUpdate = Date.now();
12799
+ lock2.updateDelay = null;
12800
+ updateLock(file, options);
12801
+ });
12802
+ });
12803
+ }, lock2.updateDelay);
12804
+ if (lock2.updateTimeout.unref) {
12805
+ lock2.updateTimeout.unref();
12806
+ }
12807
+ }
12808
+ function setLockAsCompromised(file, lock2, err) {
12809
+ lock2.released = true;
12810
+ if (lock2.updateTimeout) {
12811
+ clearTimeout(lock2.updateTimeout);
12812
+ }
12813
+ if (locks[file] === lock2) {
12814
+ delete locks[file];
12815
+ }
12816
+ lock2.options.onCompromised(err);
12817
+ }
12818
+ function lock(file, options, callback) {
12819
+ options = {
12820
+ stale: 1e4,
12821
+ update: null,
12822
+ realpath: true,
12823
+ retries: 0,
12824
+ fs: fs12,
12825
+ onCompromised: (err) => {
12826
+ throw err;
12827
+ },
12828
+ ...options
12829
+ };
12830
+ options.retries = options.retries || 0;
12831
+ options.retries = typeof options.retries === "number" ? { retries: options.retries } : options.retries;
12832
+ options.stale = Math.max(options.stale || 0, 2000);
12833
+ options.update = options.update == null ? options.stale / 2 : options.update || 0;
12834
+ options.update = Math.max(Math.min(options.update, options.stale / 2), 1000);
12835
+ resolveCanonicalPath(file, options, (err, file2) => {
12836
+ if (err) {
12837
+ return callback(err);
12838
+ }
12839
+ const operation = retry.operation(options.retries);
12840
+ operation.attempt(() => {
12841
+ acquireLock(file2, options, (err2, mtime, mtimePrecision2) => {
12842
+ if (operation.retry(err2)) {
12843
+ return;
12844
+ }
12845
+ if (err2) {
12846
+ return callback(operation.mainError());
12847
+ }
12848
+ const lock2 = locks[file2] = {
12849
+ lockfilePath: getLockFile(file2, options),
12850
+ mtime,
12851
+ mtimePrecision: mtimePrecision2,
12852
+ options,
12853
+ lastUpdate: Date.now()
12854
+ };
12855
+ updateLock(file2, options);
12856
+ callback(null, (releasedCallback) => {
12857
+ if (lock2.released) {
12858
+ return releasedCallback && releasedCallback(Object.assign(new Error("Lock is already released"), { code: "ERELEASED" }));
12859
+ }
12860
+ unlock(file2, { ...options, realpath: false }, releasedCallback);
12861
+ });
12862
+ });
12863
+ });
12864
+ });
12865
+ }
12866
+ function unlock(file, options, callback) {
12867
+ options = {
12868
+ fs: fs12,
12869
+ realpath: true,
12870
+ ...options
12871
+ };
12872
+ resolveCanonicalPath(file, options, (err, file2) => {
12873
+ if (err) {
12874
+ return callback(err);
12875
+ }
12876
+ const lock2 = locks[file2];
12877
+ if (!lock2) {
12878
+ return callback(Object.assign(new Error("Lock is not acquired/owned by you"), { code: "ENOTACQUIRED" }));
12879
+ }
12880
+ lock2.updateTimeout && clearTimeout(lock2.updateTimeout);
12881
+ lock2.released = true;
12882
+ delete locks[file2];
12883
+ removeLock(file2, options, callback);
12884
+ });
12885
+ }
12886
+ function check(file, options, callback) {
12887
+ options = {
12888
+ stale: 1e4,
12889
+ realpath: true,
12890
+ fs: fs12,
12891
+ ...options
12892
+ };
12893
+ options.stale = Math.max(options.stale || 0, 2000);
12894
+ resolveCanonicalPath(file, options, (err, file2) => {
12895
+ if (err) {
12896
+ return callback(err);
12897
+ }
12898
+ options.fs.stat(getLockFile(file2, options), (err2, stat3) => {
12899
+ if (err2) {
12900
+ return err2.code === "ENOENT" ? callback(null, false) : callback(err2);
12901
+ }
12902
+ return callback(null, !isLockStale(stat3, options));
12903
+ });
12904
+ });
12905
+ }
12906
+ function getLocks() {
12907
+ return locks;
12908
+ }
12909
+ onExit2(() => {
12910
+ for (const file in locks) {
12911
+ const options = locks[file].options;
12912
+ try {
12913
+ options.fs.rmdirSync(getLockFile(file, options));
12914
+ } catch (e2) {}
12915
+ }
12916
+ });
12917
+ exports.lock = lock;
12918
+ exports.unlock = unlock;
12919
+ exports.check = check;
12920
+ exports.getLocks = getLocks;
12921
+ });
12922
+
12923
+ // node_modules/proper-lockfile/lib/adapter.js
12924
+ var require_adapter = __commonJS((exports, module) => {
12925
+ var fs12 = require_graceful_fs();
12926
+ function createSyncFs(fs13) {
12927
+ const methods = ["mkdir", "realpath", "stat", "rmdir", "utimes"];
12928
+ const newFs = { ...fs13 };
12929
+ methods.forEach((method) => {
12930
+ newFs[method] = (...args) => {
12931
+ const callback = args.pop();
12932
+ let ret;
12933
+ try {
12934
+ ret = fs13[`${method}Sync`](...args);
12935
+ } catch (err) {
12936
+ return callback(err);
12937
+ }
12938
+ callback(null, ret);
12939
+ };
12940
+ });
12941
+ return newFs;
12942
+ }
12943
+ function toPromise(method) {
12944
+ return (...args) => new Promise((resolve3, reject) => {
12945
+ args.push((err, result) => {
12946
+ if (err) {
12947
+ reject(err);
12948
+ } else {
12949
+ resolve3(result);
12950
+ }
12951
+ });
12952
+ method(...args);
12953
+ });
12954
+ }
12955
+ function toSync(method) {
12956
+ return (...args) => {
12957
+ let err;
12958
+ let result;
12959
+ args.push((_err, _result) => {
12960
+ err = _err;
12961
+ result = _result;
12962
+ });
12963
+ method(...args);
12964
+ if (err) {
12965
+ throw err;
12966
+ }
12967
+ return result;
12968
+ };
12969
+ }
12970
+ function toSyncOptions(options) {
12971
+ options = { ...options };
12972
+ options.fs = createSyncFs(options.fs || fs12);
12973
+ if (typeof options.retries === "number" && options.retries > 0 || options.retries && typeof options.retries.retries === "number" && options.retries.retries > 0) {
12974
+ throw Object.assign(new Error("Cannot use retries with the sync api"), { code: "ESYNC" });
12975
+ }
12976
+ return options;
12977
+ }
12978
+ module.exports = {
12979
+ toPromise,
12980
+ toSync,
12981
+ toSyncOptions
12982
+ };
12983
+ });
12984
+
12985
+ // node_modules/proper-lockfile/index.js
12986
+ var require_proper_lockfile = __commonJS((exports, module) => {
12987
+ var lockfile = require_lockfile();
12988
+ var { toPromise, toSync, toSyncOptions } = require_adapter();
12989
+ async function lock(file, options) {
12990
+ const release = await toPromise(lockfile.lock)(file, options);
12991
+ return toPromise(release);
12992
+ }
12993
+ function lockSync(file, options) {
12994
+ const release = toSync(lockfile.lock)(file, toSyncOptions(options));
12995
+ return toSync(release);
12996
+ }
12997
+ function unlock(file, options) {
12998
+ return toPromise(lockfile.unlock)(file, options);
12999
+ }
13000
+ function unlockSync(file, options) {
13001
+ return toSync(lockfile.unlock)(file, toSyncOptions(options));
13002
+ }
13003
+ function check(file, options) {
13004
+ return toPromise(lockfile.check)(file, options);
13005
+ }
13006
+ function checkSync(file, options) {
13007
+ return toSync(lockfile.check)(file, toSyncOptions(options));
13008
+ }
13009
+ module.exports = lock;
13010
+ module.exports.lock = lock;
13011
+ module.exports.unlock = unlock;
13012
+ module.exports.lockSync = lockSync;
13013
+ module.exports.unlockSync = unlockSync;
13014
+ module.exports.check = check;
13015
+ module.exports.checkSync = checkSync;
13016
+ });
13017
+
12256
13018
  // src/services/package-installer/install-error-handler.ts
12257
13019
  import { existsSync as existsSync6, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "node:fs";
12258
- import { join as join24 } from "node:path";
13020
+ import { join as join25 } from "node:path";
12259
13021
  function parseNameReason(str) {
12260
13022
  const colonIndex = str.indexOf(":");
12261
13023
  if (colonIndex === -1) {
@@ -12264,7 +13026,7 @@ function parseNameReason(str) {
12264
13026
  return [str.slice(0, colonIndex).trim(), str.slice(colonIndex + 1).trim()];
12265
13027
  }
12266
13028
  function displayInstallErrors(skillsDir) {
12267
- const summaryPath = join24(skillsDir, ".install-error-summary.json");
13029
+ const summaryPath = join25(skillsDir, ".install-error-summary.json");
12268
13030
  if (!existsSync6(summaryPath)) {
12269
13031
  logger.error("Skills installation failed. Run with --verbose for details.");
12270
13032
  return;
@@ -12355,7 +13117,7 @@ async function checkNeedsSudoPackages() {
12355
13117
  }
12356
13118
  }
12357
13119
  function hasInstallState(skillsDir) {
12358
- const stateFilePath = join24(skillsDir, ".install-state.json");
13120
+ const stateFilePath = join25(skillsDir, ".install-state.json");
12359
13121
  return existsSync6(stateFilePath);
12360
13122
  }
12361
13123
  var WHICH_COMMAND_TIMEOUT_MS = 5000;
@@ -12368,19 +13130,20 @@ var exports_gemini_mcp_linker = {};
12368
13130
  __export(exports_gemini_mcp_linker, {
12369
13131
  processGeminiMcpLinking: () => processGeminiMcpLinking,
12370
13132
  linkGeminiMcpConfig: () => linkGeminiMcpConfig,
13133
+ getGeminiSettingsPath: () => getGeminiSettingsPath,
12371
13134
  findMcpConfigPath: () => findMcpConfigPath,
12372
13135
  checkExistingGeminiConfig: () => checkExistingGeminiConfig,
12373
13136
  addGeminiToGitignore: () => addGeminiToGitignore
12374
13137
  });
12375
13138
  import { existsSync as existsSync7, lstatSync, readlinkSync } from "node:fs";
12376
- import { mkdir as mkdir9, readFile as readFile14, symlink as symlink2, writeFile as writeFile12 } from "node:fs/promises";
13139
+ import { mkdir as mkdir9, readFile as readFile15, symlink as symlink2, writeFile as writeFile13 } from "node:fs/promises";
12377
13140
  import { homedir as homedir4 } from "node:os";
12378
- import { dirname as dirname6, join as join25, resolve as resolve3 } from "node:path";
13141
+ import { dirname as dirname6, join as join26, resolve as resolve3 } from "node:path";
12379
13142
  function getGlobalMcpConfigPath() {
12380
- return join25(homedir4(), ".claude", ".mcp.json");
13143
+ return join26(homedir4(), ".claude", ".mcp.json");
12381
13144
  }
12382
13145
  function getLocalMcpConfigPath(projectDir) {
12383
- return join25(projectDir, ".mcp.json");
13146
+ return join26(projectDir, ".mcp.json");
12384
13147
  }
12385
13148
  function findMcpConfigPath(projectDir) {
12386
13149
  const localPath = getLocalMcpConfigPath(projectDir);
@@ -12396,25 +13159,36 @@ function findMcpConfigPath(projectDir) {
12396
13159
  logger.debug("No MCP config found (local or global)");
12397
13160
  return null;
12398
13161
  }
12399
- function checkExistingGeminiConfig(projectDir) {
12400
- const geminiSettingsPath = join25(projectDir, ".gemini", "settings.json");
12401
- if (!existsSync7(geminiSettingsPath)) {
12402
- return { exists: false, isSymlink: false };
13162
+ function getGeminiSettingsPath(projectDir, isGlobal) {
13163
+ if (isGlobal) {
13164
+ return join26(homedir4(), ".gemini", "settings.json");
13165
+ }
13166
+ return join26(projectDir, ".gemini", "settings.json");
13167
+ }
13168
+ function checkExistingGeminiConfig(projectDir, isGlobal = false) {
13169
+ const geminiSettingsPath = getGeminiSettingsPath(projectDir, isGlobal);
13170
+ if (!existsSync7(geminiSettingsPath)) {
13171
+ return { exists: false, isSymlink: false, settingsPath: geminiSettingsPath };
12403
13172
  }
12404
13173
  try {
12405
13174
  const stats = lstatSync(geminiSettingsPath);
12406
13175
  if (stats.isSymbolicLink()) {
12407
13176
  const target = readlinkSync(geminiSettingsPath);
12408
- return { exists: true, isSymlink: true, currentTarget: target };
13177
+ return {
13178
+ exists: true,
13179
+ isSymlink: true,
13180
+ currentTarget: target,
13181
+ settingsPath: geminiSettingsPath
13182
+ };
12409
13183
  }
12410
- return { exists: true, isSymlink: false };
13184
+ return { exists: true, isSymlink: false, settingsPath: geminiSettingsPath };
12411
13185
  } catch {
12412
- return { exists: true, isSymlink: false };
13186
+ return { exists: true, isSymlink: false, settingsPath: geminiSettingsPath };
12413
13187
  }
12414
13188
  }
12415
13189
  async function readJsonFile(filePath) {
12416
13190
  try {
12417
- const content = await readFile14(filePath, "utf-8");
13191
+ const content = await readFile15(filePath, "utf-8");
12418
13192
  return JSON.parse(content);
12419
13193
  } catch (error) {
12420
13194
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
@@ -12422,20 +13196,25 @@ async function readJsonFile(filePath) {
12422
13196
  return null;
12423
13197
  }
12424
13198
  }
12425
- async function createSymlink(targetPath, linkPath, projectDir) {
13199
+ async function createSymlink(targetPath, linkPath, projectDir, isGlobal) {
12426
13200
  const isWindows5 = process.platform === "win32";
12427
13201
  const linkDir = dirname6(linkPath);
12428
13202
  if (!existsSync7(linkDir)) {
12429
13203
  await mkdir9(linkDir, { recursive: true });
12430
13204
  logger.debug(`Created directory: ${linkDir}`);
12431
13205
  }
12432
- const localMcpPath = join25(projectDir, ".mcp.json");
12433
- const isLocalConfig = targetPath === localMcpPath;
12434
- const symlinkTarget = isLocalConfig ? "../.mcp.json" : targetPath;
13206
+ let symlinkTarget;
13207
+ if (isGlobal) {
13208
+ symlinkTarget = getGlobalMcpConfigPath();
13209
+ } else {
13210
+ const localMcpPath = join26(projectDir, ".mcp.json");
13211
+ const isLocalConfig = targetPath === localMcpPath;
13212
+ symlinkTarget = isLocalConfig ? "../.mcp.json" : targetPath;
13213
+ }
12435
13214
  try {
12436
13215
  await symlink2(symlinkTarget, linkPath, isWindows5 ? "file" : undefined);
12437
13216
  logger.debug(`Created symlink: ${linkPath} → ${symlinkTarget}`);
12438
- return { success: true, method: "symlink", targetPath };
13217
+ return { success: true, method: "symlink", targetPath, geminiSettingsPath: linkPath };
12439
13218
  } catch (error) {
12440
13219
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
12441
13220
  return {
@@ -12461,7 +13240,7 @@ async function createNewSettingsWithMerge(geminiSettingsPath, mcpConfigPath) {
12461
13240
  }
12462
13241
  const newSettings = { mcpServers };
12463
13242
  try {
12464
- await writeFile12(geminiSettingsPath, JSON.stringify(newSettings, null, 2), "utf-8");
13243
+ await writeFile13(geminiSettingsPath, JSON.stringify(newSettings, null, 2), "utf-8");
12465
13244
  logger.debug(`Created new Gemini settings with mcpServers: ${geminiSettingsPath}`);
12466
13245
  return { success: true, method: "merge", targetPath: mcpConfigPath };
12467
13246
  } catch (error) {
@@ -12491,7 +13270,7 @@ async function mergeGeminiSettings(geminiSettingsPath, mcpConfigPath) {
12491
13270
  mcpServers
12492
13271
  };
12493
13272
  try {
12494
- await writeFile12(geminiSettingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
13273
+ await writeFile13(geminiSettingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
12495
13274
  logger.debug(`Merged mcpServers into: ${geminiSettingsPath}`);
12496
13275
  return { success: true, method: "merge", targetPath: mcpConfigPath };
12497
13276
  } catch (error) {
@@ -12504,12 +13283,12 @@ async function mergeGeminiSettings(geminiSettingsPath, mcpConfigPath) {
12504
13283
  }
12505
13284
  }
12506
13285
  async function addGeminiToGitignore(projectDir) {
12507
- const gitignorePath = join25(projectDir, ".gitignore");
13286
+ const gitignorePath = join26(projectDir, ".gitignore");
12508
13287
  const geminiPattern = ".gemini/";
12509
13288
  try {
12510
13289
  let content = "";
12511
13290
  if (existsSync7(gitignorePath)) {
12512
- content = await readFile14(gitignorePath, "utf-8");
13291
+ content = await readFile15(gitignorePath, "utf-8");
12513
13292
  const lines = content.split(`
12514
13293
  `).map((line) => line.trim()).filter((line) => !line.startsWith("#"));
12515
13294
  const geminiPatterns = [".gemini/", ".gemini", "/.gemini/", "/.gemini"];
@@ -12522,7 +13301,7 @@ async function addGeminiToGitignore(projectDir) {
12522
13301
  `) || content === "" ? "" : `
12523
13302
  `;
12524
13303
  const comment = "# Gemini CLI settings (contains user-specific config)";
12525
- await writeFile12(gitignorePath, `${content}${newLine}${comment}
13304
+ await writeFile13(gitignorePath, `${content}${newLine}${comment}
12526
13305
  ${geminiPattern}
12527
13306
  `, "utf-8");
12528
13307
  logger.debug(`Added ${geminiPattern} to .gitignore`);
@@ -12532,9 +13311,9 @@ ${geminiPattern}
12532
13311
  }
12533
13312
  }
12534
13313
  async function linkGeminiMcpConfig(projectDir, options = {}) {
12535
- const { skipGitignore = false } = options;
13314
+ const { skipGitignore = false, isGlobal = false } = options;
12536
13315
  const resolvedProjectDir = resolve3(projectDir);
12537
- const geminiSettingsPath = join25(resolvedProjectDir, ".gemini", "settings.json");
13316
+ const geminiSettingsPath = getGeminiSettingsPath(resolvedProjectDir, isGlobal);
12538
13317
  const mcpConfigPath = findMcpConfigPath(resolvedProjectDir);
12539
13318
  if (!mcpConfigPath) {
12540
13319
  return {
@@ -12543,32 +13322,38 @@ async function linkGeminiMcpConfig(projectDir, options = {}) {
12543
13322
  error: "No MCP config found. Create .mcp.json or ~/.claude/.mcp.json first."
12544
13323
  };
12545
13324
  }
12546
- const existing = checkExistingGeminiConfig(resolvedProjectDir);
13325
+ const existing = checkExistingGeminiConfig(resolvedProjectDir, isGlobal);
12547
13326
  let result;
12548
13327
  if (!existing.exists) {
12549
- result = await createSymlink(mcpConfigPath, geminiSettingsPath, resolvedProjectDir);
13328
+ result = await createSymlink(mcpConfigPath, geminiSettingsPath, resolvedProjectDir, isGlobal);
12550
13329
  if (!result.success && process.platform === "win32") {
12551
13330
  logger.debug("Symlink failed on Windows, falling back to creating new settings with mcpServers");
12552
13331
  result = await createNewSettingsWithMerge(geminiSettingsPath, mcpConfigPath);
12553
13332
  }
12554
13333
  } else if (existing.isSymlink) {
12555
13334
  logger.debug(`Gemini config already symlinked: ${existing.currentTarget}`);
12556
- result = { success: true, method: "skipped", targetPath: existing.currentTarget };
13335
+ result = {
13336
+ success: true,
13337
+ method: "skipped",
13338
+ targetPath: existing.currentTarget,
13339
+ geminiSettingsPath
13340
+ };
12557
13341
  } else {
12558
13342
  result = await mergeGeminiSettings(geminiSettingsPath, mcpConfigPath);
12559
13343
  }
12560
- if (result.success && !skipGitignore) {
13344
+ if (result.success && !skipGitignore && !isGlobal) {
12561
13345
  await addGeminiToGitignore(resolvedProjectDir);
12562
13346
  }
12563
13347
  return result;
12564
13348
  }
12565
- async function processGeminiMcpLinking(projectDir) {
13349
+ async function processGeminiMcpLinking(projectDir, options = {}) {
12566
13350
  logger.info("Setting up Gemini CLI MCP integration...");
12567
- const result = await linkGeminiMcpConfig(projectDir);
13351
+ const result = await linkGeminiMcpConfig(projectDir, options);
13352
+ const settingsPath = result.geminiSettingsPath || (options.isGlobal ? "~/.gemini/settings.json" : ".gemini/settings.json");
12568
13353
  if (result.success) {
12569
13354
  switch (result.method) {
12570
13355
  case "symlink":
12571
- logger.success(`Gemini MCP linked: .gemini/settings.json → ${result.targetPath}`);
13356
+ logger.success(`Gemini MCP linked: ${settingsPath} → ${result.targetPath}`);
12572
13357
  logger.info("MCP servers will auto-sync with your Claude config.");
12573
13358
  break;
12574
13359
  case "merge":
@@ -12581,7 +13366,11 @@ async function processGeminiMcpLinking(projectDir) {
12581
13366
  }
12582
13367
  } else {
12583
13368
  logger.warning(`Gemini MCP setup incomplete: ${result.error}`);
12584
- logger.info("Manual setup: mkdir -p .gemini && ln -sf .claude/.mcp.json .gemini/settings.json");
13369
+ if (options.isGlobal) {
13370
+ logger.info("Manual setup: mkdir -p ~/.gemini && ln -sf ~/.claude/.mcp.json ~/.gemini/settings.json");
13371
+ } else {
13372
+ logger.info("Manual setup: mkdir -p .gemini && ln -sf ../.mcp.json .gemini/settings.json");
13373
+ }
12585
13374
  }
12586
13375
  }
12587
13376
  var init_gemini_mcp_linker = __esm(() => {
@@ -12604,7 +13393,7 @@ __export(exports_package_installer, {
12604
13393
  getPackageVersion: () => getPackageVersion
12605
13394
  });
12606
13395
  import { exec as exec5, execFile as execFile2, spawn } from "node:child_process";
12607
- import { join as join26, resolve as resolve4 } from "node:path";
13396
+ import { join as join27, resolve as resolve4 } from "node:path";
12608
13397
  import { promisify as promisify5 } from "node:util";
12609
13398
  function executeInteractiveScript(command, args, options) {
12610
13399
  return new Promise((resolve5, reject) => {
@@ -12671,7 +13460,7 @@ async function isGeminiInstalled() {
12671
13460
  }
12672
13461
  async function isPackageInstalled(packageName) {
12673
13462
  validatePackageName(packageName);
12674
- if (isCIEnvironment3()) {
13463
+ if (isCIEnvironment()) {
12675
13464
  logger.info(`CI environment detected: skipping network check for ${packageName}`);
12676
13465
  return false;
12677
13466
  }
@@ -12707,7 +13496,7 @@ async function isPackageInstalled(packageName) {
12707
13496
  }
12708
13497
  async function getPackageVersion(packageName) {
12709
13498
  validatePackageName(packageName);
12710
- if (isCIEnvironment3()) {
13499
+ if (isCIEnvironment()) {
12711
13500
  logger.info(`CI environment detected: skipping version check for ${packageName}`);
12712
13501
  return null;
12713
13502
  }
@@ -12787,7 +13576,7 @@ async function installPackageGlobally(packageName, packageDisplayName) {
12787
13576
  }
12788
13577
  async function installOpenCode() {
12789
13578
  const displayName = "OpenCode CLI";
12790
- if (isCIEnvironment3()) {
13579
+ if (isCIEnvironment()) {
12791
13580
  logger.info("CI environment detected: skipping OpenCode installation");
12792
13581
  return {
12793
13582
  success: false,
@@ -12799,7 +13588,7 @@ async function installOpenCode() {
12799
13588
  logger.info(`Installing ${displayName}...`);
12800
13589
  const { unlink: unlink5 } = await import("node:fs/promises");
12801
13590
  const { tmpdir: tmpdir3 } = await import("node:os");
12802
- const tempScriptPath = join26(tmpdir3(), "opencode-install.sh");
13591
+ const tempScriptPath = join27(tmpdir3(), "opencode-install.sh");
12803
13592
  try {
12804
13593
  logger.info("Downloading OpenCode installation script...");
12805
13594
  await execFileAsync("curl", ["-fsSL", "https://opencode.ai/install", "-o", tempScriptPath], {
@@ -12896,7 +13685,7 @@ function validateScriptPath(skillsDir, scriptPath) {
12896
13685
  }
12897
13686
  async function installSkillsDependencies(skillsDir) {
12898
13687
  const displayName = "Skills Dependencies";
12899
- if (isCIEnvironment3()) {
13688
+ if (isCIEnvironment()) {
12900
13689
  logger.info("CI environment detected: skipping skills installation");
12901
13690
  return {
12902
13691
  success: false,
@@ -12918,7 +13707,7 @@ async function installSkillsDependencies(skillsDir) {
12918
13707
  const clack = await Promise.resolve().then(() => (init_dist2(), exports_dist));
12919
13708
  const platform9 = process.platform;
12920
13709
  const scriptName = platform9 === "win32" ? "install.ps1" : "install.sh";
12921
- const scriptPath = join26(skillsDir, scriptName);
13710
+ const scriptPath = join27(skillsDir, scriptName);
12922
13711
  try {
12923
13712
  validateScriptPath(skillsDir, scriptPath);
12924
13713
  } catch (error) {
@@ -12934,7 +13723,7 @@ async function installSkillsDependencies(skillsDir) {
12934
13723
  logger.warning(`Skills installation script not found: ${scriptPath}`);
12935
13724
  logger.info("");
12936
13725
  logger.info("\uD83D\uDCD6 Manual Installation Instructions:");
12937
- logger.info(` See: ${join26(skillsDir, "INSTALLATION.md")}`);
13726
+ logger.info(` See: ${join27(skillsDir, "INSTALLATION.md")}`);
12938
13727
  logger.info("");
12939
13728
  logger.info("Quick start:");
12940
13729
  logger.info(" cd .claude/skills/ai-multimodal/scripts");
@@ -12950,8 +13739,8 @@ async function installSkillsDependencies(skillsDir) {
12950
13739
  logger.info(` Platform: ${platform9 === "win32" ? "Windows (PowerShell)" : "Unix (bash)"}`);
12951
13740
  if (logger.isVerbose()) {
12952
13741
  try {
12953
- const { readFile: readFile15 } = await import("node:fs/promises");
12954
- const scriptContent = await readFile15(scriptPath, "utf-8");
13742
+ const { readFile: readFile16 } = await import("node:fs/promises");
13743
+ const scriptContent = await readFile16(scriptPath, "utf-8");
12955
13744
  const previewLines = scriptContent.split(`
12956
13745
  `).slice(0, 20);
12957
13746
  logger.verbose("Script preview (first 20 lines):");
@@ -12977,7 +13766,7 @@ async function installSkillsDependencies(skillsDir) {
12977
13766
  logger.info(` ${platform9 === "win32" ? `powershell -File "${scriptPath}"` : `bash ${scriptPath}`}`);
12978
13767
  logger.info("");
12979
13768
  logger.info("Or see complete guide:");
12980
- logger.info(` ${join26(skillsDir, "INSTALLATION.md")}`);
13769
+ logger.info(` ${join27(skillsDir, "INSTALLATION.md")}`);
12981
13770
  return {
12982
13771
  success: false,
12983
13772
  package: displayName,
@@ -13078,7 +13867,7 @@ async function installSkillsDependencies(skillsDir) {
13078
13867
  logger.info("\uD83D\uDCD6 Manual Installation Instructions:");
13079
13868
  logger.info("");
13080
13869
  logger.info("See complete guide:");
13081
- logger.info(` cat ${join26(skillsDir, "INSTALLATION.md")}`);
13870
+ logger.info(` cat ${join27(skillsDir, "INSTALLATION.md")}`);
13082
13871
  logger.info("");
13083
13872
  logger.info("Quick start:");
13084
13873
  logger.info(" cd .claude/skills/ai-multimodal/scripts");
@@ -14072,7 +14861,7 @@ var init_help_interceptor = __esm(() => {
14072
14861
 
14073
14862
  // src/index.ts
14074
14863
  import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
14075
- import { join as join36 } from "path";
14864
+ import { join as join37 } from "path";
14076
14865
 
14077
14866
  // node_modules/cac/dist/index.mjs
14078
14867
  import { EventEmitter } from "events";
@@ -14677,7 +15466,7 @@ var cac = (name = "") => new CAC(name);
14677
15466
  // package.json
14678
15467
  var package_default = {
14679
15468
  name: "claudekit-cli",
14680
- version: "3.10.1",
15469
+ version: "3.11.0",
14681
15470
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
14682
15471
  type: "module",
14683
15472
  repository: {
@@ -14697,7 +15486,7 @@ var package_default = {
14697
15486
  ],
14698
15487
  scripts: {
14699
15488
  dev: "bun run src/index.ts",
14700
- build: "bun build src/index.ts --outdir dist --target node --external keytar --external @octokit/rest",
15489
+ build: "bun build src/index.ts --outdir dist --target node --external @octokit/rest",
14701
15490
  compile: "bun build src/index.ts --compile --outfile ck",
14702
15491
  "compile:binary": "bun build src/index.ts --compile --outfile bin/ck",
14703
15492
  "compile:binaries": "node scripts/build-all-binaries.js",
@@ -14738,11 +15527,11 @@ var package_default = {
14738
15527
  "extract-zip": "^2.0.1",
14739
15528
  "fs-extra": "^11.2.0",
14740
15529
  ignore: "^5.3.2",
14741
- keytar: "^7.9.0",
14742
15530
  minimatch: "^10.1.1",
14743
15531
  ora: "^8.0.0",
14744
15532
  "p-limit": "^7.2.0",
14745
15533
  picocolors: "^1.1.1",
15534
+ "proper-lockfile": "^4.1.2",
14746
15535
  tar: "^7.4.3",
14747
15536
  tmp: "^0.2.3",
14748
15537
  zod: "^3.23.8"
@@ -14755,6 +15544,7 @@ var package_default = {
14755
15544
  "@types/cli-progress": "^3.11.6",
14756
15545
  "@types/fs-extra": "^11.0.4",
14757
15546
  "@types/node": "^22.10.1",
15547
+ "@types/proper-lockfile": "^4.1.4",
14758
15548
  "@types/tar": "^6.1.13",
14759
15549
  "@types/tmp": "^0.2.6",
14760
15550
  "semantic-release": "^24.2.0",
@@ -14922,7 +15712,12 @@ init_logger();
14922
15712
  import { exec } from "node:child_process";
14923
15713
  import { promisify } from "node:util";
14924
15714
  var execAsync = promisify(exec);
14925
- var isCIEnvironment = process.env.CI === "true" || process.env.CI_SAFE_MODE === "true";
15715
+ function shouldSkipExpensiveOperations() {
15716
+ if (process.env.CK_TEST_HOME) {
15717
+ return false;
15718
+ }
15719
+ return process.env.CI === "true" || process.env.CI_SAFE_MODE === "true";
15720
+ }
14926
15721
  function getOSInfo() {
14927
15722
  const platform = process.platform;
14928
15723
  const arch = process.arch;
@@ -14994,7 +15789,7 @@ var DEPENDENCIES = {
14994
15789
  }
14995
15790
  };
14996
15791
  async function commandExists(command) {
14997
- if (isCIEnvironment) {
15792
+ if (shouldSkipExpensiveOperations()) {
14998
15793
  const supportedCommands = ["node", "python", "python3", "pip", "pip3", "claude"];
14999
15794
  return supportedCommands.includes(command);
15000
15795
  }
@@ -15010,7 +15805,7 @@ async function commandExists(command) {
15010
15805
  }
15011
15806
  }
15012
15807
  async function getCommandPath(command) {
15013
- if (isCIEnvironment) {
15808
+ if (shouldSkipExpensiveOperations()) {
15014
15809
  const ciPath = getCICommandPath(command);
15015
15810
  if (ciPath)
15016
15811
  return ciPath;
@@ -15029,7 +15824,7 @@ async function getCommandPath(command) {
15029
15824
  }
15030
15825
  }
15031
15826
  async function getCommandVersion(command, versionFlag, versionRegex) {
15032
- if (isCIEnvironment) {
15827
+ if (shouldSkipExpensiveOperations()) {
15033
15828
  const mockVersions = {
15034
15829
  npm: "10.0.0",
15035
15830
  node: "20.0.0",
@@ -15361,6 +16156,12 @@ function compareVersions2(a, b) {
15361
16156
  }
15362
16157
  return 0;
15363
16158
  }
16159
+ function shouldSkipExpensiveOperations2() {
16160
+ if (process.env.CK_TEST_HOME) {
16161
+ return false;
16162
+ }
16163
+ return process.env.CI === "true" || process.env.CI_SAFE_MODE === "true";
16164
+ }
15364
16165
 
15365
16166
  class SystemChecker {
15366
16167
  group = "system";
@@ -15375,13 +16176,29 @@ class SystemChecker {
15375
16176
  logger.verbose(`SystemChecker: Processing ${dep.name}`);
15376
16177
  results.push(await this.mapDependencyToCheck(dep));
15377
16178
  }
15378
- logger.verbose("SystemChecker: Checking git");
15379
- results.push(await this.checkGit());
15380
- logger.verbose("SystemChecker: Checking GitHub CLI");
15381
- results.push(await this.checkGitHubCli());
16179
+ if (!shouldSkipExpensiveOperations2()) {
16180
+ logger.verbose("SystemChecker: Checking git");
16181
+ results.push(await this.checkGit());
16182
+ logger.verbose("SystemChecker: Checking GitHub CLI");
16183
+ results.push(await this.checkGitHubCli());
16184
+ } else {
16185
+ logger.verbose("SystemChecker: Skipping git/gh checks in CI");
16186
+ results.push(this.createCISkipResult("git-version", "Git"));
16187
+ results.push(this.createCISkipResult("gh-cli-version", "GitHub CLI"));
16188
+ }
15382
16189
  logger.verbose("SystemChecker: All system checks complete");
15383
16190
  return results;
15384
16191
  }
16192
+ createCISkipResult(id, name) {
16193
+ return {
16194
+ id,
16195
+ name,
16196
+ group: "system",
16197
+ status: "pass",
16198
+ message: "Skipped in CI",
16199
+ autoFixable: false
16200
+ };
16201
+ }
15385
16202
  async mapDependencyToCheck(dep) {
15386
16203
  const isInstalled = dep.installed && dep.meetsRequirements;
15387
16204
  const name = this.formatDependencyName(dep.name);
@@ -16069,6 +16886,12 @@ async function getClaudeKitSetup(projectDir = process.cwd()) {
16069
16886
  // src/domains/health-checks/claudekit-checker.ts
16070
16887
  init_logger();
16071
16888
  var HOOK_EXTENSIONS = [".js", ".cjs", ".mjs", ".ts", ".sh", ".ps1"];
16889
+ function shouldSkipExpensiveOperations3() {
16890
+ if (process.env.CK_TEST_HOME) {
16891
+ return false;
16892
+ }
16893
+ return process.env.CI === "true" || process.env.CI_SAFE_MODE === "true";
16894
+ }
16072
16895
 
16073
16896
  class ClaudekitChecker {
16074
16897
  group = "claudekit";
@@ -16338,6 +17161,18 @@ class ClaudekitChecker {
16338
17161
  }
16339
17162
  async checkGlobalDirReadable() {
16340
17163
  const globalDir = PathResolver.getGlobalKitDir();
17164
+ if (shouldSkipExpensiveOperations3()) {
17165
+ return {
17166
+ id: "ck-global-dir-readable",
17167
+ name: "Global Dir Readable",
17168
+ group: "claudekit",
17169
+ priority: "standard",
17170
+ status: "info",
17171
+ message: "Skipped in CI/test environment",
17172
+ details: globalDir,
17173
+ autoFixable: false
17174
+ };
17175
+ }
16341
17176
  try {
16342
17177
  await access(globalDir, constants.R_OK);
16343
17178
  return {
@@ -16366,6 +17201,18 @@ class ClaudekitChecker {
16366
17201
  }
16367
17202
  async checkGlobalDirWritable() {
16368
17203
  const globalDir = PathResolver.getGlobalKitDir();
17204
+ if (shouldSkipExpensiveOperations3()) {
17205
+ return {
17206
+ id: "ck-global-dir-writable",
17207
+ name: "Global Dir Writable",
17208
+ group: "claudekit",
17209
+ priority: "standard",
17210
+ status: "info",
17211
+ message: "Skipped in CI/test environment",
17212
+ details: globalDir,
17213
+ autoFixable: false
17214
+ };
17215
+ }
16369
17216
  const timestamp = Date.now();
16370
17217
  const random = Math.random().toString(36).substring(2);
16371
17218
  const testFile = join4(globalDir, `.ck-write-test-${timestamp}-${random}`);
@@ -17449,7 +18296,12 @@ Need help? Run with: ck new --verbose`, 404);
17449
18296
  // src/domains/health-checks/auth-checker.ts
17450
18297
  init_logger();
17451
18298
  init_types2();
17452
- var isCIEnvironment2 = process.env.CI === "true" || process.env.CI_SAFE_MODE === "true";
18299
+ function shouldSkipExpensiveOperations4() {
18300
+ if (process.env.CK_TEST_HOME) {
18301
+ return false;
18302
+ }
18303
+ return process.env.CI === "true" || process.env.CI_SAFE_MODE === "true";
18304
+ }
17453
18305
 
17454
18306
  class AuthChecker {
17455
18307
  group = "auth";
@@ -17533,14 +18385,14 @@ class AuthChecker {
17533
18385
  }
17534
18386
  async checkRepoAccess(kit) {
17535
18387
  const kitConfig = AVAILABLE_KITS[kit];
17536
- if (isCIEnvironment2) {
17537
- logger.verbose(`AuthChecker: Skipping repo access check for ${kit} in CI`);
18388
+ if (shouldSkipExpensiveOperations4()) {
18389
+ logger.verbose(`AuthChecker: Skipping repo access check for ${kit} in CI/test`);
17538
18390
  return {
17539
18391
  id: `repo-access-${kit}`,
17540
18392
  name: `Repository Access (${kit})`,
17541
18393
  group: "auth",
17542
18394
  status: "info",
17543
- message: "Skipped in CI environment",
18395
+ message: "Skipped in CI/test environment",
17544
18396
  autoFixable: false
17545
18397
  };
17546
18398
  }
@@ -17600,6 +18452,12 @@ import { constants as constants2, access as access2, mkdir as mkdir3, readFile a
17600
18452
  import { arch, homedir as homedir3, platform as platform3 } from "node:os";
17601
18453
  import { join as join6, normalize as normalize3 } from "node:path";
17602
18454
  var IS_WINDOWS = platform3() === "win32";
18455
+ function shouldSkipExpensiveOperations5() {
18456
+ if (process.env.CK_TEST_HOME) {
18457
+ return false;
18458
+ }
18459
+ return process.env.CI === "true" || process.env.CI_SAFE_MODE === "true";
18460
+ }
17603
18461
 
17604
18462
  class PlatformChecker {
17605
18463
  group = "platform";
@@ -17698,6 +18556,18 @@ class PlatformChecker {
17698
18556
  }
17699
18557
  async checkGlobalDirAccess() {
17700
18558
  const globalDir = PathResolver.getGlobalKitDir();
18559
+ if (shouldSkipExpensiveOperations5()) {
18560
+ return {
18561
+ id: "global-dir-access",
18562
+ name: "Global Dir Access",
18563
+ group: "platform",
18564
+ priority: "critical",
18565
+ status: "info",
18566
+ message: "Skipped in CI/test environment",
18567
+ details: globalDir,
18568
+ autoFixable: false
18569
+ };
18570
+ }
17701
18571
  const testFile = join6(globalDir, ".ck-doctor-access-test");
17702
18572
  try {
17703
18573
  await mkdir3(globalDir, { recursive: true });
@@ -17771,6 +18641,17 @@ class PlatformChecker {
17771
18641
  };
17772
18642
  }
17773
18643
  async checkLongPathSupport() {
18644
+ if (shouldSkipExpensiveOperations5()) {
18645
+ return {
18646
+ id: "long-path-support",
18647
+ name: "Long Path Support",
18648
+ group: "platform",
18649
+ priority: "extended",
18650
+ status: "info",
18651
+ message: "Skipped in CI/test environment",
18652
+ autoFixable: false
18653
+ };
18654
+ }
17774
18655
  try {
17775
18656
  const { execSync: execSync3 } = await import("node:child_process");
17776
18657
  const result = execSync3('reg query "HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem" /v LongPathsEnabled', { encoding: "utf-8", timeout: 2000 });
@@ -17798,6 +18679,17 @@ class PlatformChecker {
17798
18679
  }
17799
18680
  }
17800
18681
  async checkSymlinkSupport() {
18682
+ if (shouldSkipExpensiveOperations5()) {
18683
+ return {
18684
+ id: "symlink-support",
18685
+ name: "Symlink Support",
18686
+ group: "platform",
18687
+ priority: "extended",
18688
+ status: "info",
18689
+ message: "Skipped in CI/test environment",
18690
+ autoFixable: false
18691
+ };
18692
+ }
17801
18693
  const testDir = PathResolver.getGlobalKitDir();
17802
18694
  const target = join6(testDir, ".ck-symlink-test-target");
17803
18695
  const link = join6(testDir, ".ck-symlink-test-link");
@@ -18521,7 +19413,7 @@ async function doctorCommand(options = {}) {
18521
19413
  }
18522
19414
 
18523
19415
  // src/commands/init.ts
18524
- import { join as join31, resolve as resolve6 } from "node:path";
19416
+ import { join as join32, resolve as resolve6 } from "node:path";
18525
19417
 
18526
19418
  // src/domains/config/config-manager.ts
18527
19419
  init_logger();
@@ -27518,7 +28410,7 @@ Solutions:
27518
28410
  }
27519
28411
 
27520
28412
  // src/domains/installation/file-merger.ts
27521
- import { dirname as dirname5, join as join12, relative as relative2 } from "node:path";
28413
+ import { dirname as dirname5, join as join12, relative as relative3 } from "node:path";
27522
28414
 
27523
28415
  // src/domains/config/settings-merger.ts
27524
28416
  init_logger();
@@ -29164,6 +30056,129 @@ minimatch.Minimatch = Minimatch;
29164
30056
  minimatch.escape = escape;
29165
30057
  minimatch.unescape = unescape;
29166
30058
 
30059
+ // src/domains/installation/selective-merger.ts
30060
+ import { stat as stat2 } from "node:fs/promises";
30061
+
30062
+ // src/services/file-operations/ownership-checker.ts
30063
+ import { createHash } from "node:crypto";
30064
+ import { createReadStream } from "node:fs";
30065
+ import { stat } from "node:fs/promises";
30066
+ import { relative as relative2 } from "node:path";
30067
+
30068
+ class OwnershipChecker {
30069
+ static async calculateChecksum(filePath) {
30070
+ return new Promise((resolve3, reject) => {
30071
+ const hash = createHash("sha256");
30072
+ const stream = createReadStream(filePath);
30073
+ stream.on("data", (chunk) => hash.update(chunk));
30074
+ stream.on("end", () => {
30075
+ resolve3(hash.digest("hex"));
30076
+ });
30077
+ stream.on("error", (err) => {
30078
+ stream.destroy();
30079
+ reject(new Error(`Failed to calculate checksum for "${filePath}": ${err.message}`));
30080
+ });
30081
+ });
30082
+ }
30083
+ static async checkOwnership(filePath, metadata, claudeDir) {
30084
+ try {
30085
+ await stat(filePath);
30086
+ } catch {
30087
+ return { path: filePath, ownership: "user", exists: false };
30088
+ }
30089
+ if (!metadata || !metadata.files || metadata.files.length === 0) {
30090
+ return { path: filePath, ownership: "user", exists: true };
30091
+ }
30092
+ const relativePath = relative2(claudeDir, filePath).replace(/\\/g, "/");
30093
+ const tracked = metadata.files.find((f3) => f3.path === relativePath);
30094
+ if (!tracked) {
30095
+ return { path: filePath, ownership: "user", exists: true };
30096
+ }
30097
+ const actualChecksum = await OwnershipChecker.calculateChecksum(filePath);
30098
+ if (actualChecksum === tracked.checksum) {
30099
+ return {
30100
+ path: filePath,
30101
+ ownership: "ck",
30102
+ expectedChecksum: tracked.checksum,
30103
+ actualChecksum,
30104
+ exists: true
30105
+ };
30106
+ }
30107
+ return {
30108
+ path: filePath,
30109
+ ownership: "ck-modified",
30110
+ expectedChecksum: tracked.checksum,
30111
+ actualChecksum,
30112
+ exists: true
30113
+ };
30114
+ }
30115
+ static async checkBatch(filePaths, metadata, claudeDir) {
30116
+ const results = await Promise.all(filePaths.map((path9) => OwnershipChecker.checkOwnership(path9, metadata, claudeDir)));
30117
+ return new Map(results.map((r2) => [r2.path, r2]));
30118
+ }
30119
+ }
30120
+
30121
+ // src/domains/installation/selective-merger.ts
30122
+ init_logger();
30123
+
30124
+ class SelectiveMerger {
30125
+ manifest;
30126
+ manifestMap;
30127
+ constructor(manifest) {
30128
+ this.manifest = manifest;
30129
+ this.manifestMap = new Map;
30130
+ if (manifest) {
30131
+ for (const file of manifest.files) {
30132
+ this.manifestMap.set(file.path, file);
30133
+ }
30134
+ }
30135
+ }
30136
+ async shouldCopyFile(destPath, relativePath) {
30137
+ let destStat;
30138
+ try {
30139
+ destStat = await stat2(destPath);
30140
+ } catch {
30141
+ return { changed: true, reason: "new" };
30142
+ }
30143
+ const manifestEntry = this.manifestMap.get(relativePath);
30144
+ if (!manifestEntry) {
30145
+ logger.debug(`No manifest entry for ${relativePath}, will copy`);
30146
+ return { changed: true, reason: "new" };
30147
+ }
30148
+ if (destStat.size !== manifestEntry.size) {
30149
+ logger.debug(`Size differs for ${relativePath}: ${destStat.size} vs ${manifestEntry.size}`);
30150
+ return {
30151
+ changed: true,
30152
+ reason: "size-differ",
30153
+ sourceChecksum: manifestEntry.checksum
30154
+ };
30155
+ }
30156
+ const destChecksum = await OwnershipChecker.calculateChecksum(destPath);
30157
+ if (destChecksum !== manifestEntry.checksum) {
30158
+ logger.debug(`Checksum differs for ${relativePath}`);
30159
+ return {
30160
+ changed: true,
30161
+ reason: "checksum-differ",
30162
+ sourceChecksum: manifestEntry.checksum,
30163
+ destChecksum
30164
+ };
30165
+ }
30166
+ logger.debug(`Unchanged: ${relativePath}`);
30167
+ return {
30168
+ changed: false,
30169
+ reason: "unchanged",
30170
+ sourceChecksum: manifestEntry.checksum,
30171
+ destChecksum
30172
+ };
30173
+ }
30174
+ hasManifest() {
30175
+ return this.manifest !== null && this.manifestMap.size > 0;
30176
+ }
30177
+ getManifestFileCount() {
30178
+ return this.manifestMap.size;
30179
+ }
30180
+ }
30181
+
29167
30182
  // src/domains/installation/file-merger.ts
29168
30183
  class FileMerger {
29169
30184
  neverCopyChecker = import_ignore2.default().add(NEVER_COPY_PATTERNS);
@@ -29173,6 +30188,8 @@ class FileMerger {
29173
30188
  forceOverwriteSettings = false;
29174
30189
  installedFiles = new Set;
29175
30190
  installedDirectories = new Set;
30191
+ selectiveMerger = null;
30192
+ unchangedSkipped = 0;
29176
30193
  setIncludePatterns(patterns) {
29177
30194
  this.includePatterns = patterns;
29178
30195
  }
@@ -29182,6 +30199,12 @@ class FileMerger {
29182
30199
  setForceOverwriteSettings(force) {
29183
30200
  this.forceOverwriteSettings = force;
29184
30201
  }
30202
+ setManifest(manifest) {
30203
+ this.selectiveMerger = manifest ? new SelectiveMerger(manifest) : null;
30204
+ if (manifest && this.selectiveMerger?.hasManifest()) {
30205
+ logger.debug(`Selective merge enabled with ${this.selectiveMerger.getManifestFileCount()} tracked files`);
30206
+ }
30207
+ }
29185
30208
  async merge(sourceDir, destDir, skipConfirmation = false) {
29186
30209
  const conflicts = await this.detectConflicts(sourceDir, destDir);
29187
30210
  if (conflicts.length > 0 && !skipConfirmation) {
@@ -29203,7 +30226,7 @@ class FileMerger {
29203
30226
  const conflicts = [];
29204
30227
  const files = await this.getFiles(sourceDir, sourceDir);
29205
30228
  for (const file of files) {
29206
- const relativePath = relative2(sourceDir, file);
30229
+ const relativePath = relative3(sourceDir, file);
29207
30230
  const normalizedRelativePath = relativePath.replace(/\\/g, "/");
29208
30231
  const destPath = join12(destDir, relativePath);
29209
30232
  if (await import_fs_extra3.pathExists(destPath)) {
@@ -29225,7 +30248,7 @@ class FileMerger {
29225
30248
  let copiedCount = 0;
29226
30249
  let skippedCount = 0;
29227
30250
  for (const file of files) {
29228
- const relativePath = relative2(sourceDir, file);
30251
+ const relativePath = relative3(sourceDir, file);
29229
30252
  const normalizedRelativePath = relativePath.replace(/\\/g, "/");
29230
30253
  const destPath = join12(destDir, relativePath);
29231
30254
  if (this.neverCopyChecker.ignores(normalizedRelativePath)) {
@@ -29248,11 +30271,24 @@ class FileMerger {
29248
30271
  copiedCount++;
29249
30272
  continue;
29250
30273
  }
30274
+ if (this.selectiveMerger?.hasManifest()) {
30275
+ const compareResult = await this.selectiveMerger.shouldCopyFile(destPath, normalizedRelativePath);
30276
+ if (!compareResult.changed) {
30277
+ logger.debug(`Skipping unchanged: ${normalizedRelativePath}`);
30278
+ this.unchangedSkipped++;
30279
+ this.trackInstalledFile(normalizedRelativePath);
30280
+ continue;
30281
+ }
30282
+ }
29251
30283
  await import_fs_extra3.copy(file, destPath, { overwrite: true });
29252
30284
  this.trackInstalledFile(normalizedRelativePath);
29253
30285
  copiedCount++;
29254
30286
  }
29255
- logger.success(`Copied ${copiedCount} file(s), skipped ${skippedCount} protected file(s)`);
30287
+ if (this.unchangedSkipped > 0) {
30288
+ logger.success(`Updated ${copiedCount} file(s), skipped ${this.unchangedSkipped} unchanged, skipped ${skippedCount} protected`);
30289
+ } else {
30290
+ logger.success(`Copied ${copiedCount} file(s), skipped ${skippedCount} protected file(s)`);
30291
+ }
29256
30292
  }
29257
30293
  async processSettingsJson(sourceFile, destFile) {
29258
30294
  try {
@@ -29343,7 +30379,7 @@ class FileMerger {
29343
30379
  const entries = await import_fs_extra3.readdir(dir, { encoding: "utf8" });
29344
30380
  for (const entry of entries) {
29345
30381
  const fullPath = join12(dir, entry);
29346
- const relativePath = relative2(baseDir, fullPath);
30382
+ const relativePath = relative3(baseDir, fullPath);
29347
30383
  const normalizedRelativePath = relativePath.replace(/\\/g, "/");
29348
30384
  const stats = await import_fs_extra3.lstat(fullPath);
29349
30385
  if (stats.isSymbolicLink()) {
@@ -29602,14 +30638,165 @@ async function runSetupWizard(options) {
29602
30638
  }
29603
30639
 
29604
30640
  // src/domains/migration/legacy-migration.ts
29605
- import { readdir as readdir4, stat as stat2 } from "node:fs/promises";
29606
- import { join as join18, relative as relative4 } from "node:path";
30641
+ import { readdir as readdir4, stat as stat3 } from "node:fs/promises";
30642
+ import { join as join19, relative as relative4 } from "node:path";
29607
30643
 
29608
30644
  // src/services/file-operations/manifest-writer.ts
30645
+ import { join as join17 } from "node:path";
30646
+
30647
+ // src/domains/migration/metadata-migration.ts
29609
30648
  init_logger();
29610
- init_types2();
29611
30649
  var import_fs_extra7 = __toESM(require_lib(), 1);
29612
30650
  import { join as join16 } from "node:path";
30651
+ async function detectMetadataFormat(claudeDir) {
30652
+ const metadataPath = join16(claudeDir, "metadata.json");
30653
+ if (!await import_fs_extra7.pathExists(metadataPath)) {
30654
+ return { format: "none", metadata: null, detectedKit: null };
30655
+ }
30656
+ try {
30657
+ const content = await import_fs_extra7.readFile(metadataPath, "utf-8");
30658
+ const parsed = JSON.parse(content);
30659
+ if (parsed.kits && Object.keys(parsed.kits).length > 0) {
30660
+ const installedKits = Object.keys(parsed.kits);
30661
+ return {
30662
+ format: "multi-kit",
30663
+ metadata: parsed,
30664
+ detectedKit: installedKits[0] || null
30665
+ };
30666
+ }
30667
+ if (parsed.name || parsed.version || parsed.files) {
30668
+ let detectedKit = null;
30669
+ const nameToCheck = parsed.name || "";
30670
+ if (/\bengineer\b/i.test(nameToCheck)) {
30671
+ detectedKit = "engineer";
30672
+ } else if (/\bmarketing\b/i.test(nameToCheck)) {
30673
+ detectedKit = "marketing";
30674
+ } else {
30675
+ detectedKit = "engineer";
30676
+ }
30677
+ return { format: "legacy", metadata: parsed, detectedKit };
30678
+ }
30679
+ logger.warning("Metadata file exists but has unrecognized format (missing kits, name, version, or files)");
30680
+ return { format: "none", metadata: null, detectedKit: null };
30681
+ } catch (error) {
30682
+ logger.warning(`Failed to read metadata file (may be corrupted): ${error}`);
30683
+ return { format: "none", metadata: null, detectedKit: null };
30684
+ }
30685
+ }
30686
+ async function migrateToMultiKit(claudeDir) {
30687
+ const detection = await detectMetadataFormat(claudeDir);
30688
+ if (detection.format === "multi-kit") {
30689
+ return {
30690
+ success: true,
30691
+ migrated: false,
30692
+ fromFormat: "multi-kit",
30693
+ toFormat: "multi-kit"
30694
+ };
30695
+ }
30696
+ if (detection.format === "none") {
30697
+ return {
30698
+ success: true,
30699
+ migrated: false,
30700
+ fromFormat: "none",
30701
+ toFormat: "multi-kit"
30702
+ };
30703
+ }
30704
+ const metadataPath = join16(claudeDir, "metadata.json");
30705
+ const legacy = detection.metadata;
30706
+ if (!legacy) {
30707
+ return {
30708
+ success: false,
30709
+ migrated: false,
30710
+ fromFormat: "legacy",
30711
+ toFormat: "multi-kit",
30712
+ error: "Metadata exists but could not be read"
30713
+ };
30714
+ }
30715
+ const legacyKit = detection.detectedKit || "engineer";
30716
+ try {
30717
+ const kitMetadata = {
30718
+ version: legacy.version || "unknown",
30719
+ installedAt: legacy.installedAt || new Date().toISOString(),
30720
+ files: legacy.files || []
30721
+ };
30722
+ const multiKit = {
30723
+ kits: {
30724
+ [legacyKit]: kitMetadata
30725
+ },
30726
+ scope: legacy.scope,
30727
+ name: legacy.name,
30728
+ version: legacy.version,
30729
+ installedAt: legacy.installedAt,
30730
+ installedFiles: legacy.installedFiles,
30731
+ userConfigFiles: legacy.userConfigFiles,
30732
+ files: legacy.files
30733
+ };
30734
+ await import_fs_extra7.writeFile(metadataPath, JSON.stringify(multiKit, null, 2), "utf-8");
30735
+ logger.info(`Migrated metadata from legacy format to multi-kit (detected: ${legacyKit})`);
30736
+ return {
30737
+ success: true,
30738
+ migrated: true,
30739
+ fromFormat: "legacy",
30740
+ toFormat: "multi-kit"
30741
+ };
30742
+ } catch (error) {
30743
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
30744
+ logger.error(`Metadata migration failed: ${errorMsg}`);
30745
+ return {
30746
+ success: false,
30747
+ migrated: false,
30748
+ fromFormat: "legacy",
30749
+ toFormat: "multi-kit",
30750
+ error: errorMsg
30751
+ };
30752
+ }
30753
+ }
30754
+ function getKitMetadata(metadata, kit) {
30755
+ if (metadata.kits?.[kit]) {
30756
+ return metadata.kits[kit];
30757
+ }
30758
+ if (!metadata.kits && metadata.version) {
30759
+ return {
30760
+ version: metadata.version,
30761
+ installedAt: metadata.installedAt || "",
30762
+ files: metadata.files
30763
+ };
30764
+ }
30765
+ return null;
30766
+ }
30767
+ function getAllTrackedFiles(metadata) {
30768
+ if (metadata.kits) {
30769
+ const allFiles = [];
30770
+ for (const kit of Object.values(metadata.kits)) {
30771
+ if (kit.files) {
30772
+ allFiles.push(...kit.files);
30773
+ }
30774
+ }
30775
+ return allFiles;
30776
+ }
30777
+ return metadata.files || [];
30778
+ }
30779
+ function getInstalledKits(metadata) {
30780
+ if (metadata.kits) {
30781
+ return Object.keys(metadata.kits);
30782
+ }
30783
+ const nameToCheck = metadata.name || "";
30784
+ if (/\bengineer\b/i.test(nameToCheck)) {
30785
+ return ["engineer"];
30786
+ }
30787
+ if (/\bmarketing\b/i.test(nameToCheck)) {
30788
+ return ["marketing"];
30789
+ }
30790
+ if (metadata.version) {
30791
+ return ["engineer"];
30792
+ }
30793
+ return [];
30794
+ }
30795
+
30796
+ // src/services/file-operations/manifest-writer.ts
30797
+ init_logger();
30798
+ init_types2();
30799
+ var import_fs_extra8 = __toESM(require_lib(), 1);
29613
30800
 
29614
30801
  // node_modules/yocto-queue/index.js
29615
30802
  class Node2 {
@@ -29751,66 +30938,8 @@ function validateConcurrency(concurrency) {
29751
30938
  }
29752
30939
  }
29753
30940
 
29754
- // src/services/file-operations/ownership-checker.ts
29755
- import { createHash } from "node:crypto";
29756
- import { createReadStream } from "node:fs";
29757
- import { stat } from "node:fs/promises";
29758
- import { relative as relative3 } from "node:path";
29759
-
29760
- class OwnershipChecker {
29761
- static async calculateChecksum(filePath) {
29762
- return new Promise((resolve3, reject) => {
29763
- const hash = createHash("sha256");
29764
- const stream = createReadStream(filePath);
29765
- stream.on("data", (chunk) => hash.update(chunk));
29766
- stream.on("end", () => {
29767
- resolve3(hash.digest("hex"));
29768
- });
29769
- stream.on("error", (err) => {
29770
- stream.destroy();
29771
- reject(new Error(`Failed to calculate checksum for "${filePath}": ${err.message}`));
29772
- });
29773
- });
29774
- }
29775
- static async checkOwnership(filePath, metadata, claudeDir) {
29776
- try {
29777
- await stat(filePath);
29778
- } catch {
29779
- return { path: filePath, ownership: "user", exists: false };
29780
- }
29781
- if (!metadata || !metadata.files || metadata.files.length === 0) {
29782
- return { path: filePath, ownership: "user", exists: true };
29783
- }
29784
- const relativePath = relative3(claudeDir, filePath).replace(/\\/g, "/");
29785
- const tracked = metadata.files.find((f3) => f3.path === relativePath);
29786
- if (!tracked) {
29787
- return { path: filePath, ownership: "user", exists: true };
29788
- }
29789
- const actualChecksum = await OwnershipChecker.calculateChecksum(filePath);
29790
- if (actualChecksum === tracked.checksum) {
29791
- return {
29792
- path: filePath,
29793
- ownership: "ck",
29794
- expectedChecksum: tracked.checksum,
29795
- actualChecksum,
29796
- exists: true
29797
- };
29798
- }
29799
- return {
29800
- path: filePath,
29801
- ownership: "ck-modified",
29802
- expectedChecksum: tracked.checksum,
29803
- actualChecksum,
29804
- exists: true
29805
- };
29806
- }
29807
- static async checkBatch(filePaths, metadata, claudeDir) {
29808
- const results = await Promise.all(filePaths.map((path9) => OwnershipChecker.checkOwnership(path9, metadata, claudeDir)));
29809
- return new Map(results.map((r2) => [r2.path, r2]));
29810
- }
29811
- }
29812
-
29813
30941
  // src/services/file-operations/manifest-writer.ts
30942
+ var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
29814
30943
  class ManifestWriter {
29815
30944
  installedFiles = new Set;
29816
30945
  userConfigFiles = new Set;
@@ -29866,13 +30995,16 @@ class ManifestWriter {
29866
30995
  return false;
29867
30996
  }
29868
30997
  }));
29869
- let completed = 0;
29870
30998
  const progressInterval = Math.max(1, Math.floor(total / 20));
29871
- const results = await Promise.all(tasks.map(async (task) => {
30999
+ let reportedProgress = 0;
31000
+ const results = await Promise.all(tasks.map(async (task, index) => {
29872
31001
  const result = await task;
29873
- completed++;
31002
+ const completed = index + 1;
29874
31003
  if (completed % progressInterval === 0 || completed === total) {
29875
- onProgress?.(completed, total);
31004
+ if (completed > reportedProgress) {
31005
+ reportedProgress = completed;
31006
+ onProgress?.(completed, total);
31007
+ }
29876
31008
  }
29877
31009
  return result;
29878
31010
  }));
@@ -29886,39 +31018,70 @@ class ManifestWriter {
29886
31018
  getTrackedFiles() {
29887
31019
  return Array.from(this.trackedFiles.values()).sort((a3, b3) => a3.path.localeCompare(b3.path));
29888
31020
  }
29889
- async writeManifest(claudeDir, kitName, version, scope) {
29890
- const metadataPath = join16(claudeDir, "metadata.json");
29891
- let existingMetadata = {};
29892
- if (await import_fs_extra7.pathExists(metadataPath)) {
29893
- try {
29894
- const content = await import_fs_extra7.readFile(metadataPath, "utf-8");
29895
- existingMetadata = JSON.parse(content);
29896
- } catch (error) {
29897
- logger.debug(`Could not read existing metadata: ${error}`);
31021
+ async writeManifest(claudeDir, kitName, version, scope, kitType) {
31022
+ const metadataPath = join17(claudeDir, "metadata.json");
31023
+ const kit = kitType || (/\bmarketing\b/i.test(kitName) ? "marketing" : "engineer");
31024
+ await import_fs_extra8.ensureFile(metadataPath);
31025
+ let release = null;
31026
+ try {
31027
+ release = await import_proper_lockfile.lock(metadataPath, {
31028
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 1000 },
31029
+ stale: 60000
31030
+ });
31031
+ logger.debug(`Acquired lock on ${metadataPath}`);
31032
+ const migrationResult = await migrateToMultiKit(claudeDir);
31033
+ if (!migrationResult.success) {
31034
+ logger.warning(`Metadata migration warning: ${migrationResult.error}`);
31035
+ }
31036
+ let existingMetadata = { kits: {} };
31037
+ if (await import_fs_extra8.pathExists(metadataPath)) {
31038
+ try {
31039
+ const content = await import_fs_extra8.readFile(metadataPath, "utf-8");
31040
+ const parsed = JSON.parse(content);
31041
+ if (parsed && typeof parsed === "object" && Object.keys(parsed).length > 0) {
31042
+ existingMetadata = parsed;
31043
+ }
31044
+ } catch (error) {
31045
+ logger.debug(`Could not read existing metadata: ${error}`);
31046
+ }
31047
+ }
31048
+ const trackedFiles = this.getTrackedFiles();
31049
+ const installedAt = new Date().toISOString();
31050
+ const kitMetadata = {
31051
+ version,
31052
+ installedAt,
31053
+ files: trackedFiles.length > 0 ? trackedFiles : undefined
31054
+ };
31055
+ const metadata = {
31056
+ kits: {
31057
+ ...existingMetadata.kits,
31058
+ [kit]: kitMetadata
31059
+ },
31060
+ scope,
31061
+ name: kitName,
31062
+ version,
31063
+ installedAt,
31064
+ installedFiles: this.getInstalledFiles(),
31065
+ userConfigFiles: [...USER_CONFIG_PATTERNS, ...this.getUserConfigFiles()],
31066
+ files: trackedFiles.length > 0 ? trackedFiles : undefined
31067
+ };
31068
+ const validated = MetadataSchema.parse(metadata);
31069
+ await import_fs_extra8.writeFile(metadataPath, JSON.stringify(validated, null, 2), "utf-8");
31070
+ logger.debug(`Wrote manifest for kit "${kit}" with ${trackedFiles.length} tracked files`);
31071
+ } finally {
31072
+ if (release) {
31073
+ await release();
31074
+ logger.debug(`Released lock on ${metadataPath}`);
29898
31075
  }
29899
31076
  }
29900
- const trackedFiles = this.getTrackedFiles();
29901
- const metadata = {
29902
- ...existingMetadata,
29903
- name: kitName,
29904
- version,
29905
- installedAt: new Date().toISOString(),
29906
- scope,
29907
- installedFiles: this.getInstalledFiles(),
29908
- userConfigFiles: [...USER_CONFIG_PATTERNS, ...this.getUserConfigFiles()],
29909
- files: trackedFiles.length > 0 ? trackedFiles : undefined
29910
- };
29911
- const validated = MetadataSchema.parse(metadata);
29912
- await import_fs_extra7.writeFile(metadataPath, JSON.stringify(validated, null, 2), "utf-8");
29913
- logger.debug(`Wrote manifest with ${this.installedFiles.size} installed files, ${trackedFiles.length} tracked`);
29914
31077
  }
29915
31078
  static async readManifest(claudeDir) {
29916
- const metadataPath = join16(claudeDir, "metadata.json");
29917
- if (!await import_fs_extra7.pathExists(metadataPath)) {
31079
+ const metadataPath = join17(claudeDir, "metadata.json");
31080
+ if (!await import_fs_extra8.pathExists(metadataPath)) {
29918
31081
  return null;
29919
31082
  }
29920
31083
  try {
29921
- const content = await import_fs_extra7.readFile(metadataPath, "utf-8");
31084
+ const content = await import_fs_extra8.readFile(metadataPath, "utf-8");
29922
31085
  const parsed = JSON.parse(content);
29923
31086
  return MetadataSchema.parse(parsed);
29924
31087
  } catch (error) {
@@ -29926,13 +31089,82 @@ class ManifestWriter {
29926
31089
  return null;
29927
31090
  }
29928
31091
  }
29929
- static async getUninstallManifest(claudeDir) {
31092
+ static async readKitManifest(claudeDir, kit) {
29930
31093
  const metadata = await ManifestWriter.readManifest(claudeDir);
29931
- if (metadata?.installedFiles && metadata.installedFiles.length > 0) {
31094
+ if (!metadata)
31095
+ return null;
31096
+ return getKitMetadata(metadata, kit);
31097
+ }
31098
+ static async getUninstallManifest(claudeDir, kit) {
31099
+ const detection = await detectMetadataFormat(claudeDir);
31100
+ if (detection.format === "multi-kit" && detection.metadata?.kits) {
31101
+ const installedKits = Object.keys(detection.metadata.kits);
31102
+ if (kit) {
31103
+ const kitMeta = detection.metadata.kits[kit];
31104
+ if (!kitMeta?.files) {
31105
+ return {
31106
+ filesToRemove: [],
31107
+ filesToPreserve: USER_CONFIG_PATTERNS,
31108
+ hasManifest: true,
31109
+ isMultiKit: true,
31110
+ remainingKits: installedKits.filter((k2) => k2 !== kit)
31111
+ };
31112
+ }
31113
+ const kitFiles = kitMeta.files.map((f3) => f3.path);
31114
+ const sharedFiles = new Set;
31115
+ for (const otherKit of installedKits) {
31116
+ if (otherKit !== kit) {
31117
+ const otherMeta = detection.metadata.kits[otherKit];
31118
+ if (otherMeta?.files) {
31119
+ for (const f3 of otherMeta.files) {
31120
+ sharedFiles.add(f3.path);
31121
+ }
31122
+ }
31123
+ }
31124
+ }
31125
+ const filesToRemove = kitFiles.filter((f3) => !sharedFiles.has(f3));
31126
+ const filesToPreserve = [
31127
+ ...USER_CONFIG_PATTERNS,
31128
+ ...kitFiles.filter((f3) => sharedFiles.has(f3))
31129
+ ];
31130
+ return {
31131
+ filesToRemove,
31132
+ filesToPreserve,
31133
+ hasManifest: true,
31134
+ isMultiKit: true,
31135
+ remainingKits: installedKits.filter((k2) => k2 !== kit)
31136
+ };
31137
+ }
31138
+ const allFiles = getAllTrackedFiles(detection.metadata);
29932
31139
  return {
29933
- filesToRemove: metadata.installedFiles,
29934
- filesToPreserve: metadata.userConfigFiles || USER_CONFIG_PATTERNS,
29935
- hasManifest: true
31140
+ filesToRemove: allFiles.map((f3) => f3.path),
31141
+ filesToPreserve: USER_CONFIG_PATTERNS,
31142
+ hasManifest: true,
31143
+ isMultiKit: true,
31144
+ remainingKits: []
31145
+ };
31146
+ }
31147
+ if (detection.format === "legacy" && detection.metadata) {
31148
+ const legacyFiles2 = detection.metadata.files?.map((f3) => f3.path) || [];
31149
+ const installedFiles = detection.metadata.installedFiles || [];
31150
+ const hasFiles = legacyFiles2.length > 0 || installedFiles.length > 0;
31151
+ if (!hasFiles) {
31152
+ const legacyDirs2 = ["commands", "agents", "skills", "workflows", "hooks", "scripts"];
31153
+ const legacyFileList = ["metadata.json"];
31154
+ return {
31155
+ filesToRemove: [...legacyDirs2, ...legacyFileList],
31156
+ filesToPreserve: USER_CONFIG_PATTERNS,
31157
+ hasManifest: false,
31158
+ isMultiKit: false,
31159
+ remainingKits: []
31160
+ };
31161
+ }
31162
+ return {
31163
+ filesToRemove: legacyFiles2.length > 0 ? legacyFiles2 : installedFiles,
31164
+ filesToPreserve: detection.metadata.userConfigFiles || USER_CONFIG_PATTERNS,
31165
+ hasManifest: true,
31166
+ isMultiKit: false,
31167
+ remainingKits: []
29936
31168
  };
29937
31169
  }
29938
31170
  const legacyDirs = ["commands", "agents", "skills", "workflows", "hooks", "scripts"];
@@ -29940,20 +31172,55 @@ class ManifestWriter {
29940
31172
  return {
29941
31173
  filesToRemove: [...legacyDirs, ...legacyFiles],
29942
31174
  filesToPreserve: USER_CONFIG_PATTERNS,
29943
- hasManifest: false
31175
+ hasManifest: false,
31176
+ isMultiKit: false,
31177
+ remainingKits: []
29944
31178
  };
29945
31179
  }
31180
+ static async removeKitFromManifest(claudeDir, kit) {
31181
+ const metadataPath = join17(claudeDir, "metadata.json");
31182
+ if (!await import_fs_extra8.pathExists(metadataPath))
31183
+ return false;
31184
+ let release = null;
31185
+ try {
31186
+ release = await import_proper_lockfile.lock(metadataPath, {
31187
+ retries: { retries: 5, minTimeout: 100, maxTimeout: 1000 },
31188
+ stale: 60000
31189
+ });
31190
+ logger.debug(`Acquired lock on ${metadataPath} for kit removal`);
31191
+ const metadata = await ManifestWriter.readManifest(claudeDir);
31192
+ if (!metadata?.kits?.[kit])
31193
+ return false;
31194
+ const { [kit]: _removed, ...remainingKits } = metadata.kits;
31195
+ if (Object.keys(remainingKits).length === 0) {
31196
+ logger.debug("No kits remaining, metadata.json will be cleaned up");
31197
+ return true;
31198
+ }
31199
+ const updated = {
31200
+ ...metadata,
31201
+ kits: remainingKits
31202
+ };
31203
+ await import_fs_extra8.writeFile(metadataPath, JSON.stringify(updated, null, 2), "utf-8");
31204
+ logger.debug(`Removed kit "${kit}" from metadata, ${Object.keys(remainingKits).length} kit(s) remaining`);
31205
+ return true;
31206
+ } finally {
31207
+ if (release) {
31208
+ await release();
31209
+ logger.debug(`Released lock on ${metadataPath}`);
31210
+ }
31211
+ }
31212
+ }
29946
31213
  }
29947
31214
 
29948
31215
  // src/domains/migration/legacy-migration.ts
29949
31216
  init_logger();
29950
- var import_fs_extra9 = __toESM(require_lib(), 1);
31217
+ var import_fs_extra10 = __toESM(require_lib(), 1);
29951
31218
 
29952
31219
  // src/domains/migration/release-manifest.ts
29953
31220
  init_logger();
29954
31221
  init_zod();
29955
- var import_fs_extra8 = __toESM(require_lib(), 1);
29956
- import { join as join17 } from "node:path";
31222
+ var import_fs_extra9 = __toESM(require_lib(), 1);
31223
+ import { join as join18 } from "node:path";
29957
31224
  var ReleaseManifestFileSchema = exports_external.object({
29958
31225
  path: exports_external.string(),
29959
31226
  checksum: exports_external.string().regex(/^[a-f0-9]{64}$/),
@@ -29967,9 +31234,9 @@ var ReleaseManifestSchema = exports_external.object({
29967
31234
 
29968
31235
  class ReleaseManifestLoader {
29969
31236
  static async load(extractDir) {
29970
- const manifestPath = join17(extractDir, "release-manifest.json");
31237
+ const manifestPath = join18(extractDir, "release-manifest.json");
29971
31238
  try {
29972
- const content = await import_fs_extra8.readFile(manifestPath, "utf-8");
31239
+ const content = await import_fs_extra9.readFile(manifestPath, "utf-8");
29973
31240
  const parsed = JSON.parse(content);
29974
31241
  return ReleaseManifestSchema.parse(parsed);
29975
31242
  } catch (error) {
@@ -30013,10 +31280,10 @@ class LegacyMigration {
30013
31280
  for (const entry of entries) {
30014
31281
  if (entry === "metadata.json")
30015
31282
  continue;
30016
- const fullPath = join18(dir, entry);
31283
+ const fullPath = join19(dir, entry);
30017
31284
  let stats;
30018
31285
  try {
30019
- stats = await stat2(fullPath);
31286
+ stats = await stat3(fullPath);
30020
31287
  } catch (err) {
30021
31288
  const error = err;
30022
31289
  if (error.code === "ENOENT") {
@@ -30115,7 +31382,7 @@ User-created files (sample):`);
30115
31382
  ];
30116
31383
  if (filesToChecksum.length > 0) {
30117
31384
  const checksumResults = await Promise.all(filesToChecksum.map(async ({ relativePath, ownership }) => {
30118
- const fullPath = join18(claudeDir, relativePath);
31385
+ const fullPath = join19(claudeDir, relativePath);
30119
31386
  const checksum = await OwnershipChecker.calculateChecksum(fullPath);
30120
31387
  return { relativePath, checksum, ownership };
30121
31388
  }));
@@ -30136,8 +31403,8 @@ User-created files (sample):`);
30136
31403
  installedAt: new Date().toISOString(),
30137
31404
  files: trackedFiles
30138
31405
  };
30139
- const metadataPath = join18(claudeDir, "metadata.json");
30140
- await import_fs_extra9.writeFile(metadataPath, JSON.stringify(updatedMetadata, null, 2));
31406
+ const metadataPath = join19(claudeDir, "metadata.json");
31407
+ await import_fs_extra10.writeFile(metadataPath, JSON.stringify(updatedMetadata, null, 2));
30141
31408
  logger.success(`Migration complete: tracked ${trackedFiles.length} files`);
30142
31409
  return true;
30143
31410
  }
@@ -30145,24 +31412,24 @@ User-created files (sample):`);
30145
31412
 
30146
31413
  // src/domains/skills/skills-detector.ts
30147
31414
  init_logger();
30148
- var import_fs_extra11 = __toESM(require_lib(), 1);
31415
+ var import_fs_extra12 = __toESM(require_lib(), 1);
30149
31416
  import { readdir as readdir6 } from "node:fs/promises";
30150
- import { join as join20 } from "node:path";
31417
+ import { join as join21 } from "node:path";
30151
31418
 
30152
31419
  // src/domains/skills/skills-manifest.ts
30153
31420
  init_logger();
30154
31421
  init_types2();
30155
- var import_fs_extra10 = __toESM(require_lib(), 1);
31422
+ var import_fs_extra11 = __toESM(require_lib(), 1);
30156
31423
  import { createHash as createHash2 } from "node:crypto";
30157
- import { readFile as readFile12, readdir as readdir5, writeFile as writeFile11 } from "node:fs/promises";
30158
- import { join as join19, relative as relative5 } from "node:path";
31424
+ import { readFile as readFile13, readdir as readdir5, writeFile as writeFile12 } from "node:fs/promises";
31425
+ import { join as join20, relative as relative5 } from "node:path";
30159
31426
 
30160
31427
  class SkillsManifestManager {
30161
31428
  static MANIFEST_FILENAME = ".skills-manifest.json";
30162
31429
  static MANIFEST_VERSION = "1.0.0";
30163
31430
  static async generateManifest(skillsDir) {
30164
31431
  logger.debug(`Generating manifest for: ${skillsDir}`);
30165
- if (!await import_fs_extra10.pathExists(skillsDir)) {
31432
+ if (!await import_fs_extra11.pathExists(skillsDir)) {
30166
31433
  throw new SkillsMigrationError(`Skills directory does not exist: ${skillsDir}`);
30167
31434
  }
30168
31435
  const structure = await SkillsManifestManager.detectStructure(skillsDir);
@@ -30177,18 +31444,18 @@ class SkillsManifestManager {
30177
31444
  return manifest;
30178
31445
  }
30179
31446
  static async writeManifest(skillsDir, manifest) {
30180
- const manifestPath = join19(skillsDir, SkillsManifestManager.MANIFEST_FILENAME);
30181
- await writeFile11(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
31447
+ const manifestPath = join20(skillsDir, SkillsManifestManager.MANIFEST_FILENAME);
31448
+ await writeFile12(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
30182
31449
  logger.debug(`Wrote manifest to: ${manifestPath}`);
30183
31450
  }
30184
31451
  static async readManifest(skillsDir) {
30185
- const manifestPath = join19(skillsDir, SkillsManifestManager.MANIFEST_FILENAME);
30186
- if (!await import_fs_extra10.pathExists(manifestPath)) {
31452
+ const manifestPath = join20(skillsDir, SkillsManifestManager.MANIFEST_FILENAME);
31453
+ if (!await import_fs_extra11.pathExists(manifestPath)) {
30187
31454
  logger.debug(`No manifest found at: ${manifestPath}`);
30188
31455
  return null;
30189
31456
  }
30190
31457
  try {
30191
- const content = await readFile12(manifestPath, "utf-8");
31458
+ const content = await readFile13(manifestPath, "utf-8");
30192
31459
  const data = JSON.parse(content);
30193
31460
  const manifest = SkillsManifestSchema.parse(data);
30194
31461
  logger.debug(`Read manifest from: ${manifestPath}`);
@@ -30205,7 +31472,7 @@ class SkillsManifestManager {
30205
31472
  return "flat";
30206
31473
  }
30207
31474
  for (const dir of dirs.slice(0, 3)) {
30208
- const dirPath = join19(skillsDir, dir.name);
31475
+ const dirPath = join20(skillsDir, dir.name);
30209
31476
  const subEntries = await readdir5(dirPath, { withFileTypes: true });
30210
31477
  const hasSubdirs = subEntries.some((entry) => entry.isDirectory());
30211
31478
  if (hasSubdirs) {
@@ -30224,7 +31491,7 @@ class SkillsManifestManager {
30224
31491
  const entries = await readdir5(skillsDir, { withFileTypes: true });
30225
31492
  for (const entry of entries) {
30226
31493
  if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
30227
- const skillPath = join19(skillsDir, entry.name);
31494
+ const skillPath = join20(skillsDir, entry.name);
30228
31495
  const hash = await SkillsManifestManager.hashDirectory(skillPath);
30229
31496
  skills.push({
30230
31497
  name: entry.name,
@@ -30236,11 +31503,11 @@ class SkillsManifestManager {
30236
31503
  const categories = await readdir5(skillsDir, { withFileTypes: true });
30237
31504
  for (const category of categories) {
30238
31505
  if (category.isDirectory() && category.name !== "node_modules" && !category.name.startsWith(".")) {
30239
- const categoryPath = join19(skillsDir, category.name);
31506
+ const categoryPath = join20(skillsDir, category.name);
30240
31507
  const skillEntries = await readdir5(categoryPath, { withFileTypes: true });
30241
31508
  for (const skillEntry of skillEntries) {
30242
31509
  if (skillEntry.isDirectory() && !skillEntry.name.startsWith(".")) {
30243
- const skillPath = join19(categoryPath, skillEntry.name);
31510
+ const skillPath = join20(categoryPath, skillEntry.name);
30244
31511
  const hash = await SkillsManifestManager.hashDirectory(skillPath);
30245
31512
  skills.push({
30246
31513
  name: skillEntry.name,
@@ -30260,7 +31527,7 @@ class SkillsManifestManager {
30260
31527
  files.sort();
30261
31528
  for (const file of files) {
30262
31529
  const relativePath = relative5(dirPath, file);
30263
- const content = await readFile12(file);
31530
+ const content = await readFile13(file);
30264
31531
  hash.update(relativePath);
30265
31532
  hash.update(content);
30266
31533
  }
@@ -30270,7 +31537,7 @@ class SkillsManifestManager {
30270
31537
  const files = [];
30271
31538
  const entries = await readdir5(dirPath, { withFileTypes: true });
30272
31539
  for (const entry of entries) {
30273
- const fullPath = join19(dirPath, entry.name);
31540
+ const fullPath = join20(dirPath, entry.name);
30274
31541
  if (entry.name.startsWith(".") || entry.name === "node_modules") {
30275
31542
  continue;
30276
31543
  }
@@ -30390,8 +31657,8 @@ function getPathMapping(skillName, oldBasePath, newBasePath) {
30390
31657
  class SkillsMigrationDetector {
30391
31658
  static async detectMigration(oldSkillsDir, currentSkillsDir) {
30392
31659
  logger.debug("Detecting skills migration need...");
30393
- const oldExists = await import_fs_extra11.pathExists(oldSkillsDir);
30394
- const currentExists = await import_fs_extra11.pathExists(currentSkillsDir);
31660
+ const oldExists = await import_fs_extra12.pathExists(oldSkillsDir);
31661
+ const currentExists = await import_fs_extra12.pathExists(currentSkillsDir);
30395
31662
  if (!oldExists && !currentExists) {
30396
31663
  logger.debug("No skills directories found, migration not needed");
30397
31664
  return {
@@ -30502,7 +31769,7 @@ class SkillsMigrationDetector {
30502
31769
  };
30503
31770
  }
30504
31771
  static async scanDirectory(skillsDir) {
30505
- if (!await import_fs_extra11.pathExists(skillsDir)) {
31772
+ if (!await import_fs_extra12.pathExists(skillsDir)) {
30506
31773
  return ["flat", []];
30507
31774
  }
30508
31775
  const entries = await readdir6(skillsDir, { withFileTypes: true });
@@ -30513,12 +31780,12 @@ class SkillsMigrationDetector {
30513
31780
  let totalSkillLikeCount = 0;
30514
31781
  const allSkills = [];
30515
31782
  for (const dir of dirs) {
30516
- const dirPath = join20(skillsDir, dir.name);
31783
+ const dirPath = join21(skillsDir, dir.name);
30517
31784
  const subEntries = await readdir6(dirPath, { withFileTypes: true });
30518
31785
  const subdirs = subEntries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
30519
31786
  if (subdirs.length > 0) {
30520
31787
  for (const subdir of subdirs.slice(0, 3)) {
30521
- const subdirPath = join20(dirPath, subdir.name);
31788
+ const subdirPath = join21(dirPath, subdir.name);
30522
31789
  const subdirFiles = await readdir6(subdirPath, { withFileTypes: true });
30523
31790
  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"));
30524
31791
  if (hasSkillMarker) {
@@ -30555,16 +31822,16 @@ class SkillsMigrationDetector {
30555
31822
  // src/domains/skills/skills-migrator.ts
30556
31823
  init_logger();
30557
31824
  init_types2();
30558
- var import_fs_extra14 = __toESM(require_lib(), 1);
31825
+ var import_fs_extra15 = __toESM(require_lib(), 1);
30559
31826
  import { copyFile as copyFile2, mkdir as mkdir8, readdir as readdir9, rm as rm3 } from "node:fs/promises";
30560
- import { join as join23 } from "node:path";
31827
+ import { join as join24 } from "node:path";
30561
31828
 
30562
31829
  // src/domains/skills/skills-backup-manager.ts
30563
31830
  init_logger();
30564
31831
  init_types2();
30565
- var import_fs_extra12 = __toESM(require_lib(), 1);
30566
- import { copyFile, mkdir as mkdir7, readdir as readdir7, rm as rm2, stat as stat3 } from "node:fs/promises";
30567
- import { basename as basename2, join as join21, normalize as normalize4 } from "node:path";
31832
+ var import_fs_extra13 = __toESM(require_lib(), 1);
31833
+ import { copyFile, mkdir as mkdir7, readdir as readdir7, rm as rm2, stat as stat4 } from "node:fs/promises";
31834
+ import { basename as basename2, join as join22, normalize as normalize4 } from "node:path";
30568
31835
  function validatePath(path9, paramName) {
30569
31836
  if (!path9 || typeof path9 !== "string") {
30570
31837
  throw new SkillsMigrationError(`${paramName} must be a non-empty string`);
@@ -30584,13 +31851,13 @@ class SkillsBackupManager {
30584
31851
  if (parentDir) {
30585
31852
  validatePath(parentDir, "parentDir");
30586
31853
  }
30587
- if (!await import_fs_extra12.pathExists(skillsDir)) {
31854
+ if (!await import_fs_extra13.pathExists(skillsDir)) {
30588
31855
  throw new SkillsMigrationError(`Cannot create backup: Skills directory does not exist: ${skillsDir}`);
30589
31856
  }
30590
31857
  const timestamp = Date.now();
30591
31858
  const randomSuffix = Math.random().toString(36).substring(2, 8);
30592
31859
  const backupDirName = `${SkillsBackupManager.BACKUP_PREFIX}${timestamp}-${randomSuffix}`;
30593
- const backupDir = parentDir ? join21(parentDir, backupDirName) : join21(skillsDir, "..", backupDirName);
31860
+ const backupDir = parentDir ? join22(parentDir, backupDirName) : join22(skillsDir, "..", backupDirName);
30594
31861
  logger.info(`Creating backup at: ${backupDir}`);
30595
31862
  try {
30596
31863
  await mkdir7(backupDir, { recursive: true });
@@ -30607,12 +31874,12 @@ class SkillsBackupManager {
30607
31874
  static async restoreBackup(backupDir, targetDir) {
30608
31875
  validatePath(backupDir, "backupDir");
30609
31876
  validatePath(targetDir, "targetDir");
30610
- if (!await import_fs_extra12.pathExists(backupDir)) {
31877
+ if (!await import_fs_extra13.pathExists(backupDir)) {
30611
31878
  throw new SkillsMigrationError(`Cannot restore: Backup directory does not exist: ${backupDir}`);
30612
31879
  }
30613
31880
  logger.info(`Restoring from backup: ${backupDir}`);
30614
31881
  try {
30615
- if (await import_fs_extra12.pathExists(targetDir)) {
31882
+ if (await import_fs_extra13.pathExists(targetDir)) {
30616
31883
  await rm2(targetDir, { recursive: true, force: true });
30617
31884
  }
30618
31885
  await mkdir7(targetDir, { recursive: true });
@@ -30623,7 +31890,7 @@ class SkillsBackupManager {
30623
31890
  }
30624
31891
  }
30625
31892
  static async deleteBackup(backupDir) {
30626
- if (!await import_fs_extra12.pathExists(backupDir)) {
31893
+ if (!await import_fs_extra13.pathExists(backupDir)) {
30627
31894
  logger.warning(`Backup directory does not exist: ${backupDir}`);
30628
31895
  return;
30629
31896
  }
@@ -30636,12 +31903,12 @@ class SkillsBackupManager {
30636
31903
  }
30637
31904
  }
30638
31905
  static async listBackups(parentDir) {
30639
- if (!await import_fs_extra12.pathExists(parentDir)) {
31906
+ if (!await import_fs_extra13.pathExists(parentDir)) {
30640
31907
  return [];
30641
31908
  }
30642
31909
  try {
30643
31910
  const entries = await readdir7(parentDir, { withFileTypes: true });
30644
- const backups = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(SkillsBackupManager.BACKUP_PREFIX)).map((entry) => join21(parentDir, entry.name));
31911
+ const backups = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(SkillsBackupManager.BACKUP_PREFIX)).map((entry) => join22(parentDir, entry.name));
30645
31912
  backups.sort().reverse();
30646
31913
  return backups;
30647
31914
  } catch (error) {
@@ -30661,7 +31928,7 @@ class SkillsBackupManager {
30661
31928
  }
30662
31929
  }
30663
31930
  static async getBackupSize(backupDir) {
30664
- if (!await import_fs_extra12.pathExists(backupDir)) {
31931
+ if (!await import_fs_extra13.pathExists(backupDir)) {
30665
31932
  return 0;
30666
31933
  }
30667
31934
  return await SkillsBackupManager.getDirectorySize(backupDir);
@@ -30669,8 +31936,8 @@ class SkillsBackupManager {
30669
31936
  static async copyDirectory(sourceDir, destDir) {
30670
31937
  const entries = await readdir7(sourceDir, { withFileTypes: true });
30671
31938
  for (const entry of entries) {
30672
- const sourcePath = join21(sourceDir, entry.name);
30673
- const destPath = join21(destDir, entry.name);
31939
+ const sourcePath = join22(sourceDir, entry.name);
31940
+ const destPath = join22(destDir, entry.name);
30674
31941
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.isSymbolicLink()) {
30675
31942
  continue;
30676
31943
  }
@@ -30686,14 +31953,14 @@ class SkillsBackupManager {
30686
31953
  let size = 0;
30687
31954
  const entries = await readdir7(dirPath, { withFileTypes: true });
30688
31955
  for (const entry of entries) {
30689
- const fullPath = join21(dirPath, entry.name);
31956
+ const fullPath = join22(dirPath, entry.name);
30690
31957
  if (entry.isSymbolicLink()) {
30691
31958
  continue;
30692
31959
  }
30693
31960
  if (entry.isDirectory()) {
30694
31961
  size += await SkillsBackupManager.getDirectorySize(fullPath);
30695
31962
  } else if (entry.isFile()) {
30696
- const stats = await stat3(fullPath);
31963
+ const stats = await stat4(fullPath);
30697
31964
  size += stats.size;
30698
31965
  }
30699
31966
  }
@@ -30713,11 +31980,11 @@ class SkillsBackupManager {
30713
31980
  // src/domains/skills/skills-customization-scanner.ts
30714
31981
  init_logger();
30715
31982
  init_types2();
30716
- var import_fs_extra13 = __toESM(require_lib(), 1);
31983
+ var import_fs_extra14 = __toESM(require_lib(), 1);
30717
31984
  import { createHash as createHash3 } from "node:crypto";
30718
31985
  import { createReadStream as createReadStream2 } from "node:fs";
30719
- import { readFile as readFile13, readdir as readdir8 } from "node:fs/promises";
30720
- import { join as join22, normalize as normalize5, relative as relative6 } from "node:path";
31986
+ import { readFile as readFile14, readdir as readdir8 } from "node:fs/promises";
31987
+ import { join as join23, normalize as normalize5, relative as relative6 } from "node:path";
30721
31988
  function validatePath2(path9, paramName) {
30722
31989
  if (!path9 || typeof path9 !== "string") {
30723
31990
  throw new SkillsMigrationError(`${paramName} must be a non-empty string`);
@@ -30796,7 +32063,7 @@ class SkillsCustomizationScanner {
30796
32063
  static async detectFileChanges(currentSkillPath, baselineSkillPath) {
30797
32064
  const changes = [];
30798
32065
  const currentFiles = await SkillsCustomizationScanner.getAllFiles(currentSkillPath);
30799
- const baselineFiles = await import_fs_extra13.pathExists(baselineSkillPath) ? await SkillsCustomizationScanner.getAllFiles(baselineSkillPath) : [];
32066
+ const baselineFiles = await import_fs_extra14.pathExists(baselineSkillPath) ? await SkillsCustomizationScanner.getAllFiles(baselineSkillPath) : [];
30800
32067
  const currentFileMap = new Map(await Promise.all(currentFiles.map(async (f3) => {
30801
32068
  const relPath = relative6(currentSkillPath, f3);
30802
32069
  const hash = await SkillsCustomizationScanner.hashFile(f3);
@@ -30856,7 +32123,7 @@ class SkillsCustomizationScanner {
30856
32123
  return false;
30857
32124
  }
30858
32125
  static async scanSkillsDirectory(skillsDir) {
30859
- if (!await import_fs_extra13.pathExists(skillsDir)) {
32126
+ if (!await import_fs_extra14.pathExists(skillsDir)) {
30860
32127
  return ["flat", []];
30861
32128
  }
30862
32129
  const entries = await readdir8(skillsDir, { withFileTypes: true });
@@ -30864,13 +32131,13 @@ class SkillsCustomizationScanner {
30864
32131
  if (dirs.length === 0) {
30865
32132
  return ["flat", []];
30866
32133
  }
30867
- const firstDirPath = join22(skillsDir, dirs[0].name);
32134
+ const firstDirPath = join23(skillsDir, dirs[0].name);
30868
32135
  const subEntries = await readdir8(firstDirPath, { withFileTypes: true });
30869
32136
  const subdirs = subEntries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
30870
32137
  if (subdirs.length > 0) {
30871
32138
  let skillLikeCount = 0;
30872
32139
  for (const subdir of subdirs.slice(0, 3)) {
30873
- const subdirPath = join22(firstDirPath, subdir.name);
32140
+ const subdirPath = join23(firstDirPath, subdir.name);
30874
32141
  const subdirFiles = await readdir8(subdirPath, { withFileTypes: true });
30875
32142
  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"));
30876
32143
  if (hasSkillMarker) {
@@ -30880,7 +32147,7 @@ class SkillsCustomizationScanner {
30880
32147
  if (skillLikeCount > 0) {
30881
32148
  const skills = [];
30882
32149
  for (const dir of dirs) {
30883
- const categoryPath = join22(skillsDir, dir.name);
32150
+ const categoryPath = join23(skillsDir, dir.name);
30884
32151
  const skillDirs = await readdir8(categoryPath, { withFileTypes: true });
30885
32152
  skills.push(...skillDirs.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name));
30886
32153
  }
@@ -30890,8 +32157,8 @@ class SkillsCustomizationScanner {
30890
32157
  return ["flat", dirs.map((dir) => dir.name)];
30891
32158
  }
30892
32159
  static async findSkillPath(skillsDir, skillName) {
30893
- const flatPath = join22(skillsDir, skillName);
30894
- if (await import_fs_extra13.pathExists(flatPath)) {
32160
+ const flatPath = join23(skillsDir, skillName);
32161
+ if (await import_fs_extra14.pathExists(flatPath)) {
30895
32162
  return { path: flatPath, category: undefined };
30896
32163
  }
30897
32164
  const entries = await readdir8(skillsDir, { withFileTypes: true });
@@ -30899,9 +32166,9 @@ class SkillsCustomizationScanner {
30899
32166
  if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "node_modules") {
30900
32167
  continue;
30901
32168
  }
30902
- const categoryPath = join22(skillsDir, entry.name);
30903
- const skillPath = join22(categoryPath, skillName);
30904
- if (await import_fs_extra13.pathExists(skillPath)) {
32169
+ const categoryPath = join23(skillsDir, entry.name);
32170
+ const skillPath = join23(categoryPath, skillName);
32171
+ if (await import_fs_extra14.pathExists(skillPath)) {
30905
32172
  return { path: skillPath, category: entry.name };
30906
32173
  }
30907
32174
  }
@@ -30911,7 +32178,7 @@ class SkillsCustomizationScanner {
30911
32178
  const files = [];
30912
32179
  const entries = await readdir8(dirPath, { withFileTypes: true });
30913
32180
  for (const entry of entries) {
30914
- const fullPath = join22(dirPath, entry.name);
32181
+ const fullPath = join23(dirPath, entry.name);
30915
32182
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.isSymbolicLink()) {
30916
32183
  continue;
30917
32184
  }
@@ -30944,7 +32211,7 @@ class SkillsCustomizationScanner {
30944
32211
  files.sort();
30945
32212
  for (const file of files) {
30946
32213
  const relativePath = relative6(dirPath, file);
30947
- const content = await readFile13(file);
32214
+ const content = await readFile14(file);
30948
32215
  hash.update(relativePath);
30949
32216
  hash.update(content);
30950
32217
  }
@@ -31154,7 +32421,7 @@ class SkillsMigrator {
31154
32421
  }
31155
32422
  }
31156
32423
  if (options.backup && !options.dryRun) {
31157
- const claudeDir = join23(currentSkillsDir, "..");
32424
+ const claudeDir = join24(currentSkillsDir, "..");
31158
32425
  result.backupPath = await SkillsBackupManager.createBackup(currentSkillsDir, claudeDir);
31159
32426
  logger.success(`Backup created at: ${result.backupPath}`);
31160
32427
  }
@@ -31206,14 +32473,14 @@ class SkillsMigrator {
31206
32473
  const migrated = [];
31207
32474
  const preserved = [];
31208
32475
  const errors2 = [];
31209
- const tempDir = join23(currentSkillsDir, "..", ".skills-migration-temp");
32476
+ const tempDir = join24(currentSkillsDir, "..", ".skills-migration-temp");
31210
32477
  await mkdir8(tempDir, { recursive: true });
31211
32478
  try {
31212
32479
  for (const mapping of mappings) {
31213
32480
  try {
31214
32481
  const skillName = mapping.skillName;
31215
32482
  const currentSkillPath = mapping.oldPath;
31216
- if (!await import_fs_extra14.pathExists(currentSkillPath)) {
32483
+ if (!await import_fs_extra15.pathExists(currentSkillPath)) {
31217
32484
  logger.warning(`Skill not found, skipping: ${skillName}`);
31218
32485
  continue;
31219
32486
  }
@@ -31227,9 +32494,9 @@ class SkillsMigrator {
31227
32494
  }
31228
32495
  }
31229
32496
  const category = mapping.category;
31230
- const targetPath = category ? join23(tempDir, category, skillName) : join23(tempDir, skillName);
32497
+ const targetPath = category ? join24(tempDir, category, skillName) : join24(tempDir, skillName);
31231
32498
  if (category) {
31232
- await mkdir8(join23(tempDir, category), { recursive: true });
32499
+ await mkdir8(join24(tempDir, category), { recursive: true });
31233
32500
  }
31234
32501
  await SkillsMigrator.copySkillDirectory(currentSkillPath, targetPath);
31235
32502
  migrated.push(skillName);
@@ -31263,8 +32530,8 @@ class SkillsMigrator {
31263
32530
  await mkdir8(destDir, { recursive: true });
31264
32531
  const entries = await readdir9(sourceDir, { withFileTypes: true });
31265
32532
  for (const entry of entries) {
31266
- const sourcePath = join23(sourceDir, entry.name);
31267
- const destPath = join23(destDir, entry.name);
32533
+ const sourcePath = join24(sourceDir, entry.name);
32534
+ const destPath = join24(destDir, entry.name);
31268
32535
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.isSymbolicLink()) {
31269
32536
  continue;
31270
32537
  }
@@ -31852,31 +33119,31 @@ class PromptsManager {
31852
33119
 
31853
33120
  // src/services/file-operations/file-scanner.ts
31854
33121
  init_logger();
31855
- import { join as join27, relative as relative7, resolve as resolve5 } from "node:path";
31856
- var import_fs_extra15 = __toESM(require_lib(), 1);
33122
+ import { join as join28, relative as relative7, resolve as resolve5 } from "node:path";
33123
+ var import_fs_extra16 = __toESM(require_lib(), 1);
31857
33124
 
31858
33125
  class FileScanner {
31859
33126
  static async getFiles(dirPath, relativeTo) {
31860
33127
  const basePath = relativeTo || dirPath;
31861
33128
  const files = [];
31862
- if (!await import_fs_extra15.pathExists(dirPath)) {
33129
+ if (!await import_fs_extra16.pathExists(dirPath)) {
31863
33130
  return files;
31864
33131
  }
31865
33132
  try {
31866
- const entries = await import_fs_extra15.readdir(dirPath, { encoding: "utf8" });
33133
+ const entries = await import_fs_extra16.readdir(dirPath, { encoding: "utf8" });
31867
33134
  for (const entry of entries) {
31868
33135
  if (SKIP_DIRS_ALL.includes(entry)) {
31869
33136
  logger.debug(`Skipping directory: ${entry}`);
31870
33137
  continue;
31871
33138
  }
31872
- const fullPath = join27(dirPath, entry);
33139
+ const fullPath = join28(dirPath, entry);
31873
33140
  if (!FileScanner.isSafePath(basePath, fullPath)) {
31874
33141
  logger.warning(`Skipping potentially unsafe path: ${entry}`);
31875
33142
  continue;
31876
33143
  }
31877
33144
  let stats;
31878
33145
  try {
31879
- stats = await import_fs_extra15.lstat(fullPath);
33146
+ stats = await import_fs_extra16.lstat(fullPath);
31880
33147
  } catch (error) {
31881
33148
  if (error instanceof Error && "code" in error && (error.code === "EACCES" || error.code === "EPERM")) {
31882
33149
  logger.warning(`Skipping inaccessible path: ${entry}`);
@@ -31904,8 +33171,8 @@ class FileScanner {
31904
33171
  return files;
31905
33172
  }
31906
33173
  static async findCustomFiles(destDir, sourceDir, subPath) {
31907
- const destSubDir = join27(destDir, subPath);
31908
- const sourceSubDir = join27(sourceDir, subPath);
33174
+ const destSubDir = join28(destDir, subPath);
33175
+ const sourceSubDir = join28(sourceDir, subPath);
31909
33176
  logger.debug(`findCustomFiles - destDir: ${destDir}`);
31910
33177
  logger.debug(`findCustomFiles - sourceDir: ${sourceDir}`);
31911
33178
  logger.debug(`findCustomFiles - subPath: "${subPath}"`);
@@ -31915,7 +33182,7 @@ class FileScanner {
31915
33182
  const sourceFiles = await FileScanner.getFiles(sourceSubDir, sourceDir);
31916
33183
  logger.debug(`findCustomFiles - destFiles count: ${destFiles.length}`);
31917
33184
  logger.debug(`findCustomFiles - sourceFiles count: ${sourceFiles.length}`);
31918
- const sourceExists = await import_fs_extra15.pathExists(sourceSubDir);
33185
+ const sourceExists = await import_fs_extra16.pathExists(sourceSubDir);
31919
33186
  if (sourceExists && sourceFiles.length === 0 && destFiles.length > 100) {
31920
33187
  logger.warning(`Source directory exists but is empty while destination has ${destFiles.length} files. This may indicate an extraction issue. Skipping custom file detection.`);
31921
33188
  return [];
@@ -31943,10 +33210,10 @@ class FileScanner {
31943
33210
  }
31944
33211
 
31945
33212
  // src/services/transformers/commands-prefix.ts
31946
- import { lstat as lstat3, mkdir as mkdir10, readdir as readdir11, stat as stat4 } from "node:fs/promises";
31947
- import { join as join28 } from "node:path";
33213
+ import { lstat as lstat3, mkdir as mkdir10, readdir as readdir11, stat as stat5 } from "node:fs/promises";
33214
+ import { join as join29 } from "node:path";
31948
33215
  init_logger();
31949
- var import_fs_extra16 = __toESM(require_lib(), 1);
33216
+ var import_fs_extra17 = __toESM(require_lib(), 1);
31950
33217
  function stripWindowsDrivePrefix(path9) {
31951
33218
  if (path9.length >= 2 && /[a-zA-Z]/.test(path9[0]) && path9[1] === ":") {
31952
33219
  return path9.slice(2);
@@ -31985,14 +33252,14 @@ function validatePath4(path9, paramName) {
31985
33252
  class CommandsPrefix {
31986
33253
  static async applyPrefix(extractDir) {
31987
33254
  validatePath4(extractDir, "extractDir");
31988
- const commandsDir = join28(extractDir, ".claude", "commands");
31989
- if (!await import_fs_extra16.pathExists(commandsDir)) {
33255
+ const commandsDir = join29(extractDir, ".claude", "commands");
33256
+ if (!await import_fs_extra17.pathExists(commandsDir)) {
31990
33257
  logger.verbose("No commands directory found, skipping prefix application");
31991
33258
  return;
31992
33259
  }
31993
33260
  logger.info("Applying /ck: prefix to slash commands...");
31994
- const backupDir = join28(extractDir, ".commands-backup");
31995
- const tempDir = join28(extractDir, ".commands-prefix-temp");
33261
+ const backupDir = join29(extractDir, ".commands-backup");
33262
+ const tempDir = join29(extractDir, ".commands-prefix-temp");
31996
33263
  try {
31997
33264
  const entries = await readdir11(commandsDir);
31998
33265
  if (entries.length === 0) {
@@ -32000,28 +33267,28 @@ class CommandsPrefix {
32000
33267
  return;
32001
33268
  }
32002
33269
  if (entries.length === 1 && entries[0] === "ck") {
32003
- const ckDir2 = join28(commandsDir, "ck");
32004
- const ckStat = await stat4(ckDir2);
33270
+ const ckDir2 = join29(commandsDir, "ck");
33271
+ const ckStat = await stat5(ckDir2);
32005
33272
  if (ckStat.isDirectory()) {
32006
33273
  logger.verbose("Commands already have /ck: prefix, skipping");
32007
33274
  return;
32008
33275
  }
32009
33276
  }
32010
- await import_fs_extra16.copy(commandsDir, backupDir);
33277
+ await import_fs_extra17.copy(commandsDir, backupDir);
32011
33278
  logger.verbose("Created backup of commands directory");
32012
33279
  await mkdir10(tempDir, { recursive: true });
32013
- const ckDir = join28(tempDir, "ck");
33280
+ const ckDir = join29(tempDir, "ck");
32014
33281
  await mkdir10(ckDir, { recursive: true });
32015
33282
  let processedCount = 0;
32016
33283
  for (const entry of entries) {
32017
- const sourcePath = join28(commandsDir, entry);
33284
+ const sourcePath = join29(commandsDir, entry);
32018
33285
  const stats = await lstat3(sourcePath);
32019
33286
  if (stats.isSymbolicLink()) {
32020
33287
  logger.warning(`Skipping symlink for security: ${entry}`);
32021
33288
  continue;
32022
33289
  }
32023
- const destPath = join28(ckDir, entry);
32024
- await import_fs_extra16.copy(sourcePath, destPath, {
33290
+ const destPath = join29(ckDir, entry);
33291
+ await import_fs_extra17.copy(sourcePath, destPath, {
32025
33292
  overwrite: false,
32026
33293
  errorOnExist: true
32027
33294
  });
@@ -32030,35 +33297,35 @@ class CommandsPrefix {
32030
33297
  }
32031
33298
  if (processedCount === 0) {
32032
33299
  logger.warning("No files to move (all were symlinks or invalid)");
32033
- await import_fs_extra16.remove(backupDir);
32034
- await import_fs_extra16.remove(tempDir);
33300
+ await import_fs_extra17.remove(backupDir);
33301
+ await import_fs_extra17.remove(tempDir);
32035
33302
  return;
32036
33303
  }
32037
- await import_fs_extra16.remove(commandsDir);
32038
- await import_fs_extra16.move(tempDir, commandsDir);
32039
- await import_fs_extra16.remove(backupDir);
33304
+ await import_fs_extra17.remove(commandsDir);
33305
+ await import_fs_extra17.move(tempDir, commandsDir);
33306
+ await import_fs_extra17.remove(backupDir);
32040
33307
  logger.success("Successfully applied /ck: prefix to all commands");
32041
33308
  } catch (error) {
32042
- if (await import_fs_extra16.pathExists(backupDir)) {
33309
+ if (await import_fs_extra17.pathExists(backupDir)) {
32043
33310
  try {
32044
- await import_fs_extra16.remove(commandsDir).catch(() => {});
32045
- await import_fs_extra16.move(backupDir, commandsDir);
33311
+ await import_fs_extra17.remove(commandsDir).catch(() => {});
33312
+ await import_fs_extra17.move(backupDir, commandsDir);
32046
33313
  logger.info("Restored original commands directory from backup");
32047
33314
  } catch (rollbackError) {
32048
33315
  logger.error(`Rollback failed: ${rollbackError}`);
32049
33316
  }
32050
33317
  }
32051
- if (await import_fs_extra16.pathExists(tempDir)) {
32052
- await import_fs_extra16.remove(tempDir).catch(() => {});
33318
+ if (await import_fs_extra17.pathExists(tempDir)) {
33319
+ await import_fs_extra17.remove(tempDir).catch(() => {});
32053
33320
  }
32054
33321
  logger.error("Failed to apply /ck: prefix to commands");
32055
33322
  throw error;
32056
33323
  } finally {
32057
- if (await import_fs_extra16.pathExists(backupDir)) {
32058
- await import_fs_extra16.remove(backupDir).catch(() => {});
33324
+ if (await import_fs_extra17.pathExists(backupDir)) {
33325
+ await import_fs_extra17.remove(backupDir).catch(() => {});
32059
33326
  }
32060
- if (await import_fs_extra16.pathExists(tempDir)) {
32061
- await import_fs_extra16.remove(tempDir).catch(() => {});
33327
+ if (await import_fs_extra17.pathExists(tempDir)) {
33328
+ await import_fs_extra17.remove(tempDir).catch(() => {});
32062
33329
  }
32063
33330
  }
32064
33331
  }
@@ -32068,15 +33335,15 @@ class CommandsPrefix {
32068
33335
  static async cleanupCommandsDirectory(targetDir, isGlobal, options = {}) {
32069
33336
  const { dryRun = false, forceOverwrite = false } = options;
32070
33337
  validatePath4(targetDir, "targetDir");
32071
- const claudeDir = isGlobal ? targetDir : join28(targetDir, ".claude");
32072
- const commandsDir = join28(claudeDir, "commands");
33338
+ const claudeDir = isGlobal ? targetDir : join29(targetDir, ".claude");
33339
+ const commandsDir = join29(claudeDir, "commands");
32073
33340
  const result = {
32074
33341
  results: [],
32075
33342
  deletedCount: 0,
32076
33343
  preservedCount: 0,
32077
33344
  wasDryRun: dryRun
32078
33345
  };
32079
- if (!await import_fs_extra16.pathExists(commandsDir)) {
33346
+ if (!await import_fs_extra17.pathExists(commandsDir)) {
32080
33347
  logger.verbose(`Commands directory does not exist: ${commandsDir}`);
32081
33348
  return result;
32082
33349
  }
@@ -32097,7 +33364,7 @@ class CommandsPrefix {
32097
33364
  return result;
32098
33365
  }
32099
33366
  for (const entry of entries) {
32100
- const entryPath = join28(commandsDir, entry);
33367
+ const entryPath = join29(commandsDir, entry);
32101
33368
  const stats = await lstat3(entryPath);
32102
33369
  if (stats.isSymbolicLink()) {
32103
33370
  logger.warning(`Skipping symlink: ${entry}`);
@@ -32123,7 +33390,7 @@ class CommandsPrefix {
32123
33390
  action: "delete"
32124
33391
  });
32125
33392
  if (!dryRun) {
32126
- await import_fs_extra16.remove(file);
33393
+ await import_fs_extra17.remove(file);
32127
33394
  logger.verbose(`Deleted CK file: ${relativePath}`);
32128
33395
  }
32129
33396
  result.deletedCount++;
@@ -32136,7 +33403,7 @@ class CommandsPrefix {
32136
33403
  reason: "force overwrite"
32137
33404
  });
32138
33405
  if (!dryRun) {
32139
- await import_fs_extra16.remove(file);
33406
+ await import_fs_extra17.remove(file);
32140
33407
  logger.verbose(`Force-deleted modified file: ${relativePath}`);
32141
33408
  }
32142
33409
  result.deletedCount++;
@@ -32160,7 +33427,7 @@ class CommandsPrefix {
32160
33427
  reason: "force overwrite"
32161
33428
  });
32162
33429
  if (!dryRun) {
32163
- await import_fs_extra16.remove(file);
33430
+ await import_fs_extra17.remove(file);
32164
33431
  logger.verbose(`Force-deleted user file: ${relativePath}`);
32165
33432
  }
32166
33433
  result.deletedCount++;
@@ -32178,7 +33445,7 @@ class CommandsPrefix {
32178
33445
  }
32179
33446
  }
32180
33447
  if (canDeleteDir && !dryRun) {
32181
- await import_fs_extra16.remove(entryPath);
33448
+ await import_fs_extra17.remove(entryPath);
32182
33449
  logger.verbose(`Removed directory: ${entry}`);
32183
33450
  }
32184
33451
  } else {
@@ -32191,7 +33458,7 @@ class CommandsPrefix {
32191
33458
  action: "delete"
32192
33459
  });
32193
33460
  if (!dryRun) {
32194
- await import_fs_extra16.remove(entryPath);
33461
+ await import_fs_extra17.remove(entryPath);
32195
33462
  logger.verbose(`Deleted CK file: ${entry}`);
32196
33463
  }
32197
33464
  result.deletedCount++;
@@ -32204,7 +33471,7 @@ class CommandsPrefix {
32204
33471
  reason: "force overwrite"
32205
33472
  });
32206
33473
  if (!dryRun) {
32207
- await import_fs_extra16.remove(entryPath);
33474
+ await import_fs_extra17.remove(entryPath);
32208
33475
  logger.verbose(`Force-deleted modified file: ${entry}`);
32209
33476
  }
32210
33477
  result.deletedCount++;
@@ -32227,7 +33494,7 @@ class CommandsPrefix {
32227
33494
  reason: "force overwrite"
32228
33495
  });
32229
33496
  if (!dryRun) {
32230
- await import_fs_extra16.remove(entryPath);
33497
+ await import_fs_extra17.remove(entryPath);
32231
33498
  logger.verbose(`Force-deleted user file: ${entry}`);
32232
33499
  }
32233
33500
  result.deletedCount++;
@@ -32263,7 +33530,7 @@ class CommandsPrefix {
32263
33530
  const files = [];
32264
33531
  const entries = await readdir11(dir);
32265
33532
  for (const entry of entries) {
32266
- const fullPath = join28(dir, entry);
33533
+ const fullPath = join29(dir, entry);
32267
33534
  const stats = await lstat3(fullPath);
32268
33535
  if (stats.isSymbolicLink()) {
32269
33536
  continue;
@@ -32281,9 +33548,9 @@ class CommandsPrefix {
32281
33548
  // src/services/transformers/folder-path-transformer.ts
32282
33549
  init_logger();
32283
33550
  init_types2();
32284
- var import_fs_extra17 = __toESM(require_lib(), 1);
32285
- import { readFile as readFile15, readdir as readdir12, rename as rename3, writeFile as writeFile13 } from "node:fs/promises";
32286
- import { join as join29, relative as relative8 } from "node:path";
33551
+ var import_fs_extra18 = __toESM(require_lib(), 1);
33552
+ import { readFile as readFile16, readdir as readdir12, rename as rename3, writeFile as writeFile14 } from "node:fs/promises";
33553
+ import { join as join30, relative as relative8 } from "node:path";
32287
33554
  var TRANSFORMABLE_FILE_PATTERNS = [
32288
33555
  ".md",
32289
33556
  ".txt",
@@ -32329,34 +33596,34 @@ async function transformFolderPaths(extractDir, folders, options = {}) {
32329
33596
  }
32330
33597
  const dirsToRename = [];
32331
33598
  if (folders.docs !== DEFAULT_FOLDERS.docs) {
32332
- const docsPath = join29(extractDir, DEFAULT_FOLDERS.docs);
32333
- if (await import_fs_extra17.pathExists(docsPath)) {
33599
+ const docsPath = join30(extractDir, DEFAULT_FOLDERS.docs);
33600
+ if (await import_fs_extra18.pathExists(docsPath)) {
32334
33601
  dirsToRename.push({
32335
33602
  from: docsPath,
32336
- to: join29(extractDir, folders.docs)
33603
+ to: join30(extractDir, folders.docs)
32337
33604
  });
32338
33605
  }
32339
- const claudeDocsPath = join29(extractDir, ".claude", DEFAULT_FOLDERS.docs);
32340
- if (await import_fs_extra17.pathExists(claudeDocsPath)) {
33606
+ const claudeDocsPath = join30(extractDir, ".claude", DEFAULT_FOLDERS.docs);
33607
+ if (await import_fs_extra18.pathExists(claudeDocsPath)) {
32341
33608
  dirsToRename.push({
32342
33609
  from: claudeDocsPath,
32343
- to: join29(extractDir, ".claude", folders.docs)
33610
+ to: join30(extractDir, ".claude", folders.docs)
32344
33611
  });
32345
33612
  }
32346
33613
  }
32347
33614
  if (folders.plans !== DEFAULT_FOLDERS.plans) {
32348
- const plansPath = join29(extractDir, DEFAULT_FOLDERS.plans);
32349
- if (await import_fs_extra17.pathExists(plansPath)) {
33615
+ const plansPath = join30(extractDir, DEFAULT_FOLDERS.plans);
33616
+ if (await import_fs_extra18.pathExists(plansPath)) {
32350
33617
  dirsToRename.push({
32351
33618
  from: plansPath,
32352
- to: join29(extractDir, folders.plans)
33619
+ to: join30(extractDir, folders.plans)
32353
33620
  });
32354
33621
  }
32355
- const claudePlansPath = join29(extractDir, ".claude", DEFAULT_FOLDERS.plans);
32356
- if (await import_fs_extra17.pathExists(claudePlansPath)) {
33622
+ const claudePlansPath = join30(extractDir, ".claude", DEFAULT_FOLDERS.plans);
33623
+ if (await import_fs_extra18.pathExists(claudePlansPath)) {
32357
33624
  dirsToRename.push({
32358
33625
  from: claudePlansPath,
32359
- to: join29(extractDir, ".claude", folders.plans)
33626
+ to: join30(extractDir, ".claude", folders.plans)
32360
33627
  });
32361
33628
  }
32362
33629
  }
@@ -32393,7 +33660,7 @@ async function transformFileContents(dir, compiledReplacements, options) {
32393
33660
  let replacementsCount = 0;
32394
33661
  const entries = await readdir12(dir, { withFileTypes: true });
32395
33662
  for (const entry of entries) {
32396
- const fullPath = join29(dir, entry.name);
33663
+ const fullPath = join30(dir, entry.name);
32397
33664
  if (entry.isDirectory()) {
32398
33665
  if (entry.name === "node_modules" || entry.name === ".git") {
32399
33666
  continue;
@@ -32406,7 +33673,7 @@ async function transformFileContents(dir, compiledReplacements, options) {
32406
33673
  if (!shouldTransform)
32407
33674
  continue;
32408
33675
  try {
32409
- const content = await readFile15(fullPath, "utf-8");
33676
+ const content = await readFile16(fullPath, "utf-8");
32410
33677
  let newContent = content;
32411
33678
  let changeCount = 0;
32412
33679
  for (const { regex: regex2, replacement } of compiledReplacements) {
@@ -32422,7 +33689,7 @@ async function transformFileContents(dir, compiledReplacements, options) {
32422
33689
  if (options.dryRun) {
32423
33690
  logger.debug(`[dry-run] Would update ${relative8(dir, fullPath)}: ${changeCount} replacement(s)`);
32424
33691
  } else {
32425
- await writeFile13(fullPath, newContent, "utf-8");
33692
+ await writeFile14(fullPath, newContent, "utf-8");
32426
33693
  logger.debug(`Updated ${relative8(dir, fullPath)}: ${changeCount} replacement(s)`);
32427
33694
  }
32428
33695
  filesChanged++;
@@ -32502,9 +33769,9 @@ function validateFolderName(name2) {
32502
33769
 
32503
33770
  // src/services/transformers/global-path-transformer.ts
32504
33771
  init_logger();
32505
- import { readFile as readFile16, readdir as readdir13, writeFile as writeFile14 } from "node:fs/promises";
33772
+ import { readFile as readFile17, readdir as readdir13, writeFile as writeFile15 } from "node:fs/promises";
32506
33773
  import { platform as platform9 } from "node:os";
32507
- import { extname, join as join30 } from "node:path";
33774
+ import { extname, join as join31 } from "node:path";
32508
33775
  var IS_WINDOWS2 = platform9() === "win32";
32509
33776
  var HOME_PREFIX = IS_WINDOWS2 ? "%USERPROFILE%" : "$HOME";
32510
33777
  function getHomeDirPrefix() {
@@ -32596,7 +33863,7 @@ async function transformPathsForGlobalInstall(directory, options = {}) {
32596
33863
  async function processDirectory(dir) {
32597
33864
  const entries = await readdir13(dir, { withFileTypes: true });
32598
33865
  for (const entry of entries) {
32599
- const fullPath = join30(dir, entry.name);
33866
+ const fullPath = join31(dir, entry.name);
32600
33867
  if (entry.isDirectory()) {
32601
33868
  if (entry.name === "node_modules" || entry.name.startsWith(".") && entry.name !== ".claude") {
32602
33869
  continue;
@@ -32604,10 +33871,10 @@ async function transformPathsForGlobalInstall(directory, options = {}) {
32604
33871
  await processDirectory(fullPath);
32605
33872
  } else if (entry.isFile() && shouldTransformFile(entry.name)) {
32606
33873
  try {
32607
- const content = await readFile16(fullPath, "utf-8");
33874
+ const content = await readFile17(fullPath, "utf-8");
32608
33875
  const { transformed, changes } = transformContent(content);
32609
33876
  if (changes > 0) {
32610
- await writeFile14(fullPath, transformed, "utf-8");
33877
+ await writeFile15(fullPath, transformed, "utf-8");
32611
33878
  filesTransformed++;
32612
33879
  totalChanges += changes;
32613
33880
  if (options.verbose) {
@@ -32638,7 +33905,7 @@ init_environment();
32638
33905
  init_logger();
32639
33906
  init_output_manager();
32640
33907
  init_types2();
32641
- var import_fs_extra18 = __toESM(require_lib(), 1);
33908
+ var import_fs_extra19 = __toESM(require_lib(), 1);
32642
33909
  async function initCommand(options) {
32643
33910
  const prompts = new PromptsManager;
32644
33911
  prompts.intro("\uD83D\uDD27 ClaudeKit - Initialize/Update Project");
@@ -32657,8 +33924,8 @@ async function initCommand(options) {
32657
33924
  const globalKitDir = PathResolver.getGlobalKitDir();
32658
33925
  const cwdResolved = resolve6(process.cwd());
32659
33926
  const isInGlobalDir = cwdResolved === globalKitDir || cwdResolved === resolve6(globalKitDir, "..");
32660
- const localSettingsPath = join31(process.cwd(), ".claude", "settings.json");
32661
- if (!isInGlobalDir && await import_fs_extra18.pathExists(localSettingsPath)) {
33927
+ const localSettingsPath = join32(process.cwd(), ".claude", "settings.json");
33928
+ if (!isInGlobalDir && await import_fs_extra19.pathExists(localSettingsPath)) {
32662
33929
  if (isNonInteractive2) {
32663
33930
  logger.warning("Local .claude/settings.json detected. Local settings take precedence over global.");
32664
33931
  logger.warning("Consider removing local installation: rm -rf .claude");
@@ -32669,9 +33936,9 @@ async function initCommand(options) {
32669
33936
  return;
32670
33937
  }
32671
33938
  if (choice === "remove") {
32672
- const localClaudeDir = join31(process.cwd(), ".claude");
33939
+ const localClaudeDir = join32(process.cwd(), ".claude");
32673
33940
  try {
32674
- await import_fs_extra18.remove(localClaudeDir);
33941
+ await import_fs_extra19.remove(localClaudeDir);
32675
33942
  logger.success("Removed local .claude/ directory");
32676
33943
  } catch (error) {
32677
33944
  logger.error(`Failed to remove local installation: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -32715,7 +33982,7 @@ async function initCommand(options) {
32715
33982
  }
32716
33983
  const resolvedDir = resolve6(targetDir);
32717
33984
  logger.info(`Target directory: ${resolvedDir}`);
32718
- if (!await import_fs_extra18.pathExists(resolvedDir)) {
33985
+ if (!await import_fs_extra19.pathExists(resolvedDir)) {
32719
33986
  if (validOptions.global) {
32720
33987
  const { mkdir: mkdir11 } = await import("node:fs/promises");
32721
33988
  await mkdir11(resolvedDir, { recursive: true });
@@ -32728,7 +33995,7 @@ async function initCommand(options) {
32728
33995
  }
32729
33996
  if (validOptions.fresh) {
32730
33997
  const prefix = PathResolver.getPathPrefix(validOptions.global);
32731
- const claudeDir2 = prefix ? join31(resolvedDir, prefix) : resolvedDir;
33998
+ const claudeDir2 = prefix ? join32(resolvedDir, prefix) : resolvedDir;
32732
33999
  const canProceed = await handleFreshInstallation(claudeDir2, prompts);
32733
34000
  if (!canProceed) {
32734
34001
  return;
@@ -32756,7 +34023,7 @@ async function initCommand(options) {
32756
34023
  logger.info("Fetching available versions...");
32757
34024
  let currentVersion = null;
32758
34025
  try {
32759
- const metadataPath = validOptions.global ? join31(PathResolver.getGlobalKitDir(), "metadata.json") : join31(resolvedDir, ".claude", "metadata.json");
34026
+ const metadataPath = validOptions.global ? join32(PathResolver.getGlobalKitDir(), "metadata.json") : join32(resolvedDir, ".claude", "metadata.json");
32760
34027
  const metadata = await readClaudeKitMetadata(metadataPath);
32761
34028
  currentVersion = metadata?.version || null;
32762
34029
  if (currentVersion) {
@@ -32881,9 +34148,9 @@ async function initCommand(options) {
32881
34148
  }
32882
34149
  }
32883
34150
  if (!validOptions.fresh) {
32884
- const newSkillsDir = join31(extractDir, ".claude", "skills");
34151
+ const newSkillsDir = join32(extractDir, ".claude", "skills");
32885
34152
  const currentSkillsDir = PathResolver.buildSkillsPath(resolvedDir, validOptions.global);
32886
- if (await import_fs_extra18.pathExists(newSkillsDir) && await import_fs_extra18.pathExists(currentSkillsDir)) {
34153
+ if (await import_fs_extra19.pathExists(newSkillsDir) && await import_fs_extra19.pathExists(currentSkillsDir)) {
32887
34154
  logger.info("Checking for skills directory migration...");
32888
34155
  const migrationDetection = await SkillsMigrationDetector.detectMigration(newSkillsDir, currentSkillsDir);
32889
34156
  if (migrationDetection.status === "recommended" || migrationDetection.status === "required") {
@@ -32906,7 +34173,7 @@ async function initCommand(options) {
32906
34173
  let customClaudeFiles = [];
32907
34174
  if (!validOptions.fresh) {
32908
34175
  logger.info("Scanning for custom .claude files...");
32909
- const scanSourceDir = validOptions.global ? join31(extractDir, ".claude") : extractDir;
34176
+ const scanSourceDir = validOptions.global ? join32(extractDir, ".claude") : extractDir;
32910
34177
  const scanTargetSubdir = validOptions.global ? "" : ".claude";
32911
34178
  customClaudeFiles = await FileScanner.findCustomFiles(resolvedDir, scanSourceDir, scanTargetSubdir);
32912
34179
  } else {
@@ -32941,9 +34208,12 @@ async function initCommand(options) {
32941
34208
  }
32942
34209
  merger.setGlobalFlag(validOptions.global);
32943
34210
  merger.setForceOverwriteSettings(validOptions.forceOverwriteSettings);
32944
- const claudeDir = validOptions.global ? resolvedDir : join31(resolvedDir, ".claude");
34211
+ const claudeDir = validOptions.global ? resolvedDir : join32(resolvedDir, ".claude");
32945
34212
  const releaseManifest = await ReleaseManifestLoader.load(extractDir);
32946
- if (!validOptions.fresh && await import_fs_extra18.pathExists(claudeDir)) {
34213
+ if (releaseManifest) {
34214
+ merger.setManifest(releaseManifest);
34215
+ }
34216
+ if (!validOptions.fresh && await import_fs_extra19.pathExists(claudeDir)) {
32947
34217
  const legacyDetection = await LegacyMigration.detectLegacy(claudeDir);
32948
34218
  if (legacyDetection.isLegacy && releaseManifest) {
32949
34219
  logger.info("Legacy installation detected - migrating to ownership tracking...");
@@ -32963,7 +34233,7 @@ async function initCommand(options) {
32963
34233
  return;
32964
34234
  }
32965
34235
  }
32966
- const sourceDir = validOptions.global ? join31(extractDir, ".claude") : extractDir;
34236
+ const sourceDir = validOptions.global ? join32(extractDir, ".claude") : extractDir;
32967
34237
  await merger.merge(sourceDir, resolvedDir, false);
32968
34238
  const manifestWriter = new ManifestWriter;
32969
34239
  const installedFiles = merger.getAllInstalledFiles();
@@ -32972,7 +34242,7 @@ async function initCommand(options) {
32972
34242
  if (!validOptions.global && !installedPath.startsWith(".claude/"))
32973
34243
  continue;
32974
34244
  const relativePath = validOptions.global ? installedPath : installedPath.replace(/^\.claude\//, "");
32975
- const filePath = join31(claudeDir, relativePath);
34245
+ const filePath = join32(claudeDir, relativePath);
32976
34246
  const manifestEntry = releaseManifest ? ReleaseManifestLoader.findFile(releaseManifest, installedPath) : null;
32977
34247
  const ownership = manifestEntry ? "ck" : "user";
32978
34248
  filesToTrack.push({
@@ -32991,13 +34261,13 @@ async function initCommand(options) {
32991
34261
  }
32992
34262
  });
32993
34263
  trackingSpinner.succeed(`Tracked ${trackResult.success} files`);
32994
- await manifestWriter.writeManifest(claudeDir, kitConfig.name, release.tag_name, validOptions.global ? "global" : "local");
34264
+ await manifestWriter.writeManifest(claudeDir, kitConfig.name, release.tag_name, validOptions.global ? "global" : "local", kit);
32995
34265
  if (validOptions.global) {
32996
- const claudeMdSource = join31(extractDir, "CLAUDE.md");
32997
- const claudeMdDest = join31(resolvedDir, "CLAUDE.md");
32998
- if (await import_fs_extra18.pathExists(claudeMdSource)) {
32999
- if (!await import_fs_extra18.pathExists(claudeMdDest)) {
33000
- await import_fs_extra18.copy(claudeMdSource, claudeMdDest);
34266
+ const claudeMdSource = join32(extractDir, "CLAUDE.md");
34267
+ const claudeMdDest = join32(resolvedDir, "CLAUDE.md");
34268
+ if (await import_fs_extra19.pathExists(claudeMdSource)) {
34269
+ if (!await import_fs_extra19.pathExists(claudeMdDest)) {
34270
+ await import_fs_extra19.copy(claudeMdSource, claudeMdDest);
33001
34271
  logger.success("Copied CLAUDE.md to global directory");
33002
34272
  } else {
33003
34273
  logger.debug("CLAUDE.md already exists in global directory (preserved)");
@@ -33017,18 +34287,27 @@ async function initCommand(options) {
33017
34287
  const { isGeminiInstalled: isGeminiInstalled2 } = await Promise.resolve().then(() => (init_package_installer(), exports_package_installer));
33018
34288
  const { checkExistingGeminiConfig: checkExistingGeminiConfig2, findMcpConfigPath: findMcpConfigPath2, processGeminiMcpLinking: processGeminiMcpLinking2 } = await Promise.resolve().then(() => (init_gemini_mcp_linker(), exports_gemini_mcp_linker));
33019
34289
  const geminiInstalled = await isGeminiInstalled2();
33020
- const existingConfig = checkExistingGeminiConfig2(resolvedDir);
33021
- const mcpConfigExists = findMcpConfigPath2(resolvedDir) !== null;
34290
+ const existingConfig = checkExistingGeminiConfig2(resolvedDir, validOptions.global);
34291
+ const mcpConfigPath = findMcpConfigPath2(resolvedDir);
34292
+ const mcpConfigExists = mcpConfigPath !== null;
33022
34293
  if (geminiInstalled && !existingConfig.exists && mcpConfigExists) {
33023
- const shouldSetupGemini = await prompts.confirm("Gemini CLI detected. Set up MCP integration? (creates .gemini/settings.json symlink)");
34294
+ const geminiPath = validOptions.global ? "~/.gemini/settings.json" : ".gemini/settings.json";
34295
+ const mcpPath = validOptions.global ? "~/.claude/.mcp.json" : ".mcp.json";
34296
+ const promptMessage = [
34297
+ "Gemini CLI detected. Set up MCP integration?",
34298
+ ` → Creates ${geminiPath} symlink to ${mcpPath}`,
34299
+ " → Gemini CLI will share MCP servers with Claude Code"
34300
+ ].join(`
34301
+ `);
34302
+ const shouldSetupGemini = await prompts.confirm(promptMessage);
33024
34303
  if (shouldSetupGemini) {
33025
- await processGeminiMcpLinking2(resolvedDir);
34304
+ await processGeminiMcpLinking2(resolvedDir, { isGlobal: validOptions.global });
33026
34305
  }
33027
34306
  }
33028
34307
  }
33029
34308
  if (!validOptions.skipSetup && !isNonInteractive2) {
33030
- const envPath = join31(claudeDir, ".env");
33031
- if (!await import_fs_extra18.pathExists(envPath)) {
34309
+ const envPath = join32(claudeDir, ".env");
34310
+ if (!await import_fs_extra19.pathExists(envPath)) {
33032
34311
  const shouldSetup = await prompts.confirm("Set up API keys now? (Gemini API key for ai-multimodal skill, optional webhooks)");
33033
34312
  if (shouldSetup) {
33034
34313
  await runSetupWizard({
@@ -33058,13 +34337,13 @@ Protected files (.env, etc.) were not modified.`;
33058
34337
  }
33059
34338
 
33060
34339
  // src/commands/new.ts
33061
- import { join as join32, resolve as resolve7 } from "node:path";
34340
+ import { join as join33, resolve as resolve7 } from "node:path";
33062
34341
  init_package_installer();
33063
34342
  init_environment();
33064
34343
  init_logger();
33065
34344
  init_output_manager();
33066
34345
  init_types2();
33067
- var import_fs_extra19 = __toESM(require_lib(), 1);
34346
+ var import_fs_extra20 = __toESM(require_lib(), 1);
33068
34347
  async function newCommand(options) {
33069
34348
  const prompts = new PromptsManager;
33070
34349
  prompts.intro("\uD83D\uDE80 ClaudeKit - Create New Project");
@@ -33091,8 +34370,8 @@ async function newCommand(options) {
33091
34370
  }
33092
34371
  const resolvedDir = resolve7(targetDir);
33093
34372
  logger.info(`Target directory: ${resolvedDir}`);
33094
- if (await import_fs_extra19.pathExists(resolvedDir)) {
33095
- const files = await import_fs_extra19.readdir(resolvedDir);
34373
+ if (await import_fs_extra20.pathExists(resolvedDir)) {
34374
+ const files = await import_fs_extra20.readdir(resolvedDir);
33096
34375
  const isEmpty = files.length === 0;
33097
34376
  if (!isEmpty) {
33098
34377
  if (isNonInteractive2) {
@@ -33239,7 +34518,7 @@ async function newCommand(options) {
33239
34518
  await CommandsPrefix.cleanupCommandsDirectory(resolvedDir, false);
33240
34519
  }
33241
34520
  await merger.merge(extractDir, resolvedDir, true);
33242
- const claudeDir = join32(resolvedDir, ".claude");
34521
+ const claudeDir = join33(resolvedDir, ".claude");
33243
34522
  const manifestWriter = new ManifestWriter;
33244
34523
  const releaseManifest = await ReleaseManifestLoader.load(extractDir);
33245
34524
  const installedFiles = merger.getAllInstalledFiles();
@@ -33248,7 +34527,7 @@ async function newCommand(options) {
33248
34527
  if (!installedPath.startsWith(".claude/"))
33249
34528
  continue;
33250
34529
  const relativePath = installedPath.replace(/^\.claude\//, "");
33251
- const filePath = join32(claudeDir, relativePath);
34530
+ const filePath = join33(claudeDir, relativePath);
33252
34531
  const manifestEntry = releaseManifest ? ReleaseManifestLoader.findFile(releaseManifest, installedPath) : null;
33253
34532
  const ownership = manifestEntry ? "ck" : "user";
33254
34533
  filesToTrack.push({
@@ -33301,10 +34580,10 @@ async function newCommand(options) {
33301
34580
 
33302
34581
  // src/commands/uninstall.ts
33303
34582
  import { readdirSync, rmSync } from "node:fs";
33304
- import { dirname as dirname7, join as join33 } from "node:path";
34583
+ import { dirname as dirname7, join as join34 } from "node:path";
33305
34584
  init_logger();
33306
34585
  init_types2();
33307
- var import_fs_extra20 = __toESM(require_lib(), 1);
34586
+ var import_fs_extra21 = __toESM(require_lib(), 1);
33308
34587
  var import_picocolors13 = __toESM(require_picocolors(), 1);
33309
34588
  async function detectInstallations() {
33310
34589
  const installations = [];
@@ -33313,14 +34592,14 @@ async function detectInstallations() {
33313
34592
  installations.push({
33314
34593
  type: "local",
33315
34594
  path: setup.project.path,
33316
- exists: await import_fs_extra20.pathExists(setup.project.path)
34595
+ exists: await import_fs_extra21.pathExists(setup.project.path)
33317
34596
  });
33318
34597
  }
33319
34598
  if (setup.global.path && setup.global.metadata) {
33320
34599
  installations.push({
33321
34600
  type: "global",
33322
34601
  path: setup.global.path,
33323
- exists: await import_fs_extra20.pathExists(setup.global.path)
34602
+ exists: await import_fs_extra21.pathExists(setup.global.path)
33324
34603
  });
33325
34604
  }
33326
34605
  return installations.filter((i) => i.exists);
@@ -33353,14 +34632,26 @@ async function promptScope(installations) {
33353
34632
  }
33354
34633
  return selected;
33355
34634
  }
33356
- async function confirmUninstall(scope) {
34635
+ async function confirmUninstall(scope, kitLabel = "") {
33357
34636
  const scopeText = scope === "all" ? "all ClaudeKit installations" : scope === "local" ? "local ClaudeKit installation" : "global ClaudeKit installation";
33358
34637
  const confirmed = await se({
33359
- message: `Continue with uninstalling ${scopeText}?`,
34638
+ message: `Continue with uninstalling ${scopeText}${kitLabel}?`,
33360
34639
  initialValue: false
33361
34640
  });
33362
34641
  return confirmed === true;
33363
34642
  }
34643
+ function classifyFileByOwnership(ownership, forceOverwrite, deleteReason) {
34644
+ if (ownership === "ck") {
34645
+ return { action: "delete", reason: deleteReason };
34646
+ }
34647
+ if (ownership === "ck-modified") {
34648
+ if (forceOverwrite) {
34649
+ return { action: "delete", reason: "force overwrite" };
34650
+ }
34651
+ return { action: "preserve", reason: "modified by user" };
34652
+ }
34653
+ return { action: "preserve", reason: "user-created" };
34654
+ }
33364
34655
  async function cleanupEmptyDirectories(filePath, installationRoot) {
33365
34656
  let cleaned = 0;
33366
34657
  let currentDir = dirname7(filePath);
@@ -33381,33 +34672,56 @@ async function cleanupEmptyDirectories(filePath, installationRoot) {
33381
34672
  }
33382
34673
  return cleaned;
33383
34674
  }
33384
- async function analyzeInstallation(installation, forceOverwrite) {
33385
- const result = { toDelete: [], toPreserve: [] };
34675
+ async function analyzeInstallation(installation, forceOverwrite, kit) {
34676
+ const result = {
34677
+ toDelete: [],
34678
+ toPreserve: [],
34679
+ remainingKits: []
34680
+ };
33386
34681
  const metadata = await ManifestWriter.readManifest(installation.path);
34682
+ const uninstallManifest = await ManifestWriter.getUninstallManifest(installation.path, kit);
34683
+ result.remainingKits = uninstallManifest.remainingKits;
34684
+ if (uninstallManifest.isMultiKit && kit && metadata?.kits?.[kit]) {
34685
+ const kitFiles = metadata.kits[kit].files || [];
34686
+ for (const trackedFile of kitFiles) {
34687
+ const filePath = join34(installation.path, trackedFile.path);
34688
+ if (uninstallManifest.filesToPreserve.includes(trackedFile.path)) {
34689
+ result.toPreserve.push({ path: trackedFile.path, reason: "shared with other kit" });
34690
+ continue;
34691
+ }
34692
+ const ownershipResult = await OwnershipChecker.checkOwnership(filePath, metadata, installation.path);
34693
+ if (!ownershipResult.exists)
34694
+ continue;
34695
+ const classification = classifyFileByOwnership(ownershipResult.ownership, forceOverwrite, `${kit} kit (pristine)`);
34696
+ if (classification.action === "delete") {
34697
+ result.toDelete.push({ path: trackedFile.path, reason: classification.reason });
34698
+ } else {
34699
+ result.toPreserve.push({ path: trackedFile.path, reason: classification.reason });
34700
+ }
34701
+ }
34702
+ if (result.remainingKits.length === 0) {
34703
+ result.toDelete.push({ path: "metadata.json", reason: "metadata file" });
34704
+ }
34705
+ return result;
34706
+ }
33387
34707
  if (!metadata?.files || metadata.files.length === 0) {
33388
- const { filesToRemove, filesToPreserve } = await ManifestWriter.getUninstallManifest(installation.path);
33389
- for (const item of filesToRemove) {
33390
- if (!filesToPreserve.includes(item)) {
34708
+ for (const item of uninstallManifest.filesToRemove) {
34709
+ if (!uninstallManifest.filesToPreserve.includes(item)) {
33391
34710
  result.toDelete.push({ path: item, reason: "legacy installation" });
33392
34711
  }
33393
34712
  }
33394
34713
  return result;
33395
34714
  }
33396
- for (const trackedFile of metadata.files) {
33397
- const filePath = join33(installation.path, trackedFile.path);
34715
+ for (const trackedFile of metadata.files || []) {
34716
+ const filePath = join34(installation.path, trackedFile.path);
33398
34717
  const ownershipResult = await OwnershipChecker.checkOwnership(filePath, metadata, installation.path);
33399
34718
  if (!ownershipResult.exists)
33400
34719
  continue;
33401
- if (ownershipResult.ownership === "ck") {
33402
- result.toDelete.push({ path: trackedFile.path, reason: "CK-owned (pristine)" });
33403
- } else if (ownershipResult.ownership === "ck-modified") {
33404
- if (forceOverwrite) {
33405
- result.toDelete.push({ path: trackedFile.path, reason: "force overwrite" });
33406
- } else {
33407
- result.toPreserve.push({ path: trackedFile.path, reason: "modified by user" });
33408
- }
34720
+ const classification = classifyFileByOwnership(ownershipResult.ownership, forceOverwrite, "CK-owned (pristine)");
34721
+ if (classification.action === "delete") {
34722
+ result.toDelete.push({ path: trackedFile.path, reason: classification.reason });
33409
34723
  } else {
33410
- result.toPreserve.push({ path: trackedFile.path, reason: "user-created" });
34724
+ result.toPreserve.push({ path: trackedFile.path, reason: classification.reason });
33411
34725
  }
33412
34726
  }
33413
34727
  result.toDelete.push({ path: "metadata.json", reason: "metadata file" });
@@ -33442,24 +34756,32 @@ function displayDryRunPreview(analysis, installationType) {
33442
34756
  }
33443
34757
  async function removeInstallations(installations, options) {
33444
34758
  for (const installation of installations) {
33445
- const analysis = await analyzeInstallation(installation, options.forceOverwrite);
34759
+ const analysis = await analyzeInstallation(installation, options.forceOverwrite, options.kit);
33446
34760
  if (options.dryRun) {
33447
- displayDryRunPreview(analysis, installation.type);
34761
+ const label = options.kit ? `${installation.type} (${options.kit} kit)` : installation.type;
34762
+ displayDryRunPreview(analysis, label);
34763
+ if (analysis.remainingKits.length > 0) {
34764
+ log.info(`Remaining kits after uninstall: ${analysis.remainingKits.join(", ")}`);
34765
+ }
33448
34766
  continue;
33449
34767
  }
33450
- const spinner = createSpinner(`Removing ${installation.type} ClaudeKit files...`).start();
34768
+ const kitLabel = options.kit ? ` ${options.kit} kit` : "";
34769
+ const spinner = createSpinner(`Removing ${installation.type}${kitLabel} ClaudeKit files...`).start();
33451
34770
  try {
33452
34771
  let removedCount = 0;
33453
34772
  let cleanedDirs = 0;
33454
34773
  for (const item of analysis.toDelete) {
33455
- const filePath = join33(installation.path, item.path);
33456
- if (await import_fs_extra20.pathExists(filePath)) {
33457
- await import_fs_extra20.remove(filePath);
34774
+ const filePath = join34(installation.path, item.path);
34775
+ if (await import_fs_extra21.pathExists(filePath)) {
34776
+ await import_fs_extra21.remove(filePath);
33458
34777
  removedCount++;
33459
34778
  logger.debug(`Removed: ${item.path}`);
33460
34779
  cleanedDirs += await cleanupEmptyDirectories(filePath, installation.path);
33461
34780
  }
33462
34781
  }
34782
+ if (options.kit && analysis.remainingKits.length > 0) {
34783
+ await ManifestWriter.removeKitFromManifest(installation.path, options.kit);
34784
+ }
33463
34785
  try {
33464
34786
  const remaining = readdirSync(installation.path);
33465
34787
  if (remaining.length === 0) {
@@ -33467,7 +34789,8 @@ async function removeInstallations(installations, options) {
33467
34789
  logger.debug(`Removed empty installation directory: ${installation.path}`);
33468
34790
  }
33469
34791
  } catch {}
33470
- spinner.succeed(`Removed ${removedCount} files${cleanedDirs > 0 ? `, cleaned ${cleanedDirs} empty directories` : ""}, preserved ${analysis.toPreserve.length} customizations`);
34792
+ const kitsInfo = analysis.remainingKits.length > 0 ? `, ${analysis.remainingKits.join(", ")} kit(s) preserved` : "";
34793
+ spinner.succeed(`Removed ${removedCount} files${cleanedDirs > 0 ? `, cleaned ${cleanedDirs} empty directories` : ""}, preserved ${analysis.toPreserve.length} customizations${kitsInfo}`);
33471
34794
  if (analysis.toPreserve.length > 0) {
33472
34795
  log.info("Preserved customizations:");
33473
34796
  analysis.toPreserve.slice(0, 5).forEach((f3) => log.message(` - ${f3.path} (${f3.reason})`));
@@ -33489,6 +34812,23 @@ async function uninstallCommand(options) {
33489
34812
  logger.info("No ClaudeKit installations found.");
33490
34813
  return;
33491
34814
  }
34815
+ if (validOptions.kit) {
34816
+ let kitFound = false;
34817
+ for (const inst of allInstallations) {
34818
+ const metadata = await ManifestWriter.readManifest(inst.path);
34819
+ if (metadata) {
34820
+ const installedKits = getInstalledKits(metadata);
34821
+ if (installedKits.includes(validOptions.kit)) {
34822
+ kitFound = true;
34823
+ break;
34824
+ }
34825
+ }
34826
+ }
34827
+ if (!kitFound) {
34828
+ logger.info(`Kit "${validOptions.kit}" is not installed.`);
34829
+ return;
34830
+ }
34831
+ }
33492
34832
  let scope;
33493
34833
  if (validOptions.all || validOptions.local && validOptions.global) {
33494
34834
  scope = "all";
@@ -33515,11 +34855,15 @@ async function uninstallCommand(options) {
33515
34855
  return;
33516
34856
  }
33517
34857
  displayInstallations(installations, scope);
34858
+ if (validOptions.kit) {
34859
+ log.info(import_picocolors13.default.cyan(`Kit-scoped uninstall: ${validOptions.kit} kit only`));
34860
+ }
33518
34861
  if (validOptions.dryRun) {
33519
34862
  log.info(import_picocolors13.default.yellow("DRY RUN MODE - No files will be deleted"));
33520
34863
  await removeInstallations(installations, {
33521
34864
  dryRun: true,
33522
- forceOverwrite: validOptions.forceOverwrite
34865
+ forceOverwrite: validOptions.forceOverwrite,
34866
+ kit: validOptions.kit
33523
34867
  });
33524
34868
  outro("Dry-run complete. No changes were made.");
33525
34869
  return;
@@ -33529,7 +34873,8 @@ async function uninstallCommand(options) {
33529
34873
  ${import_picocolors13.default.yellow("User modifications will be permanently deleted!")}`);
33530
34874
  }
33531
34875
  if (!validOptions.yes) {
33532
- const confirmed = await confirmUninstall(scope);
34876
+ const kitLabel = validOptions.kit ? ` (${validOptions.kit} kit only)` : "";
34877
+ const confirmed = await confirmUninstall(scope, kitLabel);
33533
34878
  if (!confirmed) {
33534
34879
  logger.info("Uninstall cancelled.");
33535
34880
  return;
@@ -33537,9 +34882,11 @@ ${import_picocolors13.default.yellow("User modifications will be permanently del
33537
34882
  }
33538
34883
  await removeInstallations(installations, {
33539
34884
  dryRun: false,
33540
- forceOverwrite: validOptions.forceOverwrite
34885
+ forceOverwrite: validOptions.forceOverwrite,
34886
+ kit: validOptions.kit
33541
34887
  });
33542
- outro("ClaudeKit uninstalled successfully!");
34888
+ const kitMsg = validOptions.kit ? ` (${validOptions.kit} kit)` : "";
34889
+ outro(`ClaudeKit${kitMsg} uninstalled successfully!`);
33543
34890
  } catch (error) {
33544
34891
  logger.error(error instanceof Error ? error.message : "Unknown error");
33545
34892
  process.exit(1);
@@ -33693,7 +35040,7 @@ var import_compare_versions2 = __toESM(require_umd(), 1);
33693
35040
  // package.json
33694
35041
  var package_default2 = {
33695
35042
  name: "claudekit-cli",
33696
- version: "3.10.1",
35043
+ version: "3.11.0",
33697
35044
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
33698
35045
  type: "module",
33699
35046
  repository: {
@@ -33713,7 +35060,7 @@ var package_default2 = {
33713
35060
  ],
33714
35061
  scripts: {
33715
35062
  dev: "bun run src/index.ts",
33716
- build: "bun build src/index.ts --outdir dist --target node --external keytar --external @octokit/rest",
35063
+ build: "bun build src/index.ts --outdir dist --target node --external @octokit/rest",
33717
35064
  compile: "bun build src/index.ts --compile --outfile ck",
33718
35065
  "compile:binary": "bun build src/index.ts --compile --outfile bin/ck",
33719
35066
  "compile:binaries": "node scripts/build-all-binaries.js",
@@ -33754,11 +35101,11 @@ var package_default2 = {
33754
35101
  "extract-zip": "^2.0.1",
33755
35102
  "fs-extra": "^11.2.0",
33756
35103
  ignore: "^5.3.2",
33757
- keytar: "^7.9.0",
33758
35104
  minimatch: "^10.1.1",
33759
35105
  ora: "^8.0.0",
33760
35106
  "p-limit": "^7.2.0",
33761
35107
  picocolors: "^1.1.1",
35108
+ "proper-lockfile": "^4.1.2",
33762
35109
  tar: "^7.4.3",
33763
35110
  tmp: "^0.2.3",
33764
35111
  zod: "^3.23.8"
@@ -33771,6 +35118,7 @@ var package_default2 = {
33771
35118
  "@types/cli-progress": "^3.11.6",
33772
35119
  "@types/fs-extra": "^11.0.4",
33773
35120
  "@types/node": "^22.10.1",
35121
+ "@types/proper-lockfile": "^4.1.4",
33774
35122
  "@types/tar": "^6.1.13",
33775
35123
  "@types/tmp": "^0.2.6",
33776
35124
  "semantic-release": "^24.2.0",
@@ -34001,14 +35349,14 @@ var import_picocolors15 = __toESM(require_picocolors(), 1);
34001
35349
  // src/domains/versioning/version-cache.ts
34002
35350
  init_logger();
34003
35351
  import { existsSync as existsSync8 } from "node:fs";
34004
- import { mkdir as mkdir11, readFile as readFile17, writeFile as writeFile15 } from "node:fs/promises";
34005
- import { join as join34 } from "node:path";
35352
+ import { mkdir as mkdir11, readFile as readFile18, writeFile as writeFile16 } from "node:fs/promises";
35353
+ import { join as join35 } from "node:path";
34006
35354
  class VersionCacheManager {
34007
35355
  static CACHE_FILENAME = "version-check.json";
34008
35356
  static CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
34009
35357
  static getCacheFile() {
34010
35358
  const cacheDir = PathResolver.getCacheDir(false);
34011
- return join34(cacheDir, VersionCacheManager.CACHE_FILENAME);
35359
+ return join35(cacheDir, VersionCacheManager.CACHE_FILENAME);
34012
35360
  }
34013
35361
  static async load() {
34014
35362
  const cacheFile = VersionCacheManager.getCacheFile();
@@ -34017,7 +35365,7 @@ class VersionCacheManager {
34017
35365
  logger.debug("Version check cache not found");
34018
35366
  return null;
34019
35367
  }
34020
- const content = await readFile17(cacheFile, "utf-8");
35368
+ const content = await readFile18(cacheFile, "utf-8");
34021
35369
  const cache2 = JSON.parse(content);
34022
35370
  if (!cache2.lastCheck || !cache2.currentVersion || !cache2.latestVersion) {
34023
35371
  logger.debug("Invalid cache structure, ignoring");
@@ -34037,7 +35385,7 @@ class VersionCacheManager {
34037
35385
  if (!existsSync8(cacheDir)) {
34038
35386
  await mkdir11(cacheDir, { recursive: true, mode: 448 });
34039
35387
  }
34040
- await writeFile15(cacheFile, JSON.stringify(cache2, null, 2), "utf-8");
35388
+ await writeFile16(cacheFile, JSON.stringify(cache2, null, 2), "utf-8");
34041
35389
  logger.debug(`Version check cache saved to ${cacheFile}`);
34042
35390
  } catch (error) {
34043
35391
  logger.debug(`Failed to save version check cache: ${error}`);
@@ -34520,7 +35868,7 @@ var output2 = new OutputManager2;
34520
35868
 
34521
35869
  // src/shared/path-resolver.ts
34522
35870
  import { homedir as homedir5, platform as platform10 } from "node:os";
34523
- import { join as join35, normalize as normalize6 } from "node:path";
35871
+ import { join as join36, normalize as normalize6 } from "node:path";
34524
35872
 
34525
35873
  class PathResolver2 {
34526
35874
  static getTestHomeDir() {
@@ -34553,50 +35901,50 @@ class PathResolver2 {
34553
35901
  static getConfigDir(global3 = false) {
34554
35902
  const testHome = PathResolver2.getTestHomeDir();
34555
35903
  if (testHome) {
34556
- return global3 ? join35(testHome, ".config", "claude") : join35(testHome, ".claudekit");
35904
+ return global3 ? join36(testHome, ".config", "claude") : join36(testHome, ".claudekit");
34557
35905
  }
34558
35906
  if (!global3) {
34559
- return join35(homedir5(), ".claudekit");
35907
+ return join36(homedir5(), ".claudekit");
34560
35908
  }
34561
35909
  const os2 = platform10();
34562
35910
  if (os2 === "win32") {
34563
- const localAppData = process.env.LOCALAPPDATA || join35(homedir5(), "AppData", "Local");
34564
- return join35(localAppData, "claude");
35911
+ const localAppData = process.env.LOCALAPPDATA || join36(homedir5(), "AppData", "Local");
35912
+ return join36(localAppData, "claude");
34565
35913
  }
34566
35914
  const xdgConfigHome = process.env.XDG_CONFIG_HOME;
34567
35915
  if (xdgConfigHome) {
34568
- return join35(xdgConfigHome, "claude");
35916
+ return join36(xdgConfigHome, "claude");
34569
35917
  }
34570
- return join35(homedir5(), ".config", "claude");
35918
+ return join36(homedir5(), ".config", "claude");
34571
35919
  }
34572
35920
  static getConfigFile(global3 = false) {
34573
- return join35(PathResolver2.getConfigDir(global3), "config.json");
35921
+ return join36(PathResolver2.getConfigDir(global3), "config.json");
34574
35922
  }
34575
35923
  static getCacheDir(global3 = false) {
34576
35924
  const testHome = PathResolver2.getTestHomeDir();
34577
35925
  if (testHome) {
34578
- return global3 ? join35(testHome, ".cache", "claude") : join35(testHome, ".claudekit", "cache");
35926
+ return global3 ? join36(testHome, ".cache", "claude") : join36(testHome, ".claudekit", "cache");
34579
35927
  }
34580
35928
  if (!global3) {
34581
- return join35(homedir5(), ".claudekit", "cache");
35929
+ return join36(homedir5(), ".claudekit", "cache");
34582
35930
  }
34583
35931
  const os2 = platform10();
34584
35932
  if (os2 === "win32") {
34585
- const localAppData = process.env.LOCALAPPDATA || join35(homedir5(), "AppData", "Local");
34586
- return join35(localAppData, "claude", "cache");
35933
+ const localAppData = process.env.LOCALAPPDATA || join36(homedir5(), "AppData", "Local");
35934
+ return join36(localAppData, "claude", "cache");
34587
35935
  }
34588
35936
  const xdgCacheHome = process.env.XDG_CACHE_HOME;
34589
35937
  if (xdgCacheHome) {
34590
- return join35(xdgCacheHome, "claude");
35938
+ return join36(xdgCacheHome, "claude");
34591
35939
  }
34592
- return join35(homedir5(), ".cache", "claude");
35940
+ return join36(homedir5(), ".cache", "claude");
34593
35941
  }
34594
35942
  static getGlobalKitDir() {
34595
35943
  const testHome = PathResolver2.getTestHomeDir();
34596
35944
  if (testHome) {
34597
- return join35(testHome, ".claude");
35945
+ return join36(testHome, ".claude");
34598
35946
  }
34599
- return join35(homedir5(), ".claude");
35947
+ return join36(homedir5(), ".claude");
34600
35948
  }
34601
35949
  static getPathPrefix(global3) {
34602
35950
  return global3 ? "" : ".claude";
@@ -34604,9 +35952,9 @@ class PathResolver2 {
34604
35952
  static buildSkillsPath(baseDir, global3) {
34605
35953
  const prefix = PathResolver2.getPathPrefix(global3);
34606
35954
  if (prefix) {
34607
- return join35(baseDir, prefix, "skills");
35955
+ return join36(baseDir, prefix, "skills");
34608
35956
  }
34609
- return join35(baseDir, "skills");
35957
+ return join36(baseDir, "skills");
34610
35958
  }
34611
35959
  static buildComponentPath(baseDir, component, global3) {
34612
35960
  if (!PathResolver2.isPathSafe(component)) {
@@ -34614,9 +35962,9 @@ class PathResolver2 {
34614
35962
  }
34615
35963
  const prefix = PathResolver2.getPathPrefix(global3);
34616
35964
  if (prefix) {
34617
- return join35(baseDir, prefix, component);
35965
+ return join36(baseDir, prefix, component);
34618
35966
  }
34619
- return join35(baseDir, component);
35967
+ return join36(baseDir, component);
34620
35968
  }
34621
35969
  }
34622
35970
 
@@ -34648,9 +35996,9 @@ async function displayVersion() {
34648
35996
  let localKitVersion = null;
34649
35997
  let isGlobalOnlyKit = false;
34650
35998
  const globalKitDir = PathResolver2.getGlobalKitDir();
34651
- const globalMetadataPath = join36(globalKitDir, "metadata.json");
35999
+ const globalMetadataPath = join37(globalKitDir, "metadata.json");
34652
36000
  const prefix = PathResolver2.getPathPrefix(false);
34653
- const localMetadataPath = prefix ? join36(process.cwd(), prefix, "metadata.json") : join36(process.cwd(), "metadata.json");
36001
+ const localMetadataPath = prefix ? join37(process.cwd(), prefix, "metadata.json") : join37(process.cwd(), "metadata.json");
34654
36002
  const isLocalSameAsGlobal = localMetadataPath === globalMetadataPath;
34655
36003
  if (!isLocalSameAsGlobal && existsSync9(localMetadataPath)) {
34656
36004
  try {
@@ -34754,7 +36102,7 @@ cli.command("versions", "List available versions of ClaudeKit repositories").opt
34754
36102
  cli.command("doctor", "Comprehensive health check for ClaudeKit").option("--report", "Generate shareable diagnostic report").option("--fix", "Auto-fix all fixable issues").option("--check-only", "CI mode: no prompts, exit 1 on failures").option("--json", "Output JSON format").option("--full", "Include extended priority checks (slower)").action(async (options) => {
34755
36103
  await doctorCommand(options);
34756
36104
  });
34757
- cli.command("uninstall", "Remove ClaudeKit installations").option("-y, --yes", "Skip confirmation prompt").option("-l, --local", "Uninstall only local installation (current project)").option("-g, --global", "Uninstall only global installation (~/.claude/)").option("-A, --all", "Uninstall from both local and global locations").option("--dry-run", "Preview what would be removed without deleting").option("--force-overwrite", "Delete even user-modified files (requires confirmation)").action(async (options) => {
36105
+ cli.command("uninstall", "Remove ClaudeKit installations").option("-y, --yes", "Skip confirmation prompt").option("-l, --local", "Uninstall only local installation (current project)").option("-g, --global", "Uninstall only global installation (~/.claude/)").option("-A, --all", "Uninstall from both local and global locations").option("-k, --kit <type>", "Uninstall specific kit only (engineer, marketing)").option("--dry-run", "Preview what would be removed without deleting").option("--force-overwrite", "Delete even user-modified files (requires confirmation)").action(async (options) => {
34758
36106
  await uninstallCommand(options);
34759
36107
  });
34760
36108
  cli.option("-V, --version", "Display version number");