prove-work
Prove Work
Generate an animated GIF showing the result of work done during a session and embed it in a GitHub PR.
Quick Start
/prove-work http://localhost:3000/settings --pr 42
This records a browser interaction with localhost:3000/settings, converts the recording to a GIF, uploads it to GitHub, and embeds it in PR #42's ## Demo section.
Without arguments, the skill infers the URL from the git diff and auto-detects the PR:
/prove-work
Usage
/prove-work [url] [--pr NUMBER] [--no-upload]
url-- Override the target URL (default: inferred from context)--pr NUMBER-- PR number to embed the GIF into (default: auto-detected)--no-upload-- Save GIF locally without uploading to GitHub
Workflow
Step 1: Check Prerequisites
Run these checks before proceeding. Stop and report if any fail.
# Check Python playwright
python3 -c "import playwright" 2>/dev/null || pip install playwright
# Check playwright browsers
python3 -c "from playwright.sync_api import sync_playwright; p=sync_playwright().start(); p.chromium.launch(headless=True).close(); p.stop()" 2>/dev/null || python3 -m playwright install chromium
# Check pyav and pillow (auto-installed by video_to_gif.py on first run)
python3 -c "import av; from PIL import Image" 2>/dev/null || echo "Dependencies will be auto-installed on first conversion."
# Check gifsicle (optional but recommended)
which gifsicle >/dev/null 2>&1 || echo "WARNING: gifsicle not found. Install with: brew install gifsicle"
Step 2: Analyze Context
Determine what to demonstrate. Read the recent git diff to identify changed files:
git diff --name-only HEAD~1..HEAD 2>/dev/null || git diff --name-only --cached
Decision tree:
- User provided a URL -> Use that URL directly
- Changed files include frontend code (
.tsx,.jsx,.vue,.html,.css, templates) -> Infer the route/page affected, construct localhost URL - Changed files are backend only (
.py,.rb,.go, API routes with no views) -> Tell the user: "This change has no visual component. Skipping proof-of-work." and exit - Changed files are config/infra -> Same as above, exit with message
- Cannot determine URL -> Ask the user for the URL
When inferring the URL, look for:
- Route definitions in the changed files
- Component/page names that map to routes
- Test files that reference specific URLs
Default base URL: http://localhost:3000 (override if project uses a different port).
Verify the URL is reachable before recording:
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/ROUTE
If the server is not running (connection refused), tell the user:
"Dev server is not running on localhost:3000. Start it and run /prove-work again."
Step 3: Generate Interaction Script
Write a Python Playwright script to /tmp/prove-work/interact.py that demonstrates the feature.
Read references/recording-patterns.md for the script template, interaction patterns, and timing guidelines. Use the template from that file as the starting point -- fill in the interaction section based on context analysis.
Step 4: Execute Recording
mkdir -p /tmp/prove-work
# Clean old recordings
rm -f /tmp/prove-work/*.webm /tmp/prove-work/*.gif
# Run the interaction script
python3 /tmp/prove-work/interact.py
If the script fails, read the error output. Common issues:
- Selector not found -> Take a screenshot first to inspect the page, then adjust selectors
- Timeout -> Increase
wait_for_load_statetimeout or check if the server is responding - Navigation error -> Verify the URL is correct
Step 5: Convert to GIF
Run the conversion script:
SKILL_DIR="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/skills/prove-work}"
python3 "$SKILL_DIR/scripts/video_to_gif.py" \
--input /tmp/prove-work/ \
--output /tmp/prove-work/demo.gif \
--max-size-mb 10
The script:
- Finds the most recent .webm in the input directory
- Decodes frames via pyav at 10fps
- Quantizes and assembles into GIF via Pillow
- Optimizes with gifsicle (if available)
- Progressively reduces quality if over 10MB
Step 6: Upload and Embed
If --no-upload was specified: Report the local path and stop.
Auto-detect PR number (if not provided):
gh pr view --json number --jq .number 2>/dev/null
If a PR exists, upload and embed:
SKILL_DIR="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/skills/prove-work}"
python3 "$SKILL_DIR/scripts/video_to_gif.py" \
--input /tmp/prove-work/ \
--output /tmp/prove-work/demo.gif \
--max-size-mb 10 \
--upload \
--pr PR_NUMBER
If no PR exists, save locally and report:
GIF saved to: /tmp/prove-work/demo.gif
No open PR found for this branch. Run /prove-work --pr NUMBER after creating a PR, or use --no-upload to keep the GIF local.
Step 7: Report Result
After completion, report:
Proof of work recorded!
GIF: /tmp/prove-work/demo.gif (X.X MB)
URL: https://github.com/owner/repo/releases/download/media-assets/demo-pr42-20260224-143022.gif
PR: #42 -- ## Demo section updated
Recorded: [brief description of what was demonstrated]
Examples
Example 1: New settings page
Input: /prove-work
Diff: app/pages/settings.tsx, app/api/settings.ts
Output:
Proof of work recorded!
GIF: /tmp/prove-work/demo.gif (1.8 MB)
URL: https://github.com/acme/app/releases/download/media-assets/demo-pr12-20260224-091500.gif
PR: #12 -- ## Demo section updated
Recorded: Navigated to /settings, filled display name, clicked Save, showed success toast
Example 2: Explicit URL with PR number
Input: /prove-work http://localhost:5173/dashboard --pr 27
Output:
Proof of work recorded!
GIF: /tmp/prove-work/demo.gif (2.4 MB)
URL: https://github.com/acme/app/releases/download/media-assets/demo-pr27-20260224-143000.gif
PR: #27 -- ## Demo section updated
Recorded: Navigated to /dashboard, scrolled through widgets, toggled dark mode
Example 3: Backend-only change
Input: /prove-work
Diff: app/services/billing.py, tests/test_billing.py
Output: This change has no visual component. Skipping proof-of-work.
Example 4: Local only
Input: /prove-work --no-upload
Output:
GIF saved to: /tmp/prove-work/demo.gif (1.1 MB)
Error Handling
| Error | Action |
|---|---|
| Dev server not running | Report the error, suggest starting the server |
| Playwright not installed | Auto-install via pip + playwright install chromium |
| No .webm produced | Check interact.py output, report the error |
| GIF exceeds 10MB after all reductions | Report the size, suggest a shorter recording |
| gh CLI not authenticated | Report the error, suggest gh auth login |
| No visual changes detected | Tell the user, exit cleanly |
Reference Files
- references/recording-patterns.md -- What to record for each change type, interaction patterns, timing, script template