{"id":50270357,"url":"https://github.com/openhands/demorec","last_synced_at":"2026-05-27T17:01:21.711Z","repository":{"id":345173274,"uuid":"1184755961","full_name":"OpenHands/demorec","owner":"OpenHands","description":"Record cli and web-based demos from a script","archived":false,"fork":false,"pushed_at":"2026-04-03T19:51:31.000Z","size":11521,"stargazers_count":2,"open_issues_count":8,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-03T20:19:57.505Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/OpenHands.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-17T22:47:32.000Z","updated_at":"2026-04-03T15:54:16.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/OpenHands/demorec","commit_stats":null,"previous_names":["jpshackelford/demorec","openhands/demorec"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/OpenHands/demorec","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenHands%2Fdemorec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenHands%2Fdemorec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenHands%2Fdemorec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenHands%2Fdemorec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/OpenHands","download_url":"https://codeload.github.com/OpenHands/demorec/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/OpenHands%2Fdemorec/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33575520,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-27T17:01:20.920Z","updated_at":"2026-05-27T17:01:21.698Z","avatar_url":"https://github.com/OpenHands.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# demorec 🎬\n\n**Proof of work that's easy on the eye.**\n\n\u003cvideo src=\"https://github.com/jpshackelford/demorec/raw/main/demo/demorec-intro.mp4\" controls width=\"100%\"\u003e\u003c/video\u003e\n\n\u003e 📺 [View full demo with script](https://htmlpreview.github.io/?https://github.com/jpshackelford/demorec/blob/main/demo/index.html) | [Download video](demo/demorec-intro.mp4)\n\ndemorec is a declarative tool for creating professional demo videos that seamlessly mix terminal and browser interactions—perfect for product demos, tutorials, and PR walkthroughs.\n\nInspired by [charmbracelet/vhs](https://github.com/charmbracelet/vhs), but unified across CLI and web.\n\n## Why demorec?\n\nReal-world demos often involve both terminal and browser:\n\n- Start a server via CLI → show the web UI\n- Run a build command → verify results in browser  \n- Configure via terminal → interact with the app\n\nExisting tools make you record these separately and stitch manually. **demorec handles it all in one script.**\n\n### Key Features\n\n- **Unified Recording**: Seamlessly switch between terminal and browser in a single script\n- **Multiple Terminal Sessions**: Run servers, clients, and utilities in independent named sessions\n- **Persistent State**: Terminal state (working directory, environment variables, running processes) persists across mode switches\n- **Terminal Sub-modes**: Use `@mode terminal:vim` for vim command expansion\n- **AI Narration**: Add voiceover with Edge TTS (free) or ElevenLabs\n- **Vim Primitives**: High-level commands for code review demos (`Open`, `Highlight`, `Goto`, `Close`)\n\n## Quick Example\n\n```tape\n# my-demo.demorec\nOutput demo.mp4\nSet Width 1280\nSet Height 720\n\n# @voice edge:jenny\n\n# ─────────────────────────────────────\n# TERMINAL: Install and start server\n# ─────────────────────────────────────\n@mode terminal\nSet Theme \"Dracula\"\n\n# @narrate:before \"Let's install the CLI tool.\"\nType \"pip install myapp\"\nEnter\nSleep 2s\n\n# @narrate:before \"Now let's start the dev server.\"\nType \"myapp serve --port 3000\"\nEnter\nSleep 3s\n\n# ─────────────────────────────────────\n# BROWSER: Show the web interface\n# ─────────────────────────────────────\n@mode browser\n\n# @narrate:before \"The web interface is now live.\"\nNavigate \"http://localhost:3000\"\nSleep 2s\n\nClick \"#create-new\"\nType \"#name\" \"My Project\"\nClick \"#save\"\nSleep 2s\n\n# ─────────────────────────────────────\n# TERMINAL: Show the logs\n# ─────────────────────────────────────\n@mode terminal\n\n# @narrate:after \"And we can see the request in the terminal logs.\"\nSleep 2s\nCtrl+C\nSleep 1s\n```\n\n```bash\ndemorec record my-demo.demorec\n```\n\n## Installation\n\n### System Dependencies\n\ndemorec requires several system tools to be installed:\n\n| Dependency | Required | Purpose | Installation |\n|------------|----------|---------|--------------|\n| **FFmpeg** | ✅ Yes | Video/audio processing | `sudo apt install ffmpeg` (Ubuntu) or `brew install ffmpeg` (macOS) |\n| **ttyd** | ✅ Yes | Terminal PTY server | See below |\n| **tmux** | ✅ Yes | Persistent terminal sessions | `sudo apt install tmux` (Ubuntu) or `brew install tmux` (macOS) |\n| **Chromium** | ✅ Yes | Browser automation | Installed via `playwright install chromium` |\n| **vim** | Optional | Only for vim primitives (`Open`, `Highlight`, etc.) | `sudo apt install vim` (Ubuntu) or `brew install vim` (macOS) |\n| **Marp CLI** | Optional | Only for presentation mode | `npm install -g @marp-team/marp-cli` |\n\n#### Installing ttyd\n\nttyd is a terminal sharing tool that provides the PTY backend. Install it with:\n\n```bash\n# Linux (x86_64)\nwget -qO /tmp/ttyd https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.x86_64\nchmod +x /tmp/ttyd\nsudo mv /tmp/ttyd /usr/local/bin/ttyd\n\n# macOS\nbrew install ttyd\n```\n\n### Python Package\n\n```bash\n# With uv (recommended)\nuv tool install demorec\n\n# Or with pip\npip install demorec\n\n# Install Playwright browser (Chromium)\nplaywright install chromium\n```\n\n### Optional: ElevenLabs TTS\n\nFor premium voice quality, install the ElevenLabs extras:\n\n```bash\npip install \"demorec[tts]\"\n```\n\nThis requires an `ELEVENLABS_API_KEY` environment variable. Edge TTS (included by default) works without any API key.\n\n## CLI Usage\n\n```bash\n# Record a demo\ndemorec record my-demo.demorec\n\n# Record with options\ndemorec record my-demo.demorec -o output.mp4 --voice adam\n\n# Validate syntax without recording\ndemorec validate my-demo.demorec\n\n# List available TTS voices\ndemorec voices\n\n# Show version\ndemorec --version\n```\n\n## Agent Workflow Tools\n\ndemorec includes commands designed for AI agents creating vim-based code review demos.\n\n### Stage Directions\n\nCalculate optimal vim commands to display specific line ranges:\n\n```bash\n# Get vim commands for highlighting specific line ranges\ndemorec stage --rows 30 --highlights \"6-8,11-16,27-35,63-73\"\n\n# Output formats: text (default), json, demorec\ndemorec stage --rows 30 --highlights \"6-8,27-35\" --format json\ndemorec stage --rows 30 --highlights \"6-8,27-35\" --format demorec\n```\n\nExample output:\n```\nStage Directions (30 rows)\n\nBlock 1: lines 6-8 (3 lines)\n  Goto:      6G\n  Center:    zz\n  Select:    V8G\n  Rationale: Block fits in viewport, using zz to center\n\nBlock 2: lines 27-35 (9 lines)  \n  Goto:      31G\n  Center:    zz\n  Select:    V27G then 35G\n  Rationale: Block fits in viewport, centering on middle line\n```\n\n### Preview\n\nRun through a script and verify checkpoints without recording video:\n\n```bash\n# Preview with verification (screenshots only on errors)\ndemorec preview script.demorec --rows 30\n\n# Always capture screenshots at checkpoints\ndemorec preview script.demorec --rows 30 --screenshots\n\n# Never capture screenshots (fastest)\ndemorec preview script.demorec --rows 30 --no-screenshots\n\n# Capture frame-by-frame snapshots for AI debugging\ndemorec preview script.demorec --rows 30 -o ./frames\n\n# Capture frames without output directory (disable frame capture explicitly)\ndemorec preview script.demorec --rows 30 -o ./frames --no-frames\n```\n\nPreview auto-detects \"show moments\" (visual selections in vim) and verifies that expected lines are visible:\n\n```\n[PASS] Checkpoint 1 (line 11): lines 6-8 visible\n[PASS] Checkpoint 2 (line 33): lines 27-35 visible\nFrames captured: 15 frames to ./frames\nSummary: 2/2 passed\n```\n\n#### Frame-by-Frame Capture\n\nWhen `--output-dir` is specified (or `--frames` is used), preview captures the terminal/browser state at every step:\n\n- **Terminal frames**: Saved as `.txt` files containing the visible terminal buffer\n- **Browser frames**: Saved as `.png` screenshots\n\nFrame naming convention: `frame_{NNNN}_{SSSS.ss}.{ext}`\n- `NNNN`: Zero-padded 4-digit frame number (0001, 0002, ...)\n- `SSSS.ss`: Elapsed time in seconds with 2 decimal places (0000.00, 0001.25, ...)\n- `ext`: File extension (`.txt` for terminal, `.png` for browser)\n\nExample output:\n```\nframes/\n├── frame_0001_0000.00.txt    # Initial terminal state\n├── frame_0002_0000.05.txt    # After first command\n├── frame_0003_0000.28.txt    # After Type \"vim file.py\"\n├── frame_0004_0001.52.txt    # After Enter\n└── ...\n```\n\nThis is useful for AI agents debugging recordings and verifying terminal output at each step.\n\n### Checkpoints\n\nAnalyze a script to find natural checkpoint locations:\n\n```bash\n# List detected checkpoints\ndemorec checkpoints script.demorec\n\n# JSON output for programmatic use\ndemorec checkpoints script.demorec --format json\n```\n\n### Terminal Sizing\n\nControl terminal dimensions for consistent viewport sizing using segment settings:\n\n```tape\n@mode terminal\nrows 30                     # Exact row count (10-100)\nsize \"medium\"               # Or use a preset size\ntheme \"Dracula\"\n---\nType \"vim myfile.py\"\nEnter\n```\n\n**Size presets:**\n\n| Preset | Rows | Best for |\n|--------|------|----------|\n| `large` | 24 | Classic terminal, easy to read |\n| `medium` | 36 | Balanced readability and content |\n| `small` | 44 | Default xterm.js density |\n| `tiny` | 50 | Maximum content, smaller text |\n\n### High-Level Vim Primitives\n\nFor AI agents creating code review demos, these commands handle vim complexity internally:\n\n```tape\n@mode terminal:vim\nrows 30\n---\nOpen \"src/api.py\"           # Open file with line numbers enabled\nHighlight \"10-20\"           # Navigate to lines and select visually\nHighlight \"45-55\"           # Jump to next highlight\nGoto 100                    # Jump to line with centering\nClose                       # Exit vim cleanly\n```\n\n| Command | Description | Example |\n|---------|-------------|---------|\n| `Open \"\u003cfile\u003e\"` | Open file in vim with line numbers | `Open \"src/api.py\"` |\n| `Highlight \"\u003crange\u003e\"` | Navigate to lines and select visually | `Highlight \"10-20\"` |\n| `Goto \u003cline\u003e` | Jump to specific line with centering | `Goto 50` |\n| `Close` | Exit vim cleanly | `Close` |\n\n### Complete Agent Workflow\n\n```bash\n# 1. View file with line numbers\ncat -n examples/sample_code.py\n\n# 2. Get stage directions for highlights\ndemorec stage --rows 30 --highlights \"6-8,11-16,27-35\"\n\n# 3. Write the .demorec script using generated vim commands\n\n# 4. Preview to verify checkpoints\ndemorec preview script.demorec --rows 30\n\n# 5. If issues, adjust script and re-preview\n\n# 6. Record final video\ndemorec record script.demorec\n```\n\n## DSL Reference\n\n### Global Settings\n\n```tape\nOutput demo.mp4              # Output file (.mp4, .webm)\nSet Width 1280               # Video width\nSet Height 720               # Video height  \nSet Framerate 30             # Video framerate\n```\n\n### Mode Switching \u0026 Multiple Terminal Sessions\n\ndemorec supports switching between terminal and browser modes, with **persistent terminal sessions** that maintain state across mode switches.\n\n```tape\n@mode terminal               # Default terminal session\n@mode terminal:vim           # Terminal with vim sub-mode (command expansion)\n@mode browser                # Browser recording\n\n# Named sessions use the 'name' setting:\n@mode terminal\nname \"server\"\n---\n# Commands for the \"server\" session...\n# Note: The old @mode terminal:server syntax is no longer supported.\n# Use the 'name' setting instead (as shown above).\n```\n\n#### Segment Settings Syntax\n\nSettings can be specified immediately after `@mode`. Use a blank line or `---` delimiter to end settings and begin commands:\n\n```tape\n@mode terminal:vim\nrows 30\ntheme \"Dracula\"\nname \"editor\"\n---\nOpen \"file.py\"\nHighlight \"6-8\"\nClose\n```\n\nSupported settings: `rows`, `size`, `theme`, `name`\n\nSub-modes (e.g., `terminal:vim`) and session names can be combined, as shown in the example above.\n\n#### Terminal Sub-modes\n\nSub-modes enable specialized command expansion:\n\n| Sub-mode | Syntax | Purpose |\n|----------|--------|---------|\n| `vim` | `@mode terminal:vim` | Enables vim command expansion for `Open`, `Highlight`, `Goto`, `Close` |\n\n```tape\n@mode terminal:vim\nrows 30\n---\nOpen \"src/api.py\"\nHighlight \"10-20\"\nClose\n```\n\n#### Session Persistence\n\nEach terminal session is backed by tmux, which means:\n\n| What Persists | Example |\n|---------------|---------|\n| Working directory | `cd /app` stays in `/app` after switching modes |\n| Environment variables | `export API_KEY=xxx` remains set |\n| Running processes | `python server.py \u0026` keeps running |\n| Command history | Up arrow recalls previous commands |\n\n#### Named Sessions vs Default Session\n\n| Session | Syntax | Use Case |\n|---------|--------|----------|\n| Default | `@mode terminal` | General commands, setup |\n| Named | `@mode terminal` + `name \"server\"` | Long-running server process |\n| Named | `@mode terminal` + `name \"client\"` | Client/testing commands |\n| Named | `@mode terminal` + `name \"logs\"` | Tail logs or monitoring |\n\nNamed sessions are **completely independent**—each has its own shell process, environment, and working directory. The default session (`@mode terminal`) is also persistent but separate from named sessions.\n\n#### Typical Multi-Session Workflow\n\n```tape\n# 1. Start server in dedicated session\n@mode terminal\nname \"server\"\n---\nType \"npm run dev\"\nEnter\nSleep 2s\n\n# 2. Switch to browser - server keeps running!\n@mode browser\nNavigate \"http://localhost:3000\"\nSleep 2s\n\n# 3. Make API calls from client session\n@mode terminal\nname \"client\"\n---\nType \"curl localhost:3000/api/health\"\nEnter\n\n# 4. Return to server session - see the request logs\n@mode terminal\nname \"server\"\n---\nSleep 1s\n```\n\n#### Tips for Effective Use\n\n1. **Set up state early:** Initialize environment variables and working directories at the start—they persist throughout.\n\n2. **Use named sessions for servers:** Start long-running processes in a named session so switching modes won't kill them.\n\n3. **Show state preservation explicitly:** Run `pwd` or `echo $VAR` after switching back to demonstrate persistence—viewers love this!\n\n4. **Clean up gracefully:** Use `Ctrl+C` in server terminals before ending to show clean shutdown.\n\n5. **Use meaningful names:** Prefer descriptive names like `\"api\"`, `\"frontend\"`, `\"logs\"` for clarity.\n\n### Terminal Commands\n\n| Command | Description | Example |\n|---------|-------------|---------|\n| `Set Theme \"\u003cname\u003e\"` | Terminal theme | `Set Theme \"Dracula\"` |\n| `Type \"\u003ctext\u003e\"` | Type text with delay | `Type \"echo hello\"` |\n| `Enter` | Press Enter | `Enter` |\n| `Run \"\u003ccmd\u003e\" [wait]` | Type, execute, and wait | `Run \"npm test\" 3s` |\n| `Sleep \u003ctime\u003e` | Pause | `Sleep 2s` or `Sleep 500ms` |\n| `Ctrl+C` | Send interrupt | `Ctrl+C` |\n| `Ctrl+D` | Send EOF | `Ctrl+D` |\n| `Ctrl+L` | Clear screen | `Ctrl+L` |\n| `Ctrl+Z` | Suspend process | `Ctrl+Z` |\n| `Tab` | Press Tab (autocomplete) | `Tab` |\n| `Up` / `Down` | Arrow keys (history) | `Up` |\n| `Backspace [n]` | Delete characters | `Backspace 5` |\n| `Escape` | Press Escape | `Escape` |\n| `Space` | Press Space | `Space` |\n| `Clear` | Clear terminal | `Clear` |\n| `Hide` | Stop recording frames | `Hide` |\n| `Show` | Resume recording | `Show` |\n\n### Browser Commands\n\n| Command | Description | Example |\n|---------|-------------|---------|\n| `Navigate \"\u003curl\u003e\"` | Go to URL | `Navigate \"http://localhost:3000\"` |\n| `Click \"\u003cselector\u003e\"` | Click element | `Click \"#submit-btn\"` |\n| `Type \"\u003cselector\u003e\" \"\u003ctext\u003e\"` | Type into element | `Type \"#email\" \"user@example.com\"` |\n| `Fill \"\u003cselector\u003e\" \"\u003ctext\u003e\"` | Fill instantly | `Fill \"#name\" \"John\"` |\n| `Press \"\u003ckey\u003e\"` | Press key | `Press \"Enter\"` |\n| `Sleep \u003ctime\u003e` | Pause | `Sleep 2s` |\n| `Wait \"\u003cselector\u003e\"` | Wait for element | `Wait \".loaded\"` |\n| `Scroll \"\u003cdir\u003e\" \u003camount\u003e` | Scroll page | `Scroll \"down\" \"300\"` |\n| `Hover \"\u003cselector\u003e\"` | Hover element | `Hover \".tooltip\"` |\n| `Highlight \"\u003cselector\u003e\"` | Add red outline | `Highlight \"#important\"` |\n| `Unhighlight \"\u003cselector\u003e\"` | Remove outline | `Unhighlight \"#important\"` |\n| `Screenshot \"\u003cfile\u003e\"` | Save screenshot | `Screenshot \"step1.png\"` |\n\n### Narration (AI Voice-Over)\n\n```tape\n# Set the voice\n# @voice edge:jenny            # Microsoft Edge TTS (recommended, free)\n# @voice eleven:rachel         # ElevenLabs (requires API key)\n\n# Narration modes\n# @narrate:before \"Spoken before the next action\"\n# @narrate:during \"Spoken while action runs\"\n# @narrate:after \"Spoken after action completes\"\n```\n\n**Microsoft Edge TTS voices (free, high quality - recommended):**\n\n| Voice | Description |\n|-------|-------------|\n| `edge:jenny` | Female, US (default) |\n| `edge:guy` | Male, US |\n| `edge:aria` | Female, US |\n| `edge:davis` | Male, US |\n| `edge:emma` | Female, US |\n| `edge:brian` | Male, US |\n| `edge:sonia` | Female, UK |\n| `edge:ryan` | Male, UK |\n| `edge:natasha` | Female, AU |\n| `edge:william` | Male, AU |\n\n**ElevenLabs voices (requires paid API subscription):**\n\n`eleven:rachel`, `eleven:adam`, `eleven:josh`, `eleven:bella`, `eleven:sam`, `eleven:antoni`, `eleven:arnold`, `eleven:domi`, `eleven:elli`\n\n### Time Formats\n\n- Seconds: `2s`, `1.5s`\n- Milliseconds: `500ms`, `100ms`\n\n## Environment Variables\n\n| Variable | Description |\n|----------|-------------|\n| `ELEVENLABS_API_KEY` | ElevenLabs API key (only needed for ElevenLabs voices) |\n\nEdge TTS works without any API key and is recommended for most use cases.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                     my-demo.demorec                         │\n└────────────────────────┬────────────────────────────────────┘\n                         │ parse\n                         ▼\n┌─────────────────────────────────────────────────────────────┐\n│                    Segment Plan                             │\n│   [terminal:0-15s] → [browser:15-45s] → [terminal:45-55s]   │\n└────────────────────────┬────────────────────────────────────┘\n                         │\n         ┌───────────────┼───────────────┐\n         ▼               ▼               ▼\n   ┌───────────┐   ┌───────────┐   ┌───────────┐\n   │ Terminal  │   │  Browser  │   │ Terminal  │\n   │  (xterm)  │   │(Playwright)│  │  (xterm)  │\n   └─────┬─────┘   └─────┬─────┘   └─────┬─────┘\n         │               │               │\n         ▼               ▼               ▼\n    segment_0.mp4   segment_1.mp4   segment_2.mp4\n         │               │               │\n         └───────────────┼───────────────┘\n                         │\n                         ▼\n              ┌─────────────────────┐\n              │  FFmpeg Concat +    │\n              │  TTS Audio Mix      │\n              └──────────┬──────────┘\n                         │\n                         ▼\n                    demo.mp4 (final)\n```\n\n**Key insight:** Both terminal and browser recording use Playwright. Terminal segments render via xterm.js in a headless browser, enabling seamless video concatenation.\n\nFor detailed architecture documentation including terminal size management, persistent sessions, preview verification, and more, see **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)**.\n\n## Examples\n\n### Bug Fix Demo\n\n```tape\nOutput bugfix-demo.mp4\nSet Width 1280\nSet Height 720\n\n# @voice edge:guy\n\n@mode terminal\nSet Theme \"Dracula\"\n\n# @narrate:before \"This demo shows the fix for issue 42.\"\nType \"git checkout fix/issue-42\"\nEnter\nSleep 1s\n\nType \"npm test\"\nEnter\nSleep 3s\n\n# @narrate:after \"All tests pass. The bug is fixed!\"\nSleep 2s\n```\n\n### Full Stack Demo\n\n```tape\nOutput fullstack-demo.mp4\n\n# @voice edge:jenny\n\n@mode terminal\nSet Theme \"GitHub Dark\"\n\n# @narrate:before \"Let's start the backend server.\"\nType \"cd backend \u0026\u0026 npm start\"\nEnter\nSleep 3s\n\n@mode browser\n\n# @narrate:before \"Now let's see the frontend.\"\nNavigate \"http://localhost:3000\"\nSleep 2s\n\n# @narrate:during \"I'll create a new user account.\"\nClick \"a.signup\"\nType \"#email\" \"demo@example.com\"\nType \"#password\" \"SecurePass123\"\nClick \"#submit\"\nSleep 3s\n\n# @narrate:after \"Account created successfully!\"\nSleep 2s\n```\n\n### Multiple Terminal Sessions\n\nThis example demonstrates a server/client workflow with persistent state:\n\n```tape\nOutput multi-terminal-demo.mp4\nSet Width 1280\nSet Height 720\n\n# ─────────────────────────────────────\n# Set up environment in default terminal\n# ─────────────────────────────────────\n@mode terminal\n\nType \"export API_KEY='demo-key-123'\"\nEnter\nType \"cd /tmp \u0026\u0026 mkdir -p myapp \u0026\u0026 cd myapp\"\nEnter\nSleep 500ms\n\n# ─────────────────────────────────────\n# Start server in named terminal\n# ─────────────────────────────────────\n@mode terminal\nname \"server\"\n---\nType \"cd /tmp/myapp\"\nEnter\nType \"echo '\u003ch1\u003eHello World\u003c/h1\u003e' \u003e index.html\"\nEnter\nType \"python3 -m http.server 3000\"\nEnter\nSleep 2s\n\n# ─────────────────────────────────────\n# View in browser (server keeps running!)\n# ─────────────────────────────────────\n@mode browser\nNavigate \"http://localhost:3000\"\nSleep 2s\n\n# ─────────────────────────────────────\n# Test from client terminal\n# ─────────────────────────────────────\n@mode terminal\nname \"client\"\n---\nType \"curl http://localhost:3000/\"\nEnter\nSleep 1s\n\n# ─────────────────────────────────────\n# Check server logs (session preserved)\n# ─────────────────────────────────────\n@mode terminal\nname \"server\"\n---\n# We see the server still running with request logs\nSleep 2s\n\n# ─────────────────────────────────────\n# Original terminal state is intact!\n# ─────────────────────────────────────\n@mode terminal\n\nType \"echo $API_KEY \u0026\u0026 pwd\"\nEnter\n# Shows: demo-key-123 and /tmp/myapp\nSleep 1s\n\n# ─────────────────────────────────────\n# Clean up\n# ─────────────────────────────────────\n@mode terminal\nname \"server\"\n---\nCtrl+C\nSleep 500ms\n```\n\n**Key points demonstrated:**\n- State in the default terminal (env vars, working dir) persists across all mode switches\n- Server in the `\"server\"` session keeps running while you switch to browser and client\n- Each named session is independent—`\"client\"` doesn't share state with `\"server\"`\n- Returning to any terminal reconnects to the same session\n\n### Code Review Demo (Vim Primitives)\n\n```tape\nOutput code-review.mp4\nSet Width 1280\nSet Height 720\n\n# @voice edge:jenny\n\n@mode terminal:vim\nrows 30\ntheme \"Dracula\"\n---\n\n# Open the file using high-level primitives\nOpen \"src/api.py\"\nSleep 0.5s\n# @narrate:after \"Let's review this API client code.\"\nSleep 1s\n\n# Highlight the imports\nHighlight \"4-7\"\nSleep 0.5s\n# @narrate:after \"First, notice the imports for dataclasses and typing.\"\nSleep 1s\n\n# Highlight the main class\nHighlight \"10-25\"\nSleep 0.5s\n# @narrate:after \"Here's our User dataclass with type hints.\"\nSleep 1s\n\n# Highlight error handling\nHighlight \"45-55\"\nSleep 0.5s\n# @narrate:after \"The error handling follows best practices.\"\nSleep 1s\n\n# Exit cleanly\nClose\nSleep 0.5s\n# @narrate:after \"That's a quick tour of the code!\"\nSleep 1s\n```\n\n---\n\n## Prior Art\n\n- [charmbracelet/vhs](https://github.com/charmbracelet/vhs) - Terminal GIF recorder (inspiration)\n- [fnando/demotape](https://github.com/fnando/demotape) - Ruby terminal recorder\n- [Playwright](https://playwright.dev/) - Browser automation\n- [xterm.js](https://xtermjs.org/) - Terminal emulator for the web\n\n## License\n\nMIT\n\n## Support\n\nThis is an OpenHands Sandbox project, meaning that it is a preview of technology and not yet supported for production use. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenhands%2Fdemorec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenhands%2Fdemorec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenhands%2Fdemorec/lists"}