tend_
Status of the AI coding agents running inside your tmux session — at a glance.
tend scans your tmux panes, figures out which ones are AI agents (Claude Code, Codex, …), and reports whether each is blocked waiting on you, working, or idle — plus the git branch state of each pane's directory. One question, answered continuously: what are my agents doing right now?
Node ≥ 22.18 · tmux · zero runtime dependencies · MIT
A faithful re-animation of the dashboard — the real thing is tend in any terminal where tmux is running.
● ⠧ ○ the three states
A glyph carries each state — shape and color, so it reads even without color.
blocked
The agent is waiting on you — a permission prompt or approval UI is on screen. Deliberately strict: only a positive match of a known approval UI, so you don't get false "needs you" alarms.
working
The pane's content changed since the last look. An animated spinner in the dashboard, so a busy board visibly ticks.
idle
Nothing happening, nothing needed. Debounced — it must persist across two scans, so a single quiet frame mid-task doesn't flap the status.
◌ grey means unknown — a pane tend recognizes as an agent but can't read a state from yet.
scope
Small and focused, on purpose.
tend does one job. If you're deciding whether it's useful to you, this is the honest split:
tend is for you if…
- You run several AI coding agents in tmux panes and keep missing the moment one stops to ask permission.
- You want a single surface showing every agent across every session — and a way to jump straight to any of them.
- You want the status in your tmux status bar or a script (
tend --json). - You care about git state per pane — branch, dirty, ahead/behind — at the same glance.
it deliberately does not…
- Multiplex, split panes, manage windows, or persist sessions — tmux already does all of that.
- Wrap or launch your agents. They run exactly as they do now; tend only observes.
- Hook into agent APIs. Detection reads the rendered screen, so it works with any TUI agent — and needs occasional rule tuning as TUIs drift.
- Work outside tmux. No tmux server, nothing to scan.
install
Two dependencies: Node and tmux. Nothing else.
The tool itself is Node stdlib plus shelling out to tmux and git — zero runtime dependencies. Install it globally from npm:
$ npm install -g @sandropadin/tend $ tend
Or run it without installing — npx @sandropadin/tend. To hack on it, clone the repo — Node ≥ 22.18 runs the TypeScript directly, no build step:
$ git clone https://github.com/spadin/tend.git && cd tend $ npm install # dev-only type stubs $ node src/index.ts # or `npm link` to put `tend` on your PATH
Note: the unscoped tend on npm is a different, unrelated package — install @sandropadin/tend as above.
usage
One command, a few modes.
By default tend scans every tmux session and groups agents by session. Bare tend opens the live dashboard in a terminal, but falls back to a one-off snapshot when piped or redirected — so tend | grep and tend > file just work.
| tend | live, selectable dashboard (default in a terminal) |
| tend --once | print a grouped snapshot and exit |
| tend --json | machine-readable snapshot, for status bars and scripts |
| tend jump %3 | jump straight to a pane id — handy on a tmux key binding |
| tend jump %3 --to T | open pane %3 in another terminal window (client tty T) |
| tend clients | list attached tmux clients (terminal windows) |
| tend --current | limit to the current session only |
| tend --readonly | dashboard without selection — a display-only status pane |
| tend --debug %3 | dump what each detection region sees, for rule tuning |
the dashboard
A monitor that doubles as a navigator.
The dashboard is a live, grouped list of every agent — and it's selectable. Move the cursor, hit Enter, and you're in that agent's pane. Jumping keeps the dashboard alive: inside tmux it switch-clients you to the agent but leaves the monitor running in its own pane, a persistent surface you can switch back to — not a one-shot chooser.
The two-window setup is where it earns its keep. tmux is client-server: every attached terminal window is a separate client, and tend can move a specific one. Run the dashboard in window A, keep window B for actually working, press o once to aim at B — now every Enter opens the selected agent over there while the dashboard never moves.
tend ↑/↓ move · enter jump · o target opens in: ttys023 · scratch ▸ api (1) · 1 blocked ❯ ● %1 claude feature/x ✱ ▸ web (1) ⠧ %5 claude main
╭──────────────────────────────╮ │ Allow Bash command? │ │ ❯ 1. Yes │ │ 2. No │ ╰──────────────────────────────╯ agent %1 · you're unblocking it
The other window must be an attached tmux client on the same server — check with tend clients.
how it works
Two signals, arbitrated. All data, no daemons.
Which agent is in a pane: tmux's pane_current_command gives the foreground process name. (Claude Code renames its process to its version string — e.g. 2.1.200 — so tend matches a semver pattern, not a literal name.) If the command is generic, it falls back to persistent screen chrome like the mode footer — never the welcome banner, which scrolls away.
What state it's in: tend captures the rendered screen with capture-pane and runs declarative manifest rules against slices of it, then arbitrates against pane activity — in strict order:
hold
Scrollback, model-picker, and transcript screens are recognized and hold the previous state instead of authoring a bogus one.
blocked — screen, strong
A live approval UI wins immediately. It keys on the selection cursor (❯ 1.) that disappears the instant you answer — not the question text, which lingers in scrollback. Blocked is never inherited across scans, so a stale block can't persist.
working — activity
The pane's content changed since the last scan (snapshot diff). A visible "esc to interrupt" hint is a secondary signal.
idle — debounced
Must persist across two scans before it's reported, so one quiet frame mid-task doesn't flap the status. Anything else keeps the previous state.
Git facts come from shelling out to the real git binary, memoized per repo root within each scan — N panes in one repo cost one set of calls.
Detection is data, not code. Each rule is { id, state, priority, region, contains?, regex?, not? } in src/manifests.ts. Agent TUIs drift with versions, so the shipped Claude Code and Codex rules are a starting point you can tune — run tend --debug <pane> against a live agent and it shows exactly what each region extractor sees and which rules match. Adding a new agent means adding a manifest, not touching the engine.
integration
It composes with tmux, like everything should.
Put a summary in the status bar via tend --json, or bind a key to a popup dashboard. The --popup flag makes the dashboard exit once you pick an agent, so the popup closes and drops you onto that pane:
# count blocked agents in the status bar set -g status-right "#(tend --json | jq -r '[.[] | select(.state==\"blocked\")] | length') blocked" # pop up the dashboard on prefix-g, jump, and it closes behind you bind-key g display-popup -E "tend --popup"