Getting Started
Install
bun add @pivanov/agent-hooks-bridgenpm install @pivanov/agent-hooks-bridgeyarn add @pivanov/agent-hooks-bridgepnpm add @pivanov/agent-hooks-bridgeThe package is ESM-only. Requires Bun ≥1.0 or Node ≥22.
Quickstart with init
The fastest path is the init subcommand. It scaffolds a starter hook script (no-op, all five events wired) and runs install in one step:
bunx @pivanov/agent-hooks-bridge initThis creates ./.hooks/format.ts, marks it executable, and writes .claude/settings.json, .cursor/hooks.json, .codex/hooks.toml pointing at it. Edit the script to add your logic, then re-run install only if you change the path or which events you want wired.
To verify the install:
bunx @pivanov/agent-hooks-bridge doctorThe rest of this page walks through the same flow by hand if you'd rather see what init does.
Write a hook (manual)
Create .hooks/format.ts in your project:
#!/usr/bin/env bun
// .hooks/format.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: "rm -rf is blocked by policy" };
}
return { decision: "allow" };
},
PostToolUse: async (event) => {
if (event.tool !== "Edit" && event.tool !== "Write") {
return { decision: "allow" };
}
const file = event.tool_input.file_path;
if (typeof file !== "string" || !/\.(ts|tsx|js|jsx)$/.test(file)) {
return { decision: "allow" };
}
await Bun.spawn(["biome", "check", "--write", file]).exited;
return {
decision: "allow",
additional_context: `biome auto-fixed ${file}`,
};
},
});
await run(hooks);Make it executable:
chmod +x .hooks/format.tsWire it into all three hosts
bunx @pivanov/agent-hooks-bridge install ./.hooks/format.tsThis generates / merges these files:
.claude/settings.json.cursor/hooks.json.codex/hooks.toml
Each pointing at your one script. Re-running the install is idempotent: only entries that point at your script are replaced; everything else is preserved.
Preview without writing:
bunx @pivanov/agent-hooks-bridge install ./.hooks/format.ts --dry-runTo install only into the hosts you actually have installed, pass --hosts auto. To install into your user-level config so the hook fires across every project, pass --global. See Install reference for the full flag list.
What happens at runtime
When the agent fires a hook, the host spawns your script with --host <host> and pipes a JSON payload to stdin:
host → spawn ./.hooks/format.ts --host cursor
host → stdin: { "hook_event_name": "beforeShellExecution", ... }
run() reads stdin
→ resolves host from --host argv (falls back to stdin detection)
→ adapter.parse(raw) → unified TUnifiedEvent
→ your handler runs
→ adapter.serialize(response, event) → native stdout
→ exit (0 = proceed, 2 = deny, 1 = bridge error)
host ← stdout JSON
host ← exit codeNext steps
- Capability Matrix: what works on which host
- API: defineHook, API: run
- Per-adapter notes: host-specific behavior
- Guides: worked examples
doctor: inspect installed configs after wiring or editing- JSON Schema: validate hook payloads from non-TS code