A modern asciinema player for the terminal with full media-player controls.
Bettercast replays .cast recordings (v2 and v3) with seeking, variable playback speed, text search, bookmarks, and a visual progress bar — features missing from asciinema play.
uv tool install .Or run directly:
uv run bettercast recording.castbettercast <file.cast> [--speed 1.0] [--idle-threshold 2.0] [--no-idle-compress]| Flag | Description |
|---|---|
--speed |
Initial playback speed (default: 1.0) |
--idle-threshold |
Skip idle gaps longer than this many seconds (default: 2.0) |
--no-idle-compress |
Disable idle time compression |
| Key | Action |
|---|---|
Space |
Play / Pause |
Left / Right |
Seek backward / forward 5 seconds |
Shift+Left / Shift+Right |
Seek backward / forward 30 seconds |
Home / End |
Jump to start / end |
. / , |
Step forward / backward one frame |
[ / ] |
Decrease / increase speed by 0.5x |
l |
Toggle loop mode |
| Key | Action |
|---|---|
/ |
Open search |
n / N |
Next / previous search match |
g |
Go to timestamp (enter MM:SS or H:MM:SS) |
Escape |
Dismiss search or timestamp overlay |
| Key | Action |
|---|---|
m |
Bookmark current position |
b |
Open bookmark list |
{ / } |
Jump to previous / next bookmark |
In the bookmark list:
| Key | Action |
|---|---|
Up / Down |
Navigate bookmarks |
Enter |
Jump to selected bookmark |
d |
Delete selected bookmark |
Escape |
Close bookmark list |
| Key | Action |
|---|---|
c |
Copy visible screen text to clipboard |
? |
Toggle full help overlay |
q |
Quit |
To automatically record every interactive terminal session with asciinema, add this to your ~/.zshrc. Sessions are saved to ~/.logs/ and can be replayed with bettercast:
if [[ ( "$TERM_PROGRAM" == "vscode" || "$TERM_PROGRAM" == "ghostty" || "$TERM_PROGRAM" == "iTerm.app" ) && -z "${ASCIINEMA_RECORDING:-}" ]]; then
if command -v asciinema >/dev/null 2>&1; then
mkdir -p "$HOME/.logs"
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
PREFIX="${TERM_PROGRAM}"
export ASCIINEMA_RECORDING=1
exec asciinema rec "$HOME/.logs/$PREFIX-$TIMESTAMP.cast" -c "$SHELL"
fi
fiAdjust the TERM_PROGRAM list to match the terminals you want to record. The ASCIINEMA_RECORDING guard prevents recursive recording when a shell is spawned inside an already-recorded session.
Text search requires replaying the whole recording once to build an index, which can take a minute on very large casts. The index is saved next to the recording as <name>.cast.index.gz so it travels with the log — copy or move both files together and the cache stays valid. Reopening an indexed cast is instant. The sidecar is safe to delete at any time; stale or corrupt sidecars (including after the cast grows or changes) are ignored and rebuilt automatically. If the cast's directory isn't writable, bettercast still works — the index just isn't saved.
- Asciicast v2 — absolute timestamps,
width/heightheader - Asciicast v3 — relative (delta) timestamps,
term.cols/term.rowsheader
Format is auto-detected from the header.
Four gates run in CI on every push and pull request, and each one is runnable locally with the same command CI uses:
uv run pytest -q # tests
uv run ruff check . # lint
uv run ruff format --check . # formatting
uv run mypy # type check
Rule selection and type-check scope live in pyproject.toml rather than in
workflow flags, so a local run and a CI run always agree on what passes.
.python-version pins local development to 3.12, the oldest supported
runtime, so a local venv can't silently drift onto a version CI never
exercises. CI tests 3.12 and 3.13; its explicit python-version input
overrides this file, so the matrix still covers both.
uv run ruff format . rewrites files in place.
tests/test_screenshots.py compares rendered SVG screenshots against
references in tests/snapshots/. A missing reference is a failure, not a
pass — otherwise a deleted snapshot would silently disable its test. After an
intentional UI change, regenerate the references explicitly:
BETTERCAST_UPDATE_SNAPSHOTS=1 pytest tests/test_screenshots.py
That writes the references and reports the tests as skipped, so an update run can't be mistaken for a passing one. Review the resulting diff before committing it.