Shell Scripting

You can also use the rex CLI with typical shell scripts to automate everything in Rex:

#!/bin/sh
rex new work
rex split -s work -- htop
rex run -s work --label logs -- tail -f app.log

For anything beyond a few commands in a row, consider a Lua script instead. Lua can do everything on this page with one connection to the server and without parsing JSON.

Keeping Track of IDs

Commands that create something print its ID with --json. Save the ID and pass it to later commands, so the script keeps working when a label changes or focus moves.

$ rex split --json -- htop
{
  "anchor_block_id": "block:01a10e00-7268-78c6-8b85-86c56d6a53a9",
  "block_ids": [
    "block:01a10e00-729d-7c38-90f7-d6568611b1c4"
  ],
  "split_ids": [
    "split:01a10e00-72ab-7157-b9e0-0d4c2a8a1a68"
  ],
  "revision": 2
}

jq is the easiest way to pull the ID out:

block=$(rex split --json -- htop | jq -r '.block_ids[0]')
rex zoom -b "$block"
CommandCreatesID is at
rex new --jsonA session with one window and one terminal.session_id
rex run --jsonA terminal in a new window.block_ids[0], .window_id
rex split --jsonA terminal beside an existing one.block_ids[0]

A label is a fine alternative when the script sets it. Give the block a label with --label and use that as the target:

rex split --label logs -- tail -f app.log
rex capture -b logs

Building a Layout

rex new creates a session, rex run adds a window to it, and rex split splits an existing block.

#!/bin/sh
# Open a project: an editor with a shell below it, and a second
# window that follows the logs.
rex new api --cwd ~/src/api -- nvim
rex split -s api --split=below --ratio 30 --cwd ~/src/api
rex run -s api --label logs --focus=false -- tail -f ~/src/api/app.log

These options are shared by rex run and rex split:

OptionDoes
-- COMMANDRuns a command instead of an interactive shell.
--cwd DIRSets the working directory.
--label TEXTLabels the new block.
--split DIRECTIONPlaces the block right, left, above, or below its anchor.
--ratio PCTGives the new block that percentage of the split.
--focus=falseLeaves focus where it was.
--keep-openKeeps the terminal open after its command exits.

A command runs with the environment of your login shell, so your PATH applies but aliases don’t. It isn’t parsed by a shell. To use pipes or &&, run a shell explicitly:

rex run -- sh -c 'make build && make test'

Typing Into a Terminal

rex send types text into a terminal, and rex send-key presses keys:

rex send -b logs 'make test'
rex send-key -b logs enter

rex send doesn’t press Enter for you. Follow it with rex send-key enter, or include the newline in the text. It also reads from stdin:

printf 'make test\n' | rex send -b logs

rex send-key uses the same key names as keyboard shortcuts, so rex send-key ctrl+c interrupts whatever is running.

Reading a Terminal

rex capture prints what is on a terminal’s screen:

$ rex capture -b logs --trim
hello from logs
OptionDoes
--trimRemoves trailing blank lines.
--unwrapJoins lines the terminal wrapped.
--format htmlKeeps colors and styling, as HTML.
--format vtKeeps the terminal’s control sequences.

Running a Command and Waiting

Add --wait to rex run or rex split to wait for the command to finish. The rex command then exits with the command’s own status:

if rex run --wait -- make test; then
  echo "tests passed"
fi

rex wait does the same for a terminal that is already running, given its ID or label:

block=$(rex split --json -- make test | jq -r '.block_ids[0]')
# ... do other things ...
rex wait "$block"
echo "make exited with $?"

rex wait also accepts a window or a session, and returns when it closes.

Use --for to give up after a while. A wait that times out exits with status 1:

rex wait --for 10m "$block" || echo "failed or timed out"

Performing Actions

rex do performs an action by name, with arguments as key=value pairs. It runs both the app’s actions and the custom actions in your configuration file:

rex do dev_layout file=app.log
rex do pane.split direction=right

Actions such as pane.split are performed by the app, so the app must be running, with Remote Control turned on in its settings.

Watching Events

rex events prints events as they happen and runs until interrupted. With -s it follows one session:

$ rex events -s work
14:38:07  block_created         …65951c  session …8f9d5d
14:38:07  session_view_changed  …8f9d5d  revision 11
14:38:07  block_event           …65951c  com.superlogical.terminal.bell {}

With --json it prints one JSON object per line, ready for jq. This prints the ID of each terminal that rings its bell:

rex events -s work --json |
  jq -r 'select(.payload.event.payload.name == "bell")
         | .payload.event.payload.block_id'

The events reference describes the JSON and lists every event. To act on events rather than print them, Lua event handlers are much less work than a pipeline.

Example: Run Tests in a Split

This script runs a command in a split beside the current terminal. If the command fails, it zooms the split and exits with a failure.

#!/bin/sh
# tests.sh
cmd="${1:-make test}"

block=$(rex split --keep-open --focus=false --json -- sh -c "$cmd" |
  jq -r '.block_ids[0]')

if ! rex wait --for 10m "$block"; then
  rex zoom -b "$block" --on
  exit 1
fi
./tests.sh 'go test ./...'

The same script in Lua is a useful comparison.