{"id":51333752,"url":"https://github.com/varmabudharaju/shotlist","last_synced_at":"2026-07-02T01:01:59.480Z","repository":{"id":363697617,"uuid":"1264504319","full_name":"varmabudharaju/shotlist","owner":"varmabudharaju","description":"Screenshots for your docs, as code — web pages, real terminal windows, and stateful CLI sessions, from one committed shot list.","archived":false,"fork":false,"pushed_at":"2026-06-26T03:47:38.000Z","size":1181,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T05:13:11.543Z","etag":null,"topics":["automation","cli","developer-tools","documentation","playwright","python","readme","screenshots"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/shotlist/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/varmabudharaju.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-10T00:12:49.000Z","updated_at":"2026-06-26T03:47:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/varmabudharaju/shotlist","commit_stats":null,"previous_names":["varmabudharaju/capture","varmabudharaju/shotlist"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/varmabudharaju/shotlist","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varmabudharaju%2Fshotlist","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varmabudharaju%2Fshotlist/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varmabudharaju%2Fshotlist/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varmabudharaju%2Fshotlist/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/varmabudharaju","download_url":"https://codeload.github.com/varmabudharaju/shotlist/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varmabudharaju%2Fshotlist/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35028642,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","cli","developer-tools","documentation","playwright","python","readme","screenshots"],"created_at":"2026-07-02T01:01:17.981Z","updated_at":"2026-07-02T01:01:59.471Z","avatar_url":"https://github.com/varmabudharaju.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# shotlist\n\n[![CI](https://github.com/varmabudharaju/shotlist/actions/workflows/ci.yml/badge.svg)](https://github.com/varmabudharaju/shotlist/actions/workflows/ci.yml)\n[![verify-action](https://github.com/varmabudharaju/shotlist/actions/workflows/verify-action.yml/badge.svg)](https://github.com/varmabudharaju/shotlist/actions/workflows/verify-action.yml)\n[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n\n**Screenshots for your docs — as code.** One committed shot list captures your\nweb pages, your *real* terminal windows, and stateful CLI sessions — and\nregenerates them all with a single command.\n\n\u003cimg src=\"https://raw.githubusercontent.com/varmabudharaju/shotlist/main/docs/demo.gif\" width=\"100%\" alt=\"The old way: dragging Screen Shot 2026-... files into ever-more-cursed filenames, then shipping a UI tweak that makes them all stale. The shotlist way: one `shotlist run`.\"/\u003e\n\n## Contents\n\n- [The problem](#the-problem)\n- [Quickstart](#quickstart)\n- [One shot list, four kinds of shot](#one-shot-list-four-kinds-of-shot)\n- [Use cases](#use-cases)\n- [Proof reports \u0026 pipelines](#proof-reports--pipelines)\n- [Why shotlist, and not the others](#why-shotlist-and-not-the-others)\n- [How it works](#how-it-works)\n- [Use with Claude](#use-with-claude)\n- [Commands](#commands)\n- [Develop](#develop)\n\n## The problem\n\nDocumenting a feature means launching the app, clicking to the right state,\nscreenshotting, naming the file, and embedding it — **every time the UI changes.**\nThe screenshots drift out of date the moment you ship, and nobody notices until\nthey're embarrassingly wrong.\n\n`shotlist` makes them **reproducible**: describe *how to start your app* and *what\nto shoot* once, in a committed `.shotlist.yaml`, then regenerate the whole set on\ndemand — locally or in CI. Same config + same app state → same screenshots.\n\n## Quickstart\n\n```bash\npip install shotlist             # installs the `shotlist` command\nplaywright install chromium      # one-time browser download\n\nshotlist init        # writes a starter .shotlist.yaml\nshotlist run         # boots your app, captures every shot, tears it all down\n```\n\n## One shot list, four kinds of shot\n\n```yaml\noutput:\n  dir: docs/screenshots\n  readme: README.md            # optional: splice \u003cimg\u003e snippets straight into the README\n\napp:                           # optional — omit for static sites or pure-CLI shots\n  command: \"npm run dev\"\n  ready: { url: http://localhost:5173, timeout: 30 }   # never shoot a half-booted app\n\nshots:\n  - { name: dashboard, kind: web, url: http://localhost:5173/dashboard, full_page: true, alt: \"Dashboard\" }\n  - { name: cli-help,  kind: cli, command: \"mytool --help\", alt: \"Top-level help\" }\n```\n\n| Kind | Captures | How |\n| --- | --- | --- |\n| **`web`** | a browser page — with optional click/fill/wait steps first | Playwright / Chromium |\n| **`cli` · `native`** *(macOS default)* | a **real screenshot of your Terminal.app window** — your font, your theme | AppleScript + `screencapture` |\n| **`cli` · `rendered`** *(any OS, CI-safe)* | the command's output drawn as a styled terminal card | PTY → ANSI→HTML → Chromium |\n| **`session`** | a **stateful, multi-command flow** in one persistent terminal — one shot per step | one Terminal window, captured after each step |\n\nA `session` is how you screenshot a flow whose later steps depend on earlier ones —\nthe shell state (cwd, env, background processes) carries across. Background a\nlong-running process with `\u0026` and a small `wait_ms`, keep capturing, and the\nsession tears it down on close.\n\n## Use cases\n\n`shotlist` fits anywhere a screenshot would otherwise go stale:\n\n- **README \u0026 docs screenshots** — the core: regenerate the whole set on every UI change.\n- **Test-evidence / proof** — capture a feature flow step by step (a `session`) and share the generated `index.html` as proof it works.\n- **CI drift-checking** — `shotlist check` fails the build when a screenshot changes unexpectedly (with a visual `--diff`).\n- **Blog posts \u0026 tutorials** — polished web *and* CLI shots from one config.\n- **Onboarding \u0026 demo galleries** — versioned sets you keep across releases.\n- **Long-running processes** — background a dev server with `\u0026` + `wait_ms` and shoot it live.\n\nEach one has a complete, copy-paste `.shotlist.yaml` in the recipes cookbook,\n**[`docs/recipes.md`](docs/recipes.md)**.\n\n## Proof reports \u0026 pipelines\n\nEvery `shotlist run` also writes, next to the PNGs:\n\n- **`index.html`** — a self-contained gallery you can open and share as a **proof report**;\n- **`manifest.json`** — a machine-readable record of the run (a pipeline artifact).\n\n\u003cimg src=\"https://raw.githubusercontent.com/varmabudharaju/shotlist/main/docs/proof-report.png\" width=\"100%\" alt=\"The generated index.html gallery: a title with the shot count and timestamp, then a card per shot showing the screenshot, its name, a kind badge, its alt text, and the command or URL that produced it.\"/\u003e\n\nAttach `manifest.json` to a CI job, or open `index.html` as test-evidence. Set\n`output.title` to relabel the gallery heading, and `output.evidence` to also\nsplice a captioned Markdown test-evidence doc — its own file, distinct from\n`output.dir` (where the PNGs land). Turn the report off with `--no-report`\n(or `output.report: false`).\n\n### Catch drift before your users do\n\nGate CI with **`shotlist check`** — it re-captures and fails when a screenshot\ndrifts from the committed baseline, telling you exactly *how much* moved:\n\n\u003cimg src=\"https://raw.githubusercontent.com/varmabudharaju/shotlist/main/docs/check.png\" width=\"100%\" alt=\"shotlist check output: service-status changed (1.14% pixels differ) with an arrow to its diff image, queue-drain unchanged, and a pointer to check-report.html\"/\u003e\n\nDrift comes with receipts. `--diff DIR` renders a baseline·current·diff 3-up per\nchanged shot, plus a **`check-report.html`** that lists every shot with a status\nbadge — open it locally or grab it from the CI artifact the bundled **GitHub\nAction** uploads (along with a step summary on the run page):\n\n\u003cimg src=\"https://raw.githubusercontent.com/varmabudharaju/shotlist/main/docs/check-report.png\" width=\"100%\" alt=\"check-report.html: service-status flagged CHANGED (1.14% pixels differ) with its baseline, current, and highlighted-diff images inline; queue-drain badged UNCHANGED\"/\u003e\n\nBless intended changes with `shotlist check --update` (or `--update --only NAME`\nfor one shot), set `check.max_diff_pixel_ratio` to tolerate sub-pixel jitter, and\nscript against `check --json`. The whole loop, end to end:\n\n\u003cimg src=\"https://raw.githubusercontent.com/varmabudharaju/shotlist/main/docs/diagrams/drift-workflow.png\" width=\"100%\" alt=\"Flow diagram: shotlist run creates the baseline; PNGs and manifest.json are committed; CI runs shotlist check on every PR — no drift merges, drift opens check-report.html; intended changes are re-blessed with check --update, real regressions get fixed and re-checked\"/\u003e\n\nDetails in **[`docs/pipeline.md`](docs/pipeline.md)**.\n\n## Why shotlist, and not the others\n\nThe pieces exist in isolation; `shotlist` is the one tool that does all of it under\na single committed config.\n\n| | web pages | real terminal | CLI sessions | README auto-embed | reproducible / CI |\n| --- | :---: | :---: | :---: | :---: | :---: |\n| **shotlist** | ✅ | ✅ | ✅ | ✅ | ✅ |\n| shot-scraper | ✅ | ❌ | ❌ | ❌ | ✅ |\n| freeze / carbon | ❌ | synthetic | ❌ | ❌ | ✅ |\n| Percy / Chromatic | ✅ | ❌ | ❌ | ❌ | ✅ (cloud, paid) |\n| doing it by hand | 😖 | 😖 | 😖 | ❌ | ❌ |\n\nNo cloud, no paid services, no special OS permissions for web/rendered shots.\n(Native Terminal capture needs macOS Screen-Recording permission; everything else\nneeds nothing.)\n\n## How it works\n\nOne deterministic engine: load and validate the shot list, boot your app and wait\nuntil it's *actually* ready, then route every shot to the right backend — and tear\neverything down afterwards, even on a crash:\n\n\u003cimg src=\"https://raw.githubusercontent.com/varmabudharaju/shotlist/main/docs/diagrams/shot-routing.png\" width=\"100%\" alt=\"Flow diagram: the engine routes each shot by kind — web goes to Playwright/Chromium; cli goes to a rendered terminal card (PTY, scrub, ANSI to HTML, Chromium) or a real Terminal.app window depending on style; session drives one persistent Terminal window — all paths produce PNG bytes written as NN-name.png\"/\u003e\n\nThe clever part is what *isn't* here: **no AI runs at capture time.** Claude's only\njob is to *author* the `.shotlist.yaml` once by reading your repo; after that the\nengine is a plain, deterministic program — fast, free, and re-runnable in CI with\nno model in the loop.\n\nWant the full picture? **[`docs/how-it-works.md`](docs/how-it-works.md)** walks\nevery stage with flow diagrams — the run pipeline, how shots route to backends,\nwhat one run does step by step, the `check` drift loop, and the determinism\nlayers that make the same config produce the same pixels. The design rationale\nlives in [`docs/design.md`](docs/design.md).\n\n**Robust by design.** The readiness probe (HTTP / TCP port / log line) means you\nnever screenshot a half-booted app, and the app is launched in its own process\ngroup and torn down — even on a crash or Ctrl-C — so a shotlist run never leaves an\norphaned dev server behind.\n\n**Deterministic by default.** Web shots can `mask` flaky regions (`mask:\n[selector, ...]`) and always capture with CSS animations disabled; CLI shots can\n`scrub` non-deterministic text (durations, timestamps, PIDs) with a regex before\nrendering; and rendered CLI cards embed JetBrains Mono. Baselines now match\nbyte-for-byte across macOS and Linux CI, not just on the machine that made them.\n\n## shotlist, captured by shotlist\n\nThis repo dogfoods itself: the shots below are produced by running `shotlist run`\non its own [`.shotlist.yaml`](.shotlist.yaml) and spliced in automatically.\n\n\u003c!-- shotlist:start --\u003e\n### The shotlist CLI\n\n\u003cimg src=\"docs/screenshots/01-the-shotlist-cli.png\" width=\"100%\" alt=\"shotlist --help showing the init, validate, run, and check commands\"/\u003e\n\n### Run options\n\n\u003cimg src=\"docs/screenshots/02-run-options.png\" width=\"100%\" alt=\"shotlist run options: --config, --only, and --version\"/\u003e\n\n\u003c!-- shotlist:end --\u003e\n\n## Use with Claude\n\n`shotlist` ships an optional Claude integration in [`integrations/claude/`](integrations/claude/):\n\n- a **`/shotlist` skill** that inspects your repo (routes, `--help`, README), writes\n  the `.shotlist.yaml` for you, and runs it;\n- an optional **auto-snapshot hook** that drops a raw snapshot when a dev server\n  starts (the honest \"dumb snapshot\"; the curated set always comes from `shotlist run`).\n\n## Commands\n\n| Command | What it does |\n| --- | --- |\n| `shotlist init` | Scaffold a starter `.shotlist.yaml` |\n| `shotlist validate` | Check the shot list is well-formed |\n| `shotlist run` | Capture every shot and write outputs |\n| `shotlist run --only dashboard` | Capture a single shot by name |\n| `shotlist run --version v2` | Write into a versioned subfolder |\n| `shotlist check` | Fail if a screenshot drifted from the committed baseline |\n| `shotlist check --update` | Re-shoot and accept the current screenshots as the baseline |\n| `shotlist check --diff DIR` | Also render baseline·current·diff images for changed shots |\n| `shotlist check --json` | Emit the drift report as JSON on stdout (human output moves to stderr) |\n| `shotlist check --update --only NAME` | Re-bless just one shot in place (repeatable) |\n\n## Develop\n\n```bash\ngit clone https://github.com/varmabudharaju/shotlist \u0026\u0026 cd shotlist\npython3 -m venv .venv \u0026\u0026 source .venv/bin/activate\npip install -e \".[dev]\"\nplaywright install chromium\npytest                       # the suite is fully offline\n```\n\nCI runs ruff, mypy, and pytest — with an 85% coverage gate — on Ubuntu (Python\n3.11, 3.12) and macOS (Python 3.12), so native Terminal capture stays covered\ntoo. A separate **`verify-action`** workflow dogfoods the bundled GitHub Action\non every PR two ways: `verify-release` smoke-tests the shipped `@v0.3.3` action +\nPyPI package, and `verify-source` runs the PR's own `action.yml` against its own\nsource (`package: -e .`) — so a regression in either is caught before it ships.\nReleases publish to PyPI automatically via Trusted Publishing.\n\nThe hero GIF is itself reproducible — [`demo.tape`](demo.tape) + `vhs demo.tape`.\n\n## License\n\nMIT © Varma Budharaju\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvarmabudharaju%2Fshotlist","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvarmabudharaju%2Fshotlist","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvarmabudharaju%2Fshotlist/lists"}