Why agent-hooks-bridge
The problem
Claude Code, Cursor, and Codex CLI all support hooks. Their stdin and stdout schemas don't agree. The same "before a Bash command runs" event has three different stdin shapes:
jsonc
{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "..."
}
}jsonc
{
"hook_event_name": "beforeShellExecution",
"command": "...",
"cursor_version": "0.46.0"
}jsonc
{
"hook_event_name": "PreToolUse",
"turn_id": "...",
"tool_name": "apply_patch"
}Cursor uses a camelCase event name and a flatter shape. Codex sends turn_id and uses apply_patch for file edits.
Response shapes differ similarly. Cursor's beforeSubmitPrompt accepts only continue and user_message; no context injection. Each host has its own permission-decision encoding.
To support all three, you currently write three scripts.
What this package does
You write one hook using a unified schema:
ts
import { defineHook, run } from "@pivanov/agent-hooks-bridge";
const hooks = defineHook({
PreToolUse: (event) => {
if (event.tool === "Bash" && /rm -rf/.test(String(event.tool_input.command ?? ""))) {
return { decision: "deny", reason: "blocked by policy" };
}
return { decision: "allow" };
},
});
await run(hooks);At runtime the bridge:
- Reads stdin.
- Resolves the host: explicit option, then
--hostflag in argv, then stdin markers (cursor_version,turn_id,transcript_path). - Parses the native payload into a unified event.
- Calls your handler.
- Serializes the response back into the host's native shape.
- Sets the exit code.
What it does not do
- Validate input at runtime (no Zod, no schema library).
- Ship a curated set of hook scripts.
- Provide skills, agent definitions, rules, or security scanning.
- Generate code at install time.
See Capability Matrix for the per-host field-by-field breakdown.