TamboUI can record any application to an asciinema (.cast) file, and can drive the application from a VHS-format tape script. Together they enable fully headless runs: scripted input in, terminal recording out — no TTY, no code changes, no extra dependencies. This is useful for demo capture, bug reports, CI verification, and letting AI agents interact with and verify TUI applications.
Recording a session
Set the tamboui.record system property to the output path:
java -Dtamboui.record=session.cast -jar myapp.jar
# or with jbang
jbang -Dtamboui.record=session.cast myapp.java
The application runs normally; every frame is captured and the cast is written when the
process exits (or when the configured duration is reached). Play it back with
asciinema play session.cast, or render it to a gif/svg with
agg or vhs.
Recording applies regardless of how the backend was created — including applications that
construct their own backend and pass it via TuiConfig.builder().backend(…).
Recording properties
| Property | Default | Description |
|---|---|---|
|
unset |
Output |
|
unset |
VHS-format tape file that drives the application (see below); setting it alone also enables recording, with the output derived from the tape name |
|
|
Capture rate in frames per second |
|
|
Maximum recording duration in milliseconds — raise this for longer sessions |
|
|
Terminal size the recording reports and captures |
The numbers must be positive integers. Any other value is reported on standard error and the default is used instead.
Driving an application with a tape
Supply a tape file with tamboui.record.config and the application is driven by the
scripted keystrokes instead of live input:
# explicit output path
java -Dtamboui.record=out.cast -Dtamboui.record.config=script.tape -jar myapp.jar
# single property: naming the tape enables recording, and the output
# is derived from the tape name (script.tape -> script.cast)
java -Dtamboui.record.config=script.tape -jar myapp.jar
No input file is ever read unless explicitly named — there is no automatic tape lookup — and the derived output is written to the directory the tape lives in.
Tape format
Tapes use the VHS syntax. A minimal example:
# drive the app: wait for startup, navigate, quit
Sleep 500ms
Type "hello"
Down 3
Enter
Sleep 1s
Type "q"
Supported commands:
| Command | Meaning |
|---|---|
|
Type a string (supports |
|
Type a string with a pause between the characters, e.g. |
|
Named keys |
|
Navigation keys |
|
Function keys |
|
Modifier combinations, also combined and on named and function keys (e.g. |
|
Press a key several times, e.g. |
|
Pause, e.g. |
|
Wait until a line of the screen matches the regex (at most 15s); |
|
Wait until the screen, its lines joined with newlines, matches the regex |
|
The interactions between them are played but left out of the recording, together with the time they take |
|
Marks a frame of interest in the recording |
|
Include another tape file |
|
Repeat with delay, e.g. |
Set, Output and Require directives are accepted (for compatibility with tapes shared with the
vhs tool) but the recording geometry and output come from the tamboui.record.*
properties. Any other line the player does not understand is reported on standard error and skipped,
so a typo in a tape does not go unnoticed.
Keys are sent as a terminal sends them, the xterm way: the function, cursor and editing keys as escape
sequences with the modifiers as a parameter (Shift+F8 is ESC [ 19 ; 2 ~), and Alt as an ESC
prefix. Use the named keys rather than typing escape sequences with Type, which sends the characters
one at a time.
Wait makes a tape independent of how long the application takes, for example to wait for data to
load or for a slow backend to answer, instead of guessing a Sleep. A Wait that times out is reported
on standard error and the tape continues.
Headless verification recipe
Because playback and recording need no TTY, a script or AI agent can run any TamboUI application end-to-end and assert on what happened:
cat > script.tape <<'EOF'
Sleep 500ms
Type "q"
EOF
jbang -Dtamboui.record.config=script.tape myapp.java
# the cast is plain JSON-lines: assert on rendered content
grep -q "Expected screen text" script.cast && echo OK
Each cast line is a timestamped output event containing the ANSI frame data, so expected labels, table rows, or error messages can be checked with ordinary text tools.
Relationship to pilot testing
Pilot testing drives widgets programmatically inside a JVM test with structured assertions — prefer it for unit/integration tests of your own code. Recording works from the outside with zero code access: any application, any backend, input as keystrokes, output as the actual terminal byte stream. Prefer it for black-box verification, demo capture, and reproducing bugs.