Drive a running terminal from the shell
Every running rbterm can expose a control channel, and a client built into the same binary — rbterm @ <command> — sends it JSON commands and prints the reply. Commands execute on the terminal's own main thread, which is why the same channel also drives rbterm's ~240-check self-test suite.
What it is
A tiny client, the same binary, one JSON command per invocation — a scripted action is exactly as safe as a keystroke.
Every running rbterm can expose a control channel. A tiny client built into the same binary — rbterm @ <command> — sends JSON commands to it and prints the reply. Commands are executed on the terminal's own main thread, so a scripted action is exactly as safe as a keystroke. The result: your terminal becomes a programmable surface you can wire into shell scripts, editor plugins, build tools, dashboards, and test suites.
# enable it once (Settings → Window → Scripting), then: rbterm @ ls # list windows / tabs / panes as JSON rbterm @ send-text 'make && ./run\n' # type a command into the active pane rbterm @ get-text # read the pane's text back rbterm @ screenshot --output ~/shot.png # save a PNG of a pane rbterm @ split --axis horizontal # split the focused pane rbterm @ launch --cwd ~/src htop # new tab running a command
Enable it in Settings → Window → Scripting, or launch with --listen-on unix:/tmp/rb.sock. When enabled, rbterm exports RBTERM_LISTEN_ON into every shell it opens, so rbterm @ … inside a pane just targets its own window.
Command reference
Everything the API can do, grouped by what it touches. Flags are shown in the description; every command also accepts -m / --match.
Read & inspect
| Command | What it does |
|---|---|
| ls | Dump every window, tab and pane as JSON — ids, titles, working directories, process ids, grid size, and any user variables. |
| get-text | Return a pane's on-screen text. --extent all includes scrollback; --ansi preserves colours and attributes as escape sequences. |
| get-colors | Return a pane's full palette — foreground, background, cursor, and all 256 indexed colours. |
| screenshot | Render any pane to a PNG (--output PATH) — visible or not, sized from its own grid. Pixel-accurate, no window capture. |
Input
| Command | What it does |
|---|---|
| send-text | Type text into a pane's input, with C-style escapes (\n \t \e \xNN). |
| send-key | Send key chords by name — enter, ctrl+c, up, f5, alt+x, … |
Windows, tabs & panes
| Command | What it does |
|---|---|
| launch / new-tab | Open a new tab, optionally in a given --cwd and running a command. |
| new-window / split | Split the focused pane (--axis vertical|horizontal). |
| focus-tab / focus-pane | Move focus to a tab or pane by index or matcher. |
| close-tab / close-pane | Close a tab or a single pane. |
| resize-pane | Grow or shrink a split by an --increment. |
| resize-os-window | Resize the OS window itself (--width, --height). |
Appearance
| Command | What it does |
|---|---|
| set-colors | Recolor a pane live — foreground, background, cursor, or any colorN. |
| set-tab-color | Give a tab an accent colour in the tab bar. |
| set-tab-title / set-window-title | Rename a tab or a pane. |
| set-font-size | Set the active tab's font size (absolute or +n/-n). |
| set-spacing | Adjust padding around the grid. |
| set-background-opacity | Set window background transparency (0.0–1.0). |
| disable-ligatures | Turn ligature shaping on or off. |
| scroll-window | Move the scrollback view — top, bottom, page-up, page-down, or by N lines. |
Process & environment
| Command | What it does |
|---|---|
| signal-child | Send a signal (SIGTERM, SIGINT, SIGKILL, …) to a pane's process. |
| run | Run a command and capture its output and exit code. |
| env | Set environment variables for future shells. |
| set-user-vars | Attach arbitrary key/value metadata to a pane — surfaced in ls and usable as a match target. |
| load-config | Reload settings from the config file. |
| goto-layout / action | Switch layout (e.g. maximize the focused pane) or trigger a built-in action (next tab, clear, scroll home, …). |
Targeting panes
A boolean matcher picks exactly which panes a command hits.
Pass -m / --match to any command. Match on id, title, cwd, pid, num (tab index), state, or a user var — and combine them with and, or, not, and parentheses. Without a matcher, commands act on the focused pane.
- id
- Pane id, as reported by
ls. - title
- Pane title.
- cwd
- The pane's working directory.
- pid
- The process id running in the pane.
- num
- Tab index.
- state
- Pane state — including the derived agent status.
- var
- A user variable attached with
set-user-vars. - and · or · not · ( )
- Boolean operators and parentheses, combining any of the above.
rbterm @ get-text -m '(title:vim or id:3) and not cwd:/tmp'
rbterm @ send-text -m 'all' '\x03' # Ctrl-C every pane
rbterm @ set-colors -m 'cwd:/var/log' background=#2a0000
rbterm @ signal-child -m 'pid:34521' SIGTERM
Two ways to connect
Pick automatically — no setup for the common case.
Transport 01Local socket
For external scripts and other programs. rbterm listens on a per-process Unix socket with owner-only permissions. Point the client at it with --to unix:/path, or let it inherit RBTERM_LISTEN_ON. Requests and replies are length-prefixed JSON.
Transport 02In-band escape sequence
For driving the terminal from inside one of its own panes — and, crucially, over SSH. The request rides a terminal escape sequence in the pane's output stream; the controlling rbterm intercepts it and writes the reply straight back. No socket, no port, nothing to forward.
The client chooses for you: it uses the socket when one is reachable, and otherwise falls back to the in-band channel of the terminal it's running in. A reader thread parses the framing; every command that touches tabs, panes or the renderer runs on the main loop — so remote commands are as safe as keypresses, and a flood of them can't corrupt state.
Inside tmux, both automatic paths miss: RBTERM_LISTEN_ON isn't in the environment (tmux panes inherit the tmux server's frozen environment, not rbterm's), and tmux swallows the in-band escape unless passthrough is enabled. Recover the address from the tmux client process, which was launched from the rbterm pane and still has it.
CLIENT_PID="$(tmux display-message -p '#{client_pid}')"
ps eww -p "$CLIENT_PID" | tr ' ' '\n' | grep -E '^RBTERM_(PANE_ID|LISTEN_ON|EXE)='
# then target explicitly — and re-derive next time, the id can change
"$RBTERM_EXE" @ --to "$RBTERM_LISTEN_ON" ls -m "id:$RBTERM_PANE_ID"
Encrypted & authenticated
Lock the control channel to a password — end to end.
By default the control socket is owner-only and rbterm only opens it when you opt in. For anything stronger, set a remote-control password. rbterm then refuses every plain command and accepts only authenticated, encrypted ones: an ephemeral X25519 key exchange derives a shared key (hashed with SHA-256), and each command is sealed with AES-256-GCM. The password and a timestamp travel inside the encrypted payload, with a five-minute replay window and a constant-time check.
rbterm --rc-password 'hunter2' # or set it in the config file # the client encrypts automatically when it has the password + key: export RBTERM_RC_PASSWORD=hunter2 export RBTERM_PUBLIC_KEY=... # rbterm prints this on startup rbterm @ ls # → encrypted; plain commands are rejected
Test your terminal with it
The same API is rbterm's end-to-end test harness.
Because the API can both drive a pane and read it back, it doubles as a way to test the terminal itself. The bundled suite (make test) launches rbterm, feeds escape sequences through send-text, and asserts on get-text — verifying colour and attribute rendering, cursor positioning, screen erase, auto-wrap, scrollback, split/close behaviour, and live recolouring. Your own scripts can do the same.
rbterm @ send-text 'printf "\\033[1mBOLD\\033[0m\\n"\n'
rbterm @ get-text --ansi # assert the bold SGR is present
A small example
Spin up a working layout from a script.
#!/bin/sh # open a build pane and a log pane side by side, then tail the log rbterm @ launch --cwd ~/project rbterm @ split --axis vertical rbterm @ send-text -m 'cwd:~/project' 'make watch\n' rbterm @ send-text -m 'all' '\n' rbterm @ set-tab-title 'build' rbterm @ screenshot --output ~/build-layout.png