polyhook
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Pass
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Write AI coding agent hooks once. Run them everywhere. Multi-language SDK (TS/Rust/Go/Python/.NET) over a shared WASM core — supports Claude Code, Cursor, Windsurf, Cline, and Amp.
polyhook
Write AI coding agent hooks once. Run them everywhere.
polyhook is a multi-language SDK for AI coding agent hooks. Instead of parsing each tool's bespoke stdin/stdout format, you call polyhook — it detects which tool invoked your binary, deserializes the event into a normalized struct, and serializes your response back in the format that tool expects.
Your hook binary runs unchanged whether Claude Code, Cursor, Windsurf, Cline, Amp, or Pi invoked it.
Architecture
Rust core compiled to WASM. Every language SDK is a thin shim over the same binary — no logic re-implemented per language. See ARCHITECTURE.md.
The Problem
Every AI coding tool triggers hooks differently. The same logical event — "bash command about to run" — arrives in a different shape and expects a different response format:
Claude Code → stdin: { "tool_name": "Bash", "tool_input": { "command": "..." } }
stdout: { "decision": "block", "reason": "..." }
Cursor → stdin: { "type": "BeforeToolCall", "toolCall": { "name": "run_terminal_cmd", "args": {...} } }
stdout: { "action": "deny", "message": "..." }
Windsurf → stdin: { "event": "pre_tool", "tool": "run_command", "parameters": {...} }
stdout: { "allow": false, "reason": "..." }
Without polyhook you write a parser and serializer for each tool. With polyhook you call read() and respond().
How It Works
AI tool ──stdin──▶ your binary
│
polyhook.read() ← WASM: detects tool, parses format
│
your hook logic ← tool-agnostic
│
polyhook.respond() ← WASM: serializes to tool's format
│
your binary ──stdout──▶ AI tool
Normalized Types
Types are auto-generated in every SDK from core/schema.json — not hand-written. See ARCHITECTURE.md for the full generation pipeline.
HookEvent
interface HookEvent {
event: "tool:before" | "tool:after" | "session:start" | "session:stop" | "turn:stop" | "agent:stop" | "prompt:submit" | "notification";
tool?: string; // normalized tool name, e.g. "bash", "write_file"
input?: Record<string, unknown>; // tool input arguments
prompt?: string; // user prompt (prompt:submit only)
reply?: string; // agent's final reply text (turn:stop only)
transcriptPath?: string; // session transcript file, when the caller sends one
output?: Record<string, unknown>; // tool output (tool:after only)
sessionId: string;
agentId?: string;
caller: "claude-code" | "cursor" | "windsurf" | "cline" | "amp" | "gemini-cli" | "hermes" | "pi" | "codex" | "unknown";
}
Normalized tool names: docs/tool-names.md
HookResponse
type HookResponse =
| { action: "approve" }
| { action: "block"; message: string }
| { action: "modify"; input: Record<string, unknown> }
| { action: "context"; context: string } // prompt:submit: add text to the model's context
Language SDKs
All SDKs expose the same two functions. The WASM module does all the work.
TypeScript / JavaScript
npm install @polyhook/sdk
Examples: packages/sdk-ts/examples/
Rust
The only SDK that links core natively — no WASM overhead.
cargo add polyhook
Examples: packages/sdk-rust/examples/
Go
go get github.com/tupe12334/polyhook/packages/sdk-go
Examples: packages/sdk-go/examples/
C# / .NET
dotnet add package Polyhook.Sdk
Examples: packages/sdk-dotnet/examples/
Python
pip install polyhook
Examples: packages/sdk-python/examples/
Any language with a WASM runtime can bind polyhook. See BINDINGS.md for the raw WASM API.
Supported Tools
| Tool | Status |
|---|---|
| Claude Code | ✅ Supported |
| Cursor | ✅ Supported |
| Windsurf | ✅ Supported |
| Cline | ✅ Supported |
| Amp | ✅ Supported |
| Gemini CLI | ✅ Supported |
| Hermes Agent | ✅ Supported |
| Pi | ✅ Supported |
| Codex | ✅ Supported |
| Continue | 🚧 In progress |
| Aider | 🚧 In progress |
| Copilot | 📋 Planned |
prompt:submit context injection ({ action: "context" }) works on Claude Code, Codex, Gemini CLI (BeforeAgent), Hermes (pre_llm_call) and Cline (UserPromptSubmit). Cursor, Windsurf and Amp have no prompt hook that can add context, so there a context response is sent as approve.
Pi uses the Claude Code-compatible hook payload format; set POLYHOOK_CALLER=pi if you want it labeled explicitly.
Source of truth: tools.toml — hook documentation links: docs/tool-names.md
Design Goals
- One implementation. All detection and serialization logic lives in
core. Language SDKs cannot diverge. - Runtime only. No code generation. polyhook runs inside your binary at hook invocation time.
- Transparent fallback. Unknown caller →
caller: "unknown", best-effort parse. Your logic keeps running. - Bring your own runtime. Each SDK ships
polyhook.wasmand a thin host binding. Swap the WASM runtime if your platform requires it.
Contributing
See CONTRIBUTING.md. To add tool support, add detection heuristics and mappings to core — all language SDKs pick it up automatically on the next WASM build.
License
MIT
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found