shotlist 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- shotlist-0.1.0/.capture.yaml +22 -0
- shotlist-0.1.0/.github/workflows/ci.yml +35 -0
- shotlist-0.1.0/.github/workflows/publish.yml +45 -0
- shotlist-0.1.0/.gitignore +22 -0
- shotlist-0.1.0/CONTRIBUTING.md +58 -0
- shotlist-0.1.0/LICENSE +21 -0
- shotlist-0.1.0/PKG-INFO +203 -0
- shotlist-0.1.0/README.md +177 -0
- shotlist-0.1.0/action.yml +28 -0
- shotlist-0.1.0/demo.tape +39 -0
- shotlist-0.1.0/docs/design.md +153 -0
- shotlist-0.1.0/docs/pipeline.md +116 -0
- shotlist-0.1.0/docs/recipes.md +161 -0
- shotlist-0.1.0/integrations/claude/README.md +61 -0
- shotlist-0.1.0/integrations/claude/hooks/README.md +72 -0
- shotlist-0.1.0/integrations/claude/hooks/auto-snapshot.sh +21 -0
- shotlist-0.1.0/integrations/claude/skills/capture/SKILL.md +237 -0
- shotlist-0.1.0/pyproject.toml +71 -0
- shotlist-0.1.0/src/capture/__init__.py +8 -0
- shotlist-0.1.0/src/capture/backends/__init__.py +6 -0
- shotlist-0.1.0/src/capture/backends/cli.py +96 -0
- shotlist-0.1.0/src/capture/backends/native_terminal.py +234 -0
- shotlist-0.1.0/src/capture/backends/web.py +38 -0
- shotlist-0.1.0/src/capture/check.py +65 -0
- shotlist-0.1.0/src/capture/cli.py +258 -0
- shotlist-0.1.0/src/capture/config.py +201 -0
- shotlist-0.1.0/src/capture/diff.py +123 -0
- shotlist-0.1.0/src/capture/engine.py +192 -0
- shotlist-0.1.0/src/capture/lifecycle.py +165 -0
- shotlist-0.1.0/src/capture/output.py +135 -0
- shotlist-0.1.0/src/capture/render.py +39 -0
- shotlist-0.1.0/src/capture/report.py +173 -0
- shotlist-0.1.0/tests/__init__.py +0 -0
- shotlist-0.1.0/tests/conftest.py +23 -0
- shotlist-0.1.0/tests/test_check.py +97 -0
- shotlist-0.1.0/tests/test_cli_app.py +213 -0
- shotlist-0.1.0/tests/test_cli_backend.py +65 -0
- shotlist-0.1.0/tests/test_config.py +290 -0
- shotlist-0.1.0/tests/test_diff.py +50 -0
- shotlist-0.1.0/tests/test_engine.py +281 -0
- shotlist-0.1.0/tests/test_lifecycle.py +76 -0
- shotlist-0.1.0/tests/test_native_terminal.py +123 -0
- shotlist-0.1.0/tests/test_output.py +183 -0
- shotlist-0.1.0/tests/test_report.py +110 -0
- shotlist-0.1.0/tests/test_web.py +70 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# capture dogfoods itself: these shots screenshot capture's own CLI for the README.
|
|
2
|
+
# Regenerate them any time with: capture run
|
|
3
|
+
output:
|
|
4
|
+
dir: docs/screenshots
|
|
5
|
+
readme: README.md
|
|
6
|
+
|
|
7
|
+
shots:
|
|
8
|
+
- name: The capture CLI
|
|
9
|
+
kind: cli
|
|
10
|
+
command: ".venv/bin/capture --help"
|
|
11
|
+
cols: 90
|
|
12
|
+
rows: 16
|
|
13
|
+
style: native # a real screenshot of Terminal.app (macOS)
|
|
14
|
+
alt: "capture --help showing the init, validate, and run commands"
|
|
15
|
+
|
|
16
|
+
- name: Run options
|
|
17
|
+
kind: cli
|
|
18
|
+
command: ".venv/bin/capture run --help"
|
|
19
|
+
cols: 90
|
|
20
|
+
rows: 20
|
|
21
|
+
style: native # a real screenshot of Terminal.app (macOS)
|
|
22
|
+
alt: "capture run options: --config, --only, and --version"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.11", "3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
cache: pip
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: pip install -e ".[dev]"
|
|
24
|
+
|
|
25
|
+
- name: Install Playwright Chromium
|
|
26
|
+
run: python -m playwright install --with-deps chromium
|
|
27
|
+
|
|
28
|
+
- name: Lint (ruff)
|
|
29
|
+
run: ruff check src tests
|
|
30
|
+
|
|
31
|
+
- name: Type check (mypy)
|
|
32
|
+
run: mypy src tests
|
|
33
|
+
|
|
34
|
+
- name: Test (pytest)
|
|
35
|
+
run: python -m pytest -q
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI when a GitHub Release is published, using PyPI Trusted
|
|
4
|
+
# Publishing (OIDC) — no API token or secret is stored in the repo.
|
|
5
|
+
on:
|
|
6
|
+
release:
|
|
7
|
+
types: [published]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.11"
|
|
21
|
+
|
|
22
|
+
- name: Build sdist + wheel
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade build twine
|
|
25
|
+
python -m build
|
|
26
|
+
python -m twine check dist/*
|
|
27
|
+
|
|
28
|
+
- uses: actions/upload-artifact@v4
|
|
29
|
+
with:
|
|
30
|
+
name: dist
|
|
31
|
+
path: dist/
|
|
32
|
+
|
|
33
|
+
publish:
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
permissions:
|
|
37
|
+
id-token: write # required for Trusted Publishing (OIDC)
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/download-artifact@v4
|
|
40
|
+
with:
|
|
41
|
+
name: dist
|
|
42
|
+
path: dist/
|
|
43
|
+
|
|
44
|
+
- name: Publish to PyPI
|
|
45
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
|
|
8
|
+
# Environments
|
|
9
|
+
.venv/
|
|
10
|
+
venv/
|
|
11
|
+
.env
|
|
12
|
+
|
|
13
|
+
# Tooling caches
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
|
|
18
|
+
# OS
|
|
19
|
+
.DS_Store
|
|
20
|
+
|
|
21
|
+
# Auto-snapshot scratch output (the curated set under docs/screenshots/ IS committed)
|
|
22
|
+
docs/screenshots/_auto/
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Contributing to capture
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in improving `capture`. This guide covers local setup
|
|
4
|
+
and the checks every change must pass.
|
|
5
|
+
|
|
6
|
+
## Dev setup
|
|
7
|
+
|
|
8
|
+
`capture` targets Python 3.11+. From the repository root:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
python3 -m venv .venv
|
|
12
|
+
source .venv/bin/activate
|
|
13
|
+
pip install -e ".[dev]"
|
|
14
|
+
playwright install chromium
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The editable install (`-e`) means source edits take effect immediately. The
|
|
18
|
+
`[dev]` extra pulls in `pytest`, `ruff`, and `mypy`. Both backends render through
|
|
19
|
+
Playwright/Chromium, so `playwright install chromium` is required before the
|
|
20
|
+
tests can run — there are no other external binaries or OS permissions to set up.
|
|
21
|
+
|
|
22
|
+
## Running the checks
|
|
23
|
+
|
|
24
|
+
Run all three from the repository root (with the virtualenv activated):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
ruff check src tests # lint + import order
|
|
28
|
+
mypy src tests # strict type checking
|
|
29
|
+
pytest # test suite
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
CI runs the same commands on Python 3.11 and 3.12 (see
|
|
33
|
+
[`.github/workflows/ci.yml`](.github/workflows/ci.yml)); a green local run
|
|
34
|
+
should mean a green CI run.
|
|
35
|
+
|
|
36
|
+
### Tooling notes
|
|
37
|
+
|
|
38
|
+
- **ruff** — line length 100; rule sets `E, F, I, UP, B, C4, SIM` (configured in
|
|
39
|
+
`pyproject.toml`). Let `ruff check --fix` handle import sorting.
|
|
40
|
+
- **mypy** — runs in `strict` mode. Every function needs full type annotations,
|
|
41
|
+
and every module needs a docstring.
|
|
42
|
+
- Use modern syntax: `X | Y` unions (not `typing.Union`), `list[...]` /
|
|
43
|
+
`dict[...]` builtins, and `from __future__` is unnecessary on 3.11+.
|
|
44
|
+
|
|
45
|
+
## Development style
|
|
46
|
+
|
|
47
|
+
`capture` is built test-first (TDD): tests live in `tests/` alongside the module
|
|
48
|
+
they exercise — `test_config.py` for `config.py`, `test_lifecycle.py` for
|
|
49
|
+
`lifecycle.py`, `test_web.py` / `test_cli_backend.py` for the backends, and so
|
|
50
|
+
on. When adding a feature or fixing a bug:
|
|
51
|
+
|
|
52
|
+
1. Write a failing test that captures the desired behaviour.
|
|
53
|
+
2. Implement the smallest change that makes it pass.
|
|
54
|
+
3. Run `ruff check src tests`, `mypy src tests`, and `pytest` before opening a PR.
|
|
55
|
+
|
|
56
|
+
Keep changes focused, match the surrounding conventions (Pydantic `_Strict`
|
|
57
|
+
models, clear error types, module + function docstrings), and make sure the full
|
|
58
|
+
check suite is green.
|
shotlist-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Varma Budharaju
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
shotlist-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shotlist
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reproducible screenshot capture for docs — drive a web app or CLI from a declarative shot list and capture polished feature screenshots.
|
|
5
|
+
Project-URL: Homepage, https://github.com/varmabudharaju/capture
|
|
6
|
+
Project-URL: Repository, https://github.com/varmabudharaju/capture
|
|
7
|
+
Project-URL: Issues, https://github.com/varmabudharaju/capture/issues
|
|
8
|
+
Author-email: Varma Budharaju <sairam.vzf33@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,documentation,playwright,readme,screenshots
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Requires-Dist: ansi2html>=1.9
|
|
14
|
+
Requires-Dist: pillow>=10.0
|
|
15
|
+
Requires-Dist: playwright>=1.40
|
|
16
|
+
Requires-Dist: pydantic>=2.6
|
|
17
|
+
Requires-Dist: pyyaml>=6.0
|
|
18
|
+
Requires-Dist: rich>=13.7
|
|
19
|
+
Requires-Dist: typer>=0.12
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
24
|
+
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# capture
|
|
28
|
+
|
|
29
|
+
[](https://github.com/varmabudharaju/capture/actions/workflows/ci.yml)
|
|
30
|
+
[](https://www.python.org/downloads/)
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
|
|
33
|
+
**Screenshots for your docs — as code.** One committed shot list captures your
|
|
34
|
+
web pages, your *real* terminal windows, and stateful CLI sessions — and
|
|
35
|
+
regenerates them all with a single command.
|
|
36
|
+
|
|
37
|
+
<img src="https://raw.githubusercontent.com/varmabudharaju/capture/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 capture way: one `capture run`."/>
|
|
38
|
+
|
|
39
|
+
## The problem
|
|
40
|
+
|
|
41
|
+
Documenting a feature means launching the app, clicking to the right state,
|
|
42
|
+
screenshotting, naming the file, and embedding it — **every time the UI changes.**
|
|
43
|
+
The screenshots drift out of date the moment you ship, and nobody notices until
|
|
44
|
+
they're embarrassingly wrong.
|
|
45
|
+
|
|
46
|
+
`capture` makes them **reproducible**: describe *how to start your app* and *what
|
|
47
|
+
to shoot* once, in a committed `.capture.yaml`, then regenerate the whole set on
|
|
48
|
+
demand — locally or in CI. Same config + same app state → same screenshots.
|
|
49
|
+
|
|
50
|
+
## Quickstart
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install shotlist # installs the `capture` command
|
|
54
|
+
playwright install chromium # one-time browser download
|
|
55
|
+
|
|
56
|
+
capture init # writes a starter .capture.yaml
|
|
57
|
+
capture run # boots your app, captures every shot, tears it all down
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## One shot list, four kinds of shot
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
output:
|
|
64
|
+
dir: docs/screenshots
|
|
65
|
+
readme: README.md # optional: splice <img> snippets straight into the README
|
|
66
|
+
|
|
67
|
+
app: # optional — omit for static sites or pure-CLI shots
|
|
68
|
+
command: "npm run dev"
|
|
69
|
+
ready: { url: http://localhost:5173, timeout: 30 } # never shoot a half-booted app
|
|
70
|
+
|
|
71
|
+
shots:
|
|
72
|
+
- { name: dashboard, kind: web, url: http://localhost:5173/dashboard, full_page: true, alt: "Dashboard" }
|
|
73
|
+
- { name: cli-help, kind: cli, command: "mytool --help", alt: "Top-level help" }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Kind | Captures | How |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| **`web`** | a browser page — with optional click/fill/wait steps first | Playwright / Chromium |
|
|
79
|
+
| **`cli` · `native`** *(macOS default)* | a **real screenshot of your Terminal.app window** — your font, your theme | AppleScript + `screencapture` |
|
|
80
|
+
| **`cli` · `rendered`** *(any OS, CI-safe)* | the command's output drawn as a styled terminal card | PTY → ANSI→HTML → Chromium |
|
|
81
|
+
| **`session`** | a **stateful, multi-command flow** in one persistent terminal — one shot per step | one Terminal window, captured after each step |
|
|
82
|
+
|
|
83
|
+
A `session` is how you screenshot a flow whose later steps depend on earlier ones —
|
|
84
|
+
the shell state (cwd, env, background processes) carries across. Background a
|
|
85
|
+
long-running process with `&` and a small `wait_ms`, keep capturing, and the
|
|
86
|
+
session tears it down on close.
|
|
87
|
+
|
|
88
|
+
## Recipes
|
|
89
|
+
|
|
90
|
+
Copy-paste `.capture.yaml` configs for the common jobs — test-evidence proofs, CI
|
|
91
|
+
regeneration, long-running servers, web flows with interactions, versioned visual
|
|
92
|
+
history — live in **[`docs/recipes.md`](docs/recipes.md)**.
|
|
93
|
+
|
|
94
|
+
## Proof reports & pipelines
|
|
95
|
+
|
|
96
|
+
Every `capture run` also writes, next to the PNGs:
|
|
97
|
+
|
|
98
|
+
- **`index.html`** — a self-contained gallery you can open and share as a **proof report**;
|
|
99
|
+
- **`manifest.json`** — a machine-readable record of the run (a pipeline artifact).
|
|
100
|
+
|
|
101
|
+
<img src="https://raw.githubusercontent.com/varmabudharaju/capture/main/docs/proof-report.png" width="100%" alt="The generated index.html gallery: a header with the shot count and timestamp, then a card per shot showing the screenshot, its name, a kind badge, and its alt text."/>
|
|
102
|
+
|
|
103
|
+
Attach `manifest.json` to a CI job, or open `index.html` as test-evidence. Gate CI
|
|
104
|
+
with **`capture check`** — it re-captures and fails when a screenshot drifts from
|
|
105
|
+
the committed baseline (`capture check --update` to accept intended changes; add
|
|
106
|
+
`--diff DIR` to render baseline·current·diff images) — or drop in the bundled
|
|
107
|
+
**GitHub Action**. Turn the report off with `--no-report` (or `output.report:
|
|
108
|
+
false`). Details in **[`docs/pipeline.md`](docs/pipeline.md)**.
|
|
109
|
+
|
|
110
|
+
## Why capture, and not the others
|
|
111
|
+
|
|
112
|
+
The pieces exist in isolation; `capture` is the one tool that does all of it under
|
|
113
|
+
a single committed config.
|
|
114
|
+
|
|
115
|
+
| | web pages | real terminal | CLI sessions | README auto-embed | reproducible / CI |
|
|
116
|
+
| --- | :---: | :---: | :---: | :---: | :---: |
|
|
117
|
+
| **capture** | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
118
|
+
| shot-scraper | ✅ | ❌ | ❌ | ❌ | ✅ |
|
|
119
|
+
| freeze / carbon | ❌ | synthetic | ❌ | ❌ | ✅ |
|
|
120
|
+
| Percy / Chromatic | ✅ | ❌ | ❌ | ❌ | ✅ (cloud, paid) |
|
|
121
|
+
| doing it by hand | 😖 | 😖 | 😖 | ❌ | ❌ |
|
|
122
|
+
|
|
123
|
+
No cloud, no paid services, no special OS permissions for web/rendered shots.
|
|
124
|
+
(Native Terminal capture needs macOS Screen-Recording permission; everything else
|
|
125
|
+
needs nothing.)
|
|
126
|
+
|
|
127
|
+
## How it works
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
.capture.yaml ─► load + validate ─► [ boot app, wait until ready ] ─► one engine
|
|
131
|
+
routes each
|
|
132
|
+
shot by kind:
|
|
133
|
+
web ───────► Playwright / Chromium
|
|
134
|
+
cli·native ► a real Terminal.app window
|
|
135
|
+
cli·render ► PTY → ANSI→HTML → Chromium
|
|
136
|
+
session ───► one persistent Terminal, a shot per step
|
|
137
|
+
─► NN-name.png
|
|
138
|
+
+ README splice
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The clever part is what *isn't* here: **no AI runs at capture time.** Claude's only
|
|
142
|
+
job is to *author* the `.capture.yaml` once by reading your repo; after that the
|
|
143
|
+
engine is a plain, deterministic program — fast, free, and re-runnable in CI with
|
|
144
|
+
no model in the loop. See the full design in [`docs/design.md`](docs/design.md).
|
|
145
|
+
|
|
146
|
+
**Robust by design.** The readiness probe (HTTP / TCP port / log line) means you
|
|
147
|
+
never screenshot a half-booted app, and the app is launched in its own process
|
|
148
|
+
group and torn down — even on a crash or Ctrl-C — so a capture run never leaves an
|
|
149
|
+
orphaned dev server behind.
|
|
150
|
+
|
|
151
|
+
## Capture, captured by capture
|
|
152
|
+
|
|
153
|
+
This repo dogfoods itself: the shots below are produced by running `capture run`
|
|
154
|
+
on its own [`.capture.yaml`](.capture.yaml) and spliced in automatically.
|
|
155
|
+
|
|
156
|
+
<!-- capture:start -->
|
|
157
|
+
### The capture CLI
|
|
158
|
+
|
|
159
|
+
<img src="https://raw.githubusercontent.com/varmabudharaju/capture/main/docs/screenshots/01-the-capture-cli.png" width="100%" alt="capture --help showing the init, validate, and run commands"/>
|
|
160
|
+
|
|
161
|
+
### Run options
|
|
162
|
+
|
|
163
|
+
<img src="https://raw.githubusercontent.com/varmabudharaju/capture/main/docs/screenshots/02-run-options.png" width="100%" alt="capture run options: --config, --only, and --version"/>
|
|
164
|
+
|
|
165
|
+
<!-- capture:end -->
|
|
166
|
+
|
|
167
|
+
## Use with Claude
|
|
168
|
+
|
|
169
|
+
`capture` ships an optional Claude integration in [`integrations/claude/`](integrations/claude/):
|
|
170
|
+
|
|
171
|
+
- a **`/capture` skill** that inspects your repo (routes, `--help`, README), writes
|
|
172
|
+
the `.capture.yaml` for you, and runs it;
|
|
173
|
+
- an optional **auto-snapshot hook** that drops a raw snapshot when a dev server
|
|
174
|
+
starts (the honest "dumb snapshot"; the curated set always comes from `capture run`).
|
|
175
|
+
|
|
176
|
+
## Commands
|
|
177
|
+
|
|
178
|
+
| Command | What it does |
|
|
179
|
+
| --- | --- |
|
|
180
|
+
| `capture init` | Scaffold a starter `.capture.yaml` |
|
|
181
|
+
| `capture validate` | Check the shot list is well-formed |
|
|
182
|
+
| `capture run` | Capture every shot and write outputs |
|
|
183
|
+
| `capture run --only dashboard` | Capture a single shot by name |
|
|
184
|
+
| `capture run --version v2` | Write into a versioned subfolder |
|
|
185
|
+
| `capture check` | Fail if a screenshot drifted from the committed baseline |
|
|
186
|
+
| `capture check --update` | Re-shoot and accept the current screenshots as the baseline |
|
|
187
|
+
| `capture check --diff DIR` | Also render baseline·current·diff images for changed shots |
|
|
188
|
+
|
|
189
|
+
## Develop
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
git clone https://github.com/varmabudharaju/capture && cd capture
|
|
193
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
194
|
+
pip install -e ".[dev]"
|
|
195
|
+
playwright install chromium
|
|
196
|
+
pytest # the suite is fully offline
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The hero GIF is itself reproducible — [`demo.tape`](demo.tape) + `vhs demo.tape`.
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT © Varma Budharaju
|
shotlist-0.1.0/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# capture
|
|
2
|
+
|
|
3
|
+
[](https://github.com/varmabudharaju/capture/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
**Screenshots for your docs — as code.** One committed shot list captures your
|
|
8
|
+
web pages, your *real* terminal windows, and stateful CLI sessions — and
|
|
9
|
+
regenerates them all with a single command.
|
|
10
|
+
|
|
11
|
+
<img src="https://raw.githubusercontent.com/varmabudharaju/capture/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 capture way: one `capture run`."/>
|
|
12
|
+
|
|
13
|
+
## The problem
|
|
14
|
+
|
|
15
|
+
Documenting a feature means launching the app, clicking to the right state,
|
|
16
|
+
screenshotting, naming the file, and embedding it — **every time the UI changes.**
|
|
17
|
+
The screenshots drift out of date the moment you ship, and nobody notices until
|
|
18
|
+
they're embarrassingly wrong.
|
|
19
|
+
|
|
20
|
+
`capture` makes them **reproducible**: describe *how to start your app* and *what
|
|
21
|
+
to shoot* once, in a committed `.capture.yaml`, then regenerate the whole set on
|
|
22
|
+
demand — locally or in CI. Same config + same app state → same screenshots.
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install shotlist # installs the `capture` command
|
|
28
|
+
playwright install chromium # one-time browser download
|
|
29
|
+
|
|
30
|
+
capture init # writes a starter .capture.yaml
|
|
31
|
+
capture run # boots your app, captures every shot, tears it all down
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## One shot list, four kinds of shot
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
output:
|
|
38
|
+
dir: docs/screenshots
|
|
39
|
+
readme: README.md # optional: splice <img> snippets straight into the README
|
|
40
|
+
|
|
41
|
+
app: # optional — omit for static sites or pure-CLI shots
|
|
42
|
+
command: "npm run dev"
|
|
43
|
+
ready: { url: http://localhost:5173, timeout: 30 } # never shoot a half-booted app
|
|
44
|
+
|
|
45
|
+
shots:
|
|
46
|
+
- { name: dashboard, kind: web, url: http://localhost:5173/dashboard, full_page: true, alt: "Dashboard" }
|
|
47
|
+
- { name: cli-help, kind: cli, command: "mytool --help", alt: "Top-level help" }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Kind | Captures | How |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| **`web`** | a browser page — with optional click/fill/wait steps first | Playwright / Chromium |
|
|
53
|
+
| **`cli` · `native`** *(macOS default)* | a **real screenshot of your Terminal.app window** — your font, your theme | AppleScript + `screencapture` |
|
|
54
|
+
| **`cli` · `rendered`** *(any OS, CI-safe)* | the command's output drawn as a styled terminal card | PTY → ANSI→HTML → Chromium |
|
|
55
|
+
| **`session`** | a **stateful, multi-command flow** in one persistent terminal — one shot per step | one Terminal window, captured after each step |
|
|
56
|
+
|
|
57
|
+
A `session` is how you screenshot a flow whose later steps depend on earlier ones —
|
|
58
|
+
the shell state (cwd, env, background processes) carries across. Background a
|
|
59
|
+
long-running process with `&` and a small `wait_ms`, keep capturing, and the
|
|
60
|
+
session tears it down on close.
|
|
61
|
+
|
|
62
|
+
## Recipes
|
|
63
|
+
|
|
64
|
+
Copy-paste `.capture.yaml` configs for the common jobs — test-evidence proofs, CI
|
|
65
|
+
regeneration, long-running servers, web flows with interactions, versioned visual
|
|
66
|
+
history — live in **[`docs/recipes.md`](docs/recipes.md)**.
|
|
67
|
+
|
|
68
|
+
## Proof reports & pipelines
|
|
69
|
+
|
|
70
|
+
Every `capture run` also writes, next to the PNGs:
|
|
71
|
+
|
|
72
|
+
- **`index.html`** — a self-contained gallery you can open and share as a **proof report**;
|
|
73
|
+
- **`manifest.json`** — a machine-readable record of the run (a pipeline artifact).
|
|
74
|
+
|
|
75
|
+
<img src="https://raw.githubusercontent.com/varmabudharaju/capture/main/docs/proof-report.png" width="100%" alt="The generated index.html gallery: a header with the shot count and timestamp, then a card per shot showing the screenshot, its name, a kind badge, and its alt text."/>
|
|
76
|
+
|
|
77
|
+
Attach `manifest.json` to a CI job, or open `index.html` as test-evidence. Gate CI
|
|
78
|
+
with **`capture check`** — it re-captures and fails when a screenshot drifts from
|
|
79
|
+
the committed baseline (`capture check --update` to accept intended changes; add
|
|
80
|
+
`--diff DIR` to render baseline·current·diff images) — or drop in the bundled
|
|
81
|
+
**GitHub Action**. Turn the report off with `--no-report` (or `output.report:
|
|
82
|
+
false`). Details in **[`docs/pipeline.md`](docs/pipeline.md)**.
|
|
83
|
+
|
|
84
|
+
## Why capture, and not the others
|
|
85
|
+
|
|
86
|
+
The pieces exist in isolation; `capture` is the one tool that does all of it under
|
|
87
|
+
a single committed config.
|
|
88
|
+
|
|
89
|
+
| | web pages | real terminal | CLI sessions | README auto-embed | reproducible / CI |
|
|
90
|
+
| --- | :---: | :---: | :---: | :---: | :---: |
|
|
91
|
+
| **capture** | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
92
|
+
| shot-scraper | ✅ | ❌ | ❌ | ❌ | ✅ |
|
|
93
|
+
| freeze / carbon | ❌ | synthetic | ❌ | ❌ | ✅ |
|
|
94
|
+
| Percy / Chromatic | ✅ | ❌ | ❌ | ❌ | ✅ (cloud, paid) |
|
|
95
|
+
| doing it by hand | 😖 | 😖 | 😖 | ❌ | ❌ |
|
|
96
|
+
|
|
97
|
+
No cloud, no paid services, no special OS permissions for web/rendered shots.
|
|
98
|
+
(Native Terminal capture needs macOS Screen-Recording permission; everything else
|
|
99
|
+
needs nothing.)
|
|
100
|
+
|
|
101
|
+
## How it works
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
.capture.yaml ─► load + validate ─► [ boot app, wait until ready ] ─► one engine
|
|
105
|
+
routes each
|
|
106
|
+
shot by kind:
|
|
107
|
+
web ───────► Playwright / Chromium
|
|
108
|
+
cli·native ► a real Terminal.app window
|
|
109
|
+
cli·render ► PTY → ANSI→HTML → Chromium
|
|
110
|
+
session ───► one persistent Terminal, a shot per step
|
|
111
|
+
─► NN-name.png
|
|
112
|
+
+ README splice
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The clever part is what *isn't* here: **no AI runs at capture time.** Claude's only
|
|
116
|
+
job is to *author* the `.capture.yaml` once by reading your repo; after that the
|
|
117
|
+
engine is a plain, deterministic program — fast, free, and re-runnable in CI with
|
|
118
|
+
no model in the loop. See the full design in [`docs/design.md`](docs/design.md).
|
|
119
|
+
|
|
120
|
+
**Robust by design.** The readiness probe (HTTP / TCP port / log line) means you
|
|
121
|
+
never screenshot a half-booted app, and the app is launched in its own process
|
|
122
|
+
group and torn down — even on a crash or Ctrl-C — so a capture run never leaves an
|
|
123
|
+
orphaned dev server behind.
|
|
124
|
+
|
|
125
|
+
## Capture, captured by capture
|
|
126
|
+
|
|
127
|
+
This repo dogfoods itself: the shots below are produced by running `capture run`
|
|
128
|
+
on its own [`.capture.yaml`](.capture.yaml) and spliced in automatically.
|
|
129
|
+
|
|
130
|
+
<!-- capture:start -->
|
|
131
|
+
### The capture CLI
|
|
132
|
+
|
|
133
|
+
<img src="https://raw.githubusercontent.com/varmabudharaju/capture/main/docs/screenshots/01-the-capture-cli.png" width="100%" alt="capture --help showing the init, validate, and run commands"/>
|
|
134
|
+
|
|
135
|
+
### Run options
|
|
136
|
+
|
|
137
|
+
<img src="https://raw.githubusercontent.com/varmabudharaju/capture/main/docs/screenshots/02-run-options.png" width="100%" alt="capture run options: --config, --only, and --version"/>
|
|
138
|
+
|
|
139
|
+
<!-- capture:end -->
|
|
140
|
+
|
|
141
|
+
## Use with Claude
|
|
142
|
+
|
|
143
|
+
`capture` ships an optional Claude integration in [`integrations/claude/`](integrations/claude/):
|
|
144
|
+
|
|
145
|
+
- a **`/capture` skill** that inspects your repo (routes, `--help`, README), writes
|
|
146
|
+
the `.capture.yaml` for you, and runs it;
|
|
147
|
+
- an optional **auto-snapshot hook** that drops a raw snapshot when a dev server
|
|
148
|
+
starts (the honest "dumb snapshot"; the curated set always comes from `capture run`).
|
|
149
|
+
|
|
150
|
+
## Commands
|
|
151
|
+
|
|
152
|
+
| Command | What it does |
|
|
153
|
+
| --- | --- |
|
|
154
|
+
| `capture init` | Scaffold a starter `.capture.yaml` |
|
|
155
|
+
| `capture validate` | Check the shot list is well-formed |
|
|
156
|
+
| `capture run` | Capture every shot and write outputs |
|
|
157
|
+
| `capture run --only dashboard` | Capture a single shot by name |
|
|
158
|
+
| `capture run --version v2` | Write into a versioned subfolder |
|
|
159
|
+
| `capture check` | Fail if a screenshot drifted from the committed baseline |
|
|
160
|
+
| `capture check --update` | Re-shoot and accept the current screenshots as the baseline |
|
|
161
|
+
| `capture check --diff DIR` | Also render baseline·current·diff images for changed shots |
|
|
162
|
+
|
|
163
|
+
## Develop
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
git clone https://github.com/varmabudharaju/capture && cd capture
|
|
167
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
168
|
+
pip install -e ".[dev]"
|
|
169
|
+
playwright install chromium
|
|
170
|
+
pytest # the suite is fully offline
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The hero GIF is itself reproducible — [`demo.tape`](demo.tape) + `vhs demo.tape`.
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT © Varma Budharaju
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: "capture screenshots"
|
|
2
|
+
description: "Regenerate or drift-check your documentation screenshots with capture."
|
|
3
|
+
branding:
|
|
4
|
+
icon: "camera"
|
|
5
|
+
color: "purple"
|
|
6
|
+
|
|
7
|
+
inputs:
|
|
8
|
+
command:
|
|
9
|
+
description: "Subcommand to run: 'check' (fail on drift) or 'run' (regenerate)."
|
|
10
|
+
required: false
|
|
11
|
+
default: "check"
|
|
12
|
+
config:
|
|
13
|
+
description: "Path to the shot list."
|
|
14
|
+
required: false
|
|
15
|
+
default: ".capture.yaml"
|
|
16
|
+
|
|
17
|
+
runs:
|
|
18
|
+
using: "composite"
|
|
19
|
+
steps:
|
|
20
|
+
- name: Install capture
|
|
21
|
+
shell: bash
|
|
22
|
+
run: pip install shotlist
|
|
23
|
+
- name: Install Chromium
|
|
24
|
+
shell: bash
|
|
25
|
+
run: python -m playwright install --with-deps chromium
|
|
26
|
+
- name: capture ${{ inputs.command }}
|
|
27
|
+
shell: bash
|
|
28
|
+
run: capture ${{ inputs.command }} --config "${{ inputs.config }}"
|
shotlist-0.1.0/demo.tape
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# demo.tape — the hero GIF for capture's README (renders docs/demo.gif).
|
|
2
|
+
# Re-render anytime with: vhs demo.tape (run from the repo root)
|
|
3
|
+
Output docs/demo.gif
|
|
4
|
+
|
|
5
|
+
Set Shell "bash"
|
|
6
|
+
Set FontSize 18
|
|
7
|
+
Set Width 1200
|
|
8
|
+
Set Height 280
|
|
9
|
+
Set Padding 24
|
|
10
|
+
Set Theme "Catppuccin Mocha"
|
|
11
|
+
Set TypingSpeed 18ms
|
|
12
|
+
|
|
13
|
+
# --- stage a throwaway playground, off camera ---
|
|
14
|
+
Hide
|
|
15
|
+
Type "source demo/_setup.sh" Enter
|
|
16
|
+
Sleep 1.5s
|
|
17
|
+
Show
|
|
18
|
+
|
|
19
|
+
# --- Scene 1: adding docs screenshots, the old way ---
|
|
20
|
+
Type "# adding a screenshot to the docs, the old way..." Enter
|
|
21
|
+
Sleep 600ms
|
|
22
|
+
Type `mv "Desktop/Screen Shot 2026-06-24 at 3.47.11 PM.png" docs/dashboard.png` Enter
|
|
23
|
+
Sleep 350ms
|
|
24
|
+
Type `mv "Desktop/Screen Shot 2026-06-24 at 3.52.02 PM.png" docs/dashboard-final.png` Enter
|
|
25
|
+
Sleep 350ms
|
|
26
|
+
Type `mv "Desktop/Screen Shot 2026-06-24 at 4.02.59 PM.png" docs/dashboard-final-FINAL-v2-USE-THIS.png` Enter
|
|
27
|
+
Sleep 700ms
|
|
28
|
+
Type "# ...then you ship one UI tweak and every screenshot is stale." Enter
|
|
29
|
+
Sleep 1.6s
|
|
30
|
+
Type "clear" Enter
|
|
31
|
+
Sleep 300ms
|
|
32
|
+
|
|
33
|
+
# --- Scene 2: the capture way (a real run) ---
|
|
34
|
+
Type "# the capture way:" Enter
|
|
35
|
+
Sleep 500ms
|
|
36
|
+
Type "capture run" Enter
|
|
37
|
+
Sleep 5s
|
|
38
|
+
Type "# one shot list. one command. never stale again." Enter
|
|
39
|
+
Sleep 2s
|