How to record mew's TUI for docs, demos, tests, and agent feedback — harness, daemon-connected, and vhs.
TUI capture methods
Section titled “TUI capture methods”mew has three ways to capture its terminal UI. They trade off determinism, fidelity, and setup complexity differently.
mew tui-capture |
mew tui-capture --connect |
vhs |
|
|---|---|---|---|
| Determinism | Fully deterministic (in-process FakeProvider) | Non-deterministic (real daemon turn) | Non-deterministic (real pty + real provider) |
| Dependencies | ffmpeg (for video only) | running mew daemon + ffmpeg | vhs, ttyd, ffmpeg, Chrome |
| Terminal chrome | None — raw rasterized buffer | None — raw rasterized buffer | Full — window decorations, fonts, themes |
| Network | Never | Local WebSocket to daemon | Required (LLM or fake-provider daemon) |
| CI-runnable | Yes | Yes (if daemon is fake-provider) | No (needs Chrome + display) |
| Best for | Regression tests, agent feedback, fast iteration | Real chat/turn behavior for demos | Polished README gifs, social media |
Method 1: mew tui-capture (deterministic, recommended)
Section titled “Method 1: mew tui-capture (deterministic, recommended)”Runs a harness script against a headless TestBackend with FakeProvider. No
network, no credentials, no daemon. Output is byte-for-byte deterministic.
Prerequisites
Section titled “Prerequisites”cargo build -p mew(produces./target/debug/mew)ffmpegon PATH (only needed for--mp4)
# Screenshot: produce a PNG from a scriptmew tui-capture --script capture.txt --width 80 --height 24
# Video: auto-wraps the script in recording and produces an MP4mew tui-capture --script capture.txt --mp4 demo.mp4 --fps 30
# Interactive puppet mode: agent drives the TUI via stdinmew tui-capture -i --screenshot-dir tmp/framesScript verbs
Section titled “Script verbs”| Verb | Description |
|---|---|
type <text> |
Type text into the composer |
key <name> |
Send a key: enter, esc, tab, backspace, arrows, ctrl+c, etc. |
submit |
Shorthand for key enter |
say <text> |
Inject an assistant text turn (streams in chunks) |
error <text> |
Inject a terminal error event |
snapshot [label] |
Render current frame as text to stdout |
screenshot <path> |
Save current frame as a PNG |
start_recording |
Begin capturing frames after each verb |
stop_recording |
Stop capturing; reports frame count |
pause <ms> |
Duplicate the last frame to simulate elapsed time |
record <path> [fps] |
Encode recorded frames to MP4 via ffmpeg |
size <w> <h> |
Resize the virtual terminal |
settings |
Open the settings overlay with the default config |
settings_config <path> |
Open the settings overlay from a TOML config |
Example:
# Type a prompt and capture the responsetype hello worldsubmitsay Welcome to mew! This is a deterministic capture.pause 500snapshot after-responsescreenshot output.pngMethod 2: mew tui-capture --connect (real daemon turn)
Section titled “Method 2: mew tui-capture --connect (real daemon turn)”Connects to a running mew-daemon so the recording exercises the real
chat/turn pipeline: streaming, tool calls, subagents, and session rail. The
rendering is still headless through the rasterizer.
Prerequisites
Section titled “Prerequisites”- A running daemon with a provider credential configured.
ffmpegon PATH for--mp4.
Start the daemon
Section titled “Start the daemon”export MEW_CRED_UMANS=<key>mew daemon \ --provider umans \ --model umans/umans-coder \ --port 127.0.0.1:0 \ --background \ --log /tmp/mew-capture.log
# Find the actual portgrep "mew daemon listening (tcp)" /tmp/mew-capture.logDaemon-mode verbs
Section titled “Daemon-mode verbs”All harness verbs work, plus:
| Verb | Description |
|---|---|
send "<text>" |
Submit a user message to the daemon and wait for the turn to start streaming |
wait_turn [timeout_ms] |
Block until the response stream finishes (default 30s) |
expect "<text>" |
Fail the script if the current text frame does not contain <text> |
screenshot_dir "<path>" |
Save a numbered PNG after every subsequent verb |
pause is especially useful in daemon mode because the response arrives
instantly in the final frame. Add pause 4000 after wait_turn to hold the
last frame for a few seconds.
Example:
send "What's the optimised way of reversing a binary string in Rust?"wait_turn 120000expect "reverse"pause 4000screenshot response.pngRun:
mew tui-capture \ --script capture.txt \ --connect ws://127.0.0.1:<port> \ --mp4 demo.mp4 \ --width 100 --height 30Stop the daemon when done:
mew daemon --stopHow it works
Section titled “How it works”crates/mew/src/commands/tui_capture.rs implements DaemonBackend, which:
- Connects to the daemon with
DaemonClient::connect(url). - Creates a session and reads
model/providerfromServerMessage::SessionReady. - Runs the script verb-by-verb, pumping
AgentEvents andServerMessages into a headlessmew_tui::App. - During
wait_turn, whileapp.streamingis true, draws and captures a frame roughly every 16 ms so the recorded video shows the response appearing progressively. - Encodes the captured frames to MP4 with ffmpeg.
Method 3: charm vhs (glamour shots)
Section titled “Method 3: charm vhs (glamour shots)”Use charm vhs to record mew’s terminal UI with full chrome: window decorations, fonts, colors, and cursor blink. vhs drives a real terminal via headless Chrome + ttyd.
Prerequisites
Section titled “Prerequisites”| Dependency | How to verify |
|---|---|
vhs |
vhs --version |
ttyd |
which ttyd |
ffmpeg |
which ffmpeg |
| Chrome | ls "/Applications/Google Chrome.app" |
Start a fake-provider daemon
Section titled “Start a fake-provider daemon”mew daemon --fake-provider --port 127.0.0.1:9847 --background --log /tmp/mew-vhs.logsleep 2Example tape
Section titled “Example tape”Output "demo.mp4"Set FontSize 16Set Width 1200Set Height 600Set Padding 20Set TypingSpeed 30ms
Type "./target/debug/mew chat --connect ws://127.0.0.1:9847"EnterSleep 3s
Screenshot "welcome.png"
Type "hello, what can you do?"Sleep 3s
Screenshot "response.png"
Ctrl+CSleep 1sRun with vhs demo.tape.
vhs tips
Section titled “vhs tips”- Paths must be quoted:
Output "demo.mp4". - Use
Screenshot "file.png"for a single image;Output "*.png"creates a frame directory. - Add generous
Sleepafter launch, after submitting a prompt, and before screenshots. Setcommands (exceptTypingSpeed) must appear at the top of the tape.
Agent-driven interactive mode
Section titled “Agent-driven interactive mode”Both mew tui-capture -i (harness) and mew tui-capture -i --connect <url>
(daemon) read verbs from stdin and print a text frame + optional screenshot
path after each verb. This lets an agent drive the TUI in a feedback loop:
mkfifo /tmp/mew-capture-fifo./target/debug/mew tui-capture -i --connect ws://127.0.0.1:<port> \ --screenshot-dir /tmp/frames < /tmp/mew-capture-fifo > /tmp/mew-capture.log 2>&1 &
# Agent sends verbs and reads framesprintf 'send "hi"\n' > /tmp/mew-capture-fifoprintf 'wait_turn 60000\n' > /tmp/mew-capture-fifoprintf 'snapshot response\n' > /tmp/mew-capture-fifoprintf 'quit\n' > /tmp/mew-capture-fifoThe agent reads /tmp/mew-capture.log for --- frame --- blocks and inspects
/tmp/frames/frame_*.png for visual state.
Choosing a method
Section titled “Choosing a method”- Regression tests / agent feedback: Method 1. Deterministic, fast, no setup.
- Real model demos where headless is fine: Method 2. Uses real provider, still scriptable and CI-friendly.
- Polished human-facing videos: Method 3. Full terminal chrome, but needs Chrome + display.
Common pitfalls
Section titled “Common pitfalls”- ffmpeg must be on PATH for any MP4 output in methods 1 and 2.
pause <ms>duplicates frames — at 30fps,pause 1000adds 30 identical frames. Use it to add natural timing.- Screenshots and text snapshots are independent —
screenshotwrites a PNG,snapshotprints text. Use both for the same frame when debugging. - Daemon mode is non-deterministic. The daemon controls when tokens arrive;
always use
wait_turnaftersendand generous timeouts. - State.toml corruption can make the daemon fail to start with “unrecognized
values”. Heal it interactively or edit
last_provider/last_modelmanually.