A hook collector and live status board for local coding-agent sessions.
curl -L https://github.com/samling/agent-status/releases/latest/download/agent-status_<version>_linux-amd64.tar.gz | tar xz
sudo install agent-status_*/agent-status /usr/local/bin/go install github.com/samling/agent-status/cmd/agent-status@latestgit clone https://github.com/samling/agent-status.git
cd agent-status
make installAI agents like Claude Code, Codex, and opencode support hooks or plugins that enable users to execute actions at various points in the agent's lifecycle (new session, prompt submitted, tool used, etc.). We configure Claude Code and Codex hooks to execute a forwarder script, and configure opencode to load a plugin, to POST events to the collector (agent-status server). The collector receives those events over local HTTP and persists per-session state. Agent state files and databases are also checked for locally to discover live sessions and reap stale ones. Clients (the TUI agent-status ui, agent-status statusline, agent-status focus <session_id>) read live state through the collector's GET /state HTTP endpoints, so the daemon is the single source of truth.
The data read by agent-status is local and only data provided by the supported agents. Nothing ever leaves your machine. agent-status uses the presence of certain files (e.g. ~/.claude/sessions/*.jsonl) to infer the existence of sessions; it uses the contents to provide information on that session (model name, agent version, a preview of the last submitted prompt). This data already exists unencrypted on your machine; poke around ~/.claude, ~/.codex, ~/.local/share/opencode, etc. if you're curious. Absolutely no data is collected by me from service nor will it ever be. A non-exhaustive list of data and locations checked includes:
- Claude Code hook payloads (event-specific data - prompt submitted, tool used, session started/ended, awaiting input, etc.)
- Claude Code data in
~/.claude, specificallysessionsandprojects(session-specific data - model used, creation date, current session status, etc.) - Codex hook payloads (event-specific data - prompt submitted, tool used, session started/ended, awaiting input, etc.)
- Codex data in
~/.codex, specificallystate_*.sqlite,logs_*.sqlite, andsessions(session-specific data - model used, creation date, current session status, etc.)shell_snapshots/is checked for the presence of a session file as it's currently the only way I've found to determine when a new Codex session is started before sending the first message
- opencode plugin events: session/message/tool/permission activity posted to the local collector
- opencode data in
$XDG_DATA_HOME/opencode(default~/.local/share/opencode), specificallyopencode.dbfor session metadata and messages
This tool aggregates that data, tracks state by agent and session id, and presents it in a terminal UI.
I made this tool for me initially, which means I've prioritized my own use cases first. It builds for ARM and so I presume it will work there too.
Note on arm64: the SQLite driver is
modernc.org/sqlite(pure Go, no CGo) so cross-compilation stays simple, but it is meaningfully slower than the CGo-based driver on arm64. Personal use is fine; if you ever pin this against larger Codex state databases on an arm64 host and notice query latency, that's the trade-off.
The mechanism to bring focus to active sessions (called "activating a window") varies greatly across OSes, desktop environments and sometimes applications. I primarily work in tmux and using the VSCode extension in CLI mode, so those were my initial targets. It has limited or (more likely) no support (yet) for other DEs/compositors.
| OS | Shell/DE/Compositor | Supported | Notes |
|---|---|---|---|
| Windows (WSL) | Windows Shell | ✅ | Can focus VSCode and VSCode-variant host applications from WSL terminal |
| Linux | tmux | ✅ | Can focus tmux panes directly |
| niri | ✅ | Can focus other windows running claude-code | |
| hyprland | ⛔ | ||
| gnome | ⛔ | ||
| kde | ⛔ | ||
| others | ⛔ | ||
| MacOS | MacOS | ⛔ |
Run the bootstrap subcommand from the installed binary:
agent-status bootstrapIt configures Claude Code, Codex, and opencode and will:
- Install a single shared forwarder script to
$XDG_CONFIG_HOME/agent-status/post-agent-status.sh(default~/.config/agent-status/post-agent-status.sh). Claude Code and Codex hook configs point at this one location. - Install the opencode plugin asset to
$XDG_CONFIG_HOME/agent-status/opencode-agent-status-plugin.js. - Render the embedded hook templates with the absolute path to the forwarder.
- Merge the rendered Claude Code hooks into
~/.claude/settings.json, Codex hooks into~/.codex/hooks.json, and the opencode plugin path into the opencode config. If a file already exists, the merge leaves a.bak.<timestamp>next to the original. Existing hook entries that reference any priorpost-agent-status.sh, and existing opencode plugin entries that reference any prioropencode-agent-status-plugin.js, are replaced (not duplicated) with the new one. - Remove any per-agent script copies left behind by earlier installs (
~/.claude/scripts/post-agent-status.sh,~/.codex/scripts/post-agent-status.sh); modified copies are backed up first.
The planned changes are printed before any files are written, and the command asks for confirmation. Useful flags:
--yesskip the confirmation prompt--dry-runprint the plan without writing anything--agents=claude,--agents=codex, or--agents=opencodeconfigure just one agent--claude-dir/--codex-dir/--opencode-diroverride the config dir for an agent
CLAUDE_CONFIG_DIR, CODEX_HOME, and OPENCODE_CONFIG_DIR are honored when their respective
--*-dir flags aren't passed.
The forwarder and opencode plugin discover the collector's address at runtime,
so changing --addr / --port (or the matching config keys) only requires
restarting the server — no need to re-run bootstrap. Both assets use this
shared resolution order:
$AGENT_STATUS_ENDPOINT(e.g.127.0.0.1:7878) if set in the agent's environment.$XDG_STATE_HOME/agent-status/endpoint(default~/.local/state/agent-status/endpoint), which the server writes on startup.127.0.0.1:7878fallback.
If you'd rather wire the hooks yourself, the templates, forwarder, and
opencode plugin asset live at
internal/bootstrap/assets/. Copy
post-agent-status.sh and opencode-agent-status-plugin.js to
$XDG_CONFIG_HOME/agent-status/, replace path-to-post-agent-status in
claude-code.json / codex.json with that absolute path, then merge the
rendered JSON into ~/.claude/settings.json and write it to
~/.codex/hooks.json. Add the opencode plugin asset path to the plugin
array in your opencode config (opencode.jsonc, opencode.json, or
config.json). The script and plugin read the collector's address from
$XDG_STATE_HOME/agent-status/endpoint at runtime, so the same assets
work regardless of which port the server runs on.
Configuration is stored in $XDG_CONFIG_HOME/agent-status/config.yaml. On most systems XDG_CONFIG_HOME=$HOME/.config.
The state file is stored in $XDG_STATE_HOME/agent-status/state.json. On most systems XDG_STATE_HOME=$HOME/.local/state.
Start the server:
agent-status serverBy default the collector listens on 127.0.0.1:7878. Override with
agent-status server --addr <addr> --port <port>.
Open the UI:
agent-status uiSee agent-status -h and subcommand -h output for configuration.
The collector exposes a local JSON API:
POST /hookrecords agent hook events.GET /statelists live sessions.GET /state/{session_id}returns one live session.GET /state/{session_id}/transcriptreturns parsed transcript metadata.GET /metareturns discovery metadata keyed by session id.GET /healthzreturns collector health.GET /versionreturns the running collector version.
Desktop notifications are enabled by default when the server runs, and fire
immediately when a session starts waiting. Disable them with --notify=false or
server.notify.enabled: false in config:
agent-status server --notify=falseUseful notification flags include --notify-initial-delay,
--notify-repeat, --notify-title, --notify-body, and
--notify-action-label.
A reference systemd user unit lives at contrib/systemd/user/agent-status.service. To install it:
make install-service
systemctl --user enable --now agent-statusOr by hand:
mkdir -p ~/.config/systemd/user
cp contrib/systemd/user/agent-status.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now agent-statusThe unit assumes agent-status is on PATH at /usr/bin/agent-status. Edit ExecStart= if yours lives elsewhere (e.g. ~/go/bin/agent-status).
Bind the UI to a tmux popup so it overlays the current pane and dismisses itself once you focus a session (requires tmux >= 3.2 for popup support):
set-hook -g session-created 'if -F "#{==:#{session_name},agent-status}" "set-option -t agent-status status off"'
bind o if-shell -F "#{==:#{session_name},agent-status}" {
detach-client
} {
display-popup -E "tmux new-session -A -s agent-status 'agent-status ui --quit-after-focus'"
}The --quit-after-focus flag exits the TUI after enter focuses a session, which lets display-popup -E close the popup automatically. Omit the flag if you'd rather the popup stay open until you press q.
Logging is log/slog-based and gated by LOG_LEVEL (or log.level
in config / --log-level). Set LOG_FORMAT=json for machine-friendly
output.
make build # bin/agent-status
make test # go test ./...
make clean