Differences from VHS
Betamax exists because of VHS. VHS made terminal demos something you write as a script and check into a repository, and it has rendered countless README GIFs, including years of Ratatui demos. It’s still a great tool. Betamax keeps its tape format so those tapes keep working, and takes a different architecture so it can grow into terminal testing and richer presentation over time.
This page explains what Betamax does differently and why, what it adds, and what to check when moving tapes from VHS. It was last compared against VHS 0.12.1.
How it works
Section titled “How it works”VHS records a tape by running several programs together. It starts ttyd to serve the shell over a web socket, drives xterm.js in headless Chrome to display it, takes screenshots of the browser page, and passes them to ffmpeg to encode every output, GIF and PNG included. That’s a clever reuse of mature parts, but each part has to be installed and kept in step, and the terminal state lives inside a browser page that the tape can only see through screenshots and text scraping.
Betamax runs a tape in one process. It starts the shell on a PTY, parses its output with
libghostty-vt, the terminal core from Ghostty, and renders frames
itself. GIF, WebP, PNG, and state JSON need only the betamax binary; ffmpeg is needed only for MP4
and WebM. Because the parsed terminal state is right there, a tape can wait on it, assert on it, and
save it as JSON. That is what makes the testing features possible, and it
gives new features one place to live instead of several programs to coordinate.
The terminal emulator matters as much as the plumbing. Both tools run your real shell; what differs
is the emulator interpreting its output. xterm.js is a terminal built for the browser, best known as
the integrated terminal in VS Code. libghostty-vt is the core of Ghostty, a native
terminal that aims for thorough, standards-faithful coverage of modern escape sequences. A tape
renders what a native terminal would show, so TUIs that use newer terminal features look the way
your users see them.
Outputs
Section titled “Outputs”Output path |
VHS | Betamax |
|---|---|---|
.gif |
Encoded with ffmpeg | Encoded in process |
.webp |
Not supported | Animated WebP, encoded in process |
.png |
Directory of PNG frames | One PNG of the final frame |
frames/ |
Directory of PNG frames | Directory of PNG frames |
.mp4 |
Encoded with ffmpeg | Encoded with ffmpeg |
.webm |
Encoded with ffmpeg | Encoded with ffmpeg |
.json |
Not supported | State JSON of the final view |
.txt, .ascii |
Text dump after each command | Same format, viewport only |
Screenshot |
.png |
.png or .webp |
MP4 and WebM need ffmpeg on PATH, and MP4 can’t use a transparent margin. See
Outputs for installation notes and choosing a format.
What carries over
Section titled “What carries over”The core tape flow is the same:
Outputdeclares artifacts.Setcontrols size (in pixels or cells), theme, timing, typography, and frame styling.Type,Enter, key commands,Sleep, andWaitdrive the terminal.Wait,Wait+Line, andWait+Screenkeep the VHS defaults: pattern>$and a 15s timeout.ScrollUpandScrollDownmove the viewport through scrollback; typing returns it to the bottom.HideandShowkeep setup or teardown out of the final animation.Screenshotwrites checkpoint images.EnvandRequireprepare the environment.
Betamax starts bash, zsh, and fish with the same clean recording setup as VHS, so the prompt is the
familiar > instead of your own. Other shells only get a bash-style PS1. Betamax sets
TERM=xterm-256color and COLORTERM=truecolor and removes NO_COLOR; Env overrides all of them.
Not supported
Section titled “Not supported”| VHS feature | Use instead |
|---|---|
vhs record |
Write the tape by hand, or start from betamax new |
vhs serve, vhs publish |
Not planned; host the rendered files yourself |
vhs manual, themes --markdown |
The tape reference and betamax themes |
record, serve, and publish are out of scope for the first stable release: Betamax focuses on
local rendering and terminal testing. The --publish flag is still accepted so existing scripts get
a clear error.
Same syntax, different behavior
Section titled “Same syntax, different behavior”These tapes parse, but run differently or print a warning that VHS doesn’t.
| VHS tape uses | What happens in Betamax | Change it to |
|---|---|---|
Env or Require after commands |
Warns; Require is checked, VHS skips it |
Move them to the top |
Type "a" "b" (several strings) |
Types a b, as VHS does, and warns |
One string: Type "a b" |
Env before a Set |
Set applies; VHS ignores it |
Nothing |
Backtick strings or \ in "…" |
Strings follow shell quoting rules | Single quotes for literal text: 'C:\path' |
Output frames.png |
Writes one PNG of the final frame | Output frames/ for a frames directory |
A Set after runtime commands works as in VHS: Set TypingSpeed applies from that point, and other
settings are ignored with a warning. See
Startup commands after runtime commands.
All 348 VHS theme names work in Set Theme. VHS’s theme list is a 2023 snapshot of
iTerm2-Color-Schemes, which has since renamed most themes, so Betamax maps the old VHS spelling to
the current name (tokyonight loads TokyoNight, catppuccin-mocha loads Catppuccin Mocha).
betamax themes lists only the current names. Some themes changed colors upstream since the VHS
snapshot, so they can look slightly different: usually bright black (color 8), sometimes more, as
in the Catppuccin, Rosé Pine, and Ayu themes. The 29 VHS themes with no Ghostty counterpart, such as
Ganyu and CyberPunk2077 (Cyberpunk 2077), are bundled with VHS’s colors. Other names are
matched ignoring case, spaces, and punctuation, and an unknown name is reported with close
suggestions. Theme JSON works as one quoted token.
Smaller differences:
- Key repeats.
Enter 3and similar repeats wait one frame between presses, notTypingSpeed. - Waits. A bare
Waitpattern is a substring match;/…/is a regular expression. - Clipboard.
CopyandPasteuse a clipboard private to the tape. Betamax never reads or writes your system clipboard. - Source.
Sourcepaths resolve against the sourcing tape’s directory first, then the current directory; VHS uses only the current directory. Sourced tapes may nest, which VHS rejects. - CLI. Run tapes with
betamax run demo.tape.-oadds an output to the ones in the tape; VHS’s-oreplaces them.
Presentation defaults
Section titled “Presentation defaults”Betamax renders tapes with the Framed preset unless they select another: Aardvark Ink, 1200×720,
22px text, zero terminal padding and letter spacing, a 32px margin with a gradient from the theme
background, and a rounded window with a Colorful bar, an outline, and a shadow. VHS defaults to
1200×600, 60px padding, no margin, and no window bar.
Add Set Preset Default-0.1 to restore Betamax 0.1 defaults, which are close to VHS’s sizing, or
Set Preset Bare to remove decoration while keeping the current sizing and typography. Neither
makes rendered pixels identical to VHS. See Presets.
Betamax additions
Section titled “Betamax additions”Test the terminal, not just record it
Section titled “Test the terminal, not just record it”A VHS tape produces media; checking that a program behaved correctly means looking at the GIF. Betamax tapes can assert on the terminal while they run, so the same tape that renders a demo can fail CI when the program regresses.
Type "my-tool --help"EnterWait /Usage/AssertText "--verbose"State help.jsonState checkpoint.jsonandOutput demo.jsonwrite the viewport, scrollback, cursor, and styled text as JSON, ready for snapshot tests.AssertText,AssertAbsent,AssertCells,AssertStyle, andAssertStatecheck text, cell ranges, colors, and attributes in the tape itself.Output demo.txtand.asciikeep VHS golden files working. Betamax skips the snapshots VHS writes for header commands and always dumps the visible screen, where VHS dumps the oldest scrollback rows once output scrolls.- When a command fails, Betamax saves a diagnostics bundle with the terminal state at the failure.
See Terminal testing and State JSON.
Drive full-screen programs
Section titled “Drive full-screen programs”TUIs need more input than typed text. MouseMove, MouseDown, MouseUp, and MouseScroll send
mouse input with modifiers, and Resize C R changes the terminal size mid-tape to exercise resize
handling. Keys also cover Home, End, F1 to F25, and any Ctrl/Alt/Shift combination,
such as Ctrl+Tab.
Explain what’s happening
Section titled “Explain what’s happening”Caption shows narration under the terminal, WindowTitle changes the title bar, and
Set KeyboardOverlay shows key chips as keys are pressed, so viewers can follow along without
reading every keystroke.
Show terminal images
Section titled “Show terminal images”VHS records through xterm.js, which draws no inline images. Betamax renders images that programs
draw with the kitty graphics protocol, kitty unicode placeholders, sixel, and iTerm2
OSC 1337 File=inline=1, so plots and previews from tools such as ratatui-image appear in
screenshots, GIFs, and videos. Images scroll and clip with the terminal grid.
Betamax also answers the capability queries those programs send first: kitty graphics queries,
sixel in the primary device attributes, and the CSI 14 t and CSI 16 t pixel-size reports.
Colour queries (OSC 10, OSC 11, OSC 12, and OSC 4) report the active theme, so programs
that match plots or UI to the terminal’s colours see the theme rather than falling back to
defaults.
Kitty animation frames and iTerm2 multipart transfers are not rendered.
Style the window
Section titled “Style the window”Presets, gradient, image, and transparent MarginFill values, WindowBarColor, WindowTitle,
WindowTitleColor, WindowTitleFromTerminal, WindowBorder, and Shadow make polished demos
without post-processing. See Window styling.