caveat-cli 0.13.0 → 0.14.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.
- package/dist/caveat.js +0 -0
- package/dist/{chunk-WTHGWCPM.js → chunk-CSXD73IX.js} +180 -8
- package/dist/chunk-CSXD73IX.js.map +1 -0
- package/dist/index.js +676 -24
- package/dist/index.js.map +1 -1
- package/dist/{server-IWDZZESV.js → server-BP54BTEL.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-WTHGWCPM.js.map +0 -1
- /package/dist/{server-IWDZZESV.js.map → server-BP54BTEL.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
loadConfig,
|
|
28
28
|
markHit,
|
|
29
29
|
openDb,
|
|
30
|
+
readCodexSessionSignals,
|
|
30
31
|
readSessionSignals,
|
|
31
32
|
rebuildAll,
|
|
32
33
|
recordEntry,
|
|
@@ -39,7 +40,7 @@ import {
|
|
|
39
40
|
toolErrorReminderText,
|
|
40
41
|
updateEntry,
|
|
41
42
|
userPromptSubmitReminderText
|
|
42
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-CSXD73IX.js";
|
|
43
44
|
|
|
44
45
|
// ../../node_modules/.pnpm/ajv@8.18.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
45
46
|
var require_code = __commonJS({
|
|
@@ -6860,6 +6861,7 @@ import { copyFileSync, existsSync, mkdirSync, readFileSync as readFileSync2, wri
|
|
|
6860
6861
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
6861
6862
|
var EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
|
|
6862
6863
|
var EVENT_POST_TOOL_USE = "PostToolUse";
|
|
6864
|
+
var EVENT_POST_TOOL_USE_FAILURE = "PostToolUseFailure";
|
|
6863
6865
|
var EVENT_STOP = "Stop";
|
|
6864
6866
|
function quote(p) {
|
|
6865
6867
|
return p.includes(" ") ? `"${p}"` : p;
|
|
@@ -6891,6 +6893,15 @@ function removeHook(settings, event, command) {
|
|
|
6891
6893
|
settings.hooks[event] = filtered;
|
|
6892
6894
|
return true;
|
|
6893
6895
|
}
|
|
6896
|
+
function findExistingHookCommand(settings, event, command) {
|
|
6897
|
+
const list = settings.hooks?.[event];
|
|
6898
|
+
if (!list) return void 0;
|
|
6899
|
+
for (const entry of list) {
|
|
6900
|
+
const found = entry.hooks?.find((h) => isSameHookCommand(h.command, command));
|
|
6901
|
+
if (found) return found.command;
|
|
6902
|
+
}
|
|
6903
|
+
return void 0;
|
|
6904
|
+
}
|
|
6894
6905
|
function isSameHookCommand(actual, expected) {
|
|
6895
6906
|
return actual === expected || actual.endsWith(` ${expected}`);
|
|
6896
6907
|
}
|
|
@@ -6970,21 +6981,31 @@ function installClaudeIntegration(opts) {
|
|
|
6970
6981
|
const usCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "user-prompt-submit");
|
|
6971
6982
|
const ptCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "post-tool-use");
|
|
6972
6983
|
const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
|
|
6984
|
+
const ptInstallCmd = findExistingHookCommand(settings, EVENT_POST_TOOL_USE, ptCmd) ?? ptCmd;
|
|
6973
6985
|
const userPromptSubmit = upsertHook(settings, EVENT_USER_PROMPT_SUBMIT, usCmd);
|
|
6974
|
-
const postToolUse = upsertHook(settings, EVENT_POST_TOOL_USE,
|
|
6986
|
+
const postToolUse = upsertHook(settings, EVENT_POST_TOOL_USE, ptInstallCmd);
|
|
6987
|
+
const postToolUseFailure = upsertHook(
|
|
6988
|
+
settings,
|
|
6989
|
+
EVENT_POST_TOOL_USE_FAILURE,
|
|
6990
|
+
ptInstallCmd
|
|
6991
|
+
);
|
|
6975
6992
|
const stop = upsertHook(settings, EVENT_STOP, stopCmd);
|
|
6976
6993
|
let backupPath;
|
|
6977
|
-
const anyAdded = userPromptSubmit === "added" || postToolUse === "added" || stop === "added";
|
|
6994
|
+
const anyAdded = userPromptSubmit === "added" || postToolUse === "added" || postToolUseFailure === "added" || stop === "added";
|
|
6978
6995
|
if (!opts.dryRun && anyAdded) {
|
|
6979
6996
|
const backup = writeSettings(settingsPath, settings);
|
|
6980
6997
|
if (backup) backupPath = backup;
|
|
6981
6998
|
} else if (opts.dryRun) {
|
|
6982
6999
|
opts.logger.info(
|
|
6983
|
-
`[dry-run] would ${userPromptSubmit === "added" ? "add" : "keep"} UserPromptSubmit, ${postToolUse === "added" ? "add" : "keep"} PostToolUse, ${stop === "added" ? "add" : "keep"} Stop hook in ${settingsPath}`
|
|
7000
|
+
`[dry-run] would ${userPromptSubmit === "added" ? "add" : "keep"} UserPromptSubmit, ${postToolUse === "added" ? "add" : "keep"} PostToolUse, ${postToolUseFailure === "added" ? "add" : "keep"} PostToolUseFailure, ${stop === "added" ? "add" : "keep"} Stop hook in ${settingsPath}`
|
|
6984
7001
|
);
|
|
6985
7002
|
}
|
|
6986
7003
|
const mcp = opts.skipMcpRegistration ? { action: "skipped", detail: "skipped by caller" } : registerMcp(opts.nodePath, opts.cliScriptPath, opts.dryRun, opts.logger);
|
|
6987
|
-
return {
|
|
7004
|
+
return {
|
|
7005
|
+
mcp,
|
|
7006
|
+
hooks: { userPromptSubmit, postToolUse, postToolUseFailure, stop },
|
|
7007
|
+
backupPath
|
|
7008
|
+
};
|
|
6988
7009
|
}
|
|
6989
7010
|
function uninstallClaudeIntegration(opts) {
|
|
6990
7011
|
const settingsPath = join3(opts.claudeDir, "settings.json");
|
|
@@ -6994,9 +7015,10 @@ function uninstallClaudeIntegration(opts) {
|
|
|
6994
7015
|
const stopCmd = hookCommand(opts.nodePath, opts.cliScriptPath, "stop");
|
|
6995
7016
|
const removedUs = removeHook(settings, EVENT_USER_PROMPT_SUBMIT, usCmd);
|
|
6996
7017
|
const removedPt = removeHook(settings, EVENT_POST_TOOL_USE, ptCmd);
|
|
7018
|
+
const removedPtf = removeHook(settings, EVENT_POST_TOOL_USE_FAILURE, ptCmd);
|
|
6997
7019
|
const removedStop = removeHook(settings, EVENT_STOP, stopCmd);
|
|
6998
7020
|
let backupPath;
|
|
6999
|
-
if (!opts.dryRun && (removedUs || removedPt || removedStop)) {
|
|
7021
|
+
if (!opts.dryRun && (removedUs || removedPt || removedPtf || removedStop)) {
|
|
7000
7022
|
const backup = writeSettings(settingsPath, settings);
|
|
7001
7023
|
if (backup) backupPath = backup;
|
|
7002
7024
|
}
|
|
@@ -7006,6 +7028,7 @@ function uninstallClaudeIntegration(opts) {
|
|
|
7006
7028
|
hooks: {
|
|
7007
7029
|
userPromptSubmit: removedUs ? "added" : "unchanged",
|
|
7008
7030
|
postToolUse: removedPt ? "added" : "unchanged",
|
|
7031
|
+
postToolUseFailure: removedPtf ? "added" : "unchanged",
|
|
7009
7032
|
stop: removedStop ? "added" : "unchanged"
|
|
7010
7033
|
},
|
|
7011
7034
|
backupPath
|
|
@@ -7109,6 +7132,12 @@ function runUninstall(ctx, opts) {
|
|
|
7109
7132
|
ctx.logger.info(
|
|
7110
7133
|
`UserPromptSubmit hook: ${result.hooks.userPromptSubmit === "added" ? "removed" : "not present"}`
|
|
7111
7134
|
);
|
|
7135
|
+
ctx.logger.info(
|
|
7136
|
+
`PostToolUse hook: ${result.hooks.postToolUse === "added" ? "removed" : "not present"}`
|
|
7137
|
+
);
|
|
7138
|
+
ctx.logger.info(
|
|
7139
|
+
`PostToolUseFailure hook: ${result.hooks.postToolUseFailure === "added" ? "removed" : "not present"}`
|
|
7140
|
+
);
|
|
7112
7141
|
ctx.logger.info(
|
|
7113
7142
|
`Stop hook: ${result.hooks.stop === "added" ? "removed" : "not present"}`
|
|
7114
7143
|
);
|
|
@@ -7124,6 +7153,10 @@ function reportInstallResult(ctx, result, dryRun) {
|
|
|
7124
7153
|
ctx.logger.info(
|
|
7125
7154
|
`${prefix}UserPromptSubmit hook: ${result.hooks.userPromptSubmit}`
|
|
7126
7155
|
);
|
|
7156
|
+
ctx.logger.info(`${prefix}PostToolUse hook: ${result.hooks.postToolUse}`);
|
|
7157
|
+
ctx.logger.info(
|
|
7158
|
+
`${prefix}PostToolUseFailure hook: ${result.hooks.postToolUseFailure}`
|
|
7159
|
+
);
|
|
7127
7160
|
ctx.logger.info(`${prefix}Stop hook: ${result.hooks.stop}`);
|
|
7128
7161
|
if (result.backupPath) {
|
|
7129
7162
|
ctx.logger.info(`settings.json backed up: ${result.backupPath}`);
|
|
@@ -7312,7 +7345,7 @@ function runStats(ctx) {
|
|
|
7312
7345
|
|
|
7313
7346
|
// src/commands/serve.ts
|
|
7314
7347
|
async function runServe(opts) {
|
|
7315
|
-
const { startServer } = await import("./server-
|
|
7348
|
+
const { startServer } = await import("./server-BP54BTEL.js");
|
|
7316
7349
|
const { port, host } = startServer({ port: opts.port });
|
|
7317
7350
|
process.stdout.write(`[caveat] web portal: http://${host}:${port}/
|
|
7318
7351
|
`);
|
|
@@ -21934,11 +21967,13 @@ function extractToolResponseText(response) {
|
|
|
21934
21967
|
return "";
|
|
21935
21968
|
}
|
|
21936
21969
|
function isToolError(payload) {
|
|
21970
|
+
if (payload.hook_event_name === "PostToolUseFailure") return true;
|
|
21937
21971
|
const resp = payload.tool_response ?? payload.toolResponse;
|
|
21938
21972
|
if (resp !== null && typeof resp === "object" && !Array.isArray(resp)) {
|
|
21939
21973
|
if (resp.is_error === true) return true;
|
|
21940
21974
|
}
|
|
21941
21975
|
if (payload.is_error === true) return true;
|
|
21976
|
+
if (typeof payload.error === "string" && payload.error.length > 0) return true;
|
|
21942
21977
|
return false;
|
|
21943
21978
|
}
|
|
21944
21979
|
function spawnWorker(job) {
|
|
@@ -22162,7 +22197,9 @@ async function runHook(name, arg) {
|
|
|
22162
22197
|
}
|
|
22163
22198
|
if (name === "post-tool-use") {
|
|
22164
22199
|
if (!isToolError(payload)) process.exit(0);
|
|
22165
|
-
const errText = extractToolResponseText(
|
|
22200
|
+
const errText = extractToolResponseText(
|
|
22201
|
+
payload.tool_response ?? payload.toolResponse ?? payload.error ?? payload
|
|
22202
|
+
);
|
|
22166
22203
|
if (errText) {
|
|
22167
22204
|
spawnWorker({ sessionId, searchText: errText });
|
|
22168
22205
|
}
|
|
@@ -22183,11 +22220,570 @@ async function runHook(name, arg) {
|
|
|
22183
22220
|
process.exit(0);
|
|
22184
22221
|
}
|
|
22185
22222
|
|
|
22223
|
+
// src/commands/codexHookCmd.ts
|
|
22224
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
22225
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
22226
|
+
import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
|
|
22227
|
+
import { join as join10 } from "node:path";
|
|
22228
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
22229
|
+
|
|
22230
|
+
// src/codexHookInstall.ts
|
|
22231
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
22232
|
+
import { dirname as dirname5, join as join9 } from "node:path";
|
|
22233
|
+
function quote2(p) {
|
|
22234
|
+
return p.includes(" ") ? `"${p}"` : p;
|
|
22235
|
+
}
|
|
22236
|
+
function hookCommand2(nodePath, cliScriptPath, event) {
|
|
22237
|
+
return `${quote2(nodePath)} ${quote2(cliScriptPath)} codex-hook ${event}`;
|
|
22238
|
+
}
|
|
22239
|
+
function eventCommandFragment(event) {
|
|
22240
|
+
return `codex-hook ${event}`;
|
|
22241
|
+
}
|
|
22242
|
+
function isSameHookCommand2(actual, expected) {
|
|
22243
|
+
return actual === expected || actual.endsWith(` ${expected}`);
|
|
22244
|
+
}
|
|
22245
|
+
function hasCaveatHook(hooksJson, event, fragment) {
|
|
22246
|
+
return hooksJson.hooks?.[event]?.some(
|
|
22247
|
+
(entry) => entry.hooks?.some((h) => h.command.includes(fragment))
|
|
22248
|
+
) ?? false;
|
|
22249
|
+
}
|
|
22250
|
+
function readHooks(path) {
|
|
22251
|
+
if (!existsSync6(path)) return {};
|
|
22252
|
+
return JSON.parse(readFileSync4(path, "utf-8"));
|
|
22253
|
+
}
|
|
22254
|
+
function writeJsonWithBackup(path, value) {
|
|
22255
|
+
const dir = dirname5(path);
|
|
22256
|
+
if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
|
|
22257
|
+
let backupPath = "";
|
|
22258
|
+
if (existsSync6(path)) {
|
|
22259
|
+
backupPath = `${path}.caveat-backup-${Date.now()}`;
|
|
22260
|
+
copyFileSync2(path, backupPath);
|
|
22261
|
+
}
|
|
22262
|
+
writeFileSync4(path, `${JSON.stringify(value, null, 2)}
|
|
22263
|
+
`, "utf-8");
|
|
22264
|
+
return backupPath;
|
|
22265
|
+
}
|
|
22266
|
+
function upsertHook2(hooksJson, event, command) {
|
|
22267
|
+
hooksJson.hooks ??= {};
|
|
22268
|
+
const list = hooksJson.hooks[event] ??= [];
|
|
22269
|
+
const alreadyPresent = list.some(
|
|
22270
|
+
(entry) => entry.hooks?.some((h) => isSameHookCommand2(h.command, command))
|
|
22271
|
+
);
|
|
22272
|
+
if (alreadyPresent) return "unchanged";
|
|
22273
|
+
list.push({
|
|
22274
|
+
hooks: [
|
|
22275
|
+
{
|
|
22276
|
+
type: "command",
|
|
22277
|
+
command,
|
|
22278
|
+
timeoutSec: 5,
|
|
22279
|
+
async: false,
|
|
22280
|
+
statusMessage: null
|
|
22281
|
+
}
|
|
22282
|
+
]
|
|
22283
|
+
});
|
|
22284
|
+
return "added";
|
|
22285
|
+
}
|
|
22286
|
+
function removeHook2(hooksJson, event, command) {
|
|
22287
|
+
const list = hooksJson.hooks?.[event];
|
|
22288
|
+
if (!list) return false;
|
|
22289
|
+
const before = list.length;
|
|
22290
|
+
const filtered = list.filter(
|
|
22291
|
+
(entry) => !entry.hooks?.some((h) => isSameHookCommand2(h.command, command))
|
|
22292
|
+
);
|
|
22293
|
+
if (filtered.length === before) return false;
|
|
22294
|
+
hooksJson.hooks[event] = filtered;
|
|
22295
|
+
return true;
|
|
22296
|
+
}
|
|
22297
|
+
function enableCodexHooksFeature(raw) {
|
|
22298
|
+
if (/^\s*codex_hooks\s*=\s*true\s*$/m.test(raw)) return { text: raw, changed: false };
|
|
22299
|
+
const lines = raw.split(/\r?\n/);
|
|
22300
|
+
const featuresStart = lines.findIndex((line) => /^\s*\[features]\s*$/.test(line));
|
|
22301
|
+
if (featuresStart === -1) {
|
|
22302
|
+
const prefix = raw.trimEnd();
|
|
22303
|
+
const text = `${prefix}${prefix ? "\n\n" : ""}[features]
|
|
22304
|
+
codex_hooks = true
|
|
22305
|
+
`;
|
|
22306
|
+
return { text, changed: true };
|
|
22307
|
+
}
|
|
22308
|
+
let insertAt = featuresStart + 1;
|
|
22309
|
+
for (let i = featuresStart + 1; i < lines.length; i += 1) {
|
|
22310
|
+
if (/^\s*\[.+]\s*$/.test(lines[i])) {
|
|
22311
|
+
break;
|
|
22312
|
+
}
|
|
22313
|
+
if (/^\s*codex_hooks\s*=/.test(lines[i])) {
|
|
22314
|
+
lines[i] = "codex_hooks = true";
|
|
22315
|
+
return { text: `${lines.join("\n").trimEnd()}
|
|
22316
|
+
`, changed: true };
|
|
22317
|
+
}
|
|
22318
|
+
}
|
|
22319
|
+
lines.splice(insertAt, 0, "codex_hooks = true");
|
|
22320
|
+
return { text: `${lines.join("\n").trimEnd()}
|
|
22321
|
+
`, changed: true };
|
|
22322
|
+
}
|
|
22323
|
+
function writeConfigWithBackup(path, text) {
|
|
22324
|
+
const dir = dirname5(path);
|
|
22325
|
+
if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
|
|
22326
|
+
let backupPath = "";
|
|
22327
|
+
if (existsSync6(path)) {
|
|
22328
|
+
backupPath = `${path}.caveat-backup-${Date.now()}`;
|
|
22329
|
+
copyFileSync2(path, backupPath);
|
|
22330
|
+
}
|
|
22331
|
+
writeFileSync4(path, text, "utf-8");
|
|
22332
|
+
return backupPath;
|
|
22333
|
+
}
|
|
22334
|
+
function installCodexHooks(opts) {
|
|
22335
|
+
const hooksPath = join9(opts.codexHome, "hooks.json");
|
|
22336
|
+
const configPath = join9(opts.codexHome, "config.toml");
|
|
22337
|
+
const hooksJson = readHooks(hooksPath);
|
|
22338
|
+
const userPromptSubmit = upsertHook2(
|
|
22339
|
+
hooksJson,
|
|
22340
|
+
"UserPromptSubmit",
|
|
22341
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit")
|
|
22342
|
+
);
|
|
22343
|
+
const postToolUse = upsertHook2(
|
|
22344
|
+
hooksJson,
|
|
22345
|
+
"PostToolUse",
|
|
22346
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use")
|
|
22347
|
+
);
|
|
22348
|
+
const stop = upsertHook2(
|
|
22349
|
+
hooksJson,
|
|
22350
|
+
"Stop",
|
|
22351
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "stop")
|
|
22352
|
+
);
|
|
22353
|
+
const rawConfig = existsSync6(configPath) ? readFileSync4(configPath, "utf-8") : "";
|
|
22354
|
+
const enabled = enableCodexHooksFeature(rawConfig);
|
|
22355
|
+
const anyHookAdded = userPromptSubmit === "added" || postToolUse === "added" || stop === "added";
|
|
22356
|
+
let backupPath;
|
|
22357
|
+
let configBackupPath;
|
|
22358
|
+
if (opts.dryRun) {
|
|
22359
|
+
opts.logger.info(`[dry-run] would update ${hooksPath}`);
|
|
22360
|
+
opts.logger.info(`[dry-run] would ensure [features].codex_hooks = true in ${configPath}`);
|
|
22361
|
+
} else {
|
|
22362
|
+
if (anyHookAdded) {
|
|
22363
|
+
const backup = writeJsonWithBackup(hooksPath, hooksJson);
|
|
22364
|
+
if (backup) backupPath = backup;
|
|
22365
|
+
}
|
|
22366
|
+
if (enabled.changed) {
|
|
22367
|
+
const backup = writeConfigWithBackup(configPath, enabled.text);
|
|
22368
|
+
if (backup) configBackupPath = backup;
|
|
22369
|
+
}
|
|
22370
|
+
}
|
|
22371
|
+
return {
|
|
22372
|
+
hooks: { userPromptSubmit, postToolUse, stop },
|
|
22373
|
+
feature: enabled.changed ? "enabled" : "unchanged",
|
|
22374
|
+
backupPath,
|
|
22375
|
+
configBackupPath
|
|
22376
|
+
};
|
|
22377
|
+
}
|
|
22378
|
+
function uninstallCodexHooks(opts) {
|
|
22379
|
+
const hooksPath = join9(opts.codexHome, "hooks.json");
|
|
22380
|
+
const hooksJson = readHooks(hooksPath);
|
|
22381
|
+
const userPromptSubmitRemoved = removeHook2(
|
|
22382
|
+
hooksJson,
|
|
22383
|
+
"UserPromptSubmit",
|
|
22384
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "user-prompt-submit")
|
|
22385
|
+
);
|
|
22386
|
+
const postToolUseRemoved = removeHook2(
|
|
22387
|
+
hooksJson,
|
|
22388
|
+
"PostToolUse",
|
|
22389
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "post-tool-use")
|
|
22390
|
+
);
|
|
22391
|
+
const stopRemoved = removeHook2(
|
|
22392
|
+
hooksJson,
|
|
22393
|
+
"Stop",
|
|
22394
|
+
hookCommand2(opts.nodePath, opts.cliScriptPath, "stop")
|
|
22395
|
+
);
|
|
22396
|
+
let backupPath;
|
|
22397
|
+
if (opts.dryRun) {
|
|
22398
|
+
opts.logger.info(`[dry-run] would remove Caveat Codex hooks from ${hooksPath}`);
|
|
22399
|
+
} else if (userPromptSubmitRemoved || postToolUseRemoved || stopRemoved) {
|
|
22400
|
+
const backup = writeJsonWithBackup(hooksPath, hooksJson);
|
|
22401
|
+
if (backup) backupPath = backup;
|
|
22402
|
+
}
|
|
22403
|
+
return {
|
|
22404
|
+
hooks: {
|
|
22405
|
+
userPromptSubmit: userPromptSubmitRemoved ? "added" : "unchanged",
|
|
22406
|
+
postToolUse: postToolUseRemoved ? "added" : "unchanged",
|
|
22407
|
+
stop: stopRemoved ? "added" : "unchanged"
|
|
22408
|
+
},
|
|
22409
|
+
feature: "unchanged",
|
|
22410
|
+
backupPath
|
|
22411
|
+
};
|
|
22412
|
+
}
|
|
22413
|
+
function detectCodexHookInstallation(codexHome) {
|
|
22414
|
+
const hooksPath = join9(codexHome, "hooks.json");
|
|
22415
|
+
const hooksJson = readHooks(hooksPath);
|
|
22416
|
+
const hooks = {
|
|
22417
|
+
userPromptSubmit: hasCaveatHook(
|
|
22418
|
+
hooksJson,
|
|
22419
|
+
"UserPromptSubmit",
|
|
22420
|
+
eventCommandFragment("user-prompt-submit")
|
|
22421
|
+
),
|
|
22422
|
+
postToolUse: hasCaveatHook(hooksJson, "PostToolUse", eventCommandFragment("post-tool-use")),
|
|
22423
|
+
stop: hasCaveatHook(hooksJson, "Stop", eventCommandFragment("stop"))
|
|
22424
|
+
};
|
|
22425
|
+
const count = Object.values(hooks).filter(Boolean).length;
|
|
22426
|
+
return {
|
|
22427
|
+
installation: count === 0 ? "not-installed" : count === 3 ? "installed" : "partial",
|
|
22428
|
+
hooksPath,
|
|
22429
|
+
hooks
|
|
22430
|
+
};
|
|
22431
|
+
}
|
|
22432
|
+
|
|
22433
|
+
// src/commands/codexHookCmd.ts
|
|
22434
|
+
var silentLogger2 = {
|
|
22435
|
+
info: () => {
|
|
22436
|
+
},
|
|
22437
|
+
warn: () => {
|
|
22438
|
+
},
|
|
22439
|
+
error: (m) => process.stderr.write(`[caveat:codex-hook] ${m}
|
|
22440
|
+
`)
|
|
22441
|
+
};
|
|
22442
|
+
async function readStdin2() {
|
|
22443
|
+
const chunks = [];
|
|
22444
|
+
for await (const chunk of process.stdin) {
|
|
22445
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
22446
|
+
}
|
|
22447
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
22448
|
+
}
|
|
22449
|
+
function parsePayload2(raw) {
|
|
22450
|
+
if (!raw) return {};
|
|
22451
|
+
try {
|
|
22452
|
+
return JSON.parse(raw);
|
|
22453
|
+
} catch (err) {
|
|
22454
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22455
|
+
process.stderr.write(`[caveat:codex-hook] json parse error: ${msg}
|
|
22456
|
+
`);
|
|
22457
|
+
return {};
|
|
22458
|
+
}
|
|
22459
|
+
}
|
|
22460
|
+
function buildContextSafely2() {
|
|
22461
|
+
try {
|
|
22462
|
+
return buildContext(silentLogger2);
|
|
22463
|
+
} catch (err) {
|
|
22464
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22465
|
+
process.stderr.write(`[caveat:codex-hook] context error: ${msg}
|
|
22466
|
+
`);
|
|
22467
|
+
return null;
|
|
22468
|
+
}
|
|
22469
|
+
}
|
|
22470
|
+
function searchCaveatsFromTextSafely2(text) {
|
|
22471
|
+
if (!text) return [];
|
|
22472
|
+
let db;
|
|
22473
|
+
try {
|
|
22474
|
+
const ctx = buildContextSafely2();
|
|
22475
|
+
if (!ctx || !existsSync7(ctx.paths.dbPath)) return [];
|
|
22476
|
+
db = openDb({ path: ctx.paths.dbPath });
|
|
22477
|
+
const hits = findCaveatsForPrompt(db, text, {
|
|
22478
|
+
selfIdentity: defaultSelfIdentityTokens()
|
|
22479
|
+
});
|
|
22480
|
+
if (hits.length > 0) {
|
|
22481
|
+
try {
|
|
22482
|
+
markHit(db, hits);
|
|
22483
|
+
} catch (err) {
|
|
22484
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22485
|
+
process.stderr.write(`[caveat:codex-hook] markHit error: ${msg}
|
|
22486
|
+
`);
|
|
22487
|
+
}
|
|
22488
|
+
}
|
|
22489
|
+
return hits;
|
|
22490
|
+
} catch (err) {
|
|
22491
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22492
|
+
process.stderr.write(`[caveat:codex-hook] search error: ${msg}
|
|
22493
|
+
`);
|
|
22494
|
+
return [];
|
|
22495
|
+
} finally {
|
|
22496
|
+
db?.close();
|
|
22497
|
+
}
|
|
22498
|
+
}
|
|
22499
|
+
function loadSignalsSafely2(path) {
|
|
22500
|
+
try {
|
|
22501
|
+
return readCodexSessionSignals(path);
|
|
22502
|
+
} catch (err) {
|
|
22503
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22504
|
+
process.stderr.write(`[caveat:codex-hook] transcript read error: ${msg}
|
|
22505
|
+
`);
|
|
22506
|
+
return null;
|
|
22507
|
+
}
|
|
22508
|
+
}
|
|
22509
|
+
function codexSessionId(payload) {
|
|
22510
|
+
const v = payload.session_id ?? payload.sessionId;
|
|
22511
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
22512
|
+
}
|
|
22513
|
+
function extractToolResponseText2(response) {
|
|
22514
|
+
if (typeof response === "string") return response;
|
|
22515
|
+
if (Array.isArray(response)) {
|
|
22516
|
+
return response.map((item) => {
|
|
22517
|
+
if (typeof item === "string") return item;
|
|
22518
|
+
if (item !== null && typeof item === "object") {
|
|
22519
|
+
const text = item.text;
|
|
22520
|
+
return typeof text === "string" ? text : "";
|
|
22521
|
+
}
|
|
22522
|
+
return "";
|
|
22523
|
+
}).filter(Boolean).join(" ");
|
|
22524
|
+
}
|
|
22525
|
+
if (response !== null && typeof response === "object") {
|
|
22526
|
+
const r = response;
|
|
22527
|
+
if (typeof r.content === "string") return r.content;
|
|
22528
|
+
if (Array.isArray(r.content)) return extractToolResponseText2(r.content);
|
|
22529
|
+
if (typeof r.output === "string") return r.output;
|
|
22530
|
+
if (typeof r.stdout === "string" || typeof r.stderr === "string") {
|
|
22531
|
+
return [r.stdout, r.stderr].filter((x) => typeof x === "string").join(" ");
|
|
22532
|
+
}
|
|
22533
|
+
}
|
|
22534
|
+
return "";
|
|
22535
|
+
}
|
|
22536
|
+
function numericExitCode(v) {
|
|
22537
|
+
return typeof v === "number" && Number.isInteger(v) ? v : null;
|
|
22538
|
+
}
|
|
22539
|
+
function processExitCodeFromText(text) {
|
|
22540
|
+
const m = /Process exited with code\s+(-?\d+)/.exec(text);
|
|
22541
|
+
return m ? Number(m[1]) : null;
|
|
22542
|
+
}
|
|
22543
|
+
function transcriptToolOutput(transcriptPath, toolUseId) {
|
|
22544
|
+
if (!transcriptPath || !toolUseId || !existsSync7(transcriptPath)) return null;
|
|
22545
|
+
let raw = "";
|
|
22546
|
+
try {
|
|
22547
|
+
raw = readFileSync5(transcriptPath, "utf-8");
|
|
22548
|
+
} catch {
|
|
22549
|
+
return null;
|
|
22550
|
+
}
|
|
22551
|
+
for (const line of raw.split("\n")) {
|
|
22552
|
+
if (!line.includes(toolUseId)) continue;
|
|
22553
|
+
let parsed;
|
|
22554
|
+
try {
|
|
22555
|
+
parsed = JSON.parse(line);
|
|
22556
|
+
} catch {
|
|
22557
|
+
continue;
|
|
22558
|
+
}
|
|
22559
|
+
if (parsed === null || typeof parsed !== "object") continue;
|
|
22560
|
+
const payloadObj = parsed.payload;
|
|
22561
|
+
if (payloadObj === null || typeof payloadObj !== "object") continue;
|
|
22562
|
+
const p = payloadObj;
|
|
22563
|
+
if (p.type !== "function_call_output" || p.call_id !== toolUseId) continue;
|
|
22564
|
+
return typeof p.output === "string" ? p.output : "";
|
|
22565
|
+
}
|
|
22566
|
+
return null;
|
|
22567
|
+
}
|
|
22568
|
+
function transcriptExitCode(payload) {
|
|
22569
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
22570
|
+
const toolUseId = typeof payload.tool_use_id === "string" ? payload.tool_use_id : "";
|
|
22571
|
+
const output = transcriptToolOutput(transcriptPath, toolUseId);
|
|
22572
|
+
return output === null ? null : processExitCodeFromText(output);
|
|
22573
|
+
}
|
|
22574
|
+
function isShellLikeTool(payload) {
|
|
22575
|
+
const name = typeof payload.tool_name === "string" ? payload.tool_name : "";
|
|
22576
|
+
if (name === "Bash" || name === "exec_command") return true;
|
|
22577
|
+
const input = payload.tool_input;
|
|
22578
|
+
if (input !== null && typeof input === "object" && !Array.isArray(input)) {
|
|
22579
|
+
const r = input;
|
|
22580
|
+
return typeof r.command === "string" || typeof r.cmd === "string";
|
|
22581
|
+
}
|
|
22582
|
+
return false;
|
|
22583
|
+
}
|
|
22584
|
+
function toolInputText(input) {
|
|
22585
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) return "";
|
|
22586
|
+
const r = input;
|
|
22587
|
+
const command = r.command ?? r.cmd;
|
|
22588
|
+
return typeof command === "string" ? command : "";
|
|
22589
|
+
}
|
|
22590
|
+
function isCodexToolError(payload) {
|
|
22591
|
+
if (payload.is_error === true) return true;
|
|
22592
|
+
const topExit = numericExitCode(payload.exit_code ?? payload.exitCode);
|
|
22593
|
+
if (topExit !== null) return topExit !== 0;
|
|
22594
|
+
const resp = payload.tool_response ?? payload.toolResponse;
|
|
22595
|
+
if (resp !== null && typeof resp === "object" && !Array.isArray(resp)) {
|
|
22596
|
+
const r = resp;
|
|
22597
|
+
if (r.is_error === true) return true;
|
|
22598
|
+
const exit2 = numericExitCode(r.exit_code ?? r.exitCode);
|
|
22599
|
+
if (exit2 !== null) return exit2 !== 0;
|
|
22600
|
+
}
|
|
22601
|
+
const responseExit = processExitCodeFromText(extractToolResponseText2(resp));
|
|
22602
|
+
if (responseExit !== null) return responseExit !== 0;
|
|
22603
|
+
const transcriptExit = transcriptExitCode(payload);
|
|
22604
|
+
if (transcriptExit !== null) return transcriptExit !== 0;
|
|
22605
|
+
return false;
|
|
22606
|
+
}
|
|
22607
|
+
function buildCodexPostToolUseWorkerJob(payload) {
|
|
22608
|
+
const sessionId = codexSessionId(payload);
|
|
22609
|
+
if (!sessionId) return null;
|
|
22610
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : void 0;
|
|
22611
|
+
const toolUseId = typeof payload.tool_use_id === "string" ? payload.tool_use_id : void 0;
|
|
22612
|
+
const responseText = extractToolResponseText2(payload.tool_response ?? payload.toolResponse);
|
|
22613
|
+
const inputText = toolInputText(payload.tool_input);
|
|
22614
|
+
const transcriptOutput = transcriptPath && toolUseId ? transcriptToolOutput(transcriptPath, toolUseId) : null;
|
|
22615
|
+
const searchText = [inputText, responseText, transcriptOutput ?? ""].map((s) => s.trim()).filter(Boolean).join("\n");
|
|
22616
|
+
const knownError = isCodexToolError(payload);
|
|
22617
|
+
if (knownError) {
|
|
22618
|
+
return { sessionId, searchText, knownError: true, transcriptPath, toolUseId };
|
|
22619
|
+
}
|
|
22620
|
+
if (transcriptPath && toolUseId && isShellLikeTool(payload) && searchText) {
|
|
22621
|
+
return {
|
|
22622
|
+
sessionId,
|
|
22623
|
+
searchText,
|
|
22624
|
+
knownError: false,
|
|
22625
|
+
allowSymptomOnly: true,
|
|
22626
|
+
transcriptPath,
|
|
22627
|
+
toolUseId
|
|
22628
|
+
};
|
|
22629
|
+
}
|
|
22630
|
+
return null;
|
|
22631
|
+
}
|
|
22632
|
+
function codexContextOutput(text, eventName = "UserPromptSubmit") {
|
|
22633
|
+
return JSON.stringify({
|
|
22634
|
+
hookSpecificOutput: {
|
|
22635
|
+
hookEventName: eventName,
|
|
22636
|
+
additionalContext: text
|
|
22637
|
+
}
|
|
22638
|
+
});
|
|
22639
|
+
}
|
|
22640
|
+
function codexStopOutput(text) {
|
|
22641
|
+
return JSON.stringify({
|
|
22642
|
+
decision: "block",
|
|
22643
|
+
reason: text
|
|
22644
|
+
});
|
|
22645
|
+
}
|
|
22646
|
+
function drainForSession2(sessionId, eventName = "UserPromptSubmit") {
|
|
22647
|
+
const ctx = buildContextSafely2();
|
|
22648
|
+
if (!ctx) return;
|
|
22649
|
+
const reminders = drainPendingReminders(ctx.caveatHome, sessionId);
|
|
22650
|
+
for (const text of reminders) {
|
|
22651
|
+
process.stdout.write(`${codexContextOutput(text, eventName)}
|
|
22652
|
+
`);
|
|
22653
|
+
}
|
|
22654
|
+
}
|
|
22655
|
+
async function waitForTranscriptOutput(transcriptPath, toolUseId) {
|
|
22656
|
+
const deadline = Date.now() + 2e3;
|
|
22657
|
+
while (Date.now() <= deadline) {
|
|
22658
|
+
const output = transcriptToolOutput(transcriptPath, toolUseId);
|
|
22659
|
+
if (output !== null) return output;
|
|
22660
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
22661
|
+
}
|
|
22662
|
+
return null;
|
|
22663
|
+
}
|
|
22664
|
+
async function processCodexWorkerJob(job, opts = { waitForTranscript: true }) {
|
|
22665
|
+
if (!job.searchText || !job.sessionId) return;
|
|
22666
|
+
let searchText = job.searchText;
|
|
22667
|
+
let knownError = job.knownError === true;
|
|
22668
|
+
if (opts.waitForTranscript && job.transcriptPath && job.toolUseId) {
|
|
22669
|
+
const transcriptOutput = await waitForTranscriptOutput(job.transcriptPath, job.toolUseId);
|
|
22670
|
+
if (transcriptOutput) {
|
|
22671
|
+
const exit2 = processExitCodeFromText(transcriptOutput);
|
|
22672
|
+
if (exit2 !== null) {
|
|
22673
|
+
if (exit2 === 0) return;
|
|
22674
|
+
knownError = true;
|
|
22675
|
+
}
|
|
22676
|
+
searchText = [searchText, transcriptOutput].filter(Boolean).join("\n");
|
|
22677
|
+
}
|
|
22678
|
+
}
|
|
22679
|
+
if (!knownError && job.allowSymptomOnly !== true) return;
|
|
22680
|
+
const hits = searchCaveatsFromTextSafely2(searchText);
|
|
22681
|
+
if (hits.length === 0) return;
|
|
22682
|
+
const ctx = buildContextSafely2();
|
|
22683
|
+
if (!ctx) return;
|
|
22684
|
+
try {
|
|
22685
|
+
appendPendingReminder(ctx.caveatHome, job.sessionId, toolErrorReminderText(hits));
|
|
22686
|
+
} catch {
|
|
22687
|
+
}
|
|
22688
|
+
}
|
|
22689
|
+
async function runCodexWorker(workFile) {
|
|
22690
|
+
let raw;
|
|
22691
|
+
try {
|
|
22692
|
+
raw = readFileSync5(workFile, "utf-8");
|
|
22693
|
+
} catch {
|
|
22694
|
+
process.exit(0);
|
|
22695
|
+
}
|
|
22696
|
+
try {
|
|
22697
|
+
unlinkSync2(workFile);
|
|
22698
|
+
} catch {
|
|
22699
|
+
}
|
|
22700
|
+
let job;
|
|
22701
|
+
try {
|
|
22702
|
+
job = JSON.parse(raw);
|
|
22703
|
+
} catch {
|
|
22704
|
+
process.exit(0);
|
|
22705
|
+
}
|
|
22706
|
+
await processCodexWorkerJob(job);
|
|
22707
|
+
process.exit(0);
|
|
22708
|
+
}
|
|
22709
|
+
function runDiagnostics(codexHome = process.env.CODEX_HOME ?? join10(homedir3(), ".codex")) {
|
|
22710
|
+
const features = spawnSync3("codex", ["features", "list"], {
|
|
22711
|
+
encoding: "utf-8",
|
|
22712
|
+
maxBuffer: 1024 * 1024
|
|
22713
|
+
});
|
|
22714
|
+
const featureOutput = [features.stdout, features.stderr].filter(Boolean).join("\n");
|
|
22715
|
+
const hasHooks = /^codex_hooks\s+\S+\s+true\b/m.test(featureOutput);
|
|
22716
|
+
const installation = detectCodexHookInstallation(codexHome);
|
|
22717
|
+
const result = {
|
|
22718
|
+
availability: features.error || features.status !== 0 || !hasHooks ? "unavailable" : "available",
|
|
22719
|
+
codexBinary: features.error ? "missing" : "present",
|
|
22720
|
+
codexHooksFeature: hasHooks ? "enabled" : "not-enabled",
|
|
22721
|
+
installation: installation.installation,
|
|
22722
|
+
codexHome,
|
|
22723
|
+
hooksPath: installation.hooksPath,
|
|
22724
|
+
installedHooks: installation.hooks,
|
|
22725
|
+
evidence: featureOutput.split("\n").find((line) => line.trim().startsWith("codex_hooks")) ?? null
|
|
22726
|
+
};
|
|
22727
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
22728
|
+
`);
|
|
22729
|
+
}
|
|
22730
|
+
async function runCodexHook(name, arg) {
|
|
22731
|
+
if (name === "diagnostics") {
|
|
22732
|
+
runDiagnostics(arg);
|
|
22733
|
+
return;
|
|
22734
|
+
}
|
|
22735
|
+
if (name === "worker") {
|
|
22736
|
+
if (!arg) process.exit(0);
|
|
22737
|
+
await runCodexWorker(arg);
|
|
22738
|
+
return;
|
|
22739
|
+
}
|
|
22740
|
+
let raw = "";
|
|
22741
|
+
try {
|
|
22742
|
+
raw = await readStdin2();
|
|
22743
|
+
} catch (err) {
|
|
22744
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
22745
|
+
process.stderr.write(`[caveat:codex-hook] stdin read error: ${msg}
|
|
22746
|
+
`);
|
|
22747
|
+
process.exit(0);
|
|
22748
|
+
}
|
|
22749
|
+
const payload = parsePayload2(raw);
|
|
22750
|
+
const sessionId = codexSessionId(payload);
|
|
22751
|
+
if (sessionId && name === "user-prompt-submit") drainForSession2(sessionId);
|
|
22752
|
+
else if (!sessionId) process.stderr.write("[caveat:codex-hook] missing session_id; pending drain disabled\n");
|
|
22753
|
+
if (name === "user-prompt-submit") {
|
|
22754
|
+
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
22755
|
+
const hits = searchCaveatsFromTextSafely2(prompt);
|
|
22756
|
+
if (hits.length > 0) {
|
|
22757
|
+
process.stdout.write(`${codexContextOutput(userPromptSubmitReminderText(hits))}
|
|
22758
|
+
`);
|
|
22759
|
+
}
|
|
22760
|
+
process.exit(0);
|
|
22761
|
+
}
|
|
22762
|
+
if (name === "post-tool-use") {
|
|
22763
|
+
const job = buildCodexPostToolUseWorkerJob(payload);
|
|
22764
|
+
if (job) await processCodexWorkerJob(job, { waitForTranscript: false });
|
|
22765
|
+
process.exit(0);
|
|
22766
|
+
}
|
|
22767
|
+
if (name === "stop") {
|
|
22768
|
+
if (payload.stop_hook_active === true) process.exit(0);
|
|
22769
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
|
|
22770
|
+
const signals = transcriptPath ? loadSignalsSafely2(transcriptPath) : null;
|
|
22771
|
+
if (!signals || !hasAnyStruggleSignal(signals)) process.exit(0);
|
|
22772
|
+
const related = searchCaveatsFromTextSafely2(struggleSearchText(signals));
|
|
22773
|
+
process.stdout.write(`${codexStopOutput(stopReminderText(signals, related))}
|
|
22774
|
+
`);
|
|
22775
|
+
process.exit(0);
|
|
22776
|
+
}
|
|
22777
|
+
process.stderr.write(`[caveat:codex-hook] unknown hook name: ${name}
|
|
22778
|
+
`);
|
|
22779
|
+
process.exit(0);
|
|
22780
|
+
}
|
|
22781
|
+
|
|
22186
22782
|
// src/commands/pull.ts
|
|
22187
|
-
import { existsSync as
|
|
22188
|
-
import { join as
|
|
22783
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4 } from "node:fs";
|
|
22784
|
+
import { join as join11 } from "node:path";
|
|
22189
22785
|
async function runPull(ctx) {
|
|
22190
|
-
if (!
|
|
22786
|
+
if (!existsSync8(ctx.paths.communityDir)) {
|
|
22191
22787
|
ctx.logger.info(
|
|
22192
22788
|
"no community repos yet \u2014 add one with `caveat community add <github-url>`."
|
|
22193
22789
|
);
|
|
@@ -22207,15 +22803,15 @@ async function runPull(ctx) {
|
|
|
22207
22803
|
const db = openDb({ path: ctx.paths.dbPath, logger: ctx.logger });
|
|
22208
22804
|
try {
|
|
22209
22805
|
rebuildAll(db);
|
|
22210
|
-
if (
|
|
22806
|
+
if (existsSync8(ctx.paths.entriesDir)) {
|
|
22211
22807
|
const own = scanSource({ db, source: "own", entriesRoot: ctx.paths.entriesDir });
|
|
22212
22808
|
ctx.logger.info(`own: +${own.added}`);
|
|
22213
22809
|
}
|
|
22214
22810
|
for (const entry of readdirSync4(ctx.paths.communityDir, { withFileTypes: true })) {
|
|
22215
22811
|
if (!entry.isDirectory()) continue;
|
|
22216
22812
|
const source = `community/${entry.name}`;
|
|
22217
|
-
const root =
|
|
22218
|
-
if (!
|
|
22813
|
+
const root = join11(ctx.paths.communityDir, entry.name, "entries");
|
|
22814
|
+
if (!existsSync8(root)) continue;
|
|
22219
22815
|
const scan = scanSource({ db, source, entriesRoot: root });
|
|
22220
22816
|
ctx.logger.info(`${source}: +${scan.added}`);
|
|
22221
22817
|
}
|
|
@@ -22225,10 +22821,10 @@ async function runPull(ctx) {
|
|
|
22225
22821
|
}
|
|
22226
22822
|
|
|
22227
22823
|
// src/commands/codexSidecar.ts
|
|
22228
|
-
import { spawnSync as
|
|
22229
|
-
import { mkdirSync as
|
|
22230
|
-
import { tmpdir as
|
|
22231
|
-
import { dirname as
|
|
22824
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
22825
|
+
import { mkdirSync as mkdirSync5, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
22826
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
22827
|
+
import { dirname as dirname6, join as join12 } from "node:path";
|
|
22232
22828
|
import { cwd, exit } from "node:process";
|
|
22233
22829
|
function runCodexSidecarDiagnostics(logger, opts) {
|
|
22234
22830
|
const plan = buildCodexSidecarDiagnosticsCommand({
|
|
@@ -22260,8 +22856,8 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
|
|
|
22260
22856
|
process.stdout.write(JSON.stringify({ status: "skipped", decision }, null, 2) + "\n");
|
|
22261
22857
|
exit(0);
|
|
22262
22858
|
}
|
|
22263
|
-
const contextDir = mkdtempSync2(
|
|
22264
|
-
const contextFile =
|
|
22859
|
+
const contextDir = mkdtempSync2(join12(tmpdir3(), "caveat-sidecar-context-"));
|
|
22860
|
+
const contextFile = join12(contextDir, "context.json");
|
|
22265
22861
|
let status = 1;
|
|
22266
22862
|
try {
|
|
22267
22863
|
const blocks = collectCaveatContextBlocks(ctx, {
|
|
@@ -22270,7 +22866,7 @@ function runCodexSidecarWithCaveats(ctx, workflow, prompt, opts) {
|
|
|
22270
22866
|
source: opts.source,
|
|
22271
22867
|
visibility: opts.visibility
|
|
22272
22868
|
});
|
|
22273
|
-
|
|
22869
|
+
writeFileSync6(contextFile, JSON.stringify({ context: blocks }, null, 2) + "\n", "utf-8");
|
|
22274
22870
|
const plan = buildCodexSidecarRunCommand({
|
|
22275
22871
|
workflow,
|
|
22276
22872
|
projectRoot: opts.project ?? cwd(),
|
|
@@ -22325,7 +22921,7 @@ function defaultPreset(workflow) {
|
|
|
22325
22921
|
}
|
|
22326
22922
|
function executePlan(logger, command, args, options = {}) {
|
|
22327
22923
|
logger.info(`[codex-sidecar] ${command} ${args.map(shellDisplayQuote).join(" ")}`);
|
|
22328
|
-
const result =
|
|
22924
|
+
const result = spawnSync4(command, args, { encoding: "utf-8", stdio: "pipe" });
|
|
22329
22925
|
if (result.stdout) process.stdout.write(result.stdout);
|
|
22330
22926
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
22331
22927
|
if (options.saveResult && result.stdout) {
|
|
@@ -22339,8 +22935,8 @@ function executePlan(logger, command, args, options = {}) {
|
|
|
22339
22935
|
}
|
|
22340
22936
|
function saveStructuredResult(path, stdout) {
|
|
22341
22937
|
const parsed = JSON.parse(stdout);
|
|
22342
|
-
|
|
22343
|
-
|
|
22938
|
+
mkdirSync5(dirname6(path), { recursive: true });
|
|
22939
|
+
writeFileSync6(path, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
22344
22940
|
}
|
|
22345
22941
|
function shellDisplayQuote(value) {
|
|
22346
22942
|
return /[\s"'$`]/.test(value) ? JSON.stringify(value) : value;
|
|
@@ -22493,6 +23089,62 @@ program.command("hook <name> [arg]").description(
|
|
|
22493
23089
|
).action(async (name, arg) => {
|
|
22494
23090
|
await runHook(name, arg);
|
|
22495
23091
|
});
|
|
23092
|
+
var codexHook = program.command("codex-hook").description("Install or run Codex hooks for Caveat");
|
|
23093
|
+
codexHook.command("install").description("Install Caveat hooks into ~/.codex/hooks.json and enable codex_hooks").option("--dry-run", "show planned changes without writing", false).option("--codex-home <path>", "Codex home directory", process.env.CODEX_HOME ?? `${process.env.HOME}/.codex`).action((opts) => {
|
|
23094
|
+
const cliScriptPath = process.argv[1];
|
|
23095
|
+
if (!cliScriptPath) {
|
|
23096
|
+
process.stderr.write("[caveat:error] cannot determine CLI script path\n");
|
|
23097
|
+
process.exit(1);
|
|
23098
|
+
}
|
|
23099
|
+
const result = installCodexHooks({
|
|
23100
|
+
codexHome: opts.codexHome,
|
|
23101
|
+
cliScriptPath,
|
|
23102
|
+
nodePath: process.execPath,
|
|
23103
|
+
dryRun: opts.dryRun,
|
|
23104
|
+
logger: stdoutLogger
|
|
23105
|
+
});
|
|
23106
|
+
stdoutLogger.info(`UserPromptSubmit hook: ${result.hooks.userPromptSubmit}`);
|
|
23107
|
+
stdoutLogger.info(`PostToolUse hook: ${result.hooks.postToolUse}`);
|
|
23108
|
+
stdoutLogger.info(`Stop hook: ${result.hooks.stop}`);
|
|
23109
|
+
stdoutLogger.info(`codex_hooks feature: ${result.feature}`);
|
|
23110
|
+
if (result.backupPath) stdoutLogger.info(`hooks.json backed up: ${result.backupPath}`);
|
|
23111
|
+
if (result.configBackupPath) {
|
|
23112
|
+
stdoutLogger.info(`config.toml backed up: ${result.configBackupPath}`);
|
|
23113
|
+
}
|
|
23114
|
+
});
|
|
23115
|
+
codexHook.command("uninstall").description("Remove Caveat-owned Codex hooks from ~/.codex/hooks.json").option("--dry-run", "show planned changes without writing", false).option("--codex-home <path>", "Codex home directory", process.env.CODEX_HOME ?? `${process.env.HOME}/.codex`).action((opts) => {
|
|
23116
|
+
const cliScriptPath = process.argv[1];
|
|
23117
|
+
if (!cliScriptPath) {
|
|
23118
|
+
process.stderr.write("[caveat:error] cannot determine CLI script path\n");
|
|
23119
|
+
process.exit(1);
|
|
23120
|
+
}
|
|
23121
|
+
const result = uninstallCodexHooks({
|
|
23122
|
+
codexHome: opts.codexHome,
|
|
23123
|
+
cliScriptPath,
|
|
23124
|
+
nodePath: process.execPath,
|
|
23125
|
+
dryRun: opts.dryRun,
|
|
23126
|
+
logger: stdoutLogger
|
|
23127
|
+
});
|
|
23128
|
+
stdoutLogger.info(`UserPromptSubmit hook: ${result.hooks.userPromptSubmit === "added" ? "removed" : "not present"}`);
|
|
23129
|
+
stdoutLogger.info(`PostToolUse hook: ${result.hooks.postToolUse === "added" ? "removed" : "not present"}`);
|
|
23130
|
+
stdoutLogger.info(`Stop hook: ${result.hooks.stop === "added" ? "removed" : "not present"}`);
|
|
23131
|
+
if (result.backupPath) stdoutLogger.info(`hooks.json backed up: ${result.backupPath}`);
|
|
23132
|
+
});
|
|
23133
|
+
codexHook.command("diagnostics").description("Check local Codex hook availability").option("--codex-home <path>", "Codex home directory", process.env.CODEX_HOME ?? `${process.env.HOME}/.codex`).action(async (opts) => {
|
|
23134
|
+
await runCodexHook("diagnostics", opts.codexHome);
|
|
23135
|
+
});
|
|
23136
|
+
codexHook.command("user-prompt-submit").description("Run the Codex UserPromptSubmit hook").action(async () => {
|
|
23137
|
+
await runCodexHook("user-prompt-submit");
|
|
23138
|
+
});
|
|
23139
|
+
codexHook.command("post-tool-use").description("Run the Codex PostToolUse hook").action(async () => {
|
|
23140
|
+
await runCodexHook("post-tool-use");
|
|
23141
|
+
});
|
|
23142
|
+
codexHook.command("stop").description("Run the Codex Stop hook").action(async () => {
|
|
23143
|
+
await runCodexHook("stop");
|
|
23144
|
+
});
|
|
23145
|
+
codexHook.command("worker <workFile>").description("Run the detached Codex hook worker").action(async (workFile) => {
|
|
23146
|
+
await runCodexHook("worker", workFile);
|
|
23147
|
+
});
|
|
22496
23148
|
var codexSidecar = program.command("codex-sidecar").description("Check Codex sidecar availability for the current repository");
|
|
22497
23149
|
codexSidecar.command("diagnostics").description("Run codex-sidecar diagnostics for this repository").option("--project <path>", "repository root to check").option("--preset <preset>", "codex-sidecar preset", "review").option("--command <command>", "codex-sidecar executable", "codex-sidecar").option("--node-cli <path>", "development path to codex-sidecar CLI JS").option("--save-result <path>", "write structured SidecarResult JSON to this path").action((opts) => {
|
|
22498
23150
|
runCodexSidecarDiagnostics(stdoutLogger, opts);
|