agentsclimarketplace

Vhs demo

Skill TitusKirch/skills/skills/docs/vhs-demo

Creates and maintains a scripted, reproducible terminal-demo GIF for a CLI repo from a Charm VHS tape rendered headless via Docker — the .tape is the committed source of truth, the GIF a regenerated artifact. Use when the user wants to add, record, regenerate or tweak a terminal demo GIF for a kirchDev CLI repo (citty/Bun tools like envprism, forgemap), or asks about VHS tapes, demo.gif, or README terminal previews. Do not use for editing or optimising arbitrary existing GIFs.From its SKILL.md

Install
npx -y skills add TitusKirch/skills --skill vhs-demo

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.7 KB, ~1.9k tokens by cl100k_base, as published. Nobody here has run it

vhs-demo

Generate a terminal-demo GIF from a Charm VHS tape, rendered headless in Docker. The tape is committed; the GIF is a build artifact regenerated from it.

[!IMPORTANT] The tape is the source of truth. Always edit the .tape and re-render. Never hand-edit, re-time, or recompress the GIF — it cannot be reproduced and the next render will overwrite it.

Why Docker

VHS normally wants a real TTY (and on macOS, Homebrew + sometimes a screen recorder). The official ghcr.io/charmbracelet/vhs image renders fully headless, so it works in WSL/root environments with no Homebrew, no TTY, no OBS. Everything below assumes Docker is the only host dependency.

Naming convention (enforce exactly)

  • Single demo in the repo → files are demo.tape and demo.gif in .github/assets/. The README references exactly that one .github/assets/demo.gif.
  • Multiple demos → one descriptive name per demo, and the tape + its GIF always share the base name: quickstart.tape/quickstart.gif, ci.tape/ci.gif, etc.
  • The Dockerfile stays generic: always .github/assets/vhs.Dockerfile — never name it after a demo.

Quick start

  1. Scaffold .github/assets/vhs.Dockerfile from templates/vhs.Dockerfile. Keep or drop the Bun layer (see below).
  2. Scaffold the tape (demo.tape or <name>.tape) from templates/demo.tape and write the choreography for this CLI.
  3. Build the CLI so the tape runs the built artifact, not the sources: run the repo's build (e.g. pnpm build / bun run build).
  4. Build the image (once, or after editing the Dockerfile):
    docker build -f .github/assets/vhs.Dockerfile -t <repo>-vhs .
    
  5. Render (after every tape change):
    docker run --rm -v "$PWD:/vhs" <repo>-vhs .github/assets/demo.tape
    
  6. Verify the GIF (dimensions, duration ≈ sum of sleeps, file size) — see Verification.
  7. Wire the README reference and commit the tape, the Dockerfile, and the regenerated GIF together.

Does the CLI need Bun?

The official VHS image has no Bun. If the CLI is a Bun tool (uses opentui, bun:ffi, or is started with bun …), the render will fail with "bun: command not found". Fix = the derived image in the template that installs Bun (curl | bash from bun.sh, with /root/.bun/bin on PATH).

  • Bun CLI → keep the Bun layer in templates/vhs.Dockerfile.
  • Node CLI (plain citty, run with node …) → delete the Bun layer; the base image already has Node.

Resolution & render quality

Defaults baked into the tape template:

SettingDefault (Full HD)Lighter alternative
Set Width19201280
Set Height1080720
Set FontSize2016
Set Padding1616
Set Framerate2424

[!IMPORTANT] Cap the framerate. At 1080p a busy TUI usually can't repaint fast enough to sustain VHS's default 50fps capture. VHS then drops frames, the GIF plays too fast / janky, and the measured duration is shorter than the sum of your Sleeps. Set Framerate 24 caps the capture rate to something the TUI can actually hold. If verification still shows the duration running short, lower the framerate further (20, then 16) and re-render.

Use the lighter 1280×720 @ FontSize 16 profile when the GIF needs to be smaller/cheaper to load and Full HD detail isn't essential.

Pitfalls — do's & don'ts

  • Output path MUST be relative (e.g. .github/assets/demo.gif). An absolute path like /vhs/... is rejected by the VHS parser.
  • Build the CLI first — run the repo's build (e.g. pnpm build / bun run build). The tape launches the built artifact (dist/…), not the source.
  • Bun CLIs need the Bun image layer — see above.
  • Work on a throwaway copy of fixtures if the demo edits/writes files. Copy them to /tmp inside the hidden Hide … Show block so the working tree stays clean (e.g. cp -r /vhs/examples /tmp/demo).
  • Clear pre-filled edit fields before typing. Popovers/inputs seeded with the current value need emptying first: Backspace@25ms 40. Backspace on an empty field is a no-op, so over-counting is safe.
  • Don't end the demo with a quit key. Quitting makes VHS film the shell teardown (scrollback, the setup command reappearing). Instead end on a Sleep holding the frame you want — VHS kills the process at tape end, giving a clean final frame.
  • Hide setup commands. Wrap cp, the CLI launch, and any other plumbing in a Hide … Show block so only the demo itself is captured.

Verification

After each render, confirm the GIF actually matches the tape. Requires ffprobe/ffmpeg (run them on the host, or via the image: docker run --rm -v "$PWD:/vhs" --entrypoint ffprobe <repo>-vhs …).

  1. Dimensions match the tape's Set Width/Set Height:
    ffprobe -v error -select_streams v:0 -show_entries stream=width,height \
      -of csv=p=0 .github/assets/demo.gif
    
  2. Duration ≈ sum of the visible Sleeps (plus visible typing time). Count only actions that are actually captured — exclude anything inside a Hide … Show block, since VHS records nothing there, so those sleeps/typing never reach the GIF. If it's meaningfully shorter than that, frames were dropped → lower Set Framerate and re-render:
    ffprobe -v error -show_entries format=duration -of csv=p=0 .github/assets/demo.gif
    
  3. File size is reasonable for a README asset (rough target: under ~2–3 MB; lighter profile if not). ls -lh .github/assets/demo.gif.
  4. (Optional) Eyeball frames — sample one frame per second to PNGs and look at them:
    ffmpeg -i .github/assets/demo.gif -vf fps=1 /tmp/demo-frames/f%03d.png
    
    Check the first frame (no leftover setup), the final frame (the intended end state), and any edit step.

README wiring

Reference the GIF with a plain image tag near the top of the README (after the hero/badges):

![<repo> demo](.github/assets/demo.gif)

For multiple demos, embed each under the relevant section with its descriptive name. If the README is being (re)written, the write-readme skill owns layout — this skill only owns the asset and its reference.

Steady-state loop (after the first run)

Quick start covers the first run; afterwards only the cadence differs:

  • Image — rebuild only when the Dockerfile changes.
  • Every demo change — run the repo's build (e.g. pnpm build / bun run build) → re-render → verify.
  • Commit the tape, Dockerfile, and regenerated GIF together — the tape is the source of truth.

Reference

What ships with it: 3 files

7.0 KB alongside SKILL.md

templates/

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.