DrawingAgents
Sheet4 / 11
Rev0.3.66
Date08-2026
Scale1:1
MaterialC99

See what your agent made, and who needs you

AI agents live in terminals now — but a terminal can only show them talking. rbterm closes the loop: an agent working in a pane can show you what it made, and every pane carries one of 5 status badges so ten agents in ten tabs never cross wires.

display-image overlay per pane 5 tab badge states Zero config via the bell
SEC 01

See what your agent sees

One command puts the screenshot an agent just captured into the pane it is working in.

Say Claude is building a website for you. It edits the CSS, runs Playwright, captures a screenshot — and then describes the screenshot to you in prose, because the terminal is text. You end up alt-tabbing to a file manager to see what your own agent just made.

In rbterm, the agent runs one command instead:

"$RBTERM_EXE" @ display-image --path /tmp/site-preview.png

The image appears instantly as an overlay in the agent's pane — scaled to fit, with a caption bar. Glance at it, click it, it's gone, and your session is exactly as it was. The view below is real: a Claude session capturing this very website and showing it in its own tab.

A Claude Code agent in an rbterm pane with a website screenshot floating over the text as an overlay, captioned with the file name, while the session keeps running underneath
VIEW A · display-image overlay, in-pane
SEC 02

How it works

An overlay, not cell soup — designed for panes an agent is actively repainting.

Floats above the grid

The image is drawn over the pane, not injected into the cell grid like sixel/kitty graphics (which rbterm also supports). That matters because Claude Code repaints its interface constantly — a grid-anchored image would be scrolled away or overdrawn in milliseconds. The overlay is unaffected; the session keeps running underneath.

Click to dismiss

One click anywhere on the image removes it. Or script it away with --clear. Sending a second image replaces the first — an agent iterating on a design can flip through versions like a slideshow.

Scriptably verifiable

rbterm @ ls reports each pane's overlay — dimensions and whether it has actually been drawn — so scripts and test suites can assert an image really reached the screen.

Any image, no dependencies

PNG, JPEG, BMP, GIF (first frame) — decoded in-process and rendered by the GPU like everything else in rbterm. No helper programs, no protocol negotiation, works the same on every platform rbterm runs on.

SEC 03

It lands in the right tab

Run ten agents in ten tabs — each one's images appear only in its own pane.

rbterm gives every pane a stable id and exports it to the shell as RBTERM_PANE_ID. The rbterm @ client stamps that id into every request it sends, so a plain display-image from inside a pane targets that pane — not whatever tab you happen to be looking at. A busy multi-tab session never gets an image in the wrong place.

This survives restarts, too: rbterm's shells live in a background daemon, so quitting and reopening rbterm reattaches your sessions — and each reattached pane gets the same id its shell still holds in its environment. An agent that was mid-task before the restart keeps displaying to the right tab after it.

SEC 04

Wire up Claude Code

Two steps: switch it on, then tell Claude it exists.

  1. Enable scripting

    Settings → Window → Scripting (or launch with --listen-on unix:/tmp/rb.sock). rbterm then exports the control-channel environment into every pane it opens.

  2. Teach Claude the command

    Add a short instruction to your global CLAUDE.md (or package it as a skill). That's it — any Claude Code session you start inside rbterm inherits the environment and knows where to show its work.

    Paste this into ~/.claude/CLAUDE.md:

    ## rbterm: show images in the terminal
    When you produce an image the user should see — a screenshot, a chart,
    a Playwright capture — and $RBTERM_LISTEN_ON is set, display it with:
    
        "${RBTERM_EXE:-rbterm}" @ display-image --path /absolute/path/to.png
    
    Use --clear to remove it. If $RBTERM_LISTEN_ON is unset you are not
    running inside rbterm — skip silently.

Environment inside an rbterm pane

RBTERM_LISTEN_ON
Address of the control socket. Its presence means “you are inside rbterm and scripting is on.”
RBTERM_EXE
Absolute path to the running rbterm binary — the app bundle is never on PATH, so use "$RBTERM_EXE" @ ….
RBTERM_PANE_ID
The pane's stable id. Stamped into every rbterm @ request automatically, so commands self-target. Also usable explicitly: -m id:$RBTERM_PANE_ID.
SEC 05

Know when an agent needs you

Herd-style status badges in the tab bar — one glance tells you who's working, who's finished, who's stuck.

Running several agents means babysitting several tabs. rbterm derives a status for every pane and badges the most urgent one on its tab (new to the idea? — read the herd model, explained):

Close-up strip of the rbterm tab bar: tabs carrying a cyan running spinner, a green done dot, a red failed dot and a yellow exclamation mark for attention
DETAIL A · tab bar, status badges, enlarged
Tab badge states
Badge Status What sets it
Yellow exclamation mark Attention The program rang the bell or posted a notification (OSC 9) while you weren't looking — exactly what Claude Code does when it's waiting on your input — or an agent set status=waiting.
Cyan spinner Running A command is running (OSC 133 prompt marks), or an agent set status=working.
Green dot Done A command finished with exit 0 while the tab was backgrounded, or status=done.
Red dot Failed A command exited non-zero while the tab was backgrounded, or status=failed.
Amber dot Activity Plain output activity in a background tab.

Viewing a tab (tab active and app focused) clears its badges. A bell that arrives while rbterm is in the background also posts a desktop notification, throttled to one per 10 seconds per pane. The running / done / failed states come from OSC 133 prompt marks — source tools/rbterm-shell-integration.zsh (or .bash) in your shell rc, or skip that entirely and let the agent declare its status (next section).

An rbterm window with five tabs whose badges show every agent status at once, while the active shell pane lists each tab's status with rbterm @ ls, counts blocked panes with match-count, and answers the blocked pane with send-text
VIEW B · five agent tabs, one window
SEC 06

Tell rbterm what Claude is doing

Zero-config via the bell — or exact working / waiting / done transitions via hooks.

  1. Zero-config: the bell

    In Claude Code, run /config and set preferredNotifChannel to terminal_bell. Claude rings when it needs you; the tab lights up with the yellow ! and you get a desktop notification if you're in another app. Bells cross SSH natively, so this also works for claude running on a remote host in an SSH tab.

  2. Precise: hooks

    For exact state transitions, drive the status user-var from Claude Code hooks. Paste this into ~/.claude/settings.json — every Claude session started inside rbterm then badges its own pane:

    {
      "hooks": {
        "UserPromptSubmit": [{"hooks": [{"type": "command",
          "command": "\"${RBTERM_EXE:-rbterm}\" @ set-user-vars -m \"id:${RBTERM_PANE_ID:-0}\" status=working >/dev/null 2>&1 || true"}]}],
        "Notification": [{"hooks": [{"type": "command",
          "command": "\"${RBTERM_EXE:-rbterm}\" @ set-user-vars -m \"id:${RBTERM_PANE_ID:-0}\" status=waiting >/dev/null 2>&1 || true"}]}],
        "Stop": [{"hooks": [{"type": "command",
          "command": "\"${RBTERM_EXE:-rbterm}\" @ set-user-vars -m \"id:${RBTERM_PANE_ID:-0}\" status=done >/dev/null 2>&1 || true"}]}]
      }
    }

Status words

waiting · blocked · attention
Yellow ! — attention. Clears when you view the tab.
working · running · busy
Spinner — running. Persists until changed.
done · ok · success
Green dot — done. Clears when you view the tab.
failed · error · fail
Red dot — failed. Clears when you view the tab.
status= (empty)
Clears the override and returns the pane to its derived status.

Hooks talk to the local socket. For Claude running on a remote host, use the bell instead.

SEC 07

Script the herd

Statuses are queryable and waitable — agents can babysit agents.

Every pane in rbterm @ ls carries a "status" field, and the match engine accepts it as state:<word>:

rbterm @ · wait primitivessh
# see every pane's status at once:
rbterm @ ls | jq -r '.data.windows[].tabs[] | .name + "  ->  " + .panes[0].status'

# answer whichever pane is blocked, without leaving your tab:
rbterm @ send-text -m 'state:attention' 'y\n'

# wait for pane 3's command to finish — the agent-to-agent wait primitive:
until rbterm @ match-count -m 'id:3 and (state:done or state:failed)' \
      | grep -q '"count":[1-9]'; do sleep 2; done

match-count returns {"ok":true,"data":{"count":N}} and never errors on zero — zero is the answer. One subtlety worth knowing: a command that finishes in a background tab reports done or failed until you view the tab — not idle — so wait loops must poll for the completion states.

SEC 08

Command reference

The agent-facing additions — everything else is on the API sheet.

Agent commands — rev 0.3.66
CommandWhat it does
display-image Show an image as an overlay in a pane (--path FILE; alias icat). Defaults to the calling pane, else the active one; target explicitly with -m. A second call replaces the image; --clear removes it.
ls Reports each pane's overlay as "image": {width, height, shown} and a derived "status" (running / done / failed / attention / idle).
set-user-vars Attach metadata to a pane; status=working|waiting|done|failed (and synonyms) drives the tab badge directly, status= clears it.
match-count Count panes matching an expression — the cheap probe for wait loops (-m 'state:done or state:failed'). Returns {"count":N}; zero is a normal answer, not an error.