Skip to content

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.

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.

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.

The core tape flow is the same:

  • Output declares artifacts.
  • Set controls size (in pixels or cells), theme, timing, typography, and frame styling.
  • Type, Enter, key commands, Sleep, and Wait drive the terminal. Wait, Wait+Line, and Wait+Screen keep the VHS defaults: pattern >$ and a 15s timeout.
  • ScrollUp and ScrollDown move the viewport through scrollback; typing returns it to the bottom.
  • Hide and Show keep setup or teardown out of the final animation.
  • Screenshot writes checkpoint images.
  • Env and Require prepare 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.

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.

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 3 and similar repeats wait one frame between presses, not TypingSpeed.
  • Waits. A bare Wait pattern is a substring match; /…/ is a regular expression.
  • Clipboard. Copy and Paste use a clipboard private to the tape. Betamax never reads or writes your system clipboard.
  • Source. Source paths 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. -o adds an output to the ones in the tape; VHS’s -o replaces them.

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.

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"
Enter
Wait /Usage/
AssertText "--verbose"
State help.json
  • State checkpoint.json and Output demo.json write the viewport, scrollback, cursor, and styled text as JSON, ready for snapshot tests.
  • AssertText, AssertAbsent, AssertCells, AssertStyle, and AssertState check text, cell ranges, colors, and attributes in the tape itself.
  • Output demo.txt and .ascii keep 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.

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.

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.

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.

Presets, gradient, image, and transparent MarginFill values, WindowBarColor, WindowTitle, WindowTitleColor, WindowTitleFromTerminal, WindowBorder, and Shadow make polished demos without post-processing. See Window styling.