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>
This commit is contained in:
240
cli-demo-generator/references/advanced_patterns.md
Normal file
240
cli-demo-generator/references/advanced_patterns.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# 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)"
|
||||
```
|
||||
Reference in New Issue
Block a user