Reproducible screenshot capture for docs — drive a web app or CLI from a declarative shot list and capture polished feature screenshots.
Project description
shotlist
Screenshots for your docs — as code. One committed shot list captures your web pages, your real terminal windows, and stateful CLI sessions — and regenerates them all with a single command.
Contents
- The problem
- Quickstart
- One shot list, four kinds of shot
- Use cases
- Proof reports & pipelines
- Why shotlist, and not the others
- How it works
- Use with Claude
- Commands
- Develop
The problem
Documenting a feature means launching the app, clicking to the right state, screenshotting, naming the file, and embedding it — every time the UI changes. The screenshots drift out of date the moment you ship, and nobody notices until they're embarrassingly wrong.
shotlist makes them reproducible: describe how to start your app and what
to shoot once, in a committed .shotlist.yaml, then regenerate the whole set on
demand — locally or in CI. Same config + same app state → same screenshots.
Quickstart
pip install shotlist # installs the `shotlist` command
playwright install chromium # one-time browser download
shotlist init # writes a starter .shotlist.yaml
shotlist run # boots your app, captures every shot, tears it all down
One shot list, four kinds of shot
output:
dir: docs/screenshots
readme: README.md # optional: splice <img> snippets straight into the README
app: # optional — omit for static sites or pure-CLI shots
command: "npm run dev"
ready: { url: http://localhost:5173, timeout: 30 } # never shoot a half-booted app
shots:
- { name: dashboard, kind: web, url: http://localhost:5173/dashboard, full_page: true, alt: "Dashboard" }
- { name: cli-help, kind: cli, command: "mytool --help", alt: "Top-level help" }
| Kind | Captures | How |
|---|---|---|
web |
a browser page — with optional click/fill/wait steps first | Playwright / Chromium |
cli · native (macOS default) |
a real screenshot of your Terminal.app window — your font, your theme | AppleScript + screencapture |
cli · rendered (any OS, CI-safe) |
the command's output drawn as a styled terminal card | PTY → ANSI→HTML → Chromium |
session |
a stateful, multi-command flow in one persistent terminal — one shot per step | one Terminal window, captured after each step |
A session is how you screenshot a flow whose later steps depend on earlier ones —
the shell state (cwd, env, background processes) carries across. Background a
long-running process with & and a small wait_ms, keep capturing, and the
session tears it down on close.
Use cases
shotlist fits anywhere a screenshot would otherwise go stale:
- README & docs screenshots — the core: regenerate the whole set on every UI change.
- Test-evidence / proof — capture a feature flow step by step (a
session) and share the generatedindex.htmlas proof it works. - CI drift-checking —
shotlist checkfails the build when a screenshot changes unexpectedly (with a visual--diff). - Blog posts & tutorials — polished web and CLI shots from one config.
- Onboarding & demo galleries — versioned sets you keep across releases.
- Long-running processes — background a dev server with
&+wait_msand shoot it live.
Each one has a complete, copy-paste .shotlist.yaml in the recipes cookbook,
docs/recipes.md.
Proof reports & pipelines
Every shotlist run also writes, next to the PNGs:
index.html— a self-contained gallery you can open and share as a proof report;manifest.json— a machine-readable record of the run (a pipeline artifact).
Attach manifest.json to a CI job, or open index.html as test-evidence. Set
output.title to relabel the gallery heading, and output.evidence to also
splice a captioned Markdown test-evidence doc — its own file, distinct from
output.dir (where the PNGs land). Turn the report off with --no-report
(or output.report: false).
Catch drift before your users do
Gate CI with shotlist check — it re-captures and fails when a screenshot
drifts from the committed baseline, telling you exactly how much moved:
Drift comes with receipts. --diff DIR renders a baseline·current·diff 3-up per
changed shot, plus a check-report.html that lists every shot with a status
badge — open it locally or grab it from the CI artifact the bundled GitHub
Action uploads (along with a step summary on the run page):
Bless intended changes with shotlist check --update (or --update --only NAME
for one shot), set check.max_diff_pixel_ratio to tolerate sub-pixel jitter, and
script against check --json. The whole loop, end to end:
Details in docs/pipeline.md.
Why shotlist, and not the others
The pieces exist in isolation; shotlist is the one tool that does all of it under
a single committed config.
| web pages | real terminal | CLI sessions | README auto-embed | reproducible / CI | |
|---|---|---|---|---|---|
| shotlist | ✅ | ✅ | ✅ | ✅ | ✅ |
| shot-scraper | ✅ | ❌ | ❌ | ❌ | ✅ |
| freeze / carbon | ❌ | synthetic | ❌ | ❌ | ✅ |
| Percy / Chromatic | ✅ | ❌ | ❌ | ❌ | ✅ (cloud, paid) |
| doing it by hand | 😖 | 😖 | 😖 | ❌ | ❌ |
No cloud, no paid services, no special OS permissions for web/rendered shots. (Native Terminal capture needs macOS Screen-Recording permission; everything else needs nothing.)
How it works
One deterministic engine: load and validate the shot list, boot your app and wait until it's actually ready, then route every shot to the right backend — and tear everything down afterwards, even on a crash:
The clever part is what isn't here: no AI runs at capture time. Claude's only
job is to author the .shotlist.yaml once by reading your repo; after that the
engine is a plain, deterministic program — fast, free, and re-runnable in CI with
no model in the loop.
Want the full picture? docs/how-it-works.md walks
every stage with flow diagrams — the run pipeline, how shots route to backends,
what one run does step by step, the check drift loop, and the determinism
layers that make the same config produce the same pixels. The design rationale
lives in docs/design.md.
Robust by design. The readiness probe (HTTP / TCP port / log line) means you never screenshot a half-booted app, and the app is launched in its own process group and torn down — even on a crash or Ctrl-C — so a shotlist run never leaves an orphaned dev server behind.
Deterministic by default. Web shots can mask flaky regions (mask: [selector, ...]) and always capture with CSS animations disabled; CLI shots can
scrub non-deterministic text (durations, timestamps, PIDs) with a regex before
rendering; and rendered CLI cards embed JetBrains Mono. Baselines now match
byte-for-byte across macOS and Linux CI, not just on the machine that made them.
shotlist, captured by shotlist
This repo dogfoods itself: the shots below are produced by running shotlist run
on its own .shotlist.yaml and spliced in automatically.
The shotlist CLI
Run options
Use with Claude
shotlist ships an optional Claude integration in integrations/claude/:
- a
/shotlistskill that inspects your repo (routes,--help, README), writes the.shotlist.yamlfor you, and runs it; - an optional auto-snapshot hook that drops a raw snapshot when a dev server
starts (the honest "dumb snapshot"; the curated set always comes from
shotlist run).
Commands
| Command | What it does |
|---|---|
shotlist init |
Scaffold a starter .shotlist.yaml |
shotlist validate |
Check the shot list is well-formed |
shotlist run |
Capture every shot and write outputs |
shotlist run --only dashboard |
Capture a single shot by name |
shotlist run --version v2 |
Write into a versioned subfolder |
shotlist check |
Fail if a screenshot drifted from the committed baseline |
shotlist check --update |
Re-shoot and accept the current screenshots as the baseline |
shotlist check --diff DIR |
Also render baseline·current·diff images for changed shots |
shotlist check --json |
Emit the drift report as JSON on stdout (human output moves to stderr) |
shotlist check --update --only NAME |
Re-bless just one shot in place (repeatable) |
Develop
git clone https://github.com/varmabudharaju/shotlist && cd shotlist
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
playwright install chromium
pytest # the suite is fully offline
CI runs ruff, mypy, and pytest — with an 85% coverage gate — on Ubuntu (Python
3.11, 3.12) and macOS (Python 3.12), so native Terminal capture stays covered
too. A separate verify-action workflow dogfoods the bundled GitHub Action
on every PR two ways: verify-release smoke-tests the shipped @v0.3.3 action +
PyPI package, and verify-source runs the PR's own action.yml against its own
source (package: -e .) — so a regression in either is caught before it ships.
Releases publish to PyPI automatically via Trusted Publishing.
The hero GIF is itself reproducible — demo.tape + vhs demo.tape.
License
MIT © Varma Budharaju
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file shotlist-0.3.3.tar.gz.
File metadata
- Download URL: shotlist-0.3.3.tar.gz
- Upload date:
- Size: 266.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
227961d260e9bd59f5097b5cc463ed87e7561b4f9db442bf87776a134e023d77
|
|
| MD5 |
b06b77d19a7acc6034f4dd0157640f54
|
|
| BLAKE2b-256 |
ef674b1b2d6c35e047517d775cc31c7a630d2c5e17d07cf117cf024b4ff8d8b6
|
Provenance
The following attestation bundles were made for shotlist-0.3.3.tar.gz:
Publisher:
publish.yml on varmabudharaju/shotlist
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
shotlist-0.3.3.tar.gz -
Subject digest:
227961d260e9bd59f5097b5cc463ed87e7561b4f9db442bf87776a134e023d77 - Sigstore transparency entry: 2043085639
- Sigstore integration time:
-
Permalink:
varmabudharaju/shotlist@1b2d9e08479dd622ca0394fae8f962cf5fc15ce4 -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/varmabudharaju
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1b2d9e08479dd622ca0394fae8f962cf5fc15ce4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file shotlist-0.3.3-py3-none-any.whl.
File metadata
- Download URL: shotlist-0.3.3-py3-none-any.whl
- Upload date:
- Size: 231.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1de639cac142e34fe84979e580a1bedb63b947b2e03db3393e49450a9af79a4
|
|
| MD5 |
c251ed0c6986724dc8a23694bffeefb1
|
|
| BLAKE2b-256 |
cc1352cf2cfe5a2b98b8732235f8894a5f1cda3254ee1fe336e4f9d096091c48
|
Provenance
The following attestation bundles were made for shotlist-0.3.3-py3-none-any.whl:
Publisher:
publish.yml on varmabudharaju/shotlist
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
shotlist-0.3.3-py3-none-any.whl -
Subject digest:
f1de639cac142e34fe84979e580a1bedb63b947b2e03db3393e49450a9af79a4 - Sigstore transparency entry: 2043086539
- Sigstore integration time:
-
Permalink:
varmabudharaju/shotlist@1b2d9e08479dd622ca0394fae8f962cf5fc15ce4 -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/varmabudharaju
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1b2d9e08479dd622ca0394fae8f962cf5fc15ce4 -
Trigger Event:
release
-
Statement type: