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. MCP server with 6 tools, machine-readable output contract, and a
tds watchloop for live editing. Agents render, validate, lint, and debug without shell parsing. - Safe by default. Prompt-loop policies, lint gates, media redaction, bounded waits, and failure bundles with redacted diagnostics.
Quickstart
1. First render (2 minutes)
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
Your GIF is in my_demo/outputs/. Use --docker instead of --local if you don't have vhs/ffmpeg installed — Docker bundles everything automatically.
2. Connect your agent (30 seconds)
Give Claude Code, Cursor, or Windsurf full access to render, validate, lint, and debug demos — no shell parsing needed.
Claude Code
pip install terminal-demo-studio[mcp]
claude mcp add terminal-demo-studio -- tds-mcp
Done. Claude Code can now call tds_render, tds_validate, tds_lint, tds_debug, tds_list_templates, and tds_doctor as native tools.
Cursor / Windsurf / any MCP client
pip install terminal-demo-studio[mcp]
Add to your project's .mcp.json:
{
"mcpServers": {
"terminal-demo-studio": {
"type": "stdio",
"command": "tds-mcp"
}
}
}
The agent now has 6 tools available: tds_render, tds_validate, tds_lint, tds_debug, tds_list_templates, tds_doctor.
Any agent via CLI output contract
No MCP needed. Every tds render emits machine-readable keys that any agent can parse:
tds render screenplay.yaml --mode scripted --output gif --output-dir outputs
STATUS=success
RUN_DIR=outputs/.terminal_demo_studio_runs/run-abc123
MEDIA_GIF=outputs/.terminal_demo_studio_runs/run-abc123/media/demo.gif
SUMMARY=outputs/.terminal_demo_studio_runs/run-abc123/summary.json
Add this to your agent's system prompt or CLAUDE.md:
Use `tds render <screenplay> --mode scripted --output gif --output-dir outputs` to render terminal demos.
Parse STATUS, RUN_DIR, and MEDIA_GIF from stdout.
If STATUS=failed, run `tds debug <RUN_DIR> --json` and fix the screenplay.
3. Keep demos fresh in CI (1 minute)
Add this workflow to auto-render GIFs whenever screenplays or source code change on main:
# .github/workflows/auto-update-media.yml
name: auto-update-demo-media
on:
push:
branches: [main]
paths:
- 'examples/showcase/**/*.yaml'
- 'your_package/**' # your source code path
jobs:
render:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 2 }
- uses: actions/setup-python@v5
with: { python-version: "3.11", cache: pip }
- uses: actions/setup-go@v5
with: { go-version: "1.22" }
- name: Install dependencies
run: |
sudo apt-get update && sudo apt-get install -y ffmpeg ttyd
go install github.com/charmbracelet/vhs@v0.10.0
echo "$HOME/go/bin" >> "$GITHUB_PATH"
pip install -e .
- name: Render and commit
run: |
mkdir -p docs/media
for f in examples/showcase/*.yaml; do
stem=$(basename "$f" .yaml)
tds render "$f" --mode scripted_vhs --local --output gif --output-dir outputs || true
find outputs -name "${stem}.*" -path "*/media/*" -exec cp {} docs/media/ \; 2>/dev/null || true
done
git config user.email "action@github.com"
git config user.name "github-actions"
git add docs/media/
git diff --cached --quiet || (git commit -m "ci: auto-update demo media" && git push)
Or use the built-in composite action for per-PR rendering:
- uses: tomallicino/terminal-demo-studio/.github/actions/render@main
with:
screenplay: examples/showcase/onboarding_tokyo_neon.yaml
mode: scripted_vhs
outputs: gif
upload_artifact: true
See the GitHub Action guide for full options.
4. Live editing loop
Watch a screenplay and auto-render on every save:
tds watch screenplay.yaml --mode scripted --output gif --output-dir outputs
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.
Showcase gallery
Every GIF below was generated from a YAML screenplay in this repo. Each one is fully reproducible — clone, render, done.
Real agent TUI capture
The visual lane captures real interactive TUIs with live keyboard interaction, approval-prompt handling, and full-screen recording.
|
Claude Code — real onboarding flow with OAuth prompts and interactive session |
Codex — builds and verifies a hello-world app through the Codex TUI |
Themed scripted demos
Pixel-perfect renders across six popular terminal themes. Each uses a different font, color scheme, and workflow pattern.
| Demo | Theme | Font | Preview |
|---|---|---|---|
| Onboarding Neon | TokyoNightStorm | Menlo | |
| Bugfix Glow | Catppuccin Mocha | Monaco | |
| Recovery Retro | GruvboxDark | Courier New | |
| Policy Guard | Nord | SF Mono | |
| Menu Contrast | Dracula | Courier | |
| Nightshift Speedrun | TokyoNightStorm | Monaco |
Starter patterns
Ready-to-use templates that demonstrate common demo patterns. Great starting points for your own screenplays.
| Demo | Pattern | Preview |
|---|---|---|
| Install First Command | Quickstart onboarding — pip install, first render, output | |
| Before & After Bugfix | Two-scene comparison — failing tests, then the fix | |
| Error Then Fix | Error diagnosis — stack trace, root cause, resolution | |
| Interactive Menu | TUI navigation — arrow keys, selection, confirmation | |
| Policy Warning Gate | Safety enforcement — blocked action, policy explanation | |
| Speedrun Cuts | CI pipeline — lint, test, build, deploy in rapid sequence |
Regenerate all showcase media:
./scripts/render_showcase_media.sh
Screenplay catalog
28 screenplays ship with the repo across three categories. Use them as-is or as templates for your own demos.
Showcase (examples/showcase/) — polished, theme-styled demos
| Screenplay | Lane | Theme |
|---|---|---|
onboarding_tokyo_neon.yaml |
scripted | TokyoNightStorm |
bugfix_catppuccin_glow.yaml |
scripted | Catppuccin Mocha |
recovery_gruvbox_retro.yaml |
scripted | GruvboxDark |
policy_nord_guard.yaml |
scripted | Nord |
menu_dracula_contrast.yaml |
scripted | Dracula |
speedrun_nightshift.yaml |
scripted | TokyoNightStorm |
autonomous_claude_real_short.yaml |
autonomous_video | TokyoNightStorm |
autonomous_codex_real_short.yaml |
autonomous_video | GruvboxDark |
Mock (examples/mock/) — lightweight patterns for testing and learning
| Screenplay | Pattern |
|---|---|
install_first_command.yaml |
Quickstart onboarding |
before_after_bugfix.yaml |
Two-scene before/after |
error_then_fix.yaml |
Error diagnosis and fix |
interactive_menu_showcase.yaml |
Arrow-key TUI menu |
policy_warning_gate.yaml |
Safety policy gate |
speedrun_cuts.yaml |
Rapid CI pipeline |
agent_loop.yaml |
Agent tool-call loop |
list_detail_flow.yaml |
List → detail drill-down |
safety_wizard.yaml |
Multi-step safety wizard |
render_smoke.yaml |
Minimal smoke test |
autonomous_video_claude_like.yaml |
Mock Claude TUI |
autonomous_video_codex_like.yaml |
Mock Codex TUI |
Real (examples/real/) — actual agent executions for integration testing
| Screenplay | Description |
|---|---|
autonomous_video_codex_cli.yaml |
Codex CLI basic session |
autonomous_video_codex_complex_verified.yaml |
Complex multi-step Codex workflow |
autonomous_video_codex_hello_project_approval.yaml |
Codex with approval prompts accepted |
autonomous_video_codex_hello_project_deny.yaml |
Codex with approval prompts denied |
autonomous_video_codex_multiturn.yaml |
Multi-turn Codex conversation |
autonomous_video_codex_patch_flow.yaml |
Codex patch review flow |
real_agent_demo.yaml |
General agent demo session |
Production screenplays (screenplays/) — complete workflow demos
| Screenplay | Theme | What it demonstrates |
|---|---|---|
dev_bugfix_workflow.yaml |
TokyoNightStorm | Developer bugfix: regression in add(), unit tests fail, fix, tests pass |
drift_protection.yaml |
TokyoNightStorm | Drift protection: unsafe tool execution vs. policy-guarded safe mode |
single_prompt_macos_demo.yaml |
TokyoNightStorm | Log triage with macOS-style prompt, failure parsing, error pattern display |
rust_cli_demo.yaml |
Catppuccin Mocha | Rust binary safety guard: unguarded deletion vs. policy-checked execution |
agent_generated_feature_flag_fix.yaml |
Nord | Feature flag bugfix: checkout flag misconfigured, tests fail, reconfigure, pass |
agent_generated_policy_guard.yaml |
Catppuccin Mocha | Agent safety policy: raw PII export blocked, routed to secure vault |
agent_generated_release_check.yaml |
GruvboxDark | Release compliance: lockfile, security scan, changelog, approver signoff |
agent_generated_triage.yaml |
Catppuccin Mocha | Agent triage: unguided output fails validation vs. guided output passes |
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
How the visual lane works
The visual lane (autonomous_video) captures any interactive TUI — Claude Code, Codex, htop, vim — by driving a real terminal emulator:
┌─────────────┐ keystrokes ┌──────────────┐ video ┌───────────┐
│ tds render │ ──────────────────▶ │ Kitty on Xvfb│ ───────────▶ │ ffmpeg │
│ (driver) │ ◀────────────────── │ (real TTY) │ │ GIF/MP4 │
└─────────────┘ screen captures └──────────────┘ └───────────┘
│ │
▼ ▼
wait_for / assert agent prompt detection
(regex on screen) (approve / deny / manual)
- Xvfb provides a headless X display (no monitor needed)
- Kitty runs the target TUI on that display with full GPU-accelerated rendering
- tds sends keystrokes via Kitty's remote control protocol and reads the terminal screen
- ffmpeg records the display output into GIF/MP4
- Prompt policies automatically handle approval dialogs from AI agents (configurable approve/deny/manual modes with regex matching and bounded rounds)
The easiest way to try it is via Docker — no host setup required:
tds render screenplay.yaml --mode visual --docker --output mp4
See the autonomous roadmap for supported primitives and planned work.
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 watch <screenplay.yaml> Watch and auto-render on changes
--mode auto|scripted|interactive|visual
--docker | --local Runtime location
--output gif|mp4 Output format (repeat for both)
--output-dir PATH Output directory
--debounce DURATION Re-render debounce (default: 1000ms)
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
See the Quickstart for setup. Full options in the GitHub Action guide.
| Input | Default | Description |
|---|---|---|
screenplay |
(required) | Screenplay YAML path |
mode |
scripted_vhs |
Execution lane |
outputs |
gif |
Comma-separated formats (gif, mp4) |
output_dir |
outputs |
Output directory |
upload_artifact |
true |
Upload run directory as artifact |
comment_pr |
false |
Post result comment on PRs |
Agent integration
See the Quickstart for setup. Once connected, agents have access to 6 MCP tools:
| Tool | What it does |
|---|---|
tds_render |
Render a screenplay to GIF/MP4 |
tds_validate |
Parse and validate screenplay YAML |
tds_lint |
Check for policy and safety violations |
tds_debug |
Inspect run artifacts and failure diagnostics |
tds_list_templates |
List available screenplay templates |
tds_doctor |
Check environment readiness |
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.
Autonomous workflow
An agent with TDS connected can maintain your demo media end-to-end:
- Create —
tds_list_templates→ pick a template → write a screenplay - Validate —
tds_validateto catch schema errors before rendering - Lint —
tds_lint --strictto enforce safety policies - Render —
tds_renderto produce the GIF/MP4 - Debug — if render fails,
tds_debugreads the failure bundle and suggests fixes - Watch —
tds watchfor live iteration during screenplay editing
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.3.0.tar.gz.
File metadata
- Download URL: terminal_demo_studio-0.3.0.tar.gz
- Upload date:
- Size: 77.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7385f8b1159b9bbb07a78cd7fc442fa3b14f5a0a5169eeb6c508238462dbf738
|
|
| MD5 |
f5de52ad89529c8f5dce815952fd1b04
|
|
| BLAKE2b-256 |
1d3387ff4b3aadf46875d349acb777ef13ad517e60d0ffc79db5d996ad5c2f92
|
Provenance
The following attestation bundles were made for terminal_demo_studio-0.3.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.3.0.tar.gz -
Subject digest:
7385f8b1159b9bbb07a78cd7fc442fa3b14f5a0a5169eeb6c508238462dbf738 - Sigstore transparency entry: 975666840
- Sigstore integration time:
-
Permalink:
tomallicino/terminal-demo-studio@1a8791305b812ba7603ff1c0d990aaf38c934d75 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/tomallicino
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1a8791305b812ba7603ff1c0d990aaf38c934d75 -
Trigger Event:
push
-
Statement type:
File details
Details for the file terminal_demo_studio-0.3.0-py3-none-any.whl.
File metadata
- Download URL: terminal_demo_studio-0.3.0-py3-none-any.whl
- Upload date:
- Size: 65.3 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 |
03c1863a75b97bc91aeb086c7a2f80bd6f71d126a1d00dc3e555e2f1b753839d
|
|
| MD5 |
d7e7c885f57b36a1cc903ff247d1293d
|
|
| BLAKE2b-256 |
0bc74bb7afdb0f3d9c9cd5596a0dd20758667379afad0b541a371c0b1c2cbd9f
|
Provenance
The following attestation bundles were made for terminal_demo_studio-0.3.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.3.0-py3-none-any.whl -
Subject digest:
03c1863a75b97bc91aeb086c7a2f80bd6f71d126a1d00dc3e555e2f1b753839d - Sigstore transparency entry: 975666842
- Sigstore integration time:
-
Permalink:
tomallicino/terminal-demo-studio@1a8791305b812ba7603ff1c0d990aaf38c934d75 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/tomallicino
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1a8791305b812ba7603ff1c0d990aaf38c934d75 -
Trigger Event:
push
-
Statement type: