tui-verify-change
GitHub用于验证 Warp TUI 前端变更的技能。通过本地或云端无头运行终端程序,捕获渲染屏幕以确认 UI、交互及行为变更的实际效果。
Trigger Scenarios
Install
npx skills add warpdotdev/warp --skill tui-verify-change -g -y
SKILL.md
Frontmatter
{
"name": "tui-verify-change",
"description": "Verify a change to Warp's headless TUI front-end (crates\/warp_tui) by running it — locally via .\/script\/run-tui, or in a headless cloud runner via a WARP_API_KEY dogfood build — and reading the rendered screen back (under tmux when it's installed, otherwise directly). Use whenever you change TUI UI, rendering, input, or behavior and need to confirm the real on-screen result."
}
tui-verify-change
Verify a change to Warp's headless TUI front-end (crates/warp_tui, and the
cell-grid element library in crates/warpui_core/src/elements/tui) by running it
and reading back the actual rendered screen.
The whole point: the TUI is a console program, so you can run it in a real
terminal and read the actual rendered screen straight back. When tmux is
available it's the ideal driver — you run the TUI in a tmux pane, drive it with
tmux send-keys, and read the frame back with tmux capture-pane — but tmux is
not required: if it isn't installed you can still run and observe the TUI
directly (see If tmux isn't installed under Step 2). Either way you (the
agent) see the actual screen text — no computer_use, no real display, no cloud
screenshot agent, and no relying on a separate watcher's description of what it
saw. (Contrast the GUI-only gui-onboarding-verification-skill,
gui-integration-test, gui-integration-test-video, and the computer_use /
verify-ui-change-in-cloud flow.) This is the fast, preferred way to confirm a
TUI change end-to-end.
This skill covers the manual live-run verification. For a durable regression
that runs in CI, also add a render-to-lines unit test per tui-testing — the two
are complementary: the live run confirms real behavior; the snapshot test locks
it in.
When to use
- You changed anything a TUI user sees or interacts with: an element/layout in
crates/warpui_core/src/elements/tui, a TUI view/screen incrates/warp_tui(transcript, input, zero state, login placeholder, etc.), TUI keybindings, or TUI rendering/behavior. - You want to confirm the actual rendered result, not just that it compiles or a unit test passes.
If your change is GUI-only (app/, WarpUI pixel Element/View), this is the
wrong skill — use the GUI verification path instead.
Local vs cloud verification (pick your context first)
How you build, run, and log in depends on where you're running. Decide this
up front — it determines whether you use ./script/run-tui or the WARP_API_KEY
path below.
- Local context — you're working in a local dev checkout, typically
alongside the user. Just run
./script/run-tuidirectly: it buildswarp_tuiand runs it, selecting the internallocalchannel when thewarp-channel-configgenerator is available and falling back towarp-tui-ossotherwise. Do not reach forWARP_API_KEYhere — it's generally not set in a local environment, and you don't need it: a local build reaches the authenticated state through the normal interactive device-auth login flow (or you're already signed in on this machine). The non-interactiveWARP_API_KEYlogin below is a cloud-runner affordance, not a local one, so don't let a missing key block you locally. - Cloud context — you're a headless cloud agent (e.g. the factory-client
runner) with no browser for device-auth, so reaching a signed-in surface
relies on the non-interactive
WARP_API_KEYalready in the environment. That does not mean bypassing./script/run-tui: when thewarp-channel-configgenerator is available,./script/run-tuiselects the internallocalbinary; otherwise it falls back towarp-tui-oss. Both accept the inheritedWARP_API_KEY, so prefer the maintained runner unless you need a specific binary or profile. In the cloud, the inherited key signs you in without the browser device-auth flow.
The logged-out surface (the Sign in to continue placeholder and any pure
element/layout) needs neither login path — a plain OSS build is enough in either
context.
Step 1 — Build the TUI
Always build with a small job count so the large warp dependency tree doesn't
OOM the machine:
cd <warp-repo-root>
CARGO_BUILD_JOBS=2 cargo build -p warp_tui --bin warp-tui-oss
warp-tui-ossis the OSS channel binary and the safest default (no internalwarp-channel-configgenerator required)../script/run-tuidoes the equivalent, selecting the internallocalbinary when the generator is available and falling back towarp-tui-ossotherwise.- Fix all compile errors before running (see
fix-errors). The first build of this tree takes a while; incremental rebuilds after a one-line change are fast (~10s), so the edit → rebuild → re-capture loop below is quick.
Logged-in vs logged-out (important)
The OSS build starts logged out and stops at a Sign in to continue
placeholder (it drives a device-authorization login flow that needs a browser).
The login-gated root has three pre-session states you may see:
AwaitingLogin→ a centered placeholder that readsSign in to continue, thenOpening your browser…(or, once the device code is known,Open <uri> in your browserandand enter code: <code>). It does not show a Ctrl-C hint.LoggedIn→ brieflyStarting terminal…, then the zero state (Warp Agent+ version, a "What's new" list, and the project context section) with the input view.Failed→Login failed: <message>followed byPress Ctrl-C to exit.
(Exact strings live in crates/warp_tui/src/ui.rs — verify against it if you're
asserting on placeholder text.)
So: if your change is on the login placeholder or a pure element/layout, the
logged-out OSS build is enough. If your change is in the live terminal /
transcript / input surface, you must reach the authenticated state (see the
next section), or your change will sit behind the login gate and you'll only ever
see Sign in to continue.
Logging in non-interactively (WARP_API_KEY) — cloud context
This is the cloud path. In a local checkout, skip it: run ./script/run-tui
and log in interactively (see Local vs cloud verification above), since
WARP_API_KEY usually isn't set locally. Use the flow here when you're a
headless cloud runner where the key is set and there's no browser for
device-auth.
You can reach the authenticated (LoggedIn) state headlessly — no browser, no
device-auth flow — by launching any TUI channel binary with a WARP_API_KEY in
the environment. This is the fast way to verify live terminal/transcript/input
changes.
Key constraints:
- All channels are supported. API-key login works with
warp-tui-ossas well as the Preview, Dev, Local, and Stable binaries. - The key must already be in the environment. In a sandbox where
WARP_API_KEYis set, a freshly startedtmuxserver inherits it. Never echo, print, or inline the secret value in a command — just rely on the inherited environment variable. (--api-key <key>on the command line also works but would expose the secret, so prefer the env var.)
cd <warp-repo-root>
CARGO_BUILD_JOBS=2 cargo build -p warp_tui --bin warp-tui-oss
tmux kill-session -t tuicheck 2>/dev/null
# WARP_API_KEY is inherited from the environment by the new tmux server.
tmux new-session -d -s tuicheck -x 120 -y 40 './target/debug/warp-tui-oss'
sleep 20 # login + session start
tmux capture-pane -t tuicheck -p # expect the logged-in zero state
When it works you'll see the zero state (Warp Agent + input view + model
selector) instead of Sign in to continue, and you can send-keys a real prompt
and read the agent's reply back with capture-pane. (This login path was added
in warpdotdev/warp#13583.)
Step 2 — Run under tmux and read the frame back
The TUI needs a real interactive PTY — a tmux pane is exactly that, and it
lets you both send input and read the rendered screen back as text. Start the TUI
in a detached session with an explicit size (don't skip -x/-y; a degenerate
1-row pane renders nothing useful):
cd <warp-repo-root>
tmux kill-session -t tuicheck 2>/dev/null # clear any prior run
tmux new-session -d -s tuicheck -x 120 -y 40 './target/debug/warp-tui-oss'
sleep 1 # let it draw + probe the terminal
tmux capture-pane -t tuicheck -p # <-- the rendered screen, as text
tmux capture-pane -p prints the pane's current contents (the TUI's alternate
screen) to stdout, so you read the real frame directly and assert on it. Add
-e to include ANSI escape sequences when you need to check colors/styles:
tmux capture-pane -t tuicheck -p -e # includes color/style escapes
Drive interactions with tmux send-keys, sleeping to let the UI settle, then
capture again. For example (type a line, submit it, wait, then read the screen):
tmux send-keys -t tuicheck "What is 2+2? Answer in one short sentence." && \
sleep 1 && tmux send-keys -t tuicheck Enter && sleep 5 && \
tmux capture-pane -t tuicheck -p -e
Send special keys by name (Enter, Escape, C-c for Ctrl-C, Up/Down).
When done, tear the session down: tmux kill-session -t tuicheck.
If tmux isn't installed
tmux is the preferred driver because it gives you programmatic send-keys +
capture-pane, but it is not a hard requirement — never block verification
just because tmux is missing. Check with command -v tmux; if it's absent, fall
back:
- Local context: the simplest path is to run
./script/run-tuidirectly in a real terminal and read the rendered output yourself — you already have a PTY. When you're working alongside the user, you can also have them run it and report what renders. Installing tmux is optional, not a prerequisite. - Cloud / no-tmux context: run the built binary inside another PTY wrapper so
you can still capture output — e.g.
script(util-linux):script -qe -c './target/debug/warp-tui-oss' /tmp/tui.log, then read/tmp/tui.log. If tmux is installable in your environment (apt-get install -y tmux) and that's cheaper, do that and use the flow above instead. If none of these work, run the binary directly, capture whatever output you can, and say so in the PR/thread rather than implying a tmux-driven capture.
Everything else in this skill (what to look for, the snapshot test, the evidence) is identical whether or not tmux drove the run.
Iterate loop
Because incremental rebuilds are ~10s, iterate tightly: edit the TUI code →
cargo build -p warp_tui --bin warp-tui-oss → tmux kill-session + restart the
session → capture-pane and compare. Verified before/after example: changing the
login placeholder string and rebuilding flips the captured line from
Sign in to continue to the new text, visible directly in capture-pane output.
Step 3 — Check the captured frame against your change
You have the real screen text, so verify it yourself: grep/scan the
capture-pane output for the exact string or layout your change should
produce, and diff the before/after captures. No watcher interpretation needed —
if the expected text isn't in the capture, the change isn't rendering.
Pitfalls (learned hands-on)
-
Run it in a real terminal / tmux pane (a PTY), and drive + capture it via tmux as above.
-
If it exits (code 101) right after the first frame instead of staying up: don't assume it's a terminal/stdin problem — check the TUI log first:
tail -40 ~/.local/state/warp-terminal-tui/oz/warp-tui.log. One cause seen in the headless OSS/logged-out sandbox build is a debug-only binding-validation panic:crates/warpui_core/src/keymap/matcher.rs(validate_bindings, gated on#[cfg(debug_assertions)]) panics withBindings failed validationwhen a keystroke binding matches a TUI keymap context without being TUI-owned (it wasapp:reopen_closed_session, Ctrl+Alt+T). It does not reproduce in every setup — it depends on which keystroke bindings the running config loads, and the validator exempts non-keystroke (palette/custom) triggers — so treat this as one thing to check, not a guarantee. If you hit it, two ways to still verify a change:-
Build
--release— the validator is compiled out, so the TUI stays up and you cansend-keys/capture-panefreely:cargo build --release -p warp_tui --bin warp-tui-ossthen run./target/release/warp-tui-oss. -
Or poll
capture-paneright after launch on the debug build to grab the first frame before the panic:tmux new-session -d -s tuicheck -x 120 -y 40 './target/debug/warp-tui-oss' for i in $(seq 1 15); do frame=$(tmux capture-pane -t tuicheck -p | sed 's/[[:space:]]*$//' | grep .) [ -n "$frame" ] && { echo "$frame"; break; } sleep 0.2 done tmux kill-session -t tuicheck 2>/dev/null
If the debug build stays up on your machine, you can
send-keys/capture-panerepeatedly without racing. -
-
Alt screen is handled for you.
capture-panereads the alternate screen, so you don't need to fight escape-sequence soup the way piping stdout would. -
Startup probe. On launch the TUI emits terminal probes (OSC
10/11+ a device-attributes query) to pick a theme; a normal tmux pane answers them. Give it ~0.5–1s (asleep) before the first capture. -
Quitting. Ctrl-C is
tmux send-keys -t tuicheck C-c. On the login placeholder one Ctrl-C exits; in a live session it's press-again-to-exit (first press cancels/clears input and arms a ~1s window; a second within the window exits). Alwaystmux kill-sessionat the end so a stray session doesn't linger.
Step 4 — Capture screenshots and video (asciinema + agg)
capture-pane text is the fast inner-loop check (Step 3) and enough to assert
on a change. But for PR evidence — and for attaching durable image/video
artifacts — you often want an actual screenshot or a short video of the
rendered TUI. Because the TUI is a console program, capture it by recording its
PTY session with asciinema, rendering that recording with agg, and
transcoding it to an MP4 (the same format Warp's computer_use screen
recording produces) with ffmpeg; pull a still frame out with ffmpeg too.
Install the tooling (cloud runner — one-time). asciinema and ffmpeg are
packaged; agg ships as a prebuilt binary rather than in apt:
sudo apt-get update && sudo apt-get install -y asciinema ffmpeg tmux
# agg is not in apt — install a PINNED release binary and verify its checksum before
# installing as root (don't pull an unpinned `latest`). The checksum below is for the
# x86_64 build; on another arch use that asset's published checksum from the release.
AGG_VERSION=v1.9.0
AGG_SHA256=f111e315cd71056b116302342553dd765b7297579ed511f111d0cedb442aeda6
curl -fsSL -o /tmp/agg \
"https://github.com/asciinema/agg/releases/download/${AGG_VERSION}/agg-$(uname -m)-unknown-linux-gnu"
echo "${AGG_SHA256} /tmp/agg" | sha256sum -c - # aborts on mismatch
sudo install -m 0755 /tmp/agg /usr/local/bin/agg
Record the session. asciinema needs a real PTY, so run it inside tmux (a
bare asciinema rec in a non-interactive runner shell fails with "not a
terminal"). Drive the TUI with tmux send-keys exactly as in Step 2 — the keys
reach the binary running under asciinema:
cd <warp-repo-root>
tmux kill-session -t tuicap 2>/dev/null # clear only THIS capture session (not kill-server)
# asciinema records the TUI's PTY; -c runs the binary; --overwrite replaces a prior cast.
tmux new-session -d -s tuicap -x 120 -y 40 \
'asciinema rec --overwrite -c "./target/debug/warp-tui-oss" /tmp/tui.cast'
sleep 1 # let it draw + answer the theme probe
# ...drive the interaction you want to show, e.g.:
# tmux send-keys -t tuicap "hello" Enter && sleep 3
# Quit the TUI so asciinema finalizes the cast. The logged-out placeholder exits on one
# Ctrl-C, but a live/logged-in session needs a SECOND press within its ~1s window (see
# "Quitting" under Pitfalls) — so send two; the extra press is a harmless no-op if it
# already exited (the session is gone, hence 2>/dev/null).
tmux send-keys -t tuicap C-c && sleep 0.5 && tmux send-keys -t tuicap C-c 2>/dev/null
sleep 1
For a logged-in capture, build/run warp-tui-dev with WARP_API_KEY per
Step 1 instead of warp-tui-oss.
Render the video (MP4). Match the format Warp's computer_use screen
recording uses — an H.264 / yuv420p MP4 with +faststart (see
crates/computer_use/src/linux/recording.rs) — so TUI captures are consistent
with GUI/computer-use recordings. agg only emits GIF, so render to GIF and then
transcode to MP4 with those settings. libx264 + yuv420p require even
dimensions, so pad up by a pixel when the terminal render is odd-sized:
agg --cols 120 --rows 40 /tmp/tui.cast /tmp/tui.gif
ffmpeg -y -i /tmp/tui.gif -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2" \
-c:v libx264 -pix_fmt yuv420p -movflags +faststart /tmp/tui.mp4
# /tmp/tui.gif is just the intermediate; /tmp/tui.mp4 is the artifact you keep.
Pull a still (PNG) from a frame while the surface is on screen — see the frame-timing pitfall below:
ffmpeg -y -ss 1.5 -i /tmp/tui.mp4 -frames:v 1 /tmp/tui.png
Attach the capture as conversation artifacts (required, not an afterthought).
Once you have the still and/or the recording, attach each to the run as a
conversation artifact so the proof persists beyond /tmp, travels with the
task, and can surface into the PR description in the native Oz flow — don't leave
it sitting in a temp file. Call the upload_artifact tool once per file, passing
the local file_path and a short description (e.g. file_path=/tmp/tui.png,
description="TUI <surface> after <change> — verification screenshot", and
likewise /tmp/tui.mp4 for the recording). For any user-visible TUI change you
verified here, attaching the screenshot and any recording is expected. These
are FILE artifacts capped at 25 MB each, so keep recordings short (see below). If
you're running somewhere the upload_artifact tool isn't available (a plain
local dev shell rather than a cloud/ambient agent), keep the files and reference
them in the PR instead.
Capture pitfalls:
- asciinema must run under a PTY. Wrap it in tmux (above) or
script; a bareasciinema recin a non-interactive runner shell errors out. - Don't take the still from the first or last video frame. The first frame
is the blank terminal before the TUI draws, and once you quit the TUI the alt
screen is restored — so the final frames show the normal terminal (e.g. the
OSS
WARP_API_KEY ... IGNOREDstartup warning), not the TUI surface. Extract a mid-recording timestamp (when the surface is up), or stop recording while the surface is still displayed so the last frame is the surface. - Keep it short. A few seconds at 120x40 renders to tens of KB; downstream sinks (Slack, and conversation FILE artifacts) cap uploads at 25 MB, so don't record minutes of idle.
Keep capture-pane text as the fast inner loop; reach for asciinema+agg when you
need the image/video to attach.
Step 5 — Lock it in with a snapshot test
A live run proves the change works now; it is not a regression guard. For any
non-trivial TUI rendering/behavior change, add or update a render-to-lines unit
test (warpui_core::elements::tui::test_support::render_to_lines /
TuiBuffer::to_lines) per tui-testing, and run:
cargo nextest run -p warp_tui
cargo nextest run -p warpui_core
Evidence for the PR
For a user-visible TUI change, prefer a screenshot or short video as the
primary evidence — an actual image/clip of the rendered surface is always more
convincing to a reviewer than raw text. Capture it per Step 4 (a still, or an
H.264 MP4 matching computer-use recordings), attach it to the run as a
conversation artifact, and reference it in the PR. Include the tmux capture-pane lines (and/or a render_to_lines snapshot diff) as a
supplement — handy for asserting on exact text — not as the main proof. Only
fall back to text alone when an image/clip genuinely can't be produced, and say
so. This is the TUI equivalent of the GUI's computer_use screenshot (see the
TUI caveat in review-pr-local).
Related skills
tui-ui-guidelines (the TuiElement cell-grid library) and tui-testing
(render-to-lines unit tests) are companion TUI skills added alongside this one;
land them together. This skill's build/run/capture workflow stands on its own —
those cover authoring TUI UI and writing durable tests.
GUI-only counterparts (do not use for TUI work): gui-integration-test,
gui-integration-test-video, gui-onboarding-verification-skill.
Improving this skill over time (self-improvement loop)
The aim is for this skill to get better over time — not for every run to end in an edit. Most runs should need no change here; don't manufacture trivial wording tweaks just to have improved something, and never let this step turn into busywork.
Act only when a run surfaces a genuine, notable gap — a step that didn't
work as written, a command that failed, a path that moved, missing
local-vs-cloud or tmux handling, or an assumption that didn't hold. When that
happens, don't just work around it silently: capture the specific problem (what
you expected vs. what actually happened) and propose the fix in a separate
PR — separate from the change you were verifying, so the skill improvement is
reviewable on its own and the original PR stays scoped. Make the smallest correct
edit to .agents/skills/tui-verify-change/SKILL.md (follow the update-skill
conventions) that would have made the run go smoothly.
Version History
-
04a7f83
Current 2026-08-19 21:13
新增 API 密钥登录支持,移除旧认证机制,更新 TUI 验证指南并增加启动模式覆盖。
- 726c1b6 2026-07-24 20:21


