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…
Skill by TitusKirch
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/tituskirch-vhs-demo
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: devops, writing
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/vhs-demo
What's inside
- Skill name
vhs-demo- Description
- 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, README terminal previews, or terminal captures for docs pages. Do not use for editing or optimising arbitrary existing GIFs.
- License
- MIT
- Metadata
- summary: Creates and maintains reproducible README or docs terminal-demo GIFs from VHS tapes.
- Instructions
- 138 lines in SKILL.md · view on GitHub
Agent Skills spec issues
- allowed-tools: must be a space-separated string
Files (4)
- REFERENCE.md
- SKILL.md
- demo.tape · templates/
- vhs.Dockerfile · templates/
SKILL.md (first lines)
# vhs-demo
Generate a terminal-demo GIF from a [Charm VHS](https://github.com/charmbracelet/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 template builds a headless renderer on a digest-pinned Node/Debian base, installs current Chromium/ffmpeg packages and checksum-verified Bun, builds checksum-pinned VHS source with a patched Go toolchain and network dependency, and builds ttyd against current system libraries. It works in WSL/root environments with no Homebrew, no TTY, no OBS. Docker is the only host dependency; supported targets are `linux/amd64` and `linux/arm64`.
## Destination & naming convention
Resolve the intended **README or docs context before scaffolding**. An optional caller such as `write-docs` passes the target page, context, repo-relative output path, subject/ref and any required language variant; honor that target. Without a target, keep the README default.
- **README demo** → paired tape + GIF under `.github/assets/`.
- **Docs demo** → paired tape + GIF under `docs/_assets/`. This is a non-section directory: no numeric prefix, `index.md` or navigation entry.
- **One demo at the selected destination** → `demo.tape` / `demo.gif`; **multiple demos** → descriptive paired base names, e.g. `quickstart.tape` / `quickstart.gif` or `ci.tape` / `ci.gif`. Honor an explicit output name even for the first demo.
- **Shared media** → reuse an existing artifact when README and docs show the same demo; do not create a second copy solely to embed it elsewhere.
- **Language-dependent demos** → explicitly capture and verify variants when needed, e.g. `docs/_assets/de-DE/quickstart.tape` / `quickstart.gif`. Shared language-neutral media is referenced across locale pages. Translate alt text and captions; a translation sweep never produces captures.
- **The Dockerfile stays generic**: `.github/assets/vhs.Dockerfile`, shared by both destinations — never named after a demo.
Consumer-specific tapes and regenerated GIFs belong in the consumer repo, never in the published skill. The paired tape is committed, but only the GIF is published as docs media; tapes, scripts and Dockerfiles are not image outputs.
## Quick start
1. **Resolve the target** and set repo-relative paths once. README default:
```bash
tape=.github/assets/demo.tape
gif=.github/assets/demo.gif
```
Docs example:
```bash
tape=docs/_assets/quickstart.tape
gif=docs/_assets/quickstart.gif
```
These variables stay set for render and verification commands below; do not use the default README path after selecting a docs target.
2. **Scaffold** `.github/assets/vhs.Dockerfile` from [`templates/vhs.Dockerfile`](templates/vhs.Dockerfile). Reuse it when already present; keep or drop the Bun layer (see below).
3. **Scaffold** the selected tape from [`templates/demo.tape`](templates/demo.tape), set its `Output` to the exact selected `gif` path, and write the choreography for this CLI. The template's `Output` is the README default and must be replaced for docs, named or localized targets.
4. **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`).
5. **Build the image** (once, or after editing the Dockerfile):
```bash
docker build --pull --no-cache -f .github/assets/vhs.Dockerfile -t <repo>-vhs .
```
6. **Render** (after every tape change), from the consumer repo root:
```bash
docker run --rm --cap-drop=ALL --security-opt no-new-privileges:true \
-v "$PWD:/vhs" <repo>-vhs "$tape"
```
7. **Verify** the selected GIF (dimensions, duration ≈ sum of sleeps, file size, captured content) — see [Verification](#verification). A committed tape establishes reproducibility, not freshness; render against the intended CLI/ref and check the result.
8. **Wire the target page's image reference** and commit the tape, shared Dockerfile and regenerated GIF together. Report the source path, verified output path, subject/ref and reference to the caller. The writing skill owns page inclusion and layout.
## Does the CLI need Bun?
The template includes a **version-pinned Bun layer** for CLIs using `opentui`, `bun:ffi`, or `bun …`. The archive is checked against its pinned SHA-256 before installation; the amd64 build uses Bun's baseline binary. A Bun version change must update both architecture checksums together. No downloaded installer script is executed.
- **Bun CLI** → keep the Bun layer in `templates/vhs.Dockerfile`.
- **Node CLI** (plain citty, run with `node …`) → delete the Bun layer; the image already has Node. npm/npx are removed from the renderer; install dependencies and build the CLI on the host before rendering.
## Resolution & render quality
Defaults baked into the tape template:
| Setting | Default (Full HD) | Lighter alternative |
| :-------------- | :---------------- | :------------------ |
| `Set Width` | `1920` | `1280` |
| `Set Height` | `1080` | `720` |
| `Set FontSize` | `20` | `16` |
| `Set Padding` | `16` | `16` |
| `Set Framerate` | `24` | `24` |
> [!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 `Sleep`s. `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` or `docs/_assets/quickstart.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`:
```bash
ffprobe -v error -select_streams v:0 -show_entries stream=width,height \
-of csv=p=0 "$gif"
```
2. **Duration ≈ sum of the _visible_ `Sleep`s** (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:
```bash
ffprobe -v error -show_entries format=duration -of csv=p=0 "$gif"
```
3. **File size** is reasonable for a page asset (rough target: under ~2–3 MB; lighter profile if not). `ls -lh "$gif"`.
4. **(Optional) Eyeball frames** — sample one frame per second to PNGs and look at them:
```bash
ffmpeg -i "$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.
## Embedding in README or docs
Reference the GIF with a plain image tag near the top of the README (after the hero/badges):
```markdown

```
A docs page at `docs/2.guides/1.quickstart.md` references its selected artifact relative to the authored page:
```markdown

```From TitusKirch/skills, MIT.
More from TitusKirch/skills
- 0.594score
write-contributing
TitusKirch
Writes and maintains a repo's `CONTRIBUTING.md` in the house style — scaffolds one where none exists, reconciles an existing guide against the repo it…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
write-docs
TitusKirch
Scaffolds, extends, and reconciles a project's `docs/` tree in the TitusKirch docs format — one stack-agnostic convention shared across all repos, ADRs…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
decide
TitusKirch
Closes the open decisions a run has already named by putting each one in front of the human as a select dialog — concrete options with the recommendation…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
compact-readme
TitusKirch
Compacts an existing, overstuffed README so the landing view reaches the install/run command in ~30 seconds without losing any information — detail moves into…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
write-readme
TitusKirch
Generates project READMEs in the kirchDev house style — centered hero header with emoji + bold tagline, a one-liner code-snippet hook, feature bullets with…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
tituskirch-skills-config
TitusKirch
Creates, reconciles, and drift-checks `.tituskirch-skills.json` — the committed, repo-root config the other TitusKirch skills read to pick forges, trackers…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
validate-skills
TitusKirch
Validates agent skills against the Agent Skills specification — one named skill, or every skill in a repo — and reports what fails and where.
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
atomic-commit
TitusKirch
Commits the current session's working-tree changes as a series of atomic Conventional Commits in the repo's own detected conventions (scopes, types…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
merge-deps
TitusKirch
Triages and merges a repo's open dependency-bot pull requests — on GitLab, merge requests — selected strictly by author so no human's and no other bot's…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
prune-branches
TitusKirch
Reports a repo's stale branches grouped by why they are stale — merged into the integration branch (squash and rebase merges included), upstream gone, a…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
prune-comments
TitusKirch
Finds comments made redundant by the code beneath them — plain restatements, name echoes, banners, signature echoes — and reports them as removal candidates…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
pull-request
TitusKirch
Creates a pull request — on GitLab, a merge request — from the current branch in the repo's own conventions, with an umbrella Conventional-Commits title and a…
SkillAFreshness: A★ 2Claude Code +9
Signals
- Stars
- 2
- Forks
- 0
- Open issues
- 0
- Last commit (this folder)
- Oct 12, 2026 (16 days ago)
- Last release
- Oct 24, 2026 (5 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/TitusKirch/skills
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from TitusKirch
- 0.594score
write-docs
TitusKirch
Scaffolds, extends, and reconciles a project's `docs/` tree in the TitusKirch docs format — one stack-agnostic convention shared across all repos, ADRs…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
decide
TitusKirch
Closes the open decisions a run has already named by putting each one in front of the human as a select dialog — concrete options with the recommendation…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
compact-readme
TitusKirch
Compacts an existing, overstuffed README so the landing view reaches the install/run command in ~30 seconds without losing any information — detail moves into…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
write-readme
TitusKirch
Generates project READMEs in the kirchDev house style — centered hero header with emoji + bold tagline, a one-liner code-snippet hook, feature bullets with…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
tituskirch-skills-config
TitusKirch
Creates, reconciles, and drift-checks `.tituskirch-skills.json` — the committed, repo-root config the other TitusKirch skills read to pick forges, trackers…
SkillAFreshness: A★ 2Claude Code +9 - 0.594score
write-contributing
TitusKirch
Writes and maintains a repo's `CONTRIBUTING.md` in the house style — scaffolds one where none exists, reconciles an existing guide against the repo it…
SkillAFreshness: A★ 2Claude Code +9
Cite this page
vhs-demo skill. Agent Mart. https://agentmart.dev/skills/tituskirch-vhs-demo (last updated 2026-10-24)