A terminal recorder like VHS, but superior:
- Always sharp - no GIF quality loss (#625, #69).
- No timing drift - 1 second is 1 second at any resolution (#69).
- Sized in rows and cols - no dancing with pixel width and height (#578).
- Animated SVG output (#644, #109, #105).
- Animated WebP output - a modern alternative to GIF with better compression (#50).
- Highlight what matters - a selection swept over text on screen, the way a mouse drag would (#66).
- No Chromium downloaded behind your back - just
tmux+ asciinema + renderer (#528, #438, #150, #45).
A recording is a plain shell script that sources s-vhs.sh:
#!/usr/bin/env bash
# Import s-vhs straight from GitHub - no local copy needed.
# A local copy works too - use "source ./s-vhs.sh"
source <(curl -fsSL https://dimk90.github.io/s-vhs/v0.7.0) && wait "$!" || exit 1
# Where should we write the GIF?
SetOutput 'demo.gif'
# Set up a 60x4 terminal with a 40px font
SetCols 60
SetRows 4
SetFontSize 40
# Start the terminal, then the recorder
Start
Show
# Type a command in the terminal
Type "echo 'Welcome to S-VHS!"
Sleep 1 # Pause for dramatic effect...
Type " Stay awhile and listen...'"
Sleep 1
# Run the command by pressing Enter
Enter
# Admire the output for a bit
Sleep 5
# Stop recording and write every requested output
RenderSave it as demo.rec.sh, make it executable and run it:
chmod +x demo.rec.sh
./demo.rec.shYou should see a new file called demo.gif in the same directory:
s-vhs is a bash script, so all you need are its dependencies tmux and asciinema:
-
macOS
brew install tmux asciinema
-
Linux
sudo pacman -S tmux asciinema
The Linux instructions are for Arch, but you can easily adapt them to your favorite distro π
Note
Renderers are needed only for the output formats you request. Cast-only recordings need none. See Output Formats.
Tip
Some installs below use cargo, which puts binaries into ~/.cargo/bin -
make sure it is on your PATH (rustup's installer arranges that; a
distro-packaged cargo does not).
For GIF output, install agg:
-
macOS
brew install agg
-
Linux
cargo install --git https://github.com/asciinema/agg --locked
aggis not on crates.io (theaggcrate there is an unrelated project), hence the--gitinstall. Some distros package it asaggorasciinema-agg(on Arch, the AURasciinema-aggpackage).
Tip
GIF size can be reduced by gifsicle
without quality loss, see Output Size Optimization.
For animated WebP, MP4 or WebM output, install agg and
ffmpeg. MP4 and WebM require ffmpeg 6.1 or newer,
built with the libx264 encoder for MP4 and libaom-av1 for WebM - as distro
and Homebrew builds are:
-
macOS
brew install agg ffmpeg
-
Linux
cargo install --git https://github.com/asciinema/agg --locked
sudo pacman -S ffmpeg
For animated SVG output, install
asg - no package on either
platform, so cargo on both:
cargo install asg --lockedSee examples for the complete scripts behind the demos below, and a few more.
Enter 4 0.5# Regular typing
Type 'echo "whatever you want"'
Sleep 1
Enter; Sleep 0.5
# Slow typing
Type 'echo "slow down"' 0.25SetCols 40
SetRows 8
Type 'tput cols'
Enter; Sleep 1
Type 'tput lines'
Enter; Sleep 1Named keys are commands of their own: Enter, Tab, Space, Backspace,
Escape, Up, Down, Left, Right, PageUp, PageDown, Home, End,
Insert, Delete. Each takes an optional repeat count and delay. A modified
key goes through Key in tmux notation: Key C-u, Key C-r, Key M-x, see
examples/ctrl.rec.sh.
Type 'echo navigate around'; Sleep 0.5
Left 10 0.12
Sleep 0.5
Right 10 0.05Type 'echo delete anything...' 0.05; Sleep 0.5
Backspace 18 0.05Wait polls the visible pane until an extended regular expression (grep -E)
matches, so a recording keeps up with a slow command instead of guessing a sleep:
Type 'sleep 2 && echo "build succeeded"'
Enter
# Anchored: the pattern must match the output line, not the command echoed
# above it
Wait '^build succeeded'
Type 'echo "and on we go"'Highlight drags a selection across text that is already on screen, holds it,
and lets go:
SetHighlightColors 'colour214' 'black' 'bold' # selection style
SetHighlightSpeed 0.03 # seconds per swept cell
Type 'echo "s-vhs records what VHS cannot"'
Enter; Sleep 1
# Select the match closest to the cursor and hold it for 1.5s
Highlight 'VHS cannot' 1.5# A bundled theme uses the same palette in GIF and SVG.
# A comma-separated hex palette
# (background, foreground, then 8 or 16 colors) also works.
SetTheme 'kanagawa'See the theme gallery for all bundled themes, with GIF and SVG samples. Other names pass through to the renderer and may not work in both formats.
A recording runs in an isolated shell by default: no personal rc files are read, your own prompt stays out of the frame, and nothing is written to your shell history.
SetShell 'fish' # bash (default), zsh or fish
SetPrompt 'powerline' # arrow (default), plain, path or powerline
SetTheme 'nord' # the prompt picks up the theme's colors
# The theme follows the working directory
Type 'cd /tmp'
Enter; Sleep 1.5
Type 'cd ~'
Enter; Sleep 3Every prompt theme is implemented for all three shells, so a recording looks the same in bash, zsh and fish.
Tip
SetPrompt 'native' records your own shell configuration instead, and
any other value is taken as a literal prompt.
The font size is the only pixel-sized setting: it scales the whole render without changing the terminal grid the recorded shell sees.
SetFontSize 40Start # Start session / initialize terminal
# Off camera: export the variable and wipe the screen it was typed on
Run 'export HIDDEN=wow' 0.5
Run 'clear' 0.5
Show # Start recording
# The recorded shell expands it, not this script
Type 'echo $HIDDEN'
Enter; Sleep 2
Hide # Stop recording, the session keeps running
Run 'echo "nobody will ever see this"' 0.5
Run 'clear' 0.5
Show # Resume recording, appending to the same cast
Type 'echo "back on camera"'
Enter; Sleep 2Tip
RunOffRecord 'clear' 0.5 is shorthand for a Hide + Run + Show
sandwich, see examples/run-off-record.rec.sh.
Require checks the commands a recording depends on before anything starts,
and Env hands the recorded shell the variables they need - here a git
identity, so the recording does not depend on the host's git configuration:
# Reported now, not as a "command not found" frame in the middle of the GIF
Require 'git'
Env 'GIT_AUTHOR_NAME' 'Ada Lovelace'
Env 'GIT_AUTHOR_EMAIL' 'ada@example.com'
Env 'GIT_COMMITTER_NAME' 'Ada Lovelace'
Env 'GIT_COMMITTER_EMAIL' 'ada@example.com'
Start
# Off camera: an empty repository in a throwaway directory
Run 'cd "$(mktemp -d)" && git init -q . && clear' 1
Show
Type 'git commit -q --allow-empty -m "recorded"'
Enter; Sleep 1
Type 'git log -1 --format="%an <%ae>"'
Enter; Sleep 2.5Tip
Finally 'rm -rf "$TMP_DIR"' clears a fixture the recording script made in
TMP_DIR however the script ends, including the Wait timeout that fails a
recording mid-take.
Import an immutable release directly from GitHub via curl instead
of keeping a local s-vhs.sh next to the recording script:
# Remote import instead of "source ./s-vhs.sh"
source <(curl -fsSL https://dimk90.github.io/s-vhs/v0.7.0) && wait "$!" || exit 1
SetOutput "remote-import.gif"
Start
Show
Type "printf 'Imported s-vhs %s\\n' '$(svhs_version)'"
Enter; Sleep 3
RenderThe
wait "$!"is a guard: without it, process substitution can hide a failed or truncatedcurldownload.
Tip
Keep the version pinned so the same script always imports the same library.
SetOutput 'multi-output.cast'
SetOutput 'multi-output.txt'
SetOutput 'multi-output.gif'
SetOutput 'multi-output.svg' # <---The .svg written by that recording -
animated by CSS, sharp at any zoom:
Note
An SVG names fonts instead of embedding them and animates through CSS, so where it is displayed might change how it looks. Check SVG pitfalls and workarounds.
π pi-context-view - demo GIFs,
such as palettes.rec.sh.
π S-VHS logo recording.
The extension of the path passed to SetOutput picks the format:
| Format | Description | Render dependency |
|---|---|---|
.cast |
Editable, replayable asciicast recording | None |
.txt |
Plain-text terminal log | None |
.gif |
Animated raster image | agg |
.webp |
Animated raster image with lossless compression, typically smaller than GIF | agg + ffmpeg |
.mp4 |
H.264 video, for players and pages that take one rather than an animation | agg + ffmpeg |
.webm |
AV1 video, about half the bytes of MP4; much slower to encode | agg + ffmpeg |
.svg |
Sharp, CSS-animated vector image | asg |
SetOutput is repeatable - one recording, several outputs:
SetOutput 'multi-output.cast'
SetOutput 'multi-output.txt'
SetOutput 'multi-output.gif'
SetOutput 'multi-output.webp'
SetOutput 'multi-output.svg'Keeping the .cast next to the rendered files leaves the recording replayable
with asciinema play and re-renderable at any size later, while the .txt log
makes the recorded output greppable - see
examples/multi-output.rec.sh.
Start a new recording without downloading s-vhs.sh:
curl -fsSL https://dimk90.github.io/s-vhs/latest | bash -s -- new demo.rec.shor if s-vhs.sh is already local:
./s-vhs.sh new demo.rec.shIt writes:
#!/usr/bin/env bash
source <(curl -fsSL https://dimk90.github.io/s-vhs/v0.7.0) && wait "$!" || exit 1
SetOutput 'demo.gif'
# SetCols 100
# SetRows 40
# SetShell 'bash'
# SetPrompt 'arrow'
# SetFontSize 28
# SetFontFamily 'JetBrains Mono'
# SetTheme 'dracula'
# SetTypingSpeed 0.07
# SetPlaybackSpeed 1
# SetFramerate 30
# SetLoop on
# SetOptimize off
# Require 'git' 'jq'
Start
Show
Type 'echo "Hello from s-vhs"'
Enter
Sleep 3
Render-
For the full list of commands and settings, see REFERENCE.md.
-
For color themes with GIF and SVG samples, see THEMES.md.
-
For debugging a recording script, see DEBUG.md.
-
For smaller output files and per-format optimization trade-offs, see OPTIMIZATION.md.
-
For the agent skill with recording best practices, see SKILL.md. Install it with
gh skill, which places it where Claude Code, pi, Codex and others look for it:gh skill install dimk90/s-vhs s-vhs-recording --agent claude-code --scope user
-
For the architecture behind
tmux + asciinema + renderersand hows-vhsglues them together, see INTRO.md.















