caveat-cli 0.14.8 → 0.14.10

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/README.md CHANGED
@@ -12,6 +12,11 @@ caveat init # Claude Code MCP + hooks
12
12
  caveat codex-hook install # optional: native Codex hooks
13
13
  ```
14
14
 
15
+ On macOS with Homebrew Node, Caveat installs hook commands through the stable
16
+ `/opt/homebrew/bin/node` symlink when it points at the current Node binary. This
17
+ keeps Claude Code and Codex hooks working after Homebrew moves Node between
18
+ `/opt/homebrew/Cellar/node/<version>/...` directories.
19
+
15
20
  `caveat init` (idempotent, `--dry-run` supported) does the Claude Code setup:
16
21
 
17
22
  1. Scaffolds `~/.caveat/own/` (your personal knowledge repo) + `~/.caveat/index/caveat.db`
@@ -82,6 +87,16 @@ If you want `~/.caveat/own/` to live elsewhere (e.g. a git-tracked directory you
82
87
  - Claude Code installed if you want Claude MCP / hooks integration. Without it, `caveat init --skip-claude` still provisions local state.
83
88
  - Codex installed if you want native Codex hooks via `caveat codex-hook install`.
84
89
 
90
+ Release install smoke:
91
+
92
+ ```sh
93
+ npm uninstall -g caveat-cli
94
+ npm install -g caveat-cli
95
+ caveat --version
96
+ caveat init
97
+ caveat codex-hook install
98
+ ```
99
+
85
100
  ## License
86
101
 
87
102
  MIT
File without changes
package/dist/index.js CHANGED
@@ -6854,8 +6854,8 @@ var stdoutLogger = {
6854
6854
  };
6855
6855
 
6856
6856
  // src/commands/init.ts
6857
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, renameSync, rmdirSync, writeFileSync as writeFileSync2 } from "node:fs";
6858
- import { dirname as dirname3, join as join4 } from "node:path";
6857
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, renameSync, rmdirSync, writeFileSync as writeFileSync2 } from "node:fs";
6858
+ import { dirname as dirname3, join as join5 } from "node:path";
6859
6859
 
6860
6860
  // src/claudeInstall.ts
6861
6861
  import { spawnSync } from "node:child_process";
@@ -6877,10 +6877,16 @@ function hookCommand(nodePath, cliScriptPath, event) {
6877
6877
  function upsertHook(settings, event, command) {
6878
6878
  settings.hooks ??= {};
6879
6879
  const list = settings.hooks[event] ??= [];
6880
- const alreadyPresent = list.some(
6881
- (entry) => entry.hooks?.some((h) => isSameHookCommand(h.command, command))
6882
- );
6883
- if (alreadyPresent) return "unchanged";
6880
+ const subcommand = command.includes("hook user-prompt-submit") ? "user-prompt-submit" : command.includes("hook post-tool-use") ? "post-tool-use" : command.includes("hook stop") ? "stop" : null;
6881
+ for (const entry of list) {
6882
+ for (const hook of entry.hooks ?? []) {
6883
+ if (isSameHookCommand(hook.command, command)) return "unchanged";
6884
+ if (subcommand && isCaveatClaudeHookCommand(hook.command, subcommand)) {
6885
+ hook.command = command;
6886
+ return "added";
6887
+ }
6888
+ }
6889
+ }
6884
6890
  list.push({ hooks: [{ type: "command", command }] });
6885
6891
  return "added";
6886
6892
  }
@@ -6907,6 +6913,13 @@ function findExistingHookCommand(settings, event, command) {
6907
6913
  function isSameHookCommand(actual, expected) {
6908
6914
  return actual === expected || actual.endsWith(` ${expected}`);
6909
6915
  }
6916
+ function isEnvPrefixedCommand(command) {
6917
+ return /^[A-Z_][A-Z0-9_]*=/.test(command.trim());
6918
+ }
6919
+ function isCaveatClaudeHookCommand(actual, event) {
6920
+ const lower = actual.toLowerCase();
6921
+ return !isEnvPrefixedCommand(actual) && lower.includes("caveat") && actual.includes(`hook ${event}`);
6922
+ }
6910
6923
  function readSettings(path) {
6911
6924
  if (!existsSync(path)) return {};
6912
6925
  return JSON.parse(readFileSync2(path, "utf-8"));
@@ -7037,6 +7050,40 @@ function uninstallClaudeIntegration(opts) {
7037
7050
  };
7038
7051
  }
7039
7052
 
7053
+ // src/nodePath.ts
7054
+ import { existsSync as existsSync2, realpathSync } from "node:fs";
7055
+ import { delimiter, join as join4 } from "node:path";
7056
+ var defaultRealpath = (path) => realpathSync.native(path);
7057
+ function safeRealpath(path, realpath = defaultRealpath) {
7058
+ try {
7059
+ return realpath(path);
7060
+ } catch {
7061
+ return null;
7062
+ }
7063
+ }
7064
+ function resolveHookNodePath({
7065
+ env = process.env,
7066
+ execPath = process.execPath,
7067
+ platform = process.platform,
7068
+ exists = existsSync2,
7069
+ realpath = defaultRealpath
7070
+ } = {}) {
7071
+ const execRealpath = safeRealpath(execPath, realpath);
7072
+ const pathEnv = env.PATH ?? env.Path ?? "";
7073
+ const names = platform === "win32" ? ["node.exe", "node.cmd", "node.bat", "node"] : ["node"];
7074
+ for (const dir of pathEnv.split(delimiter).filter(Boolean)) {
7075
+ for (const name of names) {
7076
+ const candidate = join4(dir, name);
7077
+ if (!exists(candidate)) continue;
7078
+ const candidateRealpath = safeRealpath(candidate, realpath);
7079
+ if (execRealpath && candidateRealpath && candidateRealpath === execRealpath) {
7080
+ return candidate;
7081
+ }
7082
+ }
7083
+ }
7084
+ return execPath;
7085
+ }
7086
+
7040
7087
  // src/commands/init.ts
7041
7088
  var KNOWLEDGE_GITIGNORE = [
7042
7089
  "# Never commit entries flagged private (visibility: private is enforced by the",
@@ -7050,7 +7097,7 @@ var KNOWLEDGE_GITIGNORE = [
7050
7097
  async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
7051
7098
  ensureUserConfig(ctx.userConfigPath);
7052
7099
  ctx.logger.info(`user config: ${ctx.userConfigPath}`);
7053
- if (!existsSync2(ctx.paths.knowledgeRepo)) {
7100
+ if (!existsSync3(ctx.paths.knowledgeRepo)) {
7054
7101
  mkdirSync2(ctx.paths.knowledgeRepo, { recursive: true });
7055
7102
  mkdirSync2(ctx.paths.entriesDir, { recursive: true });
7056
7103
  ctx.logger.info(`knowledge repo scaffolded: ${ctx.paths.knowledgeRepo}`);
@@ -7058,14 +7105,14 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
7058
7105
  ctx.logger.info(`knowledge repo: ${ctx.paths.knowledgeRepo}`);
7059
7106
  }
7060
7107
  migrateLegacyCommunityDir(ctx);
7061
- const gitignorePath = join4(ctx.paths.knowledgeRepo, ".gitignore");
7062
- if (!existsSync2(gitignorePath)) {
7108
+ const gitignorePath = join5(ctx.paths.knowledgeRepo, ".gitignore");
7109
+ if (!existsSync3(gitignorePath)) {
7063
7110
  writeFileSync2(gitignorePath, KNOWLEDGE_GITIGNORE, "utf-8");
7064
7111
  ctx.logger.info(`.gitignore created: ${gitignorePath}`);
7065
7112
  }
7066
7113
  if (!opts.dryRun) {
7067
7114
  const dbDir = dirname3(ctx.paths.dbPath);
7068
- if (!existsSync2(dbDir)) mkdirSync2(dbDir, { recursive: true });
7115
+ if (!existsSync3(dbDir)) mkdirSync2(dbDir, { recursive: true });
7069
7116
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
7070
7117
  db.close();
7071
7118
  ctx.logger.info(`db initialized: ${ctx.paths.dbPath}`);
@@ -7096,20 +7143,20 @@ async function runInit(ctx, opts = { skipClaude: false, dryRun: false }) {
7096
7143
  return;
7097
7144
  }
7098
7145
  const result = installClaudeIntegration({
7099
- claudeDir: join4(ctx.userHome, ".claude"),
7146
+ claudeDir: join5(ctx.userHome, ".claude"),
7100
7147
  cliScriptPath,
7101
- nodePath: process.execPath,
7148
+ nodePath: resolveHookNodePath(),
7102
7149
  dryRun: opts.dryRun,
7103
7150
  logger: ctx.logger
7104
7151
  });
7105
7152
  reportInstallResult(ctx, result, opts.dryRun);
7106
7153
  }
7107
7154
  function migrateLegacyCommunityDir(ctx) {
7108
- const legacy = join4(ctx.paths.knowledgeRepo, "community");
7155
+ const legacy = join5(ctx.paths.knowledgeRepo, "community");
7109
7156
  const current = ctx.paths.communityDir;
7110
7157
  if (legacy === current) return;
7111
- if (!existsSync2(legacy)) return;
7112
- if (existsSync2(current)) {
7158
+ if (!existsSync3(legacy)) return;
7159
+ if (existsSync3(current)) {
7113
7160
  ctx.logger.warn(
7114
7161
  `legacy community dir still exists at ${legacy} \u2014 remove manually (new location in use)`
7115
7162
  );
@@ -7118,7 +7165,7 @@ function migrateLegacyCommunityDir(ctx) {
7118
7165
  mkdirSync2(current, { recursive: true });
7119
7166
  for (const entry of readdirSync(legacy, { withFileTypes: true })) {
7120
7167
  if (!entry.isDirectory()) continue;
7121
- renameSync(join4(legacy, entry.name), join4(current, entry.name));
7168
+ renameSync(join5(legacy, entry.name), join5(current, entry.name));
7122
7169
  }
7123
7170
  try {
7124
7171
  rmdirSync(legacy);
@@ -7133,9 +7180,9 @@ function runUninstall(ctx, opts) {
7133
7180
  process.exit(1);
7134
7181
  }
7135
7182
  const result = uninstallClaudeIntegration({
7136
- claudeDir: join4(ctx.userHome, ".claude"),
7183
+ claudeDir: join5(ctx.userHome, ".claude"),
7137
7184
  cliScriptPath,
7138
- nodePath: process.execPath,
7185
+ nodePath: resolveHookNodePath(),
7139
7186
  dryRun: opts.dryRun,
7140
7187
  logger: ctx.logger
7141
7188
  });
@@ -7180,29 +7227,29 @@ function reportInstallResult(ctx, result, dryRun) {
7180
7227
  }
7181
7228
 
7182
7229
  // src/commands/indexCmd.ts
7183
- import { existsSync as existsSync3, readdirSync as readdirSync2, mkdirSync as mkdirSync3 } from "node:fs";
7184
- import { dirname as dirname4, join as join5 } from "node:path";
7230
+ import { existsSync as existsSync4, readdirSync as readdirSync2, mkdirSync as mkdirSync3 } from "node:fs";
7231
+ import { dirname as dirname4, join as join6 } from "node:path";
7185
7232
  function runIndex(ctx, opts) {
7186
7233
  const dbDir = dirname4(ctx.paths.dbPath);
7187
- if (!existsSync3(dbDir)) mkdirSync3(dbDir, { recursive: true });
7234
+ if (!existsSync4(dbDir)) mkdirSync3(dbDir, { recursive: true });
7188
7235
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
7189
7236
  try {
7190
7237
  if (opts.full) {
7191
7238
  ctx.logger.info("full rebuild: DELETE FROM entries");
7192
7239
  rebuildAll(db);
7193
7240
  }
7194
- if (existsSync3(ctx.paths.entriesDir)) {
7241
+ if (existsSync4(ctx.paths.entriesDir)) {
7195
7242
  const result = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
7196
7243
  ctx.logger.info(`own: +${result.added} ~${result.updated} -${result.deleted}`);
7197
7244
  } else {
7198
7245
  ctx.logger.warn(`entries dir not found: ${ctx.paths.entriesDir}`);
7199
7246
  }
7200
- if (existsSync3(ctx.paths.communityDir)) {
7247
+ if (existsSync4(ctx.paths.communityDir)) {
7201
7248
  for (const entry of readdirSync2(ctx.paths.communityDir, { withFileTypes: true })) {
7202
7249
  if (!entry.isDirectory()) continue;
7203
7250
  const source = `community/${entry.name}`;
7204
- const root = join5(ctx.paths.communityDir, entry.name, "entries");
7205
- if (!existsSync3(root)) continue;
7251
+ const root = join6(ctx.paths.communityDir, entry.name, "entries");
7252
+ if (!existsSync4(root)) continue;
7206
7253
  const result = scanSource({ db, source, entriesRoot: root });
7207
7254
  ctx.logger.info(`${source}: +${result.added} ~${result.updated} -${result.deleted}`);
7208
7255
  }
@@ -12612,8 +12659,8 @@ function emoji() {
12612
12659
  }
12613
12660
  var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
12614
12661
  var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
12615
- var mac = (delimiter) => {
12616
- const escapedDelim = escapeRegex(delimiter ?? ":");
12662
+ var mac = (delimiter2) => {
12663
+ const escapedDelim = escapeRegex(delimiter2 ?? ":");
12617
12664
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
12618
12665
  };
12619
12666
  var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -31430,11 +31477,11 @@ var StdioServerTransport = class {
31430
31477
 
31431
31478
  // ../mcp/dist/context.js
31432
31479
  import { homedir as homedir2 } from "node:os";
31433
- import { join as join6 } from "node:path";
31480
+ import { join as join7 } from "node:path";
31434
31481
  function buildMcpContext(overrides = {}) {
31435
31482
  const userHome = overrides.userHome ?? homedir2();
31436
31483
  const caveatHome = overrides.caveatHome ?? findCaveatHome(userHome);
31437
- const userConfigPath = join6(userHome, ".caveatrc.json");
31484
+ const userConfigPath = join7(userHome, ".caveatrc.json");
31438
31485
  const logger = overrides.logger ?? stderrLogger;
31439
31486
  const config2 = loadConfig(userConfigPath);
31440
31487
  const paths = resolvePaths(caveatHome, config2.knowledgeRepo, userHome);
@@ -31569,13 +31616,13 @@ function handleListRecent(ctx, args) {
31569
31616
  }
31570
31617
 
31571
31618
  // ../mcp/dist/tools/pull.js
31572
- import { existsSync as existsSync4, readdirSync as readdirSync3 } from "node:fs";
31573
- import { join as join7 } from "node:path";
31619
+ import { existsSync as existsSync5, readdirSync as readdirSync3 } from "node:fs";
31620
+ import { join as join8 } from "node:path";
31574
31621
  var pullInputShape = {};
31575
31622
  async function handlePull(ctx, _args = {}) {
31576
31623
  const pulled = [];
31577
31624
  const indexed = [];
31578
- if (existsSync4(ctx.paths.communityDir)) {
31625
+ if (existsSync5(ctx.paths.communityDir)) {
31579
31626
  const results = await communityPull({
31580
31627
  communityDir: ctx.paths.communityDir,
31581
31628
  logger: ctx.logger
@@ -31585,16 +31632,16 @@ async function handlePull(ctx, _args = {}) {
31585
31632
  }
31586
31633
  }
31587
31634
  rebuildAll(ctx.db);
31588
- if (existsSync4(ctx.paths.entriesDir)) {
31635
+ if (existsSync5(ctx.paths.entriesDir)) {
31589
31636
  const own = scanSource({ db: ctx.db, source: "own", entriesRoot: ctx.paths.entriesDir });
31590
31637
  indexed.push({ source: "own", ...own });
31591
31638
  }
31592
- if (existsSync4(ctx.paths.communityDir)) {
31639
+ if (existsSync5(ctx.paths.communityDir)) {
31593
31640
  for (const entry of readdirSync3(ctx.paths.communityDir, { withFileTypes: true })) {
31594
31641
  if (!entry.isDirectory()) continue;
31595
31642
  const source = `community/${entry.name}`;
31596
- const root = join7(ctx.paths.communityDir, entry.name, "entries");
31597
- if (!existsSync4(root)) continue;
31643
+ const root = join8(ctx.paths.communityDir, entry.name, "entries");
31644
+ if (!existsSync5(root)) continue;
31598
31645
  const scan = scanSource({ db: ctx.db, source, entriesRoot: root });
31599
31646
  indexed.push({ source, ...scan });
31600
31647
  }
@@ -31709,7 +31756,7 @@ async function runMcpServer() {
31709
31756
  // src/commands/hookCmd.ts
31710
31757
  import { spawn, spawnSync as spawnSync2 } from "node:child_process";
31711
31758
  import {
31712
- existsSync as existsSync5,
31759
+ existsSync as existsSync6,
31713
31760
  mkdirSync as mkdirSync4,
31714
31761
  mkdtempSync,
31715
31762
  readFileSync as readFileSync3,
@@ -31718,7 +31765,7 @@ import {
31718
31765
  writeFileSync as writeFileSync3
31719
31766
  } from "node:fs";
31720
31767
  import { tmpdir } from "node:os";
31721
- import { join as join8 } from "node:path";
31768
+ import { join as join9 } from "node:path";
31722
31769
  import { createHash, randomBytes } from "node:crypto";
31723
31770
  var silentLogger = {
31724
31771
  info: () => {
@@ -31768,7 +31815,7 @@ function searchCaveatsFromTextSafely(text) {
31768
31815
  let db;
31769
31816
  try {
31770
31817
  const ctx = buildContextSafely();
31771
- if (!ctx || !existsSync5(ctx.paths.dbPath)) return [];
31818
+ if (!ctx || !existsSync6(ctx.paths.dbPath)) return [];
31772
31819
  db = openDb({ path: ctx.paths.dbPath });
31773
31820
  const hits = findCaveatsForPrompt(db, text, {
31774
31821
  selfIdentity: defaultSelfIdentityTokens()
@@ -31852,7 +31899,7 @@ function stopSignalKey(signals, related) {
31852
31899
  return createHash("sha256").update(body).digest("hex");
31853
31900
  }
31854
31901
  function stopStatePath(caveatHome, sessionId) {
31855
- return join8(caveatHome, CLAUDE_STOP_STATE_DIR, `${sanitizeClaudeStateId(sessionId)}.txt`);
31902
+ return join9(caveatHome, CLAUDE_STOP_STATE_DIR, `${sanitizeClaudeStateId(sessionId)}.txt`);
31856
31903
  }
31857
31904
  function wasStopReminderQueued(caveatHome, sessionId, key) {
31858
31905
  const path = stopStatePath(caveatHome, sessionId);
@@ -31864,7 +31911,7 @@ function wasStopReminderQueued(caveatHome, sessionId, key) {
31864
31911
  }
31865
31912
  function markStopReminderQueued(caveatHome, sessionId, key) {
31866
31913
  const path = stopStatePath(caveatHome, sessionId);
31867
- mkdirSync4(join8(caveatHome, CLAUDE_STOP_STATE_DIR), { recursive: true });
31914
+ mkdirSync4(join9(caveatHome, CLAUDE_STOP_STATE_DIR), { recursive: true });
31868
31915
  writeFileSync3(path, key, "utf-8");
31869
31916
  }
31870
31917
  function queueStopForSession(sessionId, signals, related) {
@@ -31915,7 +31962,7 @@ function isToolError(payload) {
31915
31962
  return false;
31916
31963
  }
31917
31964
  function spawnWorker(job) {
31918
- const workFile = join8(
31965
+ const workFile = join9(
31919
31966
  tmpdir(),
31920
31967
  `caveat-worker-${Date.now()}-${randomBytes(4).toString("hex")}.json`
31921
31968
  );
@@ -31979,7 +32026,7 @@ function buildToolErrorReminder(job, hits) {
31979
32026
  const mode = hookCodexSidecarMode();
31980
32027
  if (mode === "off") return base;
31981
32028
  const projectRoot = process.cwd();
31982
- const hasSidecarConfig = existsSync5(join8(projectRoot, ".codex-sidecar.yml"));
32029
+ const hasSidecarConfig = existsSync6(join9(projectRoot, ".codex-sidecar.yml"));
31983
32030
  if (mode === "auto" && !hasSidecarConfig) return base;
31984
32031
  const advisory = runCodexSidecarAdvisory({
31985
32032
  searchText: job.searchText,
@@ -32016,7 +32063,7 @@ function buildStopReminder(signals, related) {
32016
32063
  const mode = hookCodexSidecarMode();
32017
32064
  if (mode === "off") return base;
32018
32065
  const projectRoot = process.cwd();
32019
- const hasSidecarConfig = existsSync5(join8(projectRoot, ".codex-sidecar.yml"));
32066
+ const hasSidecarConfig = existsSync6(join9(projectRoot, ".codex-sidecar.yml"));
32020
32067
  if (mode === "auto" && !hasSidecarConfig) return base;
32021
32068
  const advisory = runCodexSidecarAdvisory({
32022
32069
  searchText: struggleSearchText(signals),
@@ -32081,8 +32128,8 @@ function runCodexSidecarAdvisory(input) {
32081
32128
  "--availability",
32082
32129
  "operational"
32083
32130
  ];
32084
- const resultDir = mkdtempSync(join8(tmpdir(), "caveat-codex-advisory-"));
32085
- const resultFile = join8(resultDir, "result.json");
32131
+ const resultDir = mkdtempSync(join9(tmpdir(), "caveat-codex-advisory-"));
32132
+ const resultFile = join9(resultDir, "result.json");
32086
32133
  args.push("--save-result", resultFile);
32087
32134
  const nodeCli = process.env.CAVEAT_CODEX_SIDECAR_NODE_CLI;
32088
32135
  if (nodeCli) {
@@ -32197,14 +32244,14 @@ async function runHook(name, arg) {
32197
32244
 
32198
32245
  // src/commands/codexHookCmd.ts
32199
32246
  import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
32200
- import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
32247
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
32201
32248
  import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
32202
- import { join as join10 } from "node:path";
32249
+ import { join as join11 } from "node:path";
32203
32250
  import { createHash as createHash2, randomBytes as randomBytes2 } from "node:crypto";
32204
32251
 
32205
32252
  // src/codexHookInstall.ts
32206
- import { copyFileSync as copyFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
32207
- import { dirname as dirname5, join as join9 } from "node:path";
32253
+ import { copyFileSync as copyFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
32254
+ import { dirname as dirname5, join as join10 } from "node:path";
32208
32255
  function quote2(p) {
32209
32256
  return p.includes(" ") ? `"${p}"` : p;
32210
32257
  }
@@ -32217,20 +32264,24 @@ function eventCommandFragment(event) {
32217
32264
  function isSameHookCommand2(actual, expected) {
32218
32265
  return actual === expected || actual.endsWith(` ${expected}`);
32219
32266
  }
32267
+ function isCaveatCodexHookCommand(actual, event) {
32268
+ const lower = actual.toLowerCase();
32269
+ return lower.includes("caveat") && actual.includes(eventCommandFragment(event));
32270
+ }
32220
32271
  function hasCaveatHook(hooksJson, event, fragment) {
32221
32272
  return hooksJson.hooks?.[event]?.some(
32222
32273
  (entry) => entry.hooks?.some((h) => h.command.includes(fragment))
32223
32274
  ) ?? false;
32224
32275
  }
32225
32276
  function readHooks(path) {
32226
- if (!existsSync6(path)) return {};
32277
+ if (!existsSync7(path)) return {};
32227
32278
  return JSON.parse(readFileSync4(path, "utf-8"));
32228
32279
  }
32229
32280
  function writeJsonWithBackup(path, value) {
32230
32281
  const dir = dirname5(path);
32231
- if (!existsSync6(dir)) mkdirSync5(dir, { recursive: true });
32282
+ if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
32232
32283
  let backupPath = "";
32233
- if (existsSync6(path)) {
32284
+ if (existsSync7(path)) {
32234
32285
  backupPath = `${path}.caveat-backup-${Date.now()}`;
32235
32286
  copyFileSync2(path, backupPath);
32236
32287
  }
@@ -32238,13 +32289,21 @@ function writeJsonWithBackup(path, value) {
32238
32289
  `, "utf-8");
32239
32290
  return backupPath;
32240
32291
  }
32241
- function upsertHook2(hooksJson, event, command) {
32292
+ function upsertHook2(hooksJson, event, command, subcommand) {
32242
32293
  hooksJson.hooks ??= {};
32243
32294
  const list = hooksJson.hooks[event] ??= [];
32244
- const alreadyPresent = list.some(
32245
- (entry) => entry.hooks?.some((h) => isSameHookCommand2(h.command, command))
32246
- );
32247
- if (alreadyPresent) return "unchanged";
32295
+ for (const entry of list) {
32296
+ for (const hook of entry.hooks ?? []) {
32297
+ if (isSameHookCommand2(hook.command, command)) return "unchanged";
32298
+ if (isCaveatCodexHookCommand(hook.command, subcommand)) {
32299
+ hook.command = command;
32300
+ hook.timeoutSec = 5;
32301
+ hook.async = false;
32302
+ hook.statusMessage = null;
32303
+ return "added";
32304
+ }
32305
+ }
32306
+ }
32248
32307
  list.push({
32249
32308
  hooks: [
32250
32309
  {
@@ -32258,12 +32317,14 @@ function upsertHook2(hooksJson, event, command) {
32258
32317
  });
32259
32318
  return "added";
32260
32319
  }
32261
- function removeHook2(hooksJson, event, command) {
32320
+ function removeHook2(hooksJson, event, command, subcommand) {
32262
32321
  const list = hooksJson.hooks?.[event];
32263
32322
  if (!list) return false;
32264
32323
  const before = list.length;
32265
32324
  const filtered = list.filter(
32266
- (entry) => !entry.hooks?.some((h) => isSameHookCommand2(h.command, command))
32325
+ (entry) => !entry.hooks?.some(
32326
+ (h) => isSameHookCommand2(h.command, command) || isCaveatCodexHookCommand(h.command, subcommand)
32327
+ )
32267
32328
  );
32268
32329
  if (filtered.length === before) return false;
32269
32330
  hooksJson.hooks[event] = filtered;
@@ -32297,9 +32358,9 @@ codex_hooks = true
32297
32358
  }
32298
32359
  function writeConfigWithBackup(path, text) {
32299
32360
  const dir = dirname5(path);
32300
- if (!existsSync6(dir)) mkdirSync5(dir, { recursive: true });
32361
+ if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
32301
32362
  let backupPath = "";
32302
- if (existsSync6(path)) {
32363
+ if (existsSync7(path)) {
32303
32364
  backupPath = `${path}.caveat-backup-${Date.now()}`;
32304
32365
  copyFileSync2(path, backupPath);
32305
32366
  }
@@ -32307,25 +32368,28 @@ function writeConfigWithBackup(path, text) {
32307
32368
  return backupPath;
32308
32369
  }
32309
32370
  function installCodexHooks(opts) {
32310
- const hooksPath = join9(opts.codexHome, "hooks.json");
32311
- const configPath = join9(opts.codexHome, "config.toml");
32371
+ const hooksPath = join10(opts.codexHome, "hooks.json");
32372
+ const configPath = join10(opts.codexHome, "config.toml");
32312
32373
  const hooksJson = readHooks(hooksPath);
32313
32374
  const userPromptSubmit = upsertHook2(
32314
32375
  hooksJson,
32315
32376
  "UserPromptSubmit",
32316
- hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit")
32377
+ hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit"),
32378
+ "user-prompt-submit"
32317
32379
  );
32318
32380
  const postToolUse = upsertHook2(
32319
32381
  hooksJson,
32320
32382
  "PostToolUse",
32321
- hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use")
32383
+ hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use"),
32384
+ "post-tool-use"
32322
32385
  );
32323
32386
  const stop = upsertHook2(
32324
32387
  hooksJson,
32325
32388
  "Stop",
32326
- hookCommand2(opts.nodePath, opts.cliScriptPath, "stop")
32389
+ hookCommand2(opts.nodePath, opts.cliScriptPath, "stop"),
32390
+ "stop"
32327
32391
  );
32328
- const rawConfig = existsSync6(configPath) ? readFileSync4(configPath, "utf-8") : "";
32392
+ const rawConfig = existsSync7(configPath) ? readFileSync4(configPath, "utf-8") : "";
32329
32393
  const enabled = enableCodexHooksFeature(rawConfig);
32330
32394
  const anyHookAdded = userPromptSubmit === "added" || postToolUse === "added" || stop === "added";
32331
32395
  let backupPath;
@@ -32351,22 +32415,25 @@ function installCodexHooks(opts) {
32351
32415
  };
32352
32416
  }
32353
32417
  function uninstallCodexHooks(opts) {
32354
- const hooksPath = join9(opts.codexHome, "hooks.json");
32418
+ const hooksPath = join10(opts.codexHome, "hooks.json");
32355
32419
  const hooksJson = readHooks(hooksPath);
32356
32420
  const userPromptSubmitRemoved = removeHook2(
32357
32421
  hooksJson,
32358
32422
  "UserPromptSubmit",
32359
- hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit")
32423
+ hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit"),
32424
+ "user-prompt-submit"
32360
32425
  );
32361
32426
  const postToolUseRemoved = removeHook2(
32362
32427
  hooksJson,
32363
32428
  "PostToolUse",
32364
- hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use")
32429
+ hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use"),
32430
+ "post-tool-use"
32365
32431
  );
32366
32432
  const stopRemoved = removeHook2(
32367
32433
  hooksJson,
32368
32434
  "Stop",
32369
- hookCommand2(opts.nodePath, opts.cliScriptPath, "stop")
32435
+ hookCommand2(opts.nodePath, opts.cliScriptPath, "stop"),
32436
+ "stop"
32370
32437
  );
32371
32438
  let backupPath;
32372
32439
  if (opts.dryRun) {
@@ -32386,7 +32453,7 @@ function uninstallCodexHooks(opts) {
32386
32453
  };
32387
32454
  }
32388
32455
  function detectCodexHookInstallation(codexHome) {
32389
- const hooksPath = join9(codexHome, "hooks.json");
32456
+ const hooksPath = join10(codexHome, "hooks.json");
32390
32457
  const hooksJson = readHooks(hooksPath);
32391
32458
  const hooks = {
32392
32459
  userPromptSubmit: hasCaveatHook(
@@ -32450,7 +32517,7 @@ function searchCaveatsFromTextSafely2(text) {
32450
32517
  let db;
32451
32518
  try {
32452
32519
  const ctx = buildContextSafely2();
32453
- if (!ctx || !existsSync7(ctx.paths.dbPath)) return [];
32520
+ if (!ctx || !existsSync8(ctx.paths.dbPath)) return [];
32454
32521
  db = openDb({ path: ctx.paths.dbPath });
32455
32522
  const hits = findCaveatsForPrompt(db, text, {
32456
32523
  selfIdentity: defaultSelfIdentityTokens()
@@ -32519,7 +32586,7 @@ function processExitCodeFromText(text) {
32519
32586
  return m ? Number(m[1]) : null;
32520
32587
  }
32521
32588
  function transcriptToolOutput(transcriptPath, toolUseId) {
32522
- if (!transcriptPath || !toolUseId || !existsSync7(transcriptPath)) return null;
32589
+ if (!transcriptPath || !toolUseId || !existsSync8(transcriptPath)) return null;
32523
32590
  let raw = "";
32524
32591
  try {
32525
32592
  raw = readFileSync5(transcriptPath, "utf-8");
@@ -32660,7 +32727,7 @@ function stopSignalKey2(signals, related) {
32660
32727
  return createHash2("sha256").update(body).digest("hex");
32661
32728
  }
32662
32729
  function stopStatePath2(caveatHome, sessionId) {
32663
- return join10(caveatHome, CODEX_STOP_STATE_DIR, `${sanitizeCodexStateId(sessionId)}.txt`);
32730
+ return join11(caveatHome, CODEX_STOP_STATE_DIR, `${sanitizeCodexStateId(sessionId)}.txt`);
32664
32731
  }
32665
32732
  function wasStopReminderQueued2(caveatHome, sessionId, key) {
32666
32733
  const path = stopStatePath2(caveatHome, sessionId);
@@ -32672,7 +32739,7 @@ function wasStopReminderQueued2(caveatHome, sessionId, key) {
32672
32739
  }
32673
32740
  function markStopReminderQueued2(caveatHome, sessionId, key) {
32674
32741
  const path = stopStatePath2(caveatHome, sessionId);
32675
- mkdirSync6(join10(caveatHome, CODEX_STOP_STATE_DIR), { recursive: true });
32742
+ mkdirSync6(join11(caveatHome, CODEX_STOP_STATE_DIR), { recursive: true });
32676
32743
  writeFileSync5(path, key, "utf-8");
32677
32744
  }
32678
32745
  function queueStopForSession2(sessionId, signals, related) {
@@ -32743,7 +32810,7 @@ async function runCodexWorker(workFile) {
32743
32810
  await processCodexWorkerJob(job);
32744
32811
  process.exit(0);
32745
32812
  }
32746
- function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join10(homedir3(), ".codex")) {
32813
+ function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join11(homedir3(), ".codex")) {
32747
32814
  const features = spawnSync3("codex", ["features", "list"], {
32748
32815
  encoding: "utf-8",
32749
32816
  maxBuffer: 1024 * 1024
@@ -32820,10 +32887,10 @@ async function runCodexHook(name, arg) {
32820
32887
  }
32821
32888
 
32822
32889
  // src/commands/pull.ts
32823
- import { existsSync as existsSync8, readdirSync as readdirSync4 } from "node:fs";
32824
- import { join as join11 } from "node:path";
32890
+ import { existsSync as existsSync9, readdirSync as readdirSync4 } from "node:fs";
32891
+ import { join as join12 } from "node:path";
32825
32892
  async function runPull(ctx) {
32826
- if (!existsSync8(ctx.paths.communityDir)) {
32893
+ if (!existsSync9(ctx.paths.communityDir)) {
32827
32894
  ctx.logger.info(
32828
32895
  "no community repos yet \u2014 add one with `caveat community add <github-url>`."
32829
32896
  );
@@ -32843,15 +32910,15 @@ async function runPull(ctx) {
32843
32910
  const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
32844
32911
  try {
32845
32912
  rebuildAll(db);
32846
- if (existsSync8(ctx.paths.entriesDir)) {
32913
+ if (existsSync9(ctx.paths.entriesDir)) {
32847
32914
  const own = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
32848
32915
  ctx.logger.info(`own: +${own.added}`);
32849
32916
  }
32850
32917
  for (const entry of readdirSync4(ctx.paths.communityDir, { withFileTypes: true })) {
32851
32918
  if (!entry.isDirectory()) continue;
32852
32919
  const source = `community/${entry.name}`;
32853
- const root = join11(ctx.paths.communityDir, entry.name, "entries");
32854
- if (!existsSync8(root)) continue;
32920
+ const root = join12(ctx.paths.communityDir, entry.name, "entries");
32921
+ if (!existsSync9(root)) continue;
32855
32922
  const scan = scanSource({ db, source, entriesRoot: root });
32856
32923
  ctx.logger.info(`${source}: +${scan.added}`);
32857
32924
  }
@@ -32864,7 +32931,7 @@ async function runPull(ctx) {
32864
32931
  import { spawnSync as spawnSync4 } from "node:child_process";
32865
32932
  import { mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
32866
32933
  import { tmpdir as tmpdir3 } from "node:os";
32867
- import { dirname as dirname6, join as join12 } from "node:path";
32934
+ import { dirname as dirname6, join as join13 } from "node:path";
32868
32935
  import { cwd, exit } from "node:process";
32869
32936
  function runCodexSidecarDiagnostics(logger, opts) {
32870
32937
  const plan = buildCodexSidecarDiagnosticsCommand({
@@ -32896,8 +32963,8 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
32896
32963
  process.stdout.write(JSON.stringify({ status: "skipped", decision }, null, 2) + "\n");
32897
32964
  exit(0);
32898
32965
  }
32899
- const contextDir = mkdtempSync2(join12(tmpdir3(), "caveat-sidecar-context-"));
32900
- const contextFile = join12(contextDir, "context.json");
32966
+ const contextDir = mkdtempSync2(join13(tmpdir3(), "caveat-sidecar-context-"));
32967
+ const contextFile = join13(contextDir, "context.json");
32901
32968
  let status = 1;
32902
32969
  try {
32903
32970
  const blocks = collectCaveatContextBlocks(ctx, {
@@ -33152,7 +33219,7 @@ codexHook.command("install").description("Install Caveat hooks into ~/.codex/hoo
33152
33219
  const result = installCodexHooks({
33153
33220
  codexHome: opts.codexHome,
33154
33221
  cliScriptPath,
33155
- nodePath: process.execPath,
33222
+ nodePath: resolveHookNodePath(),
33156
33223
  dryRun: opts.dryRun,
33157
33224
  logger: stdoutLogger
33158
33225
  });
@@ -33174,7 +33241,7 @@ codexHook.command("uninstall").description("Remove Caveat-owned Codex hooks from
33174
33241
  const result = uninstallCodexHooks({
33175
33242
  codexHome: opts.codexHome,
33176
33243
  cliScriptPath,
33177
- nodePath: process.execPath,
33244
+ nodePath: resolveHookNodePath(),
33178
33245
  dryRun: opts.dryRun,
33179
33246
  logger: stdoutLogger
33180
33247
  });