claudekit-cli 3.41.4-dev.51 → 3.41.4-dev.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli-manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "3.41.4-dev.51",
3
- "generatedAt": "2026-04-24T21:03:36.506Z",
2
+ "version": "3.41.4-dev.53",
3
+ "generatedAt": "2026-04-24T21:32:46.232Z",
4
4
  "commands": {
5
5
  "agents": {
6
6
  "name": "agents",
package/dist/index.js CHANGED
@@ -53623,6 +53623,24 @@ function sanitizeOutput(obj, rules) {
53623
53623
  return result;
53624
53624
  }
53625
53625
 
53626
+ /**
53627
+ * True when the given event's scrub rules allow a permissionDecision of "deny".
53628
+ * Used to translate Claude Code's exit-code protocol (exit 2 = block) into
53629
+ * Codex's JSON protocol ({permissionDecision: "deny"}).
53630
+ */
53631
+ function eventSupportsDeny(rules) {
53632
+ if (!rules || rules.allowedPermissionValues === null) return false;
53633
+ return rules.allowedPermissionValues.indexOf("deny") !== -1;
53634
+ }
53635
+
53636
+ function emitDeny(reason) {
53637
+ process.stdout.write(JSON.stringify({
53638
+ permissionDecision: "deny",
53639
+ reason: reason && reason.length > 0 ? reason : "Hook blocked this operation",
53640
+ }));
53641
+ process.exit(0);
53642
+ }
53643
+
53626
53644
  function main() {
53627
53645
  // Collect stdin
53628
53646
  const stdinChunks = [];
@@ -53630,6 +53648,7 @@ function main() {
53630
53648
  process.stdin.on("end", () => {
53631
53649
  const stdinData = Buffer.concat(stdinChunks).toString("utf8");
53632
53650
  const event = getEventFromStdin(stdinData);
53651
+ const rules = event && SCRUB_RULES[event];
53633
53652
 
53634
53653
  // Spawn original hook with same stdin/env
53635
53654
  const result = spawnSync(process.execPath, [ORIGINAL_HOOK], {
@@ -53645,33 +53664,57 @@ function main() {
53645
53664
  process.exit(1);
53646
53665
  }
53647
53666
 
53648
- if (result.stderr) {
53649
- process.stderr.write(result.stderr);
53650
- }
53651
-
53652
- const rawOutput = result.stdout || "";
53667
+ const stderrText = (result.stderr || "").toString();
53668
+ const rawOutput = (result.stdout || "").toString();
53669
+ const exitCode = result.status ?? 1;
53670
+ // Claude Code protocol: exit 2 + stderr = block. Codex expects JSON instead.
53671
+ // Translate only for events where the Codex capability table allows "deny".
53672
+ const isBlockSignal = exitCode === 2 && eventSupportsDeny(rules);
53653
53673
 
53654
- // If the hook produced no JSON output, pass through as-is
53674
+ // No stdout: either silent allow (exit 0) or Claude-style block (exit 2).
53655
53675
  if (!rawOutput.trim()) {
53656
- process.exit(result.status ?? 1);
53676
+ if (isBlockSignal) {
53677
+ return emitDeny(stderrText.trim());
53678
+ }
53679
+ // Non-block failure or plain allow: forward stderr and pass exit code through.
53680
+ if (stderrText) process.stderr.write(stderrText);
53681
+ process.exit(exitCode);
53657
53682
  }
53658
53683
 
53659
- // Try to parse and sanitize JSON output
53684
+ // Try to parse stdout as JSON.
53660
53685
  let parsed;
53661
53686
  try {
53662
53687
  parsed = JSON.parse(rawOutput);
53663
53688
  } catch {
53664
- // Not valid JSON pass through unchanged (Codex may handle it)
53689
+ // Non-JSON stdout. If this is a Claude block signal, treat the stdout
53690
+ // (or stderr) as the deny reason. Otherwise forward unchanged.
53691
+ if (isBlockSignal) {
53692
+ const reason = rawOutput.trim() || stderrText.trim();
53693
+ return emitDeny(reason);
53694
+ }
53695
+ if (stderrText) process.stderr.write(stderrText);
53665
53696
  process.stdout.write(rawOutput);
53666
- process.exit(result.status ?? 1);
53697
+ process.exit(exitCode);
53667
53698
  }
53668
53699
 
53700
+ // Forward stderr for JSON-emitting hooks (diagnostic output). We still
53701
+ // scrub and re-emit the JSON to Codex's stdout.
53702
+ if (stderrText) process.stderr.write(stderrText);
53703
+
53669
53704
  // Apply scrub rules for the detected event
53670
- const rules = event && SCRUB_RULES[event];
53671
53705
  const sanitized = rules ? sanitizeOutput(parsed, rules) : parsed;
53672
53706
 
53707
+ // If the hook signalled block via exit 2 but didn't emit a deny decision
53708
+ // in the JSON, translate — otherwise Codex ignores the exit code.
53709
+ if (isBlockSignal && (!sanitized || sanitized.permissionDecision !== "deny")) {
53710
+ return emitDeny(stderrText.trim());
53711
+ }
53712
+
53673
53713
  process.stdout.write(JSON.stringify(sanitized));
53674
- process.exit(result.status ?? 1);
53714
+ // When the hook already emitted a valid deny JSON but also exited 2,
53715
+ // exit 0 so Codex treats the deny as authoritative (consistent with
53716
+ // emitDeny's exit-0 contract).
53717
+ process.exit(isBlockSignal ? 0 : exitCode);
53675
53718
  });
53676
53719
  }
53677
53720
 
@@ -53811,7 +53854,7 @@ var init_gemini_hook_event_map = __esm(() => {
53811
53854
  import { existsSync as existsSync25 } from "node:fs";
53812
53855
  import { mkdir as mkdir11, readFile as readFile21, rename as rename7, rm as rm5, writeFile as writeFile12 } from "node:fs/promises";
53813
53856
  import { homedir as homedir27 } from "node:os";
53814
- import { basename as basename11, dirname as dirname12, isAbsolute as isAbsolute5, join as join39, resolve as resolve15 } from "node:path";
53857
+ import { basename as basename11, dirname as dirname12, join as join39, resolve as resolve15 } from "node:path";
53815
53858
  function validateHooksSectionShape(value) {
53816
53859
  if (!value || typeof value !== "object" || Array.isArray(value)) {
53817
53860
  return "hooks must be a non-null object";
@@ -53974,33 +54017,31 @@ async function mergeHooksIntoSettings(targetSettingsPath, newHooks) {
53974
54017
  }
53975
54018
  return { backupPath };
53976
54019
  }
53977
- function extractLeadingAbsolutePath(command) {
53978
- const trimmed = command.trim();
53979
- if (trimmed.length === 0)
53980
- return null;
53981
- const firstToken = trimmed.split(/\s+/)[0];
53982
- if (!firstToken)
53983
- return null;
53984
- if (!isAbsolute5(firstToken))
53985
- return null;
53986
- return firstToken;
53987
- }
53988
54020
  function isCkManagedHookPath(absPath) {
53989
54021
  const normalized = absPath.replace(/\\/g, "/");
53990
54022
  return normalized.includes("/.claude/hooks/") || normalized.includes("/.codex/hooks/") || normalized.includes("/.gemini/hooks/");
53991
54023
  }
54024
+ function extractAbsolutePaths(command) {
54025
+ const matches = [];
54026
+ const pathPattern = /(?:^|[\s"'(])(\/[^\s"'()]+)/g;
54027
+ let match = pathPattern.exec(command);
54028
+ while (match !== null) {
54029
+ matches.push(match[1]);
54030
+ match = pathPattern.exec(command);
54031
+ }
54032
+ return matches;
54033
+ }
53992
54034
  function pruneStaleFileHooks(existing) {
53993
54035
  const result = {};
53994
54036
  for (const [event, groups] of Object.entries(existing)) {
53995
54037
  const prunedGroups = [];
53996
54038
  for (const group of groups) {
53997
54039
  const survivingHooks = group.hooks.filter((h2) => {
53998
- const path3 = extractLeadingAbsolutePath(h2.command);
53999
- if (path3 === null)
54000
- return true;
54001
- if (!isCkManagedHookPath(path3))
54040
+ const paths = extractAbsolutePaths(h2.command);
54041
+ const ckPaths = paths.filter(isCkManagedHookPath);
54042
+ if (ckPaths.length === 0)
54002
54043
  return true;
54003
- return existsSync25(path3);
54044
+ return ckPaths.some((p) => existsSync25(p));
54004
54045
  });
54005
54046
  if (survivingHooks.length > 0) {
54006
54047
  prunedGroups.push({ ...group, hooks: survivingHooks });
@@ -57777,13 +57818,13 @@ var init_plan_scanner = () => {};
57777
57818
 
57778
57819
  // src/domains/plan-parser/plan-scope.ts
57779
57820
  import { homedir as homedir30 } from "node:os";
57780
- import { isAbsolute as isAbsolute6, join as join44, relative as relative9, resolve as resolve18 } from "node:path";
57821
+ import { isAbsolute as isAbsolute5, join as join44, relative as relative9, resolve as resolve18 } from "node:path";
57781
57822
  function resolveConfiguredDir(configuredPath, baseDir) {
57782
57823
  const trimmed = configuredPath?.trim();
57783
57824
  if (!trimmed) {
57784
57825
  return join44(baseDir, DEFAULT_PLANS_DIRNAME);
57785
57826
  }
57786
- return isAbsolute6(trimmed) ? resolve18(trimmed) : resolve18(baseDir, trimmed);
57827
+ return isAbsolute5(trimmed) ? resolve18(trimmed) : resolve18(baseDir, trimmed);
57787
57828
  }
57788
57829
  function resolveProjectPlansDir(projectRoot, config) {
57789
57830
  return resolveConfiguredDir(config?.paths?.plans, projectRoot);
@@ -57798,7 +57839,7 @@ function isWithinDir(targetPath, baseDir) {
57798
57839
  const resolvedTarget = resolve18(targetPath);
57799
57840
  const resolvedBase = resolve18(baseDir);
57800
57841
  const relativePath = relative9(resolvedBase, resolvedTarget);
57801
- return relativePath === "" || !relativePath.startsWith("..") && relativePath !== ".." && !isAbsolute6(relativePath);
57842
+ return relativePath === "" || !relativePath.startsWith("..") && relativePath !== ".." && !isAbsolute5(relativePath);
57802
57843
  }
57803
57844
  function inferPlanScopeForDir(planDir, config) {
57804
57845
  return isWithinDir(planDir, resolveGlobalPlansDir(config)) ? "global" : "project";
@@ -57807,7 +57848,7 @@ function parsePlanReference(reference, defaultScope) {
57807
57848
  const trimmed = reference.trim();
57808
57849
  const normalizePlanId = (rawPlanId) => {
57809
57850
  const planId = rawPlanId.trim();
57810
- const valid = planId.length > 0 && !isAbsolute6(planId) && !planId.includes("/") && !planId.includes("\\") && PLAN_ID_PATTERN.test(planId);
57851
+ const valid = planId.length > 0 && !isAbsolute5(planId) && !planId.includes("/") && !planId.includes("\\") && PLAN_ID_PATTERN.test(planId);
57811
57852
  return { planId, valid };
57812
57853
  };
57813
57854
  if (trimmed.startsWith(GLOBAL_PLAN_PREFIX)) {
@@ -58588,7 +58629,7 @@ import {
58588
58629
  unlinkSync,
58589
58630
  writeFileSync as writeFileSync4
58590
58631
  } from "node:fs";
58591
- import { dirname as dirname18, isAbsolute as isAbsolute7, join as join47, parse as parse2, relative as relative10, resolve as resolve19 } from "node:path";
58632
+ import { dirname as dirname18, isAbsolute as isAbsolute6, join as join47, parse as parse2, relative as relative10, resolve as resolve19 } from "node:path";
58592
58633
  function createEmptyRegistry() {
58593
58634
  return {
58594
58635
  version: 1,
@@ -58643,7 +58684,7 @@ function migrateFromProjectLocal(cwd2, globalPath) {
58643
58684
  }
58644
58685
  }
58645
58686
  function normalizeRegistryDir(cwd2, dir) {
58646
- const absoluteDir = isAbsolute7(dir) ? dir : resolve19(cwd2, dir);
58687
+ const absoluteDir = isAbsolute6(dir) ? dir : resolve19(cwd2, dir);
58647
58688
  const relativeDir = relative10(cwd2, absoluteDir) || dir;
58648
58689
  return relativeDir.replace(/\\/g, "/");
58649
58690
  }
@@ -62203,7 +62244,7 @@ var package_default;
62203
62244
  var init_package = __esm(() => {
62204
62245
  package_default = {
62205
62246
  name: "claudekit-cli",
62206
- version: "3.41.4-dev.51",
62247
+ version: "3.41.4-dev.53",
62207
62248
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
62208
62249
  type: "module",
62209
62250
  repository: {
@@ -82431,11 +82472,11 @@ init_path_resolver();
82431
82472
  init_zod();
82432
82473
  var import_fs_extra9 = __toESM(require_lib3(), 1);
82433
82474
  import { mkdir as mkdir19, readdir as readdir17, readlink, rename as rename10, symlink } from "node:fs/promises";
82434
- import { basename as basename21, dirname as dirname28, isAbsolute as isAbsolute8, join as join71, normalize as normalize4, resolve as resolve27, sep as sep8 } from "node:path";
82475
+ import { basename as basename21, dirname as dirname28, isAbsolute as isAbsolute7, join as join71, normalize as normalize4, resolve as resolve27, sep as sep8 } from "node:path";
82435
82476
  var SNAPSHOT_DIR = "snapshot";
82436
82477
  var MANIFEST_FILE = "manifest.json";
82437
82478
  function normalizeRelativePath(rootDir, inputPath) {
82438
- if (!inputPath || isAbsolute8(inputPath)) {
82479
+ if (!inputPath || isAbsolute7(inputPath)) {
82439
82480
  throw new Error(`Unsafe backup path: ${inputPath}`);
82440
82481
  }
82441
82482
  const normalized = normalize4(inputPath).replaceAll("\\", "/");
@@ -82695,7 +82736,7 @@ async function loadDestructiveOperationBackup(backupDir) {
82695
82736
  const resolvedBackupDir = await assertManagedBackupDir(backupDir);
82696
82737
  const manifestPath = join71(resolvedBackupDir, MANIFEST_FILE);
82697
82738
  const manifest = destructiveOperationBackupManifestSchema.parse(await import_fs_extra9.readJson(manifestPath));
82698
- if (!isAbsolute8(manifest.sourceRoot)) {
82739
+ if (!isAbsolute7(manifest.sourceRoot)) {
82699
82740
  throw new Error(`Backup manifest source root must be absolute: ${manifest.sourceRoot}`);
82700
82741
  }
82701
82742
  const resolvedSourceRoot = resolve27(manifest.sourceRoot);
@@ -88551,7 +88592,7 @@ init_config_version_checker();
88551
88592
 
88552
88593
  // src/domains/sync/sync-engine.ts
88553
88594
  import { lstat as lstat4, readFile as readFile43, readlink as readlink2, realpath as realpath6, stat as stat15 } from "node:fs/promises";
88554
- import { isAbsolute as isAbsolute9, join as join91, normalize as normalize8, relative as relative15 } from "node:path";
88595
+ import { isAbsolute as isAbsolute8, join as join91, normalize as normalize8, relative as relative15 } from "node:path";
88555
88596
 
88556
88597
  // src/services/file-operations/ownership-checker.ts
88557
88598
  init_metadata_migration();
@@ -89773,10 +89814,10 @@ async function validateSymlinkChain(path7, basePath, maxDepth = MAX_SYMLINK_DEPT
89773
89814
  if (!stats.isSymbolicLink())
89774
89815
  break;
89775
89816
  const target = await readlink2(current);
89776
- const resolvedTarget = isAbsolute9(target) ? target : join91(current, "..", target);
89817
+ const resolvedTarget = isAbsolute8(target) ? target : join91(current, "..", target);
89777
89818
  const normalizedTarget = normalize8(resolvedTarget);
89778
89819
  const rel = relative15(basePath, normalizedTarget);
89779
- if (rel.startsWith("..") || isAbsolute9(rel)) {
89820
+ if (rel.startsWith("..") || isAbsolute8(rel)) {
89780
89821
  throw new Error(`Symlink chain escapes base directory at depth ${depth}: ${path7}`);
89781
89822
  }
89782
89823
  current = normalizedTarget;
@@ -89803,7 +89844,7 @@ async function validateSyncPath(basePath, filePath) {
89803
89844
  throw new Error(`Path too long: ${filePath.slice(0, 50)}...`);
89804
89845
  }
89805
89846
  const normalized = normalize8(filePath);
89806
- if (isAbsolute9(normalized)) {
89847
+ if (isAbsolute8(normalized)) {
89807
89848
  throw new Error(`Absolute paths not allowed: ${filePath}`);
89808
89849
  }
89809
89850
  if (normalized.startsWith("..") || normalized.includes("/../")) {
@@ -89811,7 +89852,7 @@ async function validateSyncPath(basePath, filePath) {
89811
89852
  }
89812
89853
  const fullPath = join91(basePath, normalized);
89813
89854
  const rel = relative15(basePath, fullPath);
89814
- if (rel.startsWith("..") || isAbsolute9(rel)) {
89855
+ if (rel.startsWith("..") || isAbsolute8(rel)) {
89815
89856
  throw new Error(`Path escapes base directory: ${filePath}`);
89816
89857
  }
89817
89858
  await validateSymlinkChain(fullPath, basePath);
@@ -89819,7 +89860,7 @@ async function validateSyncPath(basePath, filePath) {
89819
89860
  const resolvedBase = await realpath6(basePath);
89820
89861
  const resolvedFull = await realpath6(fullPath);
89821
89862
  const resolvedRel = relative15(resolvedBase, resolvedFull);
89822
- if (resolvedRel.startsWith("..") || isAbsolute9(resolvedRel)) {
89863
+ if (resolvedRel.startsWith("..") || isAbsolute8(resolvedRel)) {
89823
89864
  throw new Error(`Symlink escapes base directory: ${filePath}`);
89824
89865
  }
89825
89866
  } catch (error) {
@@ -89829,7 +89870,7 @@ async function validateSyncPath(basePath, filePath) {
89829
89870
  const resolvedBase = await realpath6(basePath);
89830
89871
  const resolvedParent = await realpath6(parentPath);
89831
89872
  const resolvedRel = relative15(resolvedBase, resolvedParent);
89832
- if (resolvedRel.startsWith("..") || isAbsolute9(resolvedRel)) {
89873
+ if (resolvedRel.startsWith("..") || isAbsolute8(resolvedRel)) {
89833
89874
  throw new Error(`Parent symlink escapes base directory: ${filePath}`);
89834
89875
  }
89835
89876
  } catch (parentError) {
@@ -95277,11 +95318,11 @@ var modeFix = (mode, isDir, portable) => {
95277
95318
 
95278
95319
  // node_modules/tar/dist/esm/strip-absolute-path.js
95279
95320
  import { win32 as win322 } from "node:path";
95280
- var { isAbsolute: isAbsolute10, parse: parse5 } = win322;
95321
+ var { isAbsolute: isAbsolute9, parse: parse5 } = win322;
95281
95322
  var stripAbsolutePath = (path7) => {
95282
95323
  let r2 = "";
95283
95324
  let parsed = parse5(path7);
95284
- while (isAbsolute10(path7) || parsed.root) {
95325
+ while (isAbsolute9(path7) || parsed.root) {
95285
95326
  const root = path7.charAt(0) === "/" && path7.slice(0, 4) !== "//?/" ? "/" : parsed.root;
95286
95327
  path7 = path7.slice(root.length);
95287
95328
  r2 += root;
@@ -108922,7 +108963,7 @@ Please use only one download method.`);
108922
108963
  // src/commands/plan/plan-command.ts
108923
108964
  init_output_manager();
108924
108965
  import { existsSync as existsSync67, statSync as statSync12 } from "node:fs";
108925
- import { dirname as dirname46, isAbsolute as isAbsolute12, join as join147, parse as parse7, resolve as resolve46 } from "node:path";
108966
+ import { dirname as dirname46, isAbsolute as isAbsolute11, join as join147, parse as parse7, resolve as resolve46 } from "node:path";
108926
108967
 
108927
108968
  // src/commands/plan/plan-read-handlers.ts
108928
108969
  init_config();
@@ -108990,14 +109031,14 @@ init_config();
108990
109031
  init_plan_parser();
108991
109032
  init_plan_scope();
108992
109033
  init_plans_registry();
108993
- import { isAbsolute as isAbsolute11, resolve as resolve43 } from "node:path";
109034
+ import { isAbsolute as isAbsolute10, resolve as resolve43 } from "node:path";
108994
109035
  async function getGlobalPlansDirFromCwd() {
108995
109036
  const projectRoot = findProjectRoot(process.cwd());
108996
109037
  const { config } = await CkConfigManager.loadFull(projectRoot);
108997
109038
  return resolveGlobalPlansDir(config);
108998
109039
  }
108999
109040
  function resolveTargetFromBase(target, baseDir) {
109000
- const resolvedTarget = isAbsolute11(target) ? resolve43(target) : resolve43(baseDir, target);
109041
+ const resolvedTarget = isAbsolute10(target) ? resolve43(target) : resolve43(baseDir, target);
109001
109042
  return isWithinDir(resolvedTarget, baseDir) ? resolvedTarget : null;
109002
109043
  }
109003
109044
 
@@ -109501,7 +109542,7 @@ function resolveTargetPath(target, baseDir) {
109501
109542
  if (!baseDir) {
109502
109543
  return resolve46(target);
109503
109544
  }
109504
- if (isAbsolute12(target)) {
109545
+ if (isAbsolute11(target)) {
109505
109546
  return resolve46(target);
109506
109547
  }
109507
109548
  const cwdCandidate = resolve46(target);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudekit-cli",
3
- "version": "3.41.4-dev.51",
3
+ "version": "3.41.4-dev.53",
4
4
  "description": "CLI tool for bootstrapping and updating ClaudeKit projects",
5
5
  "type": "module",
6
6
  "repository": {