Files
claude-code-skills-reference/cli-demo-generator/references/advanced_patterns.md
daymade efda299a9e feat(cli-demo-generator): deep rewrite with battle-tested VHS patterns
SKILL.md: rewritten following Anthropic best practices
- Concise (233 lines, down from 347)
- Critical VHS parser limitations section (base64 workaround)
- Advanced patterns: self-bootstrap, output filtering, frame verification
- Better description for skill triggering

New files:
- references/advanced_patterns.md: production patterns from dbskill project
- assets/templates/self-bootstrap.tape: self-cleaning demo template

auto_generate_demo.py: new flags
- --bootstrap: hidden setup commands (self-cleaning state)
- --filter: regex pattern to filter noisy output
- --speed: post-processing speed multiplier (gifsicle)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 08:15:16 +08:00

241 lines
5.8 KiB
Markdown

# Advanced VHS Demo Patterns
Battle-tested patterns from production demo recording workflows.
## Contents
- Self-bootstrapping tapes
- Output noise filtering with base64 wrapper
- Frame-level verification
- Post-processing with gifsicle
- Auto-detection and template rendering
- Recording script structure
## Self-Bootstrapping Tapes
A self-bootstrapping tape cleans its own state before recording, so running it twice produces identical output. Three phases:
```tape
# Phase 1: HIDDEN CLEANUP — remove previous state
Hide
Type "my-tool uninstall 2>/dev/null; my-tool reset 2>/dev/null"
Enter
Sleep 3s
# Phase 2: HIDDEN SETUP — create helpers (base64 for special chars)
Type "echo <base64-wrapper> | base64 -d > /tmp/helper.sh && source /tmp/helper.sh"
Enter
Sleep 500ms
# Phase 3: CLEAR + SHOW — wipe buffer before revealing
Type "clear"
Enter
Sleep 500ms
Show
# Phase 4: VISIBLE DEMO — what the viewer sees
Type "my-tool install"
Enter
Sleep 3s
```
**Why `clear` before `Show`:** VHS's `Hide` stops recording frames, but the terminal buffer still accumulates text. Without `clear`, the hidden commands' text appears in the first visible frame.
## Output Noise Filtering with Base64
Many CLI tools produce verbose progress output that clutters demos. The solution: a hidden shell wrapper that filters noise lines.
### Step 1: Create the wrapper function
```bash
# The function you want (can't type directly in VHS due to $/" chars)
my_tool() { command my_tool "$@" 2>&1 | grep -v -E "cache|progress|downloading|timeout"; }
```
### Step 2: Base64 encode it
```bash
echo 'my_tool() { command my_tool "$@" 2>&1 | grep -v -E "cache|progress|downloading|timeout"; }' | base64
# Output: bXlfdG9vbCgpIHsgY29tbWFuZC4uLn0K
```
### Step 3: Use in tape
```tape
Hide
Type "echo bXlfdG9vbCgpIHsgY29tbWFuZC4uLn0K | base64 -d > /tmp/w.sh && source /tmp/w.sh"
Enter
Sleep 500ms
Type "clear"
Enter
Sleep 500ms
Show
# Now `my_tool` calls the wrapper — clean output
Type "my_tool deploy"
Enter
Sleep 5s
```
### When to filter
- Git operations: filter "Cloning", "Refreshing", cache messages
- Package managers: filter download progress, cache hits
- Build tools: filter intermediate compilation steps
- Any command with `SSH not configured`, `timeout: 120s`, etc.
## Frame-Level Verification
After recording, extract and inspect key frames to verify the GIF shows what you expect.
### Extract specific frames
```bash
# Frame at position N (0-indexed)
ffmpeg -i demo.gif -vf "select=eq(n\,100)" -frames:v 1 /tmp/frame_100.png -y 2>/dev/null
# Multiple frames at once
for n in 50 200 400; do
ffmpeg -i demo.gif -vf "select=eq(n\,$n)" -frames:v 1 "/tmp/frame_$n.png" -y 2>/dev/null
done
```
### Check total frame count and duration
```bash
ffmpeg -i demo.gif 2>&1 | grep -E "Duration|fps"
# Duration: 00:00:10.50, ... 25 fps → 262 frames total
```
### What to verify
| Frame | Check |
|-------|-------|
| First (~frame 5) | No leaked hidden commands |
| Mid (~frame N/2) | Key output visible, no noise |
| Final (~frame N-10) | All commands completed, result shown |
### Claude can read frames
Use the Read tool on extracted PNG files — Claude's vision can verify text content in terminal screenshots.
## Post-Processing with gifsicle
Speed up or optimize GIFs after recording, avoiding re-recording.
### Speed control
```bash
# 2x speed — halve frame delay (most common)
gifsicle -d2 input.gif "#0-" > output.gif
# 1.5x speed
gifsicle -d4 input.gif "#0-" > output.gif
# 3x speed
gifsicle -d1 input.gif "#0-" > output.gif
```
### Optimize file size
```bash
# Lossless optimization
gifsicle -O3 input.gif > optimized.gif
# Reduce colors (lossy but smaller)
gifsicle --colors 128 input.gif > smaller.gif
```
### Typical recording script pattern
```bash
# Record at normal speed
vhs demo.tape
# Speed up 2x for final output
cp demo.gif /tmp/demo_raw.gif
gifsicle -d2 /tmp/demo_raw.gif "#0-" > demo.gif
rm /tmp/demo_raw.gif
```
## Auto-Detection and Template Rendering
For demos that need to adapt to the environment (e.g., different repo URLs, detected tools).
### Template placeholders
Use `sed` to replace placeholders before recording:
```tape
# demo.tape (template)
Type "tool marketplace add REPO_PLACEHOLDER"
Enter
```
```bash
# Build script detects the correct repo
REPO=$(detect_repo)
sed "s|REPO_PLACEHOLDER|$REPO|g" demo.tape > /tmp/rendered.tape
vhs /tmp/rendered.tape
```
### Auto-detect pattern (shell function)
```bash
detect_repo() {
local upstream origin
upstream=$(git remote get-url upstream 2>/dev/null | sed 's|.*github.com[:/]||; s|\.git$||') || true
origin=$(git remote get-url origin 2>/dev/null | sed 's|.*github.com[:/]||; s|\.git$||') || true
# Check upstream first (canonical), then origin (fork)
if [[ -n "$upstream" ]] && gh api "repos/$upstream/contents/.target-file" &>/dev/null; then
echo "$upstream"
elif [[ -n "$origin" ]]; then
echo "$origin"
else
echo "fallback/default"
fi
}
```
## Recording Script Structure
A complete recording script follows this pattern:
```bash
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# 1. Check prerequisites
for cmd in vhs gifsicle; do
command -v "$cmd" &>/dev/null || { echo "Missing: $cmd"; exit 1; }
done
# 2. Auto-detect dynamic values
REPO=$(detect_repo)
echo "Using repo: $REPO"
# 3. Render tape template
sed "s|PLACEHOLDER|$REPO|g" "$SCRIPT_DIR/demo.tape" > /tmp/rendered.tape
# 4. Clean previous state
cleanup_state || true
# 5. Record
(cd "$REPO_DIR" && vhs /tmp/rendered.tape)
# 6. Speed up
cp "$REPO_DIR/demo.gif" /tmp/raw.gif
gifsicle -d2 /tmp/raw.gif "#0-" > "$REPO_DIR/demo.gif"
# 7. Clean up
cleanup_state || true
rm -f /tmp/raw.gif /tmp/rendered.tape
# 8. Report
SIZE=$(ls -lh "$REPO_DIR/demo.gif" | awk '{print $5}')
echo "Done: demo.gif ($SIZE)"
```