feat(ce-demo-reel): add demo reel skill with Python capture pipeline (#541)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trevin Chow
2026-04-09 21:29:51 -07:00
committed by GitHub
parent f3cc7545e5
commit b979143ad0
18 changed files with 1796 additions and 751 deletions

View File

@@ -0,0 +1,105 @@
# Tier: Browser Reel
Capture 3-5 browser screenshots at key UI states and stitch into an animated GIF.
**Best for:** Web apps, desktop apps accessible via localhost or CDP.
**Output:** GIF (PNG screenshots stitched via ffmpeg two-pass palette)
**Label:** "Demo"
**Required tools:** agent-browser, ffmpeg
## Step 1: Connect to the Application
**For web apps** -- verify the dev server is accessible:
- Read `package.json` `scripts` for `dev`, `start`, `serve` commands
- Check `Procfile`, `Procfile.dev`, or `bin/dev` if they exist
- Check `Gemfile` for Rails (`bin/rails server`) or Sinatra
- Check for running processes on common ports (3000, 5000, 8080)
If the server is not running, tell the user what start command was detected and ask them to start it. Do not start it automatically (it may require environment variables, database setup, etc.).
If the server cannot be reached after the user confirms it should be running, fall back to static screenshots tier.
Once accessible, note the base URL (e.g., `http://localhost:3000`).
**For Electron/desktop apps** -- connect via Chrome DevTools Protocol (CDP):
1. Check if the app is already running with CDP enabled by probing common ports:
```bash
curl -s http://localhost:9222/json/version
```
If that returns a JSON response, the app is ready -- connect agent-browser to it:
```bash
agent-browser connect 9222
```
2. If not running, the app needs to be launched with `--remote-debugging-port`. Detect the entry point from `package.json` (look for the `main` field or `electron` in scripts), then ask the user to launch it with:
```
your-electron-app --remote-debugging-port=9222
```
If port 9222 is busy, try 9223-9230.
3. Poll until CDP is ready (timeout after 30 seconds):
```bash
curl -s http://localhost:9222/json/version
```
4. Connect agent-browser:
```bash
agent-browser connect 9222
```
**CDP advantages:** Screenshots come from the renderer's frame buffer, not macOS screen capture -- no Accessibility or Screen Recording permissions needed.
**If CDP connection fails:** Fall back to static screenshots tier. Tell the user: "Could not connect to the app via CDP. Falling back to static screenshots."
## Step 2: Capture Screenshots
Navigate to the relevant pages and capture 3-5 screenshots at key UI states:
1. **Initial/empty state** -- Before the feature is used
2. **Navigation** -- How the user reaches the feature (if not the landing page)
3. **Feature in action** -- The hero shot showing the feature working
4. **Result state** -- After interaction (data present, items created, success message)
5. **Detail view** (optional) -- Expanded item, settings panel, modal
For each screenshot, write to the concrete `RUN_DIR` created by the parent skill:
```bash
agent-browser open [URL]
```
```bash
agent-browser wait 2000
```
```bash
agent-browser screenshot [RUN_DIR]/frame-01-initial.png
```
**Capture tips:**
- Use URL navigation (`agent-browser open URL`) rather than clicking SPA elements (clicks often fail on React/Vue/Svelte SPAs)
- Wait 2-3 seconds after navigation for the page to settle
- Capture the full viewport (sidebar, header give reviewers context)
## Step 3: Stitch into GIF
Use the capture pipeline script to normalize frame dimensions, stitch with two-pass palette, and auto-reduce if over 10 MB:
```bash
python3 scripts/capture-demo.py stitch [RUN_DIR]/demo.gif [RUN_DIR]/frame-*.png
```
The script handles dimension normalization (via ffprobe + ffmpeg padding), concat demuxer stitching, palette generation, and automatic frame reduction if the GIF exceeds GitHub's 10 MB inline limit. Default is 3 seconds per frame. To adjust:
```bash
python3 scripts/capture-demo.py stitch --duration 2.0 [RUN_DIR]/demo.gif [RUN_DIR]/frame-*.png
```
**If stitching fails:** Fall back to static screenshots tier using the individual PNGs already captured. If no PNGs were captured, report the failure.
## Step 4: Cleanup
After successful GIF creation, remove individual PNG frames. Keep only the final GIF for upload.
Proceed to `references/upload-and-approval.md`.

View File

@@ -0,0 +1,61 @@
# Tier: Screenshot Reel
Render styled terminal frames from text and stitch into an animated GIF. Each frame shows one step of a CLI demo (command + output).
**Best for:** CLI tools shown as discrete steps (command -> output -> next command -> output). Also useful when VHS breaks on quoting or special characters.
**Output:** GIF (silicon PNGs stitched via ffmpeg)
**Label:** "Demo"
**Required tools:** silicon, ffmpeg
## Step 1: Write Demo Content
Create a text file with `---` delimiters between frames. Each frame shows the terminal state for one step:
Write to `[RUN_DIR]/demo-steps.txt`:
```
$ your-cli-command --flag value
Output line 1
Output line 2
Success: feature works correctly
---
$ your-cli-command --another-flag
Different output showing another aspect
Result: 42 items processed
---
$ your-cli-command --verify
All checks passed
```
**Tips:**
- Include the `$` prompt to show what the user types
- Keep each frame under ~80 characters wide for readability
- 3-5 frames is ideal -- enough to tell the story, not so many the GIF is huge
- Strip unicode characters that silicon's default font can't render (checkmarks, fancy arrows)
## Step 2: Split into Frame Files
Split the demo content on `---` lines into separate text files, one per frame:
- `[RUN_DIR]/frame-001.txt`
- `[RUN_DIR]/frame-002.txt`
- `[RUN_DIR]/frame-003.txt`
- etc.
## Step 3: Render and Stitch
Use the capture pipeline script to render each text frame through silicon and stitch into an animated GIF in a single call:
```bash
python3 scripts/capture-demo.py screenshot-reel --output [RUN_DIR]/demo.gif --duration 2.5 --text [RUN_DIR]/frame-001.txt [RUN_DIR]/frame-002.txt [RUN_DIR]/frame-003.txt
```
The script handles silicon rendering, dimension normalization, two-pass palette generation, and automatic frame reduction if the GIF exceeds limits. Default duration is 2.5 seconds per frame (faster than browser reels since terminal frames are quicker to read).
**If the script fails** (silicon rendering error, stitching error, empty output): fall back to static screenshots tier. Include the raw terminal output as a code block in the PR description instead. Label as "Terminal output", not "Screenshots".
## Step 4: Cleanup
Remove individual PNGs and text files. Keep only the final GIF for upload.
Proceed to `references/upload-and-approval.md`.

View File

@@ -0,0 +1,55 @@
# Tier: Static Screenshots
Capture individual PNG screenshots. No animation, no stitching.
**Best for:** Fallback when other tools are unavailable, library demos, or features where animation doesn't add value.
**Output:** PNG files
**Label:** "Screenshots"
**Required tools:** Varies (agent-browser for web, silicon for CLI, or native screenshot)
## Capture by Project Type
### Web app or desktop app (agent-browser available)
```bash
agent-browser open [URL]
```
```bash
agent-browser wait 2000
```
```bash
agent-browser screenshot [RUN_DIR]/screenshot-01.png
```
Capture 1-3 screenshots: before state, feature in action, result state.
### CLI tool (silicon available)
Run the command, capture its output to a text file, then render with silicon:
```bash
silicon [RUN_DIR]/output.txt -o [RUN_DIR]/screenshot-01.png --theme Dracula -l bash --pad-horiz 20 --pad-vert 20
```
### CLI tool (no silicon)
Run the command and capture the raw terminal output. Include the output as a code block in the PR description instead of an image. Label it as "Terminal output", never "Screenshot".
### Library
Run example code that exercises the new API. Capture the output as above (silicon if available, code block if not).
## Upload
Each PNG is uploaded individually. Proceed to `references/upload-and-approval.md` for each file, or upload all and present them together for approval.
For multiple screenshots, the markdown embed uses multiple image lines:
```markdown
## Screenshots
![Before](url-1)
![After](url-2)
```

View File

@@ -0,0 +1,88 @@
# Tier: Terminal Recording
Record a terminal session using VHS (charmbracelet/vhs) to produce a GIF demo.
**Best for:** CLI tools, scripts, command-line features with interaction or motion (typing, streaming output, progressive rendering).
**Output:** GIF (direct from VHS)
**Label:** "Demo"
**Required tools:** vhs
## Step 1: Plan the Recording
Before generating a .tape file, determine:
- **What command(s) to run** -- The actual product command, not test commands. "I ran npm test" is test evidence, not a demo.
- **Expected output** -- What the terminal should show when the command succeeds.
- **Terminal dimensions** -- Wide enough for the longest output line, tall enough to avoid scrolling.
- **Timing** -- Target 5-10 seconds total. Enough sleep after each command for output to render.
## Step 2: Generate .tape File
Write a VHS tape file to `[RUN_DIR]/demo.tape`:
```tape
Output [RUN_DIR]/demo.gif
Set FontSize 16
Set Width 800
Set Height 500
Set Theme "Catppuccin Mocha"
Set TypingSpeed 40ms
# Hide boring setup
Hide
Type "cd /path/to/project"
Enter
Sleep 500ms
Show
# The demo
Type "your-cli-command --flag value"
Sleep 500ms
Enter
Sleep 3s
# Let viewer read the output
Sleep 2s
```
**Key .tape directives:**
- `Output [path]` -- Where to write the GIF (must be first line)
- `Set FontSize [14-18]` -- Larger for readability
- `Set Width/Height [pixels]` -- Match content needs
- `Set Theme [name]` -- "Catppuccin Mocha" or "Dracula" are readable defaults
- `Set TypingSpeed [ms]` -- 30-50ms feels natural
- `Hide`/`Show` -- Skip boring setup (cd, source, npm install)
- `Type [text]` -- Types characters (does not execute)
- `Enter` -- Presses enter (executes the typed command)
- `Sleep [duration]` -- Wait for output to render
**Avoid:**
- Non-deterministic output (random IDs, timestamps that change between runs)
- Commands that require interactive input (prompts, password entry)
- Very long output that scrolls off screen
## Step 3: Run VHS
Use the capture pipeline script to execute the tape file and validate output:
```bash
python3 scripts/capture-demo.py terminal-recording --output [RUN_DIR]/demo.gif --tape [RUN_DIR]/demo.tape
```
The script runs VHS, validates the output exists, and reports the file size. If the GIF exceeds 10 MB, reduce by adjusting the .tape: smaller terminal dimensions (`Set Width/Height`), shorter recording (fewer sleeps), or lower font size. Re-run.
## Step 4: Quality Check
Read the generated GIF to verify:
1. Commands are visible and readable
2. Output renders completely (not cut off)
3. The feature being demonstrated is clearly shown
4. No secrets, credentials, or sensitive paths are visible
If quality is poor, revise the .tape file and re-record.
**If VHS fails** (crashes, produces empty GIF, or the command being demonstrated fails): fall back to the screenshot reel tier. Write the same commands and expected output as text frames and stitch via silicon + ffmpeg. If silicon is also unavailable, fall back to static screenshots.
Proceed to `references/upload-and-approval.md`.

View File

@@ -0,0 +1,40 @@
# Upload and Approval
Get user approval for the local artifact, upload evidence to a public URL, and generate markdown for PR inclusion.
## Step 1: Local Approval Gate
Before uploading anywhere public, present the local artifact path to the user for approval. Use the platform's blocking question tool (`AskUserQuestion` in Claude Code, `request_user_input` in Codex, `ask_user` in Gemini).
**Question:** "Evidence captured at [RUN_DIR]/[artifact]. Review it locally and decide:"
**Options:**
1. **Looks good, upload for PR** -- proceed to upload
2. **Not good enough, try again** -- return to the tier execution step and re-capture
3. **Skip evidence for this PR** -- set evidence to null and proceed
If the question tool is unavailable (headless/background mode), present the numbered options and wait for the user's reply before proceeding.
## Step 2: Upload to catbox.moe
After the user approves the local artifact, upload the evidence file (GIF or PNG) using the capture pipeline script. Set `ARTIFACT_PATH` to the approved GIF or PNG path:
```bash
python3 scripts/capture-demo.py upload [ARTIFACT_PATH]
```
The script uploads to catbox.moe, validates the response starts with `https://`, and retries once on failure. The last line of output is the public URL (e.g., `https://files.catbox.moe/abc123.gif`).
**If upload fails** after retry, report the failure and the local artifact path. Do not commit evidence files to the repo — they are ephemeral artifacts, not source material. Tell the user: "Upload failed. Local artifact preserved at [ARTIFACT_PATH]. You can upload it manually or retry later."
For multiple files (static screenshots tier), upload each file separately.
## Step 3: Return Output
Return the structured output defined in the SKILL.md Output section: `Tier`, `Description`, and `URL`. The caller formats the evidence into the PR description. ce-demo-reel does not generate markdown.
## Step 4: Cleanup
Remove the `[RUN_DIR]` scratch directory and all temporary files. Preserve nothing -- the evidence lives at the public URL now.
If the upload failed and the user has not yet manually uploaded, preserve `[RUN_DIR]` so the artifact is still accessible.