Deterministic, agent-native terminal demo pipeline — turn YAML screenplays into repeatable GIF/MP4 terminal demos
Project description
terminal-demo-studio
Turn YAML screenplays into deterministic GIF/MP4 terminal demos. Capture any TUI — Claude Code, Codex, htop, vim — with full keyboard interaction, approval-prompt automation, and safety controls.
What it does
- YAML in, GIF/MP4 out. Define a screenplay, get a repeatable demo video. No screen recording by hand.
- Three execution lanes. Polished scripted renders, command/assert automation, or full-screen TUI capture with live keyboard interaction.
- Captures complex TUIs. Claude Code, Codex, htop, vim, any interactive terminal app — rendered through a real terminal emulator (Kitty), not a text-mode simulator.
- Agent-native. Machine-readable output contract (
STATUS,RUN_DIR,MEDIA_GIF,SUMMARY,EVENTS) that agents can parse and act on. - Safe by default. Prompt-loop policies, lint gates, media redaction, bounded waits, and failure bundles with redacted diagnostics.
Quickstart
pip install terminal-demo-studio
tds init --destination my_demo
tds render my_demo/screenplays/getting_started.yaml --mode scripted --local --output gif --output-dir my_demo/outputs
That's it. Your GIF is in my_demo/outputs/.
Using Docker (zero local dependencies)
tds render my_demo/screenplays/getting_started.yaml --docker --output gif --output-dir my_demo/outputs
Docker mode bundles all system dependencies (vhs, ffmpeg, kitty, xvfb) automatically.
Platform support
| Windows | macOS | Linux | |
|---|---|---|---|
Scripted (--mode scripted) |
Docker or local (vhs + ffmpeg) | Local or Docker | Local or Docker |
Interactive (--mode interactive) |
Local | Local | Local |
Visual (--mode visual) |
Docker | Docker or local (kitty + xvfb) | Local or Docker |
pip install |
Yes | Yes | Yes |
| Python 3.11+ | Yes | Yes | Yes |
tds render auto-selects Docker when local dependencies are missing. Use --local to force local mode or --docker to force Docker mode.
Three execution lanes
Scripted (--mode scripted)
Cinematic, deterministic renders for marketing and docs. Compiles YAML actions into VHS tape format, renders through a headless terminal, and produces pixel-perfect GIF/MP4.
tds render screenplay.yaml --mode scripted --output gif
Interactive (--mode interactive)
Command/assert automation. Runs commands via subprocess, evaluates wait conditions and assertions, logs runtime events. No video output — pure execution verification.
tds run screenplay.yaml --mode interactive --output-dir outputs
Visual (--mode visual)
Full-screen TUI capture. Launches a Kitty terminal on a virtual X display, sends keystrokes, captures video with FFmpeg. Handles approval prompts automatically via configurable policies.
tds run screenplay.yaml --mode visual --output mp4
Capturing complex TUIs
The visual lane can capture any interactive terminal application. Here are real demos generated from YAML screenplays:
Claude Code (autonomous_video)
Captures a real Claude Code session — onboarding flow, interactive prompts, and all.
Codex (autonomous_video)
Builds and verifies a hello-world app through the Codex TUI.
Showcase gallery
All scripted demos below were generated from YAML screenplays in this repo.
| Demo | Theme | Preview |
|---|---|---|
| Onboarding Neon | TokyoNightStorm | |
| Bugfix Glow | Catppuccin Mocha | |
| Recovery Retro | GruvboxDark | |
| Policy Guard | Nord | |
| Menu Contrast | Dracula | |
| Nightshift Speedrun | TokyoNightStorm |
Regenerate all showcase media:
./scripts/render_showcase_media.sh
Screenplay format
title: "My Demo"
output: "my_demo"
settings:
width: 1440
height: 900
theme: "TokyoNightStorm"
font_family: "Menlo"
framerate: 30
scenarios:
- label: "Setup and run"
execution_mode: "scripted_vhs" # or autonomous_pty / autonomous_video
setup:
- "npm install"
actions:
- type: "npm start"
- wait_for: "Server running"
wait_mode: "screen"
wait_timeout: "10s"
- type: "curl localhost:3000"
- wait_for: "Hello"
wait_mode: "screen"
wait_timeout: "5s"
Action types
| Action | Lanes | Description |
|---|---|---|
type / command |
all | Type text (scripted) or execute command (autonomous) |
key / hotkey |
scripted, visual | Send a keystroke (Enter, ctrl+c, Escape) |
input |
visual | Type raw text without pressing Enter |
wait_for |
all | Wait for text to appear on screen |
wait_stable / sleep |
all | Pause for a duration |
assert_screen_regex |
interactive, visual | Assert screen content matches regex |
expect_exit_code |
interactive | Assert command exit code |
CLI reference
tds render <screenplay.yaml> Render a screenplay to GIF/MP4
--mode auto|scripted|interactive|visual
--docker | --local Runtime location
--output gif|mp4 Output format (repeat for both)
--output-dir PATH Output directory
--playback sequential|simultaneous
--agent-prompts auto|manual|approve|deny
--redact auto|off|input_line
--template TEMPLATE Use built-in template instead of file
--keep-temp Keep intermediate files
tds run <screenplay.yaml> Alias for render (same options)
tds validate <screenplay.yaml> Validate YAML schema
--json-schema Print JSON schema
--explain Show screenplay summary
tds lint <screenplay.yaml> Lint for policy and safety issues
--json JSON output
--strict Treat warnings as errors
tds new <name> Create new screenplay from template
--template TEMPLATE Template name (default: before_after_bugfix)
--list-templates List available templates
--destination PATH Output directory
tds init Initialize workspace with starter screenplay
--destination PATH Workspace root (default: .)
--template TEMPLATE Starter template
--name NAME Screenplay name
tds doctor Check dependency health
--mode auto|scripted|interactive|visual
tds debug <run_dir> Inspect a completed run
--json JSON output
Docker mode
Docker bundles all system dependencies (vhs, ffmpeg, kitty, xvfb, starship) in a single container image. The image is content-addressed and cached.
# Explicit Docker mode
tds render screenplay.yaml --docker --output gif
# Auto mode (uses Docker if available, falls back to local)
tds render screenplay.yaml --output gif
# Force rebuild the Docker image
tds render screenplay.yaml --docker --rebuild --output gif
Environment variables for Docker execution:
| Variable | Default | Description |
|---|---|---|
TDS_DOCKER_HARDENING |
true |
Enable --cap-drop ALL, --security-opt no-new-privileges |
TDS_DOCKER_PIDS_LIMIT |
512 |
Container PID limit |
TDS_DOCKER_READ_ONLY |
false |
Read-only root filesystem |
TDS_DOCKER_NETWORK |
(none) | Docker network mode |
TDS_DOCKER_IMAGE_RETENTION |
3 |
Number of cached images to keep |
Safety and reliability
Prompt policy
The visual lane can automatically handle approval prompts from AI agents (Claude Code, Codex):
agent_prompts:
mode: "approve" # auto | manual | approve | deny
prompt_regex: "(?i)(proceed|confirm|allow)"
allow_regex: "safe operation"
allowed_command_prefixes: ["npm", "git"]
max_rounds: 5
approve_key: "y"
deny_key: "n"
Lint gates
tds lint screenplay.yaml --strict
Catches unsafe configurations before execution: unbounded approval, missing prompt regex, unsupported actions per lane.
Media redaction
tds render screenplay.yaml --redact auto
Modes: auto (redact detected secrets), off, input_line (mask typed input lines). Sensitive values from environment variables (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) are automatically detected and masked.
Failure bundles
Failed runs produce a diagnostic bundle at failure/:
reason.txt— redacted failure reasonscreen.txt— redacted terminal snapshotstep.json— failed step metadatavideo_runner.log— redacted process logs
GitHub Action
Add to your CI workflow:
- uses: tomallicino/terminal-demo-studio/.github/actions/render@main
with:
screenplay: examples/showcase/onboarding_tokyo_neon.yaml
mode: scripted_vhs
outputs: gif
output_dir: outputs
upload_artifact: true
comment_pr: true
See the GitHub Action guide for full options.
Agent integration
Install as a skill
npx skills add tomallicino/terminal-demo-studio --skill terminal-demo-studio
Example agent prompt
Render examples/showcase/policy_nord_guard.yaml in scripted mode.
Return STATUS, RUN_DIR, MEDIA_GIF, MEDIA_MP4, and SUMMARY.
If status is failed, run `tds debug <run_dir> --json` and summarize root cause.
Output contract
Every tds render / tds run emits machine-readable keys:
STATUS=success
RUN_DIR=outputs/.terminal_demo_studio_runs/run-abc123
MEDIA_GIF=outputs/.terminal_demo_studio_runs/run-abc123/media/demo.gif
MEDIA_MP4=outputs/.terminal_demo_studio_runs/run-abc123/media/demo.mp4
SUMMARY=outputs/.terminal_demo_studio_runs/run-abc123/summary.json
EVENTS=outputs/.terminal_demo_studio_runs/run-abc123/runtime/events.jsonl
Artifact contract
Each run writes .terminal_demo_studio_runs/<run-id>/ with:
manifest.json Run metadata
summary.json Execution summary (status, lane, media paths)
media/*.gif|*.mp4 Rendered output
scenes/scene_*.mp4 Per-scenario videos (scripted, visual)
tapes/scene_*.tape VHS tape files (scripted)
runtime/events.jsonl Event log (autonomous lanes)
failure/* Diagnostic bundle on failure
Additional docs
- Architecture
- Capability registry
- Reproducibility
- Autonomous roadmap
- GitHub Action guide
- Release checklist
- Showcase gallery index
License
MIT (LICENSE)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file terminal_demo_studio-0.2.0.tar.gz.
File metadata
- Download URL: terminal_demo_studio-0.2.0.tar.gz
- Upload date:
- Size: 64.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7db9aa54a7707653d9d67cdceac1b4aa040754d8e46fc999c3cbd595c705c0a7
|
|
| MD5 |
a44f1f3633386355e44c6904251dab06
|
|
| BLAKE2b-256 |
85292143141c56f42dc1fdf629cc14fda5db2de5e3a3b467fb5cc265d079758b
|
Provenance
The following attestation bundles were made for terminal_demo_studio-0.2.0.tar.gz:
Publisher:
publish.yml on tomallicino/terminal-demo-studio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
terminal_demo_studio-0.2.0.tar.gz -
Subject digest:
7db9aa54a7707653d9d67cdceac1b4aa040754d8e46fc999c3cbd595c705c0a7 - Sigstore transparency entry: 975638924
- Sigstore integration time:
-
Permalink:
tomallicino/terminal-demo-studio@deeb05d3903c80b31ea75ebb338f19fc6266f3de -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/tomallicino
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@deeb05d3903c80b31ea75ebb338f19fc6266f3de -
Trigger Event:
push
-
Statement type:
File details
Details for the file terminal_demo_studio-0.2.0-py3-none-any.whl.
File metadata
- Download URL: terminal_demo_studio-0.2.0-py3-none-any.whl
- Upload date:
- Size: 57.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca07b864cb76046a1019881f4679acbc6f902a36ca6b1b32ee307ddff02a2946
|
|
| MD5 |
f506bc5658ac1df864903b9f62838f29
|
|
| BLAKE2b-256 |
653dac01f86d84550764de42ee3f8d7835df4c9768f15628595c6245804c6a5c
|
Provenance
The following attestation bundles were made for terminal_demo_studio-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on tomallicino/terminal-demo-studio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
terminal_demo_studio-0.2.0-py3-none-any.whl -
Subject digest:
ca07b864cb76046a1019881f4679acbc6f902a36ca6b1b32ee307ddff02a2946 - Sigstore transparency entry: 975638935
- Sigstore integration time:
-
Permalink:
tomallicino/terminal-demo-studio@deeb05d3903c80b31ea75ebb338f19fc6266f3de -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/tomallicino
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@deeb05d3903c80b31ea75ebb338f19fc6266f3de -
Trigger Event:
push
-
Statement type: