claudekit-cli 3.34.1-dev.3 → 3.34.1-dev.5

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 +155 -28
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -44585,10 +44585,38 @@ var init_pnpm_detector = __esm(() => {
44585
44585
  });
44586
44586
 
44587
44587
  // src/domains/installation/package-managers/detection-core.ts
44588
- import { existsSync as existsSync17 } from "node:fs";
44588
+ import { existsSync as existsSync17, realpathSync } from "node:fs";
44589
44589
  import { chmod as chmod2, mkdir as mkdir6, readFile as readFile13, writeFile as writeFile7 } from "node:fs/promises";
44590
44590
  import { platform as platform4 } from "node:os";
44591
- import { join as join23 } from "node:path";
44591
+ import { join as join23, sep } from "node:path";
44592
+ function detectFromBinaryPath() {
44593
+ try {
44594
+ const scriptPath = process.argv[1];
44595
+ if (!scriptPath)
44596
+ return "unknown";
44597
+ let resolvedPath;
44598
+ try {
44599
+ resolvedPath = realpathSync(scriptPath);
44600
+ } catch {
44601
+ resolvedPath = scriptPath;
44602
+ }
44603
+ const normalized = resolvedPath.split(sep).join("/").toLowerCase();
44604
+ logger.verbose(`Binary path resolved: ${normalized}`);
44605
+ if (normalized.includes("/.bun/install/") || normalized.includes("/bun/install/global/")) {
44606
+ return "bun";
44607
+ }
44608
+ if (normalized.includes("/pnpm/global/") || normalized.includes("/.local/share/pnpm/")) {
44609
+ return "pnpm";
44610
+ }
44611
+ if (normalized.includes("/yarn/global/") || normalized.includes("/.config/yarn/")) {
44612
+ return "yarn";
44613
+ }
44614
+ if (normalized.includes("/npm/node_modules/") || normalized.includes("/usr/local/lib/node_modules/") || normalized.includes("/usr/lib/node_modules/") || normalized.includes("/opt/homebrew/lib/node_modules/") || normalized.includes("/.nvm/versions/node/") || normalized.includes("/n/versions/node/") || normalized.includes("/appdata/roaming/npm/")) {
44615
+ return "npm";
44616
+ }
44617
+ } catch {}
44618
+ return "unknown";
44619
+ }
44592
44620
  function detectFromEnv() {
44593
44621
  const userAgent = process.env.npm_config_user_agent;
44594
44622
  if (userAgent) {
@@ -44605,13 +44633,14 @@ function detectFromEnv() {
44605
44633
  const execPath = process.env.npm_execpath;
44606
44634
  if (execPath) {
44607
44635
  logger.debug(`Detected exec path: ${execPath}`);
44608
- if (execPath.includes("bun"))
44636
+ const normalizedExec = execPath.replace(/\\/g, "/").toLowerCase();
44637
+ if (/\/bun([/.]|$)/.test(normalizedExec) || normalizedExec.startsWith("bun"))
44609
44638
  return "bun";
44610
- if (execPath.includes("yarn"))
44639
+ if (/\/yarn([/.]|$)/.test(normalizedExec) || normalizedExec.startsWith("yarn"))
44611
44640
  return "yarn";
44612
- if (execPath.includes("pnpm"))
44641
+ if (/\/pnpm([/.]|$)/.test(normalizedExec) || normalizedExec.startsWith("pnpm"))
44613
44642
  return "pnpm";
44614
- if (execPath.includes("npm"))
44643
+ if (/\/npm([/.]|$)/.test(normalizedExec) || normalizedExec.startsWith("npm"))
44615
44644
  return "npm";
44616
44645
  }
44617
44646
  return "unknown";
@@ -44629,8 +44658,8 @@ async function readCachedPm() {
44629
44658
  return null;
44630
44659
  }
44631
44660
  const age = Date.now() - data.detectedAt;
44632
- if (age > CACHE_TTL) {
44633
- logger.debug("Cache expired, will re-detect");
44661
+ if (age < 0 || age > CACHE_TTL) {
44662
+ logger.debug(age < 0 ? "Cache timestamp in future, ignoring" : "Cache expired, will re-detect");
44634
44663
  return null;
44635
44664
  }
44636
44665
  const validPms = ["npm", "bun", "yarn", "pnpm"];
@@ -44739,6 +44768,18 @@ var init_package_manager_detector = __esm(() => {
44739
44768
  PackageManagerDetector = class PackageManagerDetector {
44740
44769
  static async detect() {
44741
44770
  logger.verbose("PackageManagerDetector: Starting detection");
44771
+ const binaryPm = detectFromBinaryPath();
44772
+ if (binaryPm !== "unknown") {
44773
+ logger.verbose(`PackageManagerDetector: Detected from binary path: ${binaryPm}`);
44774
+ const cachedPm2 = await readCachedPm();
44775
+ if (cachedPm2 && cachedPm2 !== binaryPm) {
44776
+ logger.verbose(`PackageManagerDetector: Cache says ${cachedPm2}, binary says ${binaryPm} — updating cache`);
44777
+ await saveCachedPm(binaryPm, PackageManagerDetector.getVersion);
44778
+ } else if (!cachedPm2) {
44779
+ await saveCachedPm(binaryPm, PackageManagerDetector.getVersion);
44780
+ }
44781
+ return binaryPm;
44782
+ }
44742
44783
  const envPm = detectFromEnv();
44743
44784
  if (envPm !== "unknown") {
44744
44785
  logger.verbose(`PackageManagerDetector: Detected from env: ${envPm}`);
@@ -45236,7 +45277,7 @@ var package_default;
45236
45277
  var init_package = __esm(() => {
45237
45278
  package_default = {
45238
45279
  name: "claudekit-cli",
45239
- version: "3.34.1-dev.3",
45280
+ version: "3.34.1-dev.5",
45240
45281
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
45241
45282
  type: "module",
45242
45283
  repository: {
@@ -45551,12 +45592,15 @@ Run 'ck update' to install`, "Update Check");
45551
45592
  s.stop("Update failed");
45552
45593
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
45553
45594
  if (errorMessage.includes("EACCES") || errorMessage.includes("EPERM") || errorMessage.includes("permission") || errorMessage.includes("Access is denied")) {
45554
- throw new CliUpdateError(`Permission denied. Try: sudo ${updateCmd}
45595
+ const permHint = pm === "npm" ? `
45555
45596
 
45556
- Or fix npm permissions: https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally`);
45597
+ Or fix npm permissions: https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally` : "";
45598
+ const isWindows3 = process.platform === "win32";
45599
+ const elevationHint = isWindows3 ? `Run your terminal as Administrator and retry: ${updateCmd}` : `sudo ${updateCmd}`;
45600
+ throw new CliUpdateError(`Permission denied. Try: ${elevationHint}${permHint}`);
45557
45601
  }
45558
45602
  logger.error(`Update failed: ${errorMessage}`);
45559
- logger.info("Try running: npm install -g claudekit-cli@latest");
45603
+ logger.info(`Try running: ${updateCmd}`);
45560
45604
  throw new CliUpdateError(`Update failed: ${errorMessage}
45561
45605
 
45562
45606
  Manual update: ${updateCmd}`);
@@ -66041,7 +66085,7 @@ init_types2();
66041
66085
 
66042
66086
  // src/domains/installation/utils/path-security.ts
66043
66087
  init_types2();
66044
- import { lstatSync as lstatSync2, realpathSync } from "node:fs";
66088
+ import { lstatSync as lstatSync2, realpathSync as realpathSync2 } from "node:fs";
66045
66089
  import { relative as relative5, resolve as resolve10 } from "node:path";
66046
66090
  var MAX_EXTRACTION_SIZE = 500 * 1024 * 1024;
66047
66091
  function isPathSafe(basePath, targetPath) {
@@ -66049,7 +66093,7 @@ function isPathSafe(basePath, targetPath) {
66049
66093
  try {
66050
66094
  const stat10 = lstatSync2(targetPath);
66051
66095
  if (stat10.isSymbolicLink()) {
66052
- const realTarget = realpathSync(targetPath);
66096
+ const realTarget = realpathSync2(targetPath);
66053
66097
  if (!realTarget.startsWith(resolvedBase)) {
66054
66098
  return false;
66055
66099
  }
@@ -73832,7 +73876,7 @@ import { join as join77 } from "node:path";
73832
73876
 
73833
73877
  // src/domains/installation/deletion-handler.ts
73834
73878
  import { existsSync as existsSync35, lstatSync as lstatSync3, readdirSync as readdirSync2, rmSync as rmSync2, rmdirSync, unlinkSync as unlinkSync3 } from "node:fs";
73835
- import { dirname as dirname14, join as join64, relative as relative7, resolve as resolve12, sep } from "node:path";
73879
+ import { dirname as dirname14, join as join64, relative as relative7, resolve as resolve12, sep as sep2 } from "node:path";
73836
73880
 
73837
73881
  // src/services/file-operations/manifest/manifest-reader.ts
73838
73882
  init_metadata_migration();
@@ -74071,7 +74115,7 @@ function cleanupEmptyDirectories(filePath, claudeDir2) {
74071
74115
  function deletePath(fullPath, claudeDir2) {
74072
74116
  const normalizedPath = resolve12(fullPath);
74073
74117
  const normalizedClaudeDir = resolve12(claudeDir2);
74074
- if (!normalizedPath.startsWith(`${normalizedClaudeDir}${sep}`) && normalizedPath !== normalizedClaudeDir) {
74118
+ if (!normalizedPath.startsWith(`${normalizedClaudeDir}${sep2}`) && normalizedPath !== normalizedClaudeDir) {
74075
74119
  throw new Error(`Path traversal detected: ${fullPath}`);
74076
74120
  }
74077
74121
  try {
@@ -74145,7 +74189,7 @@ async function handleDeletions(sourceMetadata, claudeDir2) {
74145
74189
  const fullPath = join64(claudeDir2, path11);
74146
74190
  const normalizedPath = resolve12(fullPath);
74147
74191
  const normalizedClaudeDir = resolve12(claudeDir2);
74148
- if (!normalizedPath.startsWith(`${normalizedClaudeDir}${sep}`)) {
74192
+ if (!normalizedPath.startsWith(`${normalizedClaudeDir}${sep2}`)) {
74149
74193
  logger.warning(`Skipping invalid path: ${path11}`);
74150
74194
  result.errors.push(path11);
74151
74195
  continue;
@@ -75168,8 +75212,8 @@ var path11 = {
75168
75212
  win32: { sep: "\\" },
75169
75213
  posix: { sep: "/" }
75170
75214
  };
75171
- var sep2 = defaultPlatform === "win32" ? path11.win32.sep : path11.posix.sep;
75172
- minimatch.sep = sep2;
75215
+ var sep3 = defaultPlatform === "win32" ? path11.win32.sep : path11.posix.sep;
75216
+ minimatch.sep = sep3;
75173
75217
  var GLOBSTAR = Symbol("globstar **");
75174
75218
  minimatch.GLOBSTAR = GLOBSTAR;
75175
75219
  var qmark2 = "[^/]";
@@ -75850,6 +75894,9 @@ class FileScanner {
75850
75894
  }
75851
75895
  }
75852
75896
 
75897
+ // src/domains/installation/merger/settings-processor.ts
75898
+ import { execSync as execSync4 } from "node:child_process";
75899
+
75853
75900
  // src/domains/config/installed-settings-tracker.ts
75854
75901
  init_shared();
75855
75902
  import { existsSync as existsSync36 } from "node:fs";
@@ -75948,14 +75995,17 @@ init_settings_merger();
75948
75995
  init_environment();
75949
75996
  init_logger();
75950
75997
  var import_fs_extra11 = __toESM(require_lib3(), 1);
75998
+ var import_semver2 = __toESM(require_semver2(), 1);
75951
75999
 
75952
76000
  class SettingsProcessor {
76001
+ static MIN_TEAM_HOOKS_VERSION = "2.1.33";
75953
76002
  isGlobal = false;
75954
76003
  forceOverwriteSettings = false;
75955
76004
  projectDir = "";
75956
76005
  kitName = "engineer";
75957
76006
  tracker = null;
75958
76007
  installingKit;
76008
+ cachedVersion = undefined;
75959
76009
  setGlobalFlag(isGlobal) {
75960
76010
  this.isGlobal = isGlobal;
75961
76011
  }
@@ -76001,8 +76051,9 @@ class SettingsProcessor {
76001
76051
  } else {
76002
76052
  const formattedContent = this.formatJsonContent(transformedSource);
76003
76053
  await import_fs_extra11.writeFile(destFile, formattedContent, "utf-8");
76054
+ let parsedSettings;
76004
76055
  try {
76005
- const parsedSettings = JSON.parse(formattedContent);
76056
+ parsedSettings = JSON.parse(formattedContent);
76006
76057
  if (this.forceOverwriteSettings && destExists) {
76007
76058
  logger.debug("Force overwrite enabled, replaced settings.json completely");
76008
76059
  if (this.tracker) {
@@ -76011,6 +76062,7 @@ class SettingsProcessor {
76011
76062
  }
76012
76063
  await this.trackInstalledSettings(parsedSettings);
76013
76064
  } catch {}
76065
+ await this.injectTeamHooksIfSupported(destFile, parsedSettings);
76014
76066
  }
76015
76067
  } catch (error) {
76016
76068
  logger.error(`Failed to process settings.json: ${error}`);
@@ -76071,6 +76123,7 @@ class SettingsProcessor {
76071
76123
  }
76072
76124
  await SettingsMerger.writeSettingsFile(destFile, mergeResult.merged);
76073
76125
  logger.success("Merged settings.json (user customizations preserved)");
76126
+ await this.injectTeamHooksIfSupported(destFile, mergeResult.merged);
76074
76127
  }
76075
76128
  async trackInstalledSettings(settings) {
76076
76129
  if (!this.tracker)
@@ -76142,6 +76195,80 @@ class SettingsProcessor {
76142
76195
  }
76143
76196
  return transformed;
76144
76197
  }
76198
+ detectClaudeCodeVersion() {
76199
+ if (this.cachedVersion !== undefined)
76200
+ return this.cachedVersion;
76201
+ try {
76202
+ const output2 = execSync4("claude --version", {
76203
+ encoding: "utf-8",
76204
+ timeout: 5000,
76205
+ stdio: ["ignore", "pipe", "ignore"]
76206
+ });
76207
+ const match2 = output2.match(/(\d+\.\d+\.\d+)/);
76208
+ this.cachedVersion = match2 ? match2[1] : null;
76209
+ } catch {
76210
+ this.cachedVersion = null;
76211
+ }
76212
+ return this.cachedVersion;
76213
+ }
76214
+ isVersionAtLeast(version, minimum) {
76215
+ const coerced = import_semver2.default.coerce(version);
76216
+ if (!coerced)
76217
+ return false;
76218
+ return import_semver2.default.gte(coerced, minimum);
76219
+ }
76220
+ async injectTeamHooksIfSupported(destFile, existingSettings) {
76221
+ const version = this.detectClaudeCodeVersion();
76222
+ if (!version) {
76223
+ logger.debug("Claude Code version not detected, skipping team hooks injection");
76224
+ return;
76225
+ }
76226
+ if (!this.isVersionAtLeast(version, SettingsProcessor.MIN_TEAM_HOOKS_VERSION)) {
76227
+ logger.debug(`Claude Code ${version} does not support team hooks (requires >= 2.1.33), skipping injection`);
76228
+ return;
76229
+ }
76230
+ logger.debug(`Claude Code ${version} detected, checking team hooks`);
76231
+ const settings = existingSettings ?? await SettingsMerger.readSettingsFile(destFile);
76232
+ if (!settings) {
76233
+ logger.warning("Failed to read settings file for team hooks injection");
76234
+ return;
76235
+ }
76236
+ const prefix = this.isGlobal ? isWindows() ? "%USERPROFILE%" : "$HOME" : isWindows() ? "%CLAUDE_PROJECT_DIR%" : "$CLAUDE_PROJECT_DIR";
76237
+ if (!settings.hooks) {
76238
+ settings.hooks = {};
76239
+ }
76240
+ let injected = false;
76241
+ const installedSettings = this.tracker ? await this.tracker.loadInstalledSettings() : { hooks: [], mcpServers: [] };
76242
+ const teamHooks = [
76243
+ { event: "TaskCompleted", handler: "task-completed-handler.cjs" },
76244
+ { event: "TeammateIdle", handler: "teammate-idle-handler.cjs" }
76245
+ ];
76246
+ for (const { event, handler } of teamHooks) {
76247
+ const hookCommand = `node ${prefix}/.claude/hooks/${handler}`;
76248
+ const eventHooks = settings.hooks[event];
76249
+ if (eventHooks && eventHooks.length > 0)
76250
+ continue;
76251
+ if (this.tracker?.wasHookInstalled(hookCommand, installedSettings)) {
76252
+ logger.debug(`Skipping ${event} hook injection (previously removed by user)`);
76253
+ continue;
76254
+ }
76255
+ settings.hooks[event] = [{ hooks: [{ type: "command", command: hookCommand }] }];
76256
+ logger.info(`Injected ${event} hook`);
76257
+ injected = true;
76258
+ if (this.tracker) {
76259
+ this.tracker.trackHook(hookCommand, installedSettings);
76260
+ }
76261
+ }
76262
+ if (injected) {
76263
+ await SettingsMerger.writeSettingsFile(destFile, settings);
76264
+ if (this.tracker) {
76265
+ await this.tracker.saveInstalledSettings(installedSettings);
76266
+ }
76267
+ logger.success("Team hooks injected successfully");
76268
+ } else {
76269
+ logger.debug("Team hooks already present, no injection needed");
76270
+ }
76271
+ }
76145
76272
  }
76146
76273
 
76147
76274
  // src/domains/installation/merger/copy-executor.ts
@@ -81492,10 +81619,10 @@ init_dist2();
81492
81619
 
81493
81620
  // src/commands/setup/phases/preflight-check.ts
81494
81621
  init_dist2();
81495
- import { execSync as execSync4 } from "node:child_process";
81622
+ import { execSync as execSync5 } from "node:child_process";
81496
81623
  function isGhInstalled() {
81497
81624
  try {
81498
- execSync4("gh --version", { stdio: "pipe" });
81625
+ execSync5("gh --version", { stdio: "pipe" });
81499
81626
  return true;
81500
81627
  } catch {
81501
81628
  return false;
@@ -81503,7 +81630,7 @@ function isGhInstalled() {
81503
81630
  }
81504
81631
  function checkGhAuth() {
81505
81632
  try {
81506
- execSync4("gh auth status", { stdio: "pipe" });
81633
+ execSync5("gh auth status", { stdio: "pipe" });
81507
81634
  return true;
81508
81635
  } catch {
81509
81636
  return false;
@@ -81520,11 +81647,11 @@ function checkNodeVersion() {
81520
81647
  }
81521
81648
  function checkPython() {
81522
81649
  try {
81523
- execSync4("python3 --version", { stdio: "pipe" });
81650
+ execSync5("python3 --version", { stdio: "pipe" });
81524
81651
  return true;
81525
81652
  } catch {
81526
81653
  try {
81527
- execSync4("python --version", { stdio: "pipe" });
81654
+ execSync5("python --version", { stdio: "pipe" });
81528
81655
  return true;
81529
81656
  } catch {
81530
81657
  return false;
@@ -82247,7 +82374,7 @@ async function detectInstallations() {
82247
82374
 
82248
82375
  // src/commands/uninstall/removal-handler.ts
82249
82376
  import { readdirSync as readdirSync5, rmSync as rmSync5 } from "node:fs";
82250
- import { join as join99, resolve as resolve19, sep as sep3 } from "node:path";
82377
+ import { join as join99, resolve as resolve19, sep as sep4 } from "node:path";
82251
82378
  init_logger();
82252
82379
  init_safe_prompts();
82253
82380
  init_safe_spinner();
@@ -82390,7 +82517,7 @@ async function isPathSafeToRemove(filePath, baseDir) {
82390
82517
  try {
82391
82518
  const resolvedPath = resolve19(filePath);
82392
82519
  const resolvedBase = resolve19(baseDir);
82393
- if (!resolvedPath.startsWith(resolvedBase + sep3) && resolvedPath !== resolvedBase) {
82520
+ if (!resolvedPath.startsWith(resolvedBase + sep4) && resolvedPath !== resolvedBase) {
82394
82521
  logger.debug(`Path outside installation directory: ${filePath}`);
82395
82522
  return false;
82396
82523
  }
@@ -82398,7 +82525,7 @@ async function isPathSafeToRemove(filePath, baseDir) {
82398
82525
  if (stats.isSymbolicLink()) {
82399
82526
  const realPath = await import_fs_extra37.realpath(filePath);
82400
82527
  const resolvedReal = resolve19(realPath);
82401
- if (!resolvedReal.startsWith(resolvedBase + sep3) && resolvedReal !== resolvedBase) {
82528
+ if (!resolvedReal.startsWith(resolvedBase + sep4) && resolvedReal !== resolvedBase) {
82402
82529
  logger.debug(`Symlink points outside installation directory: ${filePath} -> ${realPath}`);
82403
82530
  return false;
82404
82531
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudekit-cli",
3
- "version": "3.34.1-dev.3",
3
+ "version": "3.34.1-dev.5",
4
4
  "description": "CLI tool for bootstrapping and updating ClaudeKit projects",
5
5
  "type": "module",
6
6
  "repository": {