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:
@@ -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`.
|
||||
@@ -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`.
|
||||
@@ -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
|
||||
|
||||

|
||||

|
||||
```
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user