claudekit-cli 3.32.0 → 3.32.1-dev.1

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 +139 -31
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -23196,7 +23196,7 @@ function getPagerArgs(pagerCmd) {
23196
23196
  return [];
23197
23197
  }
23198
23198
  async function trySystemPager(content) {
23199
- return new Promise((resolve12) => {
23199
+ return new Promise((resolve13) => {
23200
23200
  const pagerCmd = process.env.PAGER || "less";
23201
23201
  const pagerArgs = getPagerArgs(pagerCmd);
23202
23202
  try {
@@ -23206,20 +23206,20 @@ async function trySystemPager(content) {
23206
23206
  });
23207
23207
  const timeout = setTimeout(() => {
23208
23208
  pager.kill();
23209
- resolve12(false);
23209
+ resolve13(false);
23210
23210
  }, 30000);
23211
23211
  pager.stdin.write(content);
23212
23212
  pager.stdin.end();
23213
23213
  pager.on("close", (code2) => {
23214
23214
  clearTimeout(timeout);
23215
- resolve12(code2 === 0);
23215
+ resolve13(code2 === 0);
23216
23216
  });
23217
23217
  pager.on("error", () => {
23218
23218
  clearTimeout(timeout);
23219
- resolve12(false);
23219
+ resolve13(false);
23220
23220
  });
23221
23221
  } catch {
23222
- resolve12(false);
23222
+ resolve13(false);
23223
23223
  }
23224
23224
  });
23225
23225
  }
@@ -23246,16 +23246,16 @@ async function basicPager(content) {
23246
23246
  break;
23247
23247
  }
23248
23248
  const remaining = lines.length - currentLine;
23249
- await new Promise((resolve12) => {
23249
+ await new Promise((resolve13) => {
23250
23250
  rl.question(`-- More (${remaining} lines) [Enter/q] --`, (answer) => {
23251
23251
  if (answer.toLowerCase() === "q") {
23252
23252
  rl.close();
23253
23253
  process.exitCode = 0;
23254
- resolve12();
23254
+ resolve13();
23255
23255
  return;
23256
23256
  }
23257
23257
  process.stdout.write("\x1B[1A\x1B[2K");
23258
- resolve12();
23258
+ resolve13();
23259
23259
  });
23260
23260
  });
23261
23261
  }
@@ -30849,27 +30849,51 @@ class PromptsManager {
30849
30849
  init_logger();
30850
30850
 
30851
30851
  // src/shared/process-lock.ts
30852
+ init_logger();
30852
30853
  var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
30853
30854
  import { mkdir as mkdir8 } from "node:fs/promises";
30854
30855
  import os2 from "node:os";
30855
30856
  import { join as join29 } from "node:path";
30856
30857
  var LOCK_CONFIG = {
30857
- stale: 300000,
30858
+ stale: 60000,
30858
30859
  retries: 0
30859
30860
  };
30861
+ var activeLocks = new Set;
30862
+ var cleanupRegistered = false;
30860
30863
  function getLocksDir() {
30861
30864
  return join29(os2.homedir(), ".claudekit", "locks");
30862
30865
  }
30866
+ function cleanupLocks() {
30867
+ for (const name of activeLocks) {
30868
+ try {
30869
+ const lockPath = join29(getLocksDir(), `${name}.lock`);
30870
+ import_proper_lockfile.default.unlockSync(lockPath, { realpath: false });
30871
+ } catch {
30872
+ try {
30873
+ logger.verbose(`Failed to cleanup lock: ${name}`);
30874
+ } catch {}
30875
+ }
30876
+ }
30877
+ activeLocks.clear();
30878
+ }
30879
+ function registerCleanupHandlers() {
30880
+ if (cleanupRegistered)
30881
+ return;
30882
+ cleanupRegistered = true;
30883
+ process.on("exit", cleanupLocks);
30884
+ }
30863
30885
  async function ensureLocksDir() {
30864
30886
  const lockDir = getLocksDir();
30865
30887
  await mkdir8(lockDir, { recursive: true });
30866
30888
  }
30867
30889
  async function withProcessLock(lockName, fn) {
30890
+ registerCleanupHandlers();
30868
30891
  await ensureLocksDir();
30869
30892
  const lockPath = join29(getLocksDir(), `${lockName}.lock`);
30870
30893
  let release;
30871
30894
  try {
30872
30895
  release = await import_proper_lockfile.default.lock(lockPath, { ...LOCK_CONFIG, realpath: false });
30896
+ activeLocks.add(lockName);
30873
30897
  return await fn();
30874
30898
  } catch (e2) {
30875
30899
  const error = e2;
@@ -30884,6 +30908,7 @@ Wait for it to complete or remove lock: ${lockPath}`);
30884
30908
  if (release) {
30885
30909
  await release();
30886
30910
  }
30911
+ activeLocks.delete(lockName);
30887
30912
  }
30888
30913
  }
30889
30914
 
@@ -47943,7 +47968,8 @@ async function initCommand(options2) {
47943
47968
  return;
47944
47969
  }
47945
47970
  logger.error(error instanceof Error ? error.message : "Unknown error occurred");
47946
- process.exit(1);
47971
+ process.exitCode = 1;
47972
+ throw error;
47947
47973
  }
47948
47974
  }
47949
47975
  // src/commands/new/new-command.ts
@@ -49300,30 +49326,45 @@ var import_picocolors23 = __toESM(require_picocolors(), 1);
49300
49326
  // src/commands/uninstall/installation-detector.ts
49301
49327
  init_path_resolver();
49302
49328
  var import_fs_extra35 = __toESM(require_lib(), 1);
49329
+ function hasClaudeKitComponents(components) {
49330
+ return components.agents > 0 || components.commands > 0 || components.rules > 0 || components.skills > 0;
49331
+ }
49303
49332
  async function detectInstallations() {
49304
49333
  const installations = [];
49305
49334
  const setup = await getClaudeKitSetup(process.cwd());
49306
49335
  const isLocalSameAsGlobal = PathResolver.isLocalSameAsGlobal();
49307
- if (setup.project.path && setup.project.metadata && !isLocalSameAsGlobal) {
49308
- installations.push({
49309
- type: "local",
49310
- path: setup.project.path,
49311
- exists: await import_fs_extra35.pathExists(setup.project.path)
49312
- });
49336
+ if (setup.project.path && !isLocalSameAsGlobal) {
49337
+ const hasMetadata = setup.project.metadata !== null;
49338
+ const hasComponents = hasClaudeKitComponents(setup.project.components);
49339
+ if (hasMetadata || hasComponents) {
49340
+ installations.push({
49341
+ type: "local",
49342
+ path: setup.project.path,
49343
+ exists: await import_fs_extra35.pathExists(setup.project.path),
49344
+ hasMetadata,
49345
+ components: setup.project.components
49346
+ });
49347
+ }
49313
49348
  }
49314
- if (setup.global.path && setup.global.metadata) {
49315
- installations.push({
49316
- type: "global",
49317
- path: setup.global.path,
49318
- exists: await import_fs_extra35.pathExists(setup.global.path)
49319
- });
49349
+ if (setup.global.path) {
49350
+ const hasMetadata = setup.global.metadata !== null;
49351
+ const hasComponents = hasClaudeKitComponents(setup.global.components);
49352
+ if (hasMetadata || hasComponents) {
49353
+ installations.push({
49354
+ type: "global",
49355
+ path: setup.global.path,
49356
+ exists: await import_fs_extra35.pathExists(setup.global.path),
49357
+ hasMetadata,
49358
+ components: setup.global.components
49359
+ });
49360
+ }
49320
49361
  }
49321
49362
  return installations.filter((i) => i.exists);
49322
49363
  }
49323
49364
 
49324
49365
  // src/commands/uninstall/removal-handler.ts
49325
49366
  import { readdirSync as readdirSync4, rmSync as rmSync5 } from "node:fs";
49326
- import { join as join81 } from "node:path";
49367
+ import { join as join81, resolve as resolve12, sep as sep3 } from "node:path";
49327
49368
  init_logger();
49328
49369
  var import_fs_extra36 = __toESM(require_lib(), 1);
49329
49370
 
@@ -49449,28 +49490,72 @@ function displayDryRunPreview(analysis, installationType) {
49449
49490
  }
49450
49491
 
49451
49492
  // src/commands/uninstall/removal-handler.ts
49493
+ async function isDirectory(filePath) {
49494
+ try {
49495
+ const stats = await import_fs_extra36.lstat(filePath);
49496
+ return stats.isDirectory();
49497
+ } catch {
49498
+ logger.debug(`Failed to check if path is directory: ${filePath}`);
49499
+ return false;
49500
+ }
49501
+ }
49502
+ async function isPathSafeToRemove(filePath, baseDir) {
49503
+ try {
49504
+ const resolvedPath = resolve12(filePath);
49505
+ const resolvedBase = resolve12(baseDir);
49506
+ if (!resolvedPath.startsWith(resolvedBase + sep3) && resolvedPath !== resolvedBase) {
49507
+ logger.debug(`Path outside installation directory: ${filePath}`);
49508
+ return false;
49509
+ }
49510
+ const stats = await import_fs_extra36.lstat(filePath);
49511
+ if (stats.isSymbolicLink()) {
49512
+ const realPath = await import_fs_extra36.realpath(filePath);
49513
+ const resolvedReal = resolve12(realPath);
49514
+ if (!resolvedReal.startsWith(resolvedBase + sep3) && resolvedReal !== resolvedBase) {
49515
+ logger.debug(`Symlink points outside installation directory: ${filePath} -> ${realPath}`);
49516
+ return false;
49517
+ }
49518
+ }
49519
+ return true;
49520
+ } catch {
49521
+ logger.debug(`Failed to validate path safety: ${filePath}`);
49522
+ return false;
49523
+ }
49524
+ }
49452
49525
  async function removeInstallations(installations, options2) {
49453
49526
  for (const installation of installations) {
49454
49527
  const analysis = await analyzeInstallation(installation, options2.forceOverwrite, options2.kit);
49455
49528
  if (options2.dryRun) {
49456
49529
  const label = options2.kit ? `${installation.type} (${options2.kit} kit)` : installation.type;
49457
- displayDryRunPreview(analysis, label);
49530
+ const legacyLabel2 = !installation.hasMetadata ? " [legacy]" : "";
49531
+ displayDryRunPreview(analysis, `${label}${legacyLabel2}`);
49458
49532
  if (analysis.remainingKits.length > 0) {
49459
49533
  log.info(`Remaining kits after uninstall: ${analysis.remainingKits.join(", ")}`);
49460
49534
  }
49535
+ if (!installation.hasMetadata) {
49536
+ log.warn("Legacy installation - directories will be removed recursively");
49537
+ }
49461
49538
  continue;
49462
49539
  }
49463
49540
  const kitLabel = options2.kit ? ` ${options2.kit} kit` : "";
49464
- const spinner = createSpinner(`Removing ${installation.type}${kitLabel} ClaudeKit files...`).start();
49541
+ const legacyLabel = !installation.hasMetadata ? " (legacy)" : "";
49542
+ const spinner = createSpinner(`Removing ${installation.type}${kitLabel}${legacyLabel} ClaudeKit files...`).start();
49465
49543
  try {
49466
49544
  let removedCount = 0;
49467
49545
  let cleanedDirs = 0;
49468
49546
  for (const item of analysis.toDelete) {
49469
49547
  const filePath = join81(installation.path, item.path);
49470
- if (await import_fs_extra36.pathExists(filePath)) {
49471
- await import_fs_extra36.remove(filePath);
49472
- removedCount++;
49473
- logger.debug(`Removed: ${item.path}`);
49548
+ if (!await import_fs_extra36.pathExists(filePath))
49549
+ continue;
49550
+ if (!await isPathSafeToRemove(filePath, installation.path)) {
49551
+ logger.debug(`Skipping unsafe path: ${item.path}`);
49552
+ continue;
49553
+ }
49554
+ const isDir = await isDirectory(filePath);
49555
+ await import_fs_extra36.remove(filePath);
49556
+ removedCount++;
49557
+ logger.debug(`Removed ${isDir ? "directory" : "file"}: ${item.path}`);
49558
+ if (!isDir) {
49474
49559
  cleanedDirs += await cleanupEmptyDirectories3(filePath, installation.path);
49475
49560
  }
49476
49561
  }
@@ -49502,11 +49587,34 @@ async function removeInstallations(installations, options2) {
49502
49587
 
49503
49588
  // src/commands/uninstall/uninstall-command.ts
49504
49589
  var prompts = new PromptsManager;
49590
+ function formatComponentSummary(inst) {
49591
+ const parts = [];
49592
+ if (inst.components.skills > 0)
49593
+ parts.push(`${inst.components.skills} skills`);
49594
+ if (inst.components.commands > 0)
49595
+ parts.push(`${inst.components.commands} commands`);
49596
+ if (inst.components.agents > 0)
49597
+ parts.push(`${inst.components.agents} agents`);
49598
+ if (inst.components.rules > 0)
49599
+ parts.push(`${inst.components.rules} rules`);
49600
+ return parts.length > 0 ? ` (${parts.join(", ")})` : "";
49601
+ }
49505
49602
  function displayInstallations(installations, scope) {
49506
49603
  prompts.intro("ClaudeKit Uninstaller");
49507
49604
  const scopeLabel = scope === "all" ? "all" : scope === "local" ? "local only" : "global only";
49508
- prompts.note(installations.map((i) => ` ${i.type === "local" ? "Local " : "Global"}: ${i.path}`).join(`
49605
+ const hasLegacy = installations.some((i) => !i.hasMetadata);
49606
+ const lines = installations.map((i) => {
49607
+ const typeLabel = i.type === "local" ? "Local " : "Global";
49608
+ const legacyTag = !i.hasMetadata ? import_picocolors23.default.yellow(" [legacy]") : "";
49609
+ const components = formatComponentSummary(i);
49610
+ return ` ${typeLabel}: ${i.path}${legacyTag}${components}`;
49611
+ });
49612
+ prompts.note(lines.join(`
49509
49613
  `), `Detected ClaudeKit installations (${scopeLabel})`);
49614
+ if (hasLegacy) {
49615
+ log.warn(import_picocolors23.default.yellow(`[!] Legacy installation(s) detected without metadata.json.
49616
+ `) + import_picocolors23.default.yellow(" These files cannot be selectively removed. Full directory cleanup will be performed."));
49617
+ }
49510
49618
  log.warn("[!] This will permanently delete ClaudeKit files from the above paths.");
49511
49619
  }
49512
49620
  async function promptScope(installations) {
@@ -49787,7 +49895,7 @@ var import_fs_extra37 = __toESM(require_lib(), 1);
49787
49895
  // package.json
49788
49896
  var package_default = {
49789
49897
  name: "claudekit-cli",
49790
- version: "3.32.0",
49898
+ version: "3.32.1-dev.1",
49791
49899
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
49792
49900
  type: "module",
49793
49901
  repository: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudekit-cli",
3
- "version": "3.32.0",
3
+ "version": "3.32.1-dev.1",
4
4
  "description": "CLI tool for bootstrapping and updating ClaudeKit projects",
5
5
  "type": "module",
6
6
  "repository": {