원클릭으로
record-demo
Record a demo GIF from a VHS tape with automatic redaction of sensitive values via OCR
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Record a demo GIF from a VHS tape with automatic redaction of sensitive values via OCR
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | record-demo |
| description | Record a demo GIF from a VHS tape with automatic redaction of sensitive values via OCR |
| user-invocable | true |
| argument-hint | <tape-file-or-description> |
Record a demo GIF using VHS with automatic OCR-based redaction of sensitive values.
~/go/bin/vhs (v0.10.0+)ffmpeg / ffprobetesseract (OCR engine)convert (ImageMagick)make build must succeed.redact-patterns file at project root (gitignored — optional, see format below; falls back to built-in AWS credential patterns).tape file, use it directly..tape file in demo/ using the conventions below.When generating a tape from a description, use these defaults:
# <Description of what the demo shows>
Output demo/<descriptive-name>.gif
Set Shell bash
Set FontSize 18
Set Width 1200
Set Height 600
Set Padding 20
Set Theme "Catppuccin Mocha"
Set TypingSpeed 60ms
Set PlaybackSpeed 1
Env PATH "/home/tim/sca-cli:/snap/bin:/usr/local/bin:/usr/bin:/bin"
# Clean prompt
Hide
Type `export PS1="$ "`
Enter
Type "clear"
Enter
Show
Sleep 500ms
Hide/Show to skip loading delays (API calls, command processing). After an Enter that triggers an API call, insert Hide, then Sleep 12s (generous wait to ensure the call completes), then Show. This makes the demo snappy and avoids blank screen waiting.Sleep generously after Enter for short waits (1-3s for local output).demo/ for examples.Run ./grant status to check authentication state. If not authenticated, invoke the /grant-login skill first.
Run make build to ensure ./grant is current.
~/go/bin/vhs <tape-file>Output directive from the tape file to determine the output GIF path.Use ffprobe to get GIF duration, frame rate, and frame count:
ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate,nb_read_frames,duration -count_frames -of json <gif>
Extract frames densely — every 10th frame (i.e., ~1 per second at 25fps) for thorough coverage:
ffmpeg -i <gif> -vf "select='not(mod(n\,10))'" -vsync vfr /tmp/demo-frames/frame_%04d.png
Dense extraction catches text that only appears briefly or during transitions.
For each frame, run OCR with bounding-box output:
tesseract <frame.png> - tsv
Load redaction patterns from .redact-patterns at the project root. If the file is missing, fall back to these built-in defaults (safe to commit — they contain no proprietary names):
value AWS_ACCESS_KEY_ID
value AWS_SECRET_ACCESS_KEY
value AWS_SESSION_TOKEN
prefix ASIA
prefix AKIA
Log a note that .redact-patterns was not found and only built-in AWS patterns are active.
Search OCR results against loaded patterns.
Visually inspect extracted frames — OCR may miss text on highlighted/colored backgrounds (e.g., survey prompt selection highlights in inverse video). Always open a few frames with the Read tool to spot sensitive text that tesseract missed, and manually add blur regions for those.
Collect bounding boxes: (x, y, w, h, frame_number) for each match.
Merge overlapping or adjacent boxes in the same region.
Compute time ranges from frame numbers: start = frame_num / fps.
For value patterns: size the blur width to cover only the actual text width of the value, NOT all the way to the right edge of the GIF. Extending blur to the right edge causes color tint artifacts in GIF palette quantization.
.redact-patterns File FormatA gitignored file at the project root. Each non-empty, non-comment line defines one redaction rule:
# Lines starting with # are comments
# Format: <mode> <pattern>
#
# Modes:
# word — blur the matched word's bounding box (case-insensitive OCR match)
# value — blur everything to the RIGHT of the match (from delimiter to edge of frame)
# used for key=value or label: value lines
# prefix — blur any word starting with this prefix (4+ trailing chars)
word CompanyName
value AWS_ACCESS_KEY_ID
value AWS_SECRET_ACCESS_KEY
value AWS_SESSION_TOKEN
value Session ID:
prefix ASIA
prefix AKIA
word: Case-insensitive whole-word match. Blurs just the word's bounding box.value: Finds the label in OCR output, then blurs from the delimiter (= or :) to the right edge of the GIF. For AWS_SESSION_TOKEN, also blur continuation lines below until the next export or blank line.prefix: Blurs any OCR word that starts with this prefix and has 4+ additional characters (catches credential strings like ASIA..., AKIA...).The user populates this file with their actual sensitive words. Since it's gitignored, the words never appear in the repo.
Use the crop + boxblur + overlay approach. Write filter graphs to temp files and use -filter_complex_script to avoid shell escaping issues.
Each blur region N (1-indexed) follows this chain pattern:
[0]crop=W:H:X:Y,boxblur=R:P[b1]; [0][b1]overlay=X:Y:enable='between(t,S,E)'[v1]
[0]crop=W:H:X:Y,boxblur=R:P[b2]; [v1][b2]overlay=X:Y:enable='between(t,S,E)'[v2]
...
[0]crop=W:H:X:Y,boxblur=R:P[bN]; [v(N-1)][bN]overlay=X:Y:enable='between(t,S,E)'[vN]
Note: The first overlay uses [0] (not [v0]) as its base. Each subsequent overlay chains from the previous [vN-1].
The chroma plane radius must not exceed min(crop_w, crop_h) / 4. Choose parameters based on region height:
| Region height | Boxblur params | Notes |
|---|---|---|
| h <= 22 | boxblur=6:3:5:3 | Separate luma/chroma radii to satisfy chroma constraint |
| 22 < h <= 26 | boxblur=6:3 | Safe for small regions |
| 26 < h <= 50 | boxblur=10:3 | Medium regions |
| h > 50 | boxblur=20:3 | Large regions |
For credential values that need stronger redaction, increase the iterations (power): e.g., boxblur=6:5 instead of boxblur=6:3.
Write filter graphs to temp files, then run two-pass encoding:
# Pass 1 filter: append palettegen to the last blur output
# Write to /tmp/<name>_pass1.txt:
<blur_chain...>
[vN]palettegen=stats_mode=diff[pal]
ffmpeg -y -i <input> -filter_complex_script /tmp/<name>_pass1.txt -map "[pal]" /tmp/<name>_palette.png
# Pass 2 filter: append paletteuse
# Write to /tmp/<name>_pass2.txt:
<blur_chain...>
[vN][1:v]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle[out]
ffmpeg -y -i <input> -i /tmp/<name>_palette.png -filter_complex_script /tmp/<name>_pass2.txt -map "[out]" <output>
Overwrite the original GIF with the redacted output.
Read tool so the user can visually confirm redaction.--psm mode, or visually inspect frames for text on colored/highlighted backgrounds that OCR cannot read.AWS_SESSION_TOKEN values that wrap lines, check multiple OCR lines after the label match.~/go/bin/vhs is on PATH and the tape syntax is valid.tesseract <frame.png> - tsv manually on a single frame to debug OCR accuracy.boxblur=6:5 instead of boxblur=6:3).drawbox filled with the terminal background color (e.g., 0x1e1e2e for Catppuccin Mocha) instead of blur. However, blur is preferred for redaction aesthetics.