| name | tui-wright |
| description | Use this skill when you need to interact with a TUI (terminal UI) application programmatically — spawning it, reading its screen, sending keyboard/mouse input, resizing, or debugging its behavior. Use when the user asks to "debug a TUI", "interact with a terminal app", "test a CLI tool", "read what's on screen", or anything involving programmatic control of a terminal application. |
tui-wright: Programmatic TUI Control
You have access to tui-wright, a tool that spawns TUI processes in a virtual terminal and lets you control them via separate CLI commands. This is how you interact with terminal applications like htop, vim, less, psql, database REPLs, or any program that draws to the terminal.
Quick Reference
tui-wright spawn <command> [args...]
tui-wright run <command>
tui-wright screen <session>
tui-wright screen <session> --json
tui-wright type <session> <text>
tui-wright key <session> <name>
tui-wright mouse <session> <action> <col> <row>
tui-wright resize <session> <cols> <rows>
tui-wright cursor <session>
tui-wright waitfor <session> <text>
tui-wright assert <session> <text>
tui-wright kill <session>
tui-wright list
tui-wright trace start <session> [--output path.cast]
tui-wright trace stop <session>
tui-wright trace marker <session> <label>
tui-wright snapshot save <session> <file>
tui-wright snapshot diff <session> <file>
Core Workflow
Every interaction follows the same pattern: act, wait, read.
SESSION=$(tui-wright spawn <command> | awk '{print $2}')
tui-wright type $SESSION "some input"
tui-wright key $SESSION enter
sleep 0.2
tui-wright screen $SESSION
Always add a short sleep (0.2-0.5s) after sending input before reading the screen. TUI applications need time to process input and redraw.
Prefer waitfor over sleep when you know what text should appear — it's both faster and more reliable:
tui-wright key $SESSION enter
tui-wright waitfor $SESSION "expected output"
Spawning Sessions
SESSION=$(tui-wright spawn bash | awk '{print $2}')
SESSION=$(tui-wright spawn htop --cols 120 --rows 40 | awk '{print $2}')
SESSION=$(tui-wright spawn vim myfile.txt | awk '{print $2}')
The spawn command starts a background daemon and returns immediately. The session ID is a short hex string like a1b2c3.
Running a command directly
If you just want to run a command in a shell (spawn + type + enter in one step), use run:
SESSION=$(tui-wright run "ls -la /tmp" | awk '{print $2}')
sleep 0.3
tui-wright screen $SESSION
SESSION=$(tui-wright run "htop" --cols 120 --rows 40 | awk '{print $2}')
This is equivalent to spawn bash followed by type and key enter, but in a single command.
Sending Input
Text
tui-wright type $SESSION "SELECT * FROM users;"
This sends characters one by one, as if typed on a keyboard. Does not add a newline — use key enter for that.
Special Keys
tui-wright key $SESSION enter
tui-wright key $SESSION tab
tui-wright key $SESSION escape
tui-wright key $SESSION up
tui-wright key $SESSION ctrl+c
tui-wright key $SESSION f5
Supported key names:
| Category | Keys |
|---|
| Navigation | up, down, left, right, home, end, pageup/pgup, pagedown/pgdn |
| Editing | enter/return, tab, backspace/bs, delete/del, insert/ins, space |
| Modifiers | ctrl+a through ctrl+z, alt+<char> |
| Function | f1 through f12 |
| Alt+Navigation | alt+up, alt+down, alt+left, alt+right, alt+home, alt+end, alt+pageup/alt+pgup, alt+pagedown/alt+pgdn, alt+insert/alt+ins, alt+delete/alt+del |
| Alt+Function | alt+f1 through alt+f12 |
| Other | escape/esc |
Both ctrl+c and ctrl-c syntax work (same for alt+ vs alt-). Key names are case-insensitive.
shift+<key> is NOT supported. To send uppercase letters (e.g., htop's M for sort-by-memory), use type instead of key:
tui-wright key $SESSION shift+m
tui-wright type $SESSION "M"
This applies to any shortcut that uses an uppercase letter.
Mouse
tui-wright mouse $SESSION press 10 5
tui-wright mouse $SESSION release 10 5
tui-wright mouse $SESSION scrollup 0 0
tui-wright mouse $SESSION scrolldown 0 0
tui-wright mouse $SESSION move 15 8
Coordinates are 0-indexed. Uses SGR mouse encoding (no column limit).
Reading the Screen
Plain text (most common)
tui-wright screen $SESSION
Returns the terminal content as plain text, one line per row, trailing whitespace trimmed, trailing empty lines removed.
JSON with cell attributes
tui-wright screen $SESSION --json
Returns a JSON object. See reference.md for the full schema.
Use --json when you need to inspect colors, bold/italic styling, or precise cell contents. For most debugging, plain text is sufficient.
Cursor position
tui-wright cursor $SESSION
Resizing
tui-wright resize $SESSION 120 40
The TUI application receives a SIGWINCH and redraws at the new size. Wait briefly after resizing before reading the screen.
Waiting and Assertions
Wait for text to appear
waitfor polls the screen until the given text appears, or times out:
tui-wright waitfor $SESSION "expected output"
tui-wright waitfor $SESSION "Loading complete" --timeout 10000
On success, prints the screen contents and exits 0. On timeout, prints an error to stderr and exits 1.
Prefer waitfor over sleep — it returns as soon as the text appears, so it's both faster and more reliable than guessing a sleep duration.
Assert text is visible
assert checks whether text is currently on screen (no polling, no waiting):
tui-wright assert $SESSION "Welcome"
Always prints the current screen contents. Use assert for quick checks after you've already waited for the screen to stabilize.
Session Management
tui-wright list
tui-wright kill $SESSION
Always kill sessions when done. Each session is a background daemon holding a PTY.
Trace Recording
Record all terminal activity as an asciicast v2 .cast file, playable with asciinema play or the embeddable web player.
tui-wright trace start $SESSION --output /tmp/my-session.cast
tui-wright type $SESSION "echo hello"
tui-wright key $SESSION enter
tui-wright trace marker $SESSION "after-setup"
tui-wright trace stop $SESSION
The trace captures:
- Output events (
"o") — raw PTY output bytes with timestamps, exactly as the terminal received them
- Input events (
"i") — every keystroke, text, and mouse event sent through tui-wright
- Markers (
"m") — automatically inserted for each Key/Type/Mouse command (e.g., "key enter", "type echo hello"), plus any custom markers you insert
- Resize events (
"r") — when resize is called
If no --output path is given, the file is written to a temp directory. The trace is automatically stopped if the session is killed.
Play back the recording:
asciinema play /tmp/my-session.cast
Snapshot Diffing
Save and compare screen snapshots for visual regression testing.
tui-wright snapshot save $SESSION baseline.json
tui-wright snapshot diff $SESSION baseline.json
snapshot diff outputs a JSON diff with:
identical — true/false overall result
dimensions_changed — old/new rows and cols (if changed)
cursor_changed — old/new cursor position (if moved)
changed_cells — list of every cell that differs, with old and new values (char, fg/bg colors, bold, italic, underline, inverse)
summary — total cells compared, number changed
Exit codes: 0 if identical, 1 if different — use in test scripts:
tui-wright snapshot diff $SESSION baseline.json && echo "PASS" || echo "FAIL"
Practical Patterns
Run a command in bash and read output
SESSION=$(tui-wright run "ls -la /tmp" | awk '{print $2}')
tui-wright waitfor $SESSION "$"
tui-wright screen $SESSION
tui-wright kill $SESSION
Navigate a menu-driven TUI
SESSION=$(tui-wright spawn htop | awk '{print $2}')
sleep 0.5
tui-wright screen $SESSION
tui-wright key $SESSION down
tui-wright key $SESSION down
sleep 0.2
tui-wright screen $SESSION
tui-wright key $SESSION f10
tui-wright kill $SESSION
Work with a REPL
SESSION=$(tui-wright spawn python3 | awk '{print $2}')
sleep 0.5
tui-wright type $SESSION "2 + 2"
tui-wright key $SESSION enter
sleep 0.2
tui-wright screen $SESSION
tui-wright key $SESSION ctrl+d
tui-wright kill $SESSION
Edit a file in vim
SESSION=$(tui-wright spawn vim test.txt | awk '{print $2}')
sleep 0.5
tui-wright key $SESSION i
tui-wright type $SESSION "Hello, world!"
tui-wright key $SESSION escape
tui-wright type $SESSION ":wq"
tui-wright key $SESSION enter
tui-wright kill $SESSION
Profile a system with htop
SESSION=$(tui-wright spawn htop --cols 120 --rows 40 | awk '{print $2}')
sleep 0.5
tui-wright screen $SESSION
tui-wright type $SESSION "M"
sleep 0.3
tui-wright screen $SESSION
tui-wright key $SESSION f4
sleep 0.2
tui-wright type $SESSION "docker"
sleep 0.3
tui-wright screen $SESSION
tui-wright key $SESSION escape
tui-wright key $SESSION f5
sleep 0.3
tui-wright screen $SESSION
tui-wright key $SESSION f3
tui-wright type $SESSION "chrome"
sleep 0.3
tui-wright screen $SESSION
tui-wright key $SESSION escape
tui-wright key $SESSION f10
tui-wright kill $SESSION
Inspect screen attributes for color/style debugging
SESSION=$(tui-wright spawn bash | awk '{print $2}')
tui-wright type $SESSION "ls --color"
tui-wright key $SESSION enter
sleep 0.3
tui-wright screen $SESSION --json | jq '.cells[1][0]'
tui-wright kill $SESSION
Tips
Chain commands in a single Bash call
Multiple tui-wright commands can be chained with && to reduce round-trips. Only add a sleep before screen reads:
tui-wright key $SESSION f4 && sleep 0.2 && tui-wright type $SESSION "filter text" && sleep 0.3 && tui-wright screen $SESSION
Sleep durations
- 0.5s for initial spawn (TUI apps need time to start and draw the first frame)
- 0.3s after typing text into interactive prompts (search bars, filters, REPLs)
- 0.2s after single key presses in an already-running app
Complex TUI apps (htop, vim) generally need longer sleeps than simple ones (bash, python REPL).
Important Notes
- Prefer
waitfor over sleep. Use tui-wright waitfor $SESSION "text" when you know what to expect — it's faster and more reliable. Fall back to sleep 0.2-0.5 only when there's no specific text to wait for.
- Always kill sessions when done. They are background daemons.
- Screen text is trimmed. Trailing whitespace per line and trailing empty lines are removed.
- Type does not add newlines. Use
tui-wright key $SESSION enter after typing a command.
- Sessions persist across your Bash calls. The session ID is all you need to reconnect.
- Use
type for uppercase shortcuts, not key shift+X. The shift+ modifier is not supported for keys. Send uppercase characters via type instead.
- Traces are asciicast v2. The
.cast files work with asciinema play, the web player, and asciinema.org.
- Snapshot diff uses exit codes. Exit 0 means identical, exit 1 means different — chain with
&& or || in scripts.
For the full JSON screen schema, diff schema, and advanced usage, see reference.md.