foley
The tape is the recording. foley adds the post-production:
camera, titles, sound. The footage is never touched.
A command line tool and Go library that renders terminal demos from scripts.
A terminal demo recorder with no screen.
foley turns a script into a finished terminal demo: your real CLI or TUI
running in a real terminal, recorded without one ever opening. You write a
.tape file (settings, keystrokes, waits), foley runs the app on a real pty and
draws every frame itself: gif, mp4, webm, webp or asciicast. Nothing is filmed; the demo is
rendered, the way a foley artist recreates sound in the studio instead of keeping the
set recording.
- The README gif. Regenerated by CI on every release instead of re-recorded by hand.
- Docs and release notes. Demos written as code, reviewed in pull requests, versioned with the app.
- Visual regression tests. The same tape renders identical bytes, so a golden file catches any change on screen.
- One take, every venue. The same tape exports video, asciicast or plain text.
The terminal exists only inside the renderer. Nothing asks for a screen-recording permission; no stray notification ever walks through a shot.
Your app runs on a real pty, an embedded terminal engine (libghostty-vt, the brain of Ghostty) keeps the screen, and foley draws every frame itself. Kitty graphics render byte-exactly, because the engine underneath is a real terminal's.
Every take renders from a supersampled master, so a camera is just arithmetic: the zoom is a 1:1 crop.
The clock is virtual: the same tape produces identical bytes on any machine, and renders faster than real time. A ~32-second script exports in under 5, and demos double as visual regression tests.
Six cues, one screen.
Direction rides as # foley: cues in the tape; the pty, the timing
and the terminal state stay exactly as recorded. Pick a cue; the screening room plays it.
Title cards and lower thirds, composited on frame. The bar can't tell it isn't vim.
Point at the output; the frame dims around it.
foley presses the keys, so the track is emitted with exact timing, not captured and guessed at.
One cue builds a fresh HOME per take. Your machine stays off camera.
Chrome for the take: macOS, noir, or none at all. Same take, only the flag changed.
Push onto a region, hold, pull back. The push-in is a 1:1 crop of the supersampled master, which is why the hold stays crisp.
Direction rides as # foley: comments in the tape. The footage is never touched. Every take is real output from the engine.
No browser offstage. No display server on set.
Takes render headless anywhere CI runs, and the whole studio builds into a small container.
Reel A · Deterministic · default
A virtual clock. Output is attributed to the step that caused it, every run of a tape produces identical bytes, and rendering runs faster than real time.
Reel B · Realtime · -mode realtime
The wall clock, every byte captured as it happened: the clock a continuously animating TUI needs. Honest limits either way: waits synchronize against the real app, so they spend the time they spend.
Every VHS tape is a foley tape.
The grammar is VHS's own, vendored from the pinned release. The upstream example corpus, all 106 tapes, parses and runs under conformance tests; where foley differs on purpose, it says so loudly at run time instead of silently drifting.
brew install GH-Jaider/foley/foley
Brings ffmpeg with it, the one thing foley needs at runtime.
tar xzf foley_*.tar.gz sudo mv foley /usr/local/bin/
Self-contained: engine and fonts baked in. Grab the tarball from the latest release.
foley new demo.tape foley validate demo.tape foley demo.tape
A starter tape, the spotting session, and the take. When in doubt,
foley doctor.
foley skill > foley-skill.md # the whole grammar, in one file any agent loads as context