A local event gateway for AI code agents.
Capture lifecycle events from Claude Code, Gemini CLI, and more — fan them out to any HTTP endpoint, webhook, or device.
Install • Quickstart • Agent Setup • Config • Commands
Every AI code agent has its own hook system — different formats, different configs, scattered across projects. Adding a new webhook means editing every project's settings for every agent you use.
agent-pulse solves this with a single global config:
- One config, all agents — Define your clients once in
~/.config/agent-pulse/config.yaml. Every project, every agent, every event routed through one place. - Zero per-project setup — No more duplicating hook configs across repositories. Register your hooks once and you're done.
- Fan-out to anything — HTTP endpoints, webhooks, IoT devices (ESP32, Raspberry Pi, etc.), scripts — if it speaks HTTP, agent-pulse can reach it.
- Fully local — Everything runs on your machine. No cloud, no accounts, no data leaves your network.
- Enrich with metadata — Attach custom data to every lifecycle hook via a project-level
.agent-pulse.json— project name, team, or anything your clients need. - Filter what matters — Route specific events to specific clients. Send
notificationevents to your desk light,session_startto your dashboard, everything to your logger. - Real-time streaming — Connect to the SSE endpoint and watch events flow in real-time. Perfect for dashboards, browser UIs, or a simple
curl -N.
- Event bridge — Local HTTP gateway that receives agent lifecycle events and dispatches them to registered clients
- Multi-provider — Support for Claude Code and Gemini CLI (Codex CLI and OpenCode on the roadmap)
- Client routing — Per-client filtering by event type and provider
- Interactive wizard —
client addwalks you through registration step by step - Custom headers — Attach headers to client requests with environment variable interpolation (
${API_TOKEN}) - Auto-start — The gateway starts automatically when a hook fires — no need to manually run the server
- Parallel dispatch — Events fan out to all matching clients concurrently
- Config hot-reload — Add or remove clients without restarting the server
- SSE streaming — Real-time event stream via
GET /events/streamfor browsers, dashboards, andcurl - Project metadata — Attach project context (name, team) to every event via a local
.agent-pulse.jsonfile - Single binary — Zero runtime dependencies, just one Go binary
brew tap SantiagoBobrik/tap
brew install agent-pulseRequires Go 1.25+:
go install github.com/SantiagoBobrik/agent-pulse@latestMake sure $GOPATH/bin (or $HOME/go/bin) is in your PATH:
# Add to your ~/.bashrc or ~/.zshrc
export PATH="$PATH:$(go env GOPATH)/bin"# 1. Register a client
agent-pulse client add
# 2. Configure hooks in your agent (see Agent Setup below)
# 3. Work normally — the gateway auto-starts when the first event firesAdd hooks to your Claude Code settings file at ~/.claude/settings.json. The pattern is the same for every event — here are two examples:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"command": "agent-pulse hook --provider claude --event stop",
"type": "command"
}
]
}
],
"Notification": [
{
"hooks": [
{
"command": "agent-pulse hook --provider claude --event notification",
"type": "command"
}
]
}
]
}
}Add as many events as you need by following the same structure. agent-pulse supports all hook events that Claude Code provides — check the Claude Code Hooks documentation for the full list of available hook points.
You can configure hooks at the user level (~/.claude/settings.json) to apply globally, or at the project level (.claude/settings.json) for per-repo hooks.
Note: Gemini CLI support is untested and under review. The hook interface may change.
Gemini CLI uses the same stdin/stdout JSON protocol as Claude Code. Add hooks to ~/.gemini/settings.json:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"command": "agent-pulse hook --provider gemini --event session_start",
"type": "command"
}
]
}
],
"Notification": [
{
"hooks": [
{
"command": "agent-pulse hook --provider gemini --event notification",
"type": "command"
}
]
}
]
}
}Add as many events as you need following the same pattern. See the Gemini CLI Hooks documentation for all available hook events.
Support for Codex CLI and OpenCode is on the roadmap. Codex CLI hooks are currently under development by the Codex team.
The global config at ~/.config/agent-pulse/config.yaml controls where events go — which clients receive them, how they're routed, and server settings. This is the only file you need to manage.
# Server settings
port: 8789 # Gateway listen port (1024-65535)
bind_address: "127.0.0.1" # IP address to bind to
gateway_url: "http://localhost" # Base URL for the gateway
# Registered clients
clients:
- name: "my-webhook" # Unique client identifier
url: "https://example.com/hook" # Destination endpoint
timeout: 5000 # Delivery timeout in milliseconds
events: # Event filter (omit for all events)
- stop
- notification
providers: # Provider filter (omit for all providers)
- claude
headers: # Custom HTTP headers
Authorization: "Bearer ${WEBHOOK_TOKEN}"
X-Source: "agent-pulse"| Key | Type | Default | Description |
|---|---|---|---|
port |
int | 8789 |
Server listen port. Must be between 1024 and 65535. |
bind_address |
string | "127.0.0.1" |
IP address the server binds to. |
gateway_url |
string | "http://localhost" |
Base URL used by the hook command to reach the gateway. |
clients |
array | [] |
List of registered event clients (see below). |
| Key | Type | Default | Description |
|---|---|---|---|
name |
string | required | Unique identifier for the client. Alphanumeric, hyphens, and underscores only. |
url |
string | required | HTTP endpoint to deliver events to. Auto-prefixed with http:// if no scheme is provided. |
timeout |
int | 2000 |
Request timeout in milliseconds. Valid range: 0–30000. |
events |
array | [] |
Event types this client receives. Empty means all events. |
providers |
array | [] |
Providers this client receives events from. Empty means all providers. |
headers |
map | {} |
Custom HTTP headers sent with each request. Values support env var interpolation with ${VAR_NAME} syntax. |
| Provider | Status |
|---|---|
claude |
Supported |
gemini |
Supported (hooks in testing) |
port: 8789
bind_address: "127.0.0.1"
gateway_url: "http://localhost"
clients:
# ESP32 on local network — lights up on agent activity
- name: "desk-light"
url: "http://192.168.1.42"
timeout: 2000
events:
- notification
- stop
# Send all events to a logging service
- name: "logger"
url: "https://logs.example.com/ingest"
timeout: 3000
headers:
Authorization: "Bearer ${LOG_API_KEY}"
# Claude-only events to a dashboard
- name: "claude-dashboard"
url: "http://localhost:3000/api/events"
timeout: 5000
providers:
- claudeYou can attach project-level context to every event by placing a .agent-pulse.json file in your project root (the directory where the agent runs):
{
"metadata": {
"project": "my-api",
"team": "backend"
}
}When present, the metadata object is included in every event sent to your clients. The structure is freeform — put whatever key/value pairs are useful for your setup.
- If the file doesn't exist, events are sent without metadata (no error).
- If the file contains invalid JSON, a warning is logged and the event is sent without metadata.
- Discovery is CWD-only — agent-pulse does not walk parent directories.
This is separate from the global config (~/.config/agent-pulse/config.yaml), which controls where events go. .agent-pulse.json controls what context travels with them.
Connect to GET /events/stream to receive all events in real-time via Server-Sent Events. This is a firehose — every event the gateway receives is broadcast to all connected SSE clients.
The server binds to 127.0.0.1 (local-only), so SSE is only available from your machine.
curl -N http://localhost:8789/events/streamEach event arrives as an SSE data: line containing the full JSON payload:
data: {"provider":"claude","event":"stop","data":{"session_id":"abc"}}
data: {"provider":"claude","event":"notification","data":{"message":"done"}}
const source = new EventSource("http://localhost:8789/events/stream");
source.onmessage = (e) => {
const event = JSON.parse(e.data);
console.log(event.provider, event.event, event.data);
};Events are delivered to clients as JSON via HTTP POST:
{
"provider": "claude",
"event": "stop",
"data": { ... },
"metadata": { "project": "my-api", "team": "backend" }
}The data field contains the raw payload from the agent, passed through without modification. The metadata field is included when a .agent-pulse.json file exists in the working directory (see Project Metadata).
Start the event bridge gateway. You normally don't need to run this yourself — when an agent hook fires, agent-pulse hook checks whether the server is running and starts it automatically in the background if it isn't. The server stays alive to handle subsequent events until you explicitly stop it with agent-pulse server down.
agent-pulse server start # Use port from config (default: 8789)
agent-pulse server start -p 9000 # Override portThe server writes its PID to ~/.config/agent-pulse/server.pid and logs to ~/.config/agent-pulse/server.log. It handles SIGINT/SIGTERM for graceful shutdown.
Stop the running gateway server.
agent-pulse server downSends SIGTERM to the server process and removes the PID file.
View server logs.
agent-pulse server logs # Show last 50 lines
agent-pulse server logs -f # Follow log output in real timeRegister a new event client. Runs an interactive wizard if no flags are provided.
# Interactive wizard
agent-pulse client add
# Non-interactive
agent-pulse client add \
--name my-webhook \
--url https://example.com/hook \
--port 443 \
--timeout 5000 \
--events stop,notification| Flag | Description |
|---|---|
--name |
Client name |
--url |
Client URL or IP address |
--port |
Port number (default: 80) |
--timeout |
Delivery timeout in ms (default: 2000) |
--events |
Comma-separated event types, or "all" |
List all registered clients and their configuration.
agent-pulse client listNAME URL TIMEOUT EVENTS PROVIDERS
logger https://logs.example.com/ingest 3000 all all
desk-light http://192.168.1.42:80 2000 notification, stop all
claude-dashboard http://localhost:3000/api/events 5000 all claude
Remove a registered client by name.
agent-pulse client remove desk-lightName lookup is case-insensitive.
Called by agent hooks to forward events to the gateway. You typically don't call this directly — it's invoked by the hook configuration in your agent's settings.
echo '{"message": "done"}' | agent-pulse hook --provider claude --event stop| Flag | Required | Description |
|---|---|---|
--provider |
Yes | Agent provider (claude, gemini) |
--event |
Yes | Lifecycle event name (session_start, session_end, stop, notification) |
Reads the event payload from stdin as JSON. If the gateway is unreachable, it automatically starts the server in the background before dispatching.
MIT

