Takewright turns deterministic terminal scripts into polished, multi-format videos without a GUI timeline. VHS records a controlled terminal, Remotion supplies the stage and motion, and FFmpeg/FFprobe validate and inspect the media. Every editable source is code, JSON, or Markdown.
The GIF above is Takewright's own output: the takewright-demo tape replays the real CLI transcript and the pipeline records, composes, and renders it. Reproduce it with pnpm demo:render takewright-demo --format landscape --output gif.
The project is intentionally marked private in package.json to prevent accidental npm publication. That does not restrict cloning, modification, or redistribution under the project license.
- Node.js 22 or newer
- pnpm 11 (Corepack is fine)
- FFmpeg and FFprobe
- VHS 0.11 or newer; Docker is an automatic render fallback when native VHS is absent
- JetBrains Mono (open source). The official VHS action provides it in CI.
macOS setup:
brew install ffmpeg vhs
brew install --cask font-jetbrains-mono
corepack enable
pnpm install
pnpm demo:doctorVHS itself requires ttyd and FFmpeg; the Homebrew formula installs ttyd. No proprietary fonts or raster backgrounds are checked in.
For other platforms, install Node.js and pnpm using their standard installers, then follow the official installation instructions for VHS and FFmpeg.
pnpm install
pnpm demo:doctor
pnpm demo:render example-demo --format landscape
pnpm demo:inspect example-demo --format landscape
open output/example-demo/inspection/contact-sheet.png # optional local viewingThe render command validates the JSON, runs VHS, probes the recording, computes duration, renders Remotion, probes the result, and prints the final path.
For a compact landscape video intended for Slack, use the copy-paste Slack landscape quickstart:
pnpm demo:new my-cli-demo --preset slack-landscapepnpm demo:new account-import
pnpm demo:render account-import
pnpm demo:render account-import --format portrait
pnpm demo:render account-import --format landscape --output gif
pnpm demo:render account-import --all
pnpm demo:render account-import --all --skip-recording
pnpm demo:inspect account-import --format landscape
pnpm demo:inspect account-import --format landscape --output gif
pnpm demo:validate account-import --all
pnpm demo:doctor
pnpm checkdemo:new accepts lowercase kebab-case IDs and never overwrites a directory. A normal render defaults to the first configured format and MP4 output. --output gif natively renders an infinitely looping, approximately 15 fps GIF at two-thirds scale (1280×720 for landscape), which keeps Slack uploads practical. --skip-recording reuses public/recordings/<id>.mp4; it fails if that recording is absent. --smoke is intended for CI and renders 25% scale for at most 90 frames.
Each directory under demos/ owns its inputs:
demos/my-demo/
├── demo.tape # terminal actions and raw output path
├── demo.json # title, visual settings, formats, zooms, callouts
├── captions.json # frame-based captions
├── README.md # demo-specific notes
└── assets/ # optional demo-owned assets
Generated assets are deliberately separate:
public/recordings/<id>.mp4: raw VHS media consumed by Remotionoutput/<id>/<id>-<format>.mp4: final videosoutput/<id>/<id>-<format>.gif: final GIFs requested with--output gifoutput/<id>/inspection/: extracted frames, contact sheet, andmanifest.json
The four presets are landscape (1920×1080), portrait (1080×1920), square (1080×1080), and social portrait (1080×1350). Portrait layouts narrow and reposition the terminal inside portrait safe areas instead of cropping a landscape composition.
Use demo.tape for shell activity and pauses. Keep all commands network-free or fixture-backed when reproducibility matters. Use frame numbers in captions.json, zooms, and callouts; the project renders at 30 fps. See demo authoring for field details.
Composition primitives live under src/: CSS/SVG-style backgrounds, the terminal frame, overlays, theme tokens, and format presets. Change JSON first for demo-specific adjustments; change components only for reusable visual behavior.
demo:inspect samples the intro, first command, every zoom and callout, midpoint, final command, and outro. It writes a contact sheet plus a machine-readable manifest containing timestamps, reasons, media metadata, and boolean checks. The agent guide defines the autonomous review loop.
demo:validate checks JSON schemas, IDs, required files, range direction and overlap, short captions, media readability, zero-byte files, raw dimensions, declared bounds, output dimensions, and subprocess exit codes. Shell commands are executed with structured argument arrays through Execa.
- VHS reports
ttydconnection refused: confirmttyd --version, stop a stalettydprocess, and rerun. The render is safe to repeat. - VHS is missing: install it with
brew install vhs, or start Docker so the renderer can useghcr.io/charmbracelet/vhs. - Tape output path is wrong: its
Outputmust bepublic/recordings/<demo-id>.mp4. - Raw dimensions differ: make
Set Width/Set Heightagree withdemo.json. - Remotion version mismatch: keep all Remotion packages and Zod pinned exactly as in
package.json, then runpnpm install. - Chrome cannot render: rerun
pnpm demo:doctor; Remotion provisions its supported browser on demand. - Caption or zoom is out of bounds: render the recording, inspect its probed duration, then move the ending frame earlier.
- A render command fails silently: the wrapper reports the failed step, full command, stderr, and a corrective action. VHS commands should themselves use
set -eor deterministic fixture programs when multiple shell operations are involved.
.github/workflows/smoke.yml uses read-only permissions and commit-pinned actions. It installs Node, pnpm, FFmpeg, and VHS; caches pnpm's store; checks types, lint, and unit tests; audits production dependencies; records and validates the example; renders low-resolution MP4 and GIF smoke artifacts; and uploads them. Full-resolution variants are intentionally not rendered on every commit.
VHS is the default. A secondary tmux-driven Ghostty/OBS or native-capture design is documented in real terminal capture. It retains Remotion as the compositor and does not rely on visual keyboard simulation.
The checked-in Takewright skill teaches Codex how to create, render, inspect, and refine demos, with Slack landscape as the default social workflow. Install it into your personal skill catalog from the repository root:
mkdir -p ~/.codex/skills
ln -sfn "$(pwd)/skills/takewright" ~/.codex/skills/takewrightThen invoke it with $takewright or ask Codex to create a terminal or Slack demo.
See architecture for data flow, determinism boundaries, and extension points.
Contributions are welcome. Read CONTRIBUTING.md before opening a pull request. Use GitHub Issues for reproducible bugs and focused feature requests, and follow the Code of Conduct. General usage guidance is in SUPPORT.md; security issues must follow SECURITY.md.
Maintainers preparing a public repository should follow the maintainer guide.
Takewright source code is available under the MIT License. Dependencies and external tools retain their own licenses. In particular, Remotion uses the Remotion License and may require a company license depending on the user or organization. Review NOTICE.md before adopting or redistributing the project.
