tend_

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.

tendlive, selectable dashboard (default in a terminal)
tend --onceprint a grouped snapshot and exit
tend --jsonmachine-readable snapshot, for status bars and scripts
tend jump %3jump straight to a pane id — handy on a tmux key binding
tend jump %3 --to Topen pane %3 in another terminal window (client tty T)
tend clientslist attached tmux clients (terminal windows)
tend --currentlimit to the current session only
tend --readonlydashboard without selection — a display-only status pane
tend --debug %3dump 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.

↑/↓ or j/k move enter jump o cycle target window r refresh q / esc quit

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
enter
╭──────────────────────────────╮
 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"