betamax 0.1.21

Rust-first VHS-style terminal capture CLI
# Betamax

Betamax is a Rust-first terminal capture CLI in the spirit of
[VHS](https://github.com/charmbracelet/vhs). It runs tape files in a real PTY, feeds terminal
output through `libghostty-vt`, rasterizes frames in process with `cosmic-text` and `swash`, and
writes GIFs, screenshots, videos, or structured terminal state.

The goal is VHS-style authoring without a browser, server, xterm.js, or shelling out to a terminal
web stack. Betamax is useful for project demos, CLI documentation, release notes, and snapshot-style
tests for terminal applications.

## Install

Betamax currently supports macOS and Linux. Windows is not supported because the upstream
`libghostty-vt-sys` native build does not support Windows.

Install the CLI with Homebrew from [joshka/homebrew-tap][homebrew-tap]:

```sh
brew install joshka/tap/betamax
```

If Homebrew requires trusted taps, trust the formula first:

```sh
brew trust --formula joshka/tap/betamax
```

Or install from crates.io with cargo-binstall:

```sh
cargo binstall betamax
```

If [cargo-binstall][cargo-binstall] is not installed, install it first:

```sh
cargo install cargo-binstall
```

Source installs are mainly for maintainers. Betamax uses the safe `libghostty-vt` Rust wrapper; its
`libghostty-vt-sys` dependency fetches a pinned Ghostty source commit and runs Zig 0.15.2 to build
the native VT library. In a checkout, use [mise][mise] to get the pinned toolchain:

```sh
mise install
mise run install-local
```

> [!NOTE]
> Source builds require **Zig 0.15.2**. Upstream Ghostty supports Zig 0.16, but the published
> `libghostty-vt-sys` dependency still pins older Ghostty source. Betamax is waiting for a
> `libghostty-rs` release containing the update. Use the mise commands above to select the
> supported toolchain.

Other toolchain managers, including [Nix][nix], or [Zig][zig] 0.15.2 on `PATH` also work.

See [build troubleshooting](https://www.joshka.net/betamax/reference/development/#build-troubleshooting)
if Cargo finds the wrong Zig version.

MP4 and WebM output require [ffmpeg][ffmpeg] on `PATH`; GIF, WebP, PNG, screenshots, and
state JSON are written in process.

```sh
# macOS
brew install ffmpeg

# Debian/Ubuntu
sudo apt-get update
sudo apt-get install ffmpeg
```

## A Tape

Tape files describe the terminal session to run and the artifacts to write:

```text
Output examples/output/basic.gif
Output examples/output/basic.png
Output examples/output/basic.state.json

Set Shell "bash"
Set Theme "Aardvark Blue"
Set FontSize 28
Set Width 924
Set Height 534
Set Margin 12
Set WindowBar Colorful
Set BorderRadius 8

Hide
Type "cargo build --quiet"
Enter
Wait
Show

Type "printf 'hello from betamax\n'"
Enter
Wait+Screen "hello from betamax"
Screenshot examples/output/basic-checkpoint.png
Hide
Type "exit"
Enter
```

Run it with:

```sh
betamax run demo.tape
```

`Hide` and `Show` let setup and teardown happen without appearing in the final animation. Wait
commands can block on time, a line, the whole screen, or a regular expression, so tapes can be
stable even when terminal programs do real work.

## What It Can Write

| Output               | Use case                                                   |
| -------------------- | ---------------------------------------------------------- |
| GIF                  | README demos, release notes, docs pages                    |
| WebP                 | Lossless animations and checkpoint screenshots             |
| PNG                  | Final-frame screenshots                                    |
| Screenshot command   | Checkpoint screenshots from the middle of a run            |
| MP4 and WebM         | Video assets encoded through `ffmpeg`                      |
| Frame directory      | Individual rendered frames for debugging or custom tooling |
| State JSON           | Viewport, scrollback, text, and style spans for tests      |

## Examples

The repository includes tapes that exercise the core behavior:

| Tape                                      | Demonstrates                                   |
| ----------------------------------------- | ---------------------------------------------- |
| `examples/quick-start.tape`               | installing, help, `new`, and nested `run`      |
| `examples/basic.tape`                     | typing, wait, theme, window bar, border radius |
| `examples/hide-show.tape`                 | hidden setup and hidden trailing cleanup       |
| `examples/waits.tape`                     | line, screen, regex, and default prompt waits  |
| `examples/keys.tape`                      | key commands, repeats, editing, and interrupt  |
| `examples/clipboard-env.tape`             | `Env`, `Copy`, and `Paste`                     |
| `examples/outputs.tape`                   | GIF, PNG, JSON, screenshot, state, frame dir   |
| `examples/scrollback.tape`                | scrollback-inclusive state JSON                |
| `examples/text-styles.tape`               | ANSI styles, truecolor, and styled state spans |
| `examples/layout.tape`                    | padding, margin, fill, window bar, radius      |
| `examples/presentation-overlays.tape`     | captions plus keyboard overlay layout          |
| `examples/themes.tape`                    | copied Ghostty themes and palette mapping      |
| `examples/webp.tape`                      | Lossless WebP animation and screenshots        |
| `examples/video.tape`                     | GIF, MP4, and WebM from one capture            |

### Quick Start

![Quick Start Betamax GIF][quick-start-gif]

### Basic

![Basic Betamax GIF][basic-gif]

### Hide And Show

![Hide and Show Betamax GIF][hide-show-gif]

### Themes

![Ghostty theme Betamax GIF][themes-gif]

## Themes And Styling

Betamax ships copied Ghostty themes and can also read user Ghostty theme directories. List theme
names with:

```sh
betamax themes
betamax themes --json
```

Tapes can control width, height, font size, padding, margin, border radius, window bar style, fill
color, typing speed, playback speed, and prompt text. The defaults are chosen to feel close to VHS:
a readable terminal size, a visible frame, and a simple `>` prompt unless a tape asks for a real
shell prompt.

Captions and keyboard overlay chips are presentation-only annotations for visual outputs.
`KeyboardOverlayLocation CaptionRow` reserves a bottom presentation row before deriving the terminal
grid so labels do not cover terminal content. Corner locations such as `BottomRight` draw the chips
inside the terminal canvas with a small inset from the terminal edge. Caption glyphs are clipped to
their reserved width as a final guard for font fallback and unusually wide characters.

## Terminal Testing

Betamax can be used as a terminal test harness, not only as a GIF generator. A tape can run an
interactive program, wait until expected text appears, capture screenshots at important states, and
write structured state JSON that snapshot tests can compare with tools such as `insta`.

State JSON includes:

- viewport text
- scrollback text
- a compact style table
- styled text spans that avoid cell-by-cell verbosity

That makes it possible to test terminal UI behavior as text and style data, while still keeping PNG
or GIF output available for visual review.

## Differences From VHS

Betamax intentionally keeps a smaller architecture than VHS:

| Area              | Betamax status                                       |
| ----------------- | ---------------------------------------------------- |
| Architecture      | PTY plus `libghostty-vt`, no browser/server/xterm.js |
| GIF/WebP/PNG      | In process                                           |
| MP4/WebM          | Supported through `ffmpeg`                           |
| Themes            | Copied Ghostty themes plus inline JSON themes        |
| Window styling    | Rust composition                                     |
| Testing snapshots | JSON state with viewport, scrollback, and styles     |
| `Source`          | Parsed but intentionally not executed                |
| `record`/`serve`  | Intentionally not implemented                        |
| `publish`         | Intentionally not implemented                        |

See [Differences From VHS][vhs-differences] for the full comparison.

## Documentation

- [Documentation Site][docs-site]
- [Tape Reference][tape-reference]
- [Terminal Testing][terminal-testing]
- [State JSON][state-json]
- [Differences From VHS][vhs-differences]
- [Repository README][repo-readme]

[basic-gif]: https://github.com/joshka/betamax/releases/download/readme-assets/basic.gif
[cargo-binstall]: https://github.com/cargo-bins/cargo-binstall
[docs-site]: https://www.joshka.net/betamax/
[ffmpeg]: https://ffmpeg.org/
[hide-show-gif]: https://github.com/joshka/betamax/releases/download/readme-assets/hide-show.gif
[homebrew-tap]: https://github.com/joshka/homebrew-tap
[mise]: https://mise.jdx.dev/
[nix]: https://nixos.org/
[quick-start-gif]: https://github.com/joshka/betamax/releases/download/readme-assets/quick-start.gif
[repo-readme]: https://github.com/joshka/betamax
[state-json]: https://www.joshka.net/betamax/testing/state-json/
[tape-reference]: https://www.joshka.net/betamax/reference/tape-reference/
[terminal-testing]: https://www.joshka.net/betamax/testing/terminal-testing/
[themes-gif]: https://github.com/joshka/betamax/releases/download/readme-assets/themes.gif
[vhs-differences]: https://www.joshka.net/betamax/reference/vhs-differences/
[zig]: https://ziglang.org/