capture-flow 0.0.0-stage → 0.1.1
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.
- package/LICENSE +21 -0
- package/README.md +256 -2
- package/bin/capture-flow.mjs +12 -0
- package/package.json +56 -4
- package/skills/capture-flow/SKILL.md +248 -0
- package/skills/capture-flow/references/capture-recipes.md +155 -0
- package/skills/capture-flow/references/store-sizes.md +91 -0
- package/src/build.ts +186 -0
- package/src/capture.ts +77 -0
- package/src/cli.ts +139 -0
- package/src/index.ts +11 -0
- package/src/manifest.ts +98 -0
- package/src/render.ts +75 -0
- package/src/verify.ts +90 -0
- package/vendor/wajha/LICENSE +21 -0
- package/vendor/wajha/NOTICE +21 -0
- package/vendor/wajha/engine/background.ts +55 -0
- package/vendor/wajha/engine/bidi.ts +589 -0
- package/vendor/wajha/engine/cache.ts +96 -0
- package/vendor/wajha/engine/color.ts +125 -0
- package/vendor/wajha/engine/devices/android.ts +53 -0
- package/vendor/wajha/engine/devices/browser.ts +170 -0
- package/vendor/wajha/engine/devices/finish.ts +154 -0
- package/vendor/wajha/engine/devices/geometry.ts +326 -0
- package/vendor/wajha/engine/devices/index.ts +143 -0
- package/vendor/wajha/engine/devices/ipad.ts +21 -0
- package/vendor/wajha/engine/devices/iphone.ts +55 -0
- package/vendor/wajha/engine/devices/macbook.ts +187 -0
- package/vendor/wajha/engine/devices/mock/apps/calendar.ts +145 -0
- package/vendor/wajha/engine/devices/mock/apps/chat.ts +213 -0
- package/vendor/wajha/engine/devices/mock/apps/finance.ts +164 -0
- package/vendor/wajha/engine/devices/mock/apps/fitness.ts +145 -0
- package/vendor/wajha/engine/devices/mock/apps/index.ts +14 -0
- package/vendor/wajha/engine/devices/mock/apps/meditation.ts +167 -0
- package/vendor/wajha/engine/devices/mock/apps/music.ts +152 -0
- package/vendor/wajha/engine/devices/mock/apps/shop.ts +116 -0
- package/vendor/wajha/engine/devices/mock/apps/social.ts +152 -0
- package/vendor/wajha/engine/devices/mock/apps/tasks.ts +131 -0
- package/vendor/wajha/engine/devices/mock/apps/travel.ts +121 -0
- package/vendor/wajha/engine/devices/mock/art.ts +557 -0
- package/vendor/wajha/engine/devices/mock/chrome.ts +337 -0
- package/vendor/wajha/engine/devices/mock/icons.ts +614 -0
- package/vendor/wajha/engine/devices/mock/kit.ts +213 -0
- package/vendor/wajha/engine/devices/mock/rng.ts +40 -0
- package/vendor/wajha/engine/devices/mock/theme.ts +133 -0
- package/vendor/wajha/engine/devices/mock/widgets.ts +231 -0
- package/vendor/wajha/engine/devices/paths.ts +113 -0
- package/vendor/wajha/engine/devices/screen.ts +75 -0
- package/vendor/wajha/engine/devices/shadow.ts +124 -0
- package/vendor/wajha/engine/devices/slab.ts +200 -0
- package/vendor/wajha/engine/devices/vision.ts +43 -0
- package/vendor/wajha/engine/devices/watch.ts +14 -0
- package/vendor/wajha/engine/geometry.ts +152 -0
- package/vendor/wajha/engine/images.ts +96 -0
- package/vendor/wajha/engine/index.ts +20 -0
- package/vendor/wajha/engine/linebreak.ts +226 -0
- package/vendor/wajha/engine/markup.ts +84 -0
- package/vendor/wajha/engine/mock.ts +158 -0
- package/vendor/wajha/engine/paint.ts +474 -0
- package/vendor/wajha/engine/patterns.ts +263 -0
- package/vendor/wajha/engine/render.ts +364 -0
- package/vendor/wajha/engine/shapes.ts +260 -0
- package/vendor/wajha/engine/testing/canvasNode.ts +121 -0
- package/vendor/wajha/engine/testing/fakeDevices.ts +85 -0
- package/vendor/wajha/engine/text.ts +694 -0
- package/vendor/wajha/engine/types.ts +190 -0
- package/vendor/wajha/export/paths.ts +197 -0
- package/vendor/wajha/export/png.ts +223 -0
- package/vendor/wajha/export/types.ts +60 -0
- package/vendor/wajha/model/adapt.ts +889 -0
- package/vendor/wajha/model/catalog.ts +369 -0
- package/vendor/wajha/model/clone.ts +58 -0
- package/vendor/wajha/model/colorMath.ts +128 -0
- package/vendor/wajha/model/defaults.ts +386 -0
- package/vendor/wajha/model/geometry.ts +95 -0
- package/vendor/wajha/model/ids.ts +13 -0
- package/vendor/wajha/model/instantiate.ts +280 -0
- package/vendor/wajha/model/mirror.ts +43 -0
- package/vendor/wajha/model/resolve.ts +87 -0
- package/vendor/wajha/model/rules.ts +249 -0
- package/vendor/wajha/model/stub.ts +4 -0
- package/vendor/wajha/model/types.ts +456 -0
- package/vendor/wajha/model/typography.ts +28 -0
- package/vendor/wajha/model/validate.ts +917 -0
- package/vendor/wajha/templates/builtin/aurora.ts +163 -0
- package/vendor/wajha/templates/builtin/bubblegum.ts +132 -0
- package/vendor/wajha/templates/builtin/citrusPop.ts +124 -0
- package/vendor/wajha/templates/builtin/clayGarden.ts +114 -0
- package/vendor/wajha/templates/builtin/desertDune.ts +97 -0
- package/vendor/wajha/templates/builtin/glassPrism.ts +139 -0
- package/vendor/wajha/templates/builtin/launchpad.ts +140 -0
- package/vendor/wajha/templates/builtin/loudAndClear.ts +109 -0
- package/vendor/wajha/templates/builtin/midnightNeon.ts +108 -0
- package/vendor/wajha/templates/builtin/monoSwiss.ts +149 -0
- package/vendor/wajha/templates/builtin/noirGold.ts +149 -0
- package/vendor/wajha/templates/builtin/noor.ts +107 -0
- package/vendor/wajha/templates/builtin/obsidian.ts +137 -0
- package/vendor/wajha/templates/builtin/panoramaPeaks.ts +145 -0
- package/vendor/wajha/templates/builtin/panoramaRibbon.ts +138 -0
- package/vendor/wajha/templates/builtin/panoramaWaves.ts +138 -0
- package/vendor/wajha/templates/builtin/paperAndInk.ts +98 -0
- package/vendor/wajha/templates/builtin/sage.ts +94 -0
- package/vendor/wajha/templates/builtin/splitTone.ts +129 -0
- package/vendor/wajha/templates/builtin/sunsetBoulevard.ts +127 -0
- package/vendor/wajha/templates/index.ts +55 -0
- package/vendor/wajha/templates/kit.ts +761 -0
- package/vendor/wajha/ui/sortShots.ts +239 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 capture-flow contributors
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,257 @@
|
|
|
1
|
-
#
|
|
1
|
+
# capture-flow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Capture once, ship store-ready screenshots.** `capture-flow` runs your own capture commands (Playwright, `simctl`, `adb`, Maestro, fastlane — anything that writes an image), then frames those captures in a polished template and exports exact App Store and Google Play sizes.
|
|
4
|
+
|
|
5
|
+
It is a small CLI and library, not a screenshot service. The capture half is bring-your-own so it can live in any pipeline; the build half is deterministic and store-aware.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
your app ──► capture commands ──► raw PNG/JPEG/WebP ──► capture-flow build ──► App Store + Google Play
|
|
9
|
+
(flow.json) (shots.json) exact sizes, RGB PNG
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install --save-dev capture-flow
|
|
16
|
+
npx capture-flow --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Node **22.18+**. The renderer uses `@napi-rs/canvas` (prebuilt binaries, no system Chrome), and the template fonts ship as dependencies.
|
|
20
|
+
|
|
21
|
+
To run the included Tilawah example you also need Playwright in your project:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install --save-dev playwright
|
|
25
|
+
npx playwright install chromium
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Install as an agent skill
|
|
29
|
+
|
|
30
|
+
The repository ships a `capture-flow` skill so coding agents know the manifest
|
|
31
|
+
contract, capture rules, and failure fixes without reading this README first.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx skills add adelpro/capture-flow
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Add `-g` for a global install, or `-a claude-code -a codex -a opencode` to target
|
|
38
|
+
specific agents. The skill also ships inside the npm package, so a local install works:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx skills add ./node_modules/capture-flow
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Skill source: [`skills/capture-flow/SKILL.md`](skills/capture-flow/SKILL.md).
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
A **flow** describes how to capture. A **manifest** describes how to frame it.
|
|
49
|
+
|
|
50
|
+
`flow.json`
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"manifest": "./shots.json",
|
|
55
|
+
"out": "./store-assets",
|
|
56
|
+
"captures": [
|
|
57
|
+
{ "name": "iPhone", "command": "node capture-iphone.mjs" },
|
|
58
|
+
{ "name": "Android", "command": "node capture-android.mjs" }
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`shots.json`
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"app": "My App",
|
|
68
|
+
"template": "noor",
|
|
69
|
+
"structure": "stores",
|
|
70
|
+
"languages": ["en-US", "ar-SA"],
|
|
71
|
+
"sizes": ["ios-6.9", "ios-6.3", "play-phone"],
|
|
72
|
+
"slides": [
|
|
73
|
+
{
|
|
74
|
+
"templateSlide": 0,
|
|
75
|
+
"headline": "Listen with *focus*",
|
|
76
|
+
"subtitle": "A calm player for long recordings.",
|
|
77
|
+
"captures": {
|
|
78
|
+
"iphone": "captures/iphone-01.png",
|
|
79
|
+
"android-phone": "captures/android-01.png"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Run the whole flow:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx capture-flow run --flow flow.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Or run the stages separately:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npx capture-flow capture --flow flow.json
|
|
96
|
+
npx capture-flow build --manifest shots.json
|
|
97
|
+
npx capture-flow verify --dir store-assets
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Commands
|
|
101
|
+
|
|
102
|
+
| Command | What it does |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| `capture-flow capture --flow <file>` | Runs each flow step in order, stopping on the first failure. |
|
|
105
|
+
| `capture-flow build --manifest <file> [--out <dir>] [--json]` | Renders the manifest to store PNGs and writes `capture-flow-report.json`. |
|
|
106
|
+
| `capture-flow verify --dir <dir> [--json]` | Checks every PNG is 8-bit RGB (no alpha) at the exact size in the report, and that no planned file is missing. |
|
|
107
|
+
| `capture-flow run --flow <file> [--manifest <file>] [--out <dir>] [--json]` | `capture` → `build` → `verify` in one command. |
|
|
108
|
+
|
|
109
|
+
Paths inside a flow are relative to the flow file; paths inside a manifest are relative to the manifest file. Capture commands run with the caller's environment plus any `env` entries.
|
|
110
|
+
|
|
111
|
+
## Flow schema
|
|
112
|
+
|
|
113
|
+
| Field | Required | Meaning |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| `captures` | yes | Ordered array of steps. |
|
|
116
|
+
| `captures[].command` | yes | Shell command (run with the system shell). |
|
|
117
|
+
| `captures[].name` | no | Label shown in the log. |
|
|
118
|
+
| `captures[].cwd` | no | Working directory, relative to the flow file. |
|
|
119
|
+
| `captures[].env` | no | Extra environment variables for this step. |
|
|
120
|
+
| `manifest` | no | Manifest to build after capture; used by `run`. |
|
|
121
|
+
| `out` | no | Output directory for `run`. Overrides the manifest's `out`. |
|
|
122
|
+
|
|
123
|
+
Because steps are plain commands, a native flow is just as natural as a web one:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"manifest": "./shots.json",
|
|
128
|
+
"captures": [
|
|
129
|
+
{ "name": "iOS", "command": "xcrun simctl io booted screenshot captures/ios-01.png" },
|
|
130
|
+
{ "name": "Android", "command": "adb exec-out screencap -p > captures/android-01.png" }
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Manifest schema
|
|
136
|
+
|
|
137
|
+
| Field | Required | Meaning |
|
|
138
|
+
| --- | --- | --- |
|
|
139
|
+
| `app` | yes | Project name (also used by the renderer). |
|
|
140
|
+
| `template` | yes | Template id from the list below. |
|
|
141
|
+
| `languages` | yes | Store locale codes, e.g. `["en-US", "ar-SA"]`. `ar-SA` exports as `ar` for Google Play. |
|
|
142
|
+
| `slides` | yes | Ordered slides; each maps a device family to a capture. |
|
|
143
|
+
| `out` | no | Output directory, relative to the manifest. Default `store-assets`. |
|
|
144
|
+
| `structure` | no | `stores` (default), `flat`, or `fastlane`. |
|
|
145
|
+
| `sizes` | no | Subset of size ids. Defaults to every default size of the families used. |
|
|
146
|
+
| `slides[].headline` | yes | `*word*` highlights a run; a newline is a hard break. |
|
|
147
|
+
| `slides[].subtitle` | no | Secondary copy. |
|
|
148
|
+
| `slides[].templateSlide` | no | Which template slide layout to use (defaults to the slide's index). |
|
|
149
|
+
| `slides[].captures` | yes | Device family → image path. |
|
|
150
|
+
|
|
151
|
+
Device families: `iphone`, `iphone-landscape`, `ipad`, `ipad-landscape`, `android-phone`, `android-landscape`, `android-tablet`, `mac`, `watch`, `apple-tv`, `vision`, `play-feature`.
|
|
152
|
+
|
|
153
|
+
Store sizes: `ios-6.9`, `ios-6.9-b`, `ios-6.5`, `ios-6.5-b`, `ios-6.3`, `ios-6.1`, `ipad-13`, `ipad-12.9`, `ipad-11`, `play-phone`, `play-phone-hd`, `play-tablet-7`, `play-tablet-10`, plus the corresponding `-land` ids. Run `build` with an unknown id to get the full list in the error.
|
|
154
|
+
|
|
155
|
+
The default `stores` layout is what the stores expect:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
store-assets/
|
|
159
|
+
App Store/
|
|
160
|
+
ar-SA/
|
|
161
|
+
iPhone 6.9/01.png … 04.png
|
|
162
|
+
iPhone 6.3/01.png … 04.png
|
|
163
|
+
iPad 13/01.png … 04.png
|
|
164
|
+
Google Play/
|
|
165
|
+
ar/
|
|
166
|
+
Phone/01.png … 04.png
|
|
167
|
+
Tablet 10/01.png … 04.png
|
|
168
|
+
capture-flow-report.json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Controlling the result
|
|
172
|
+
|
|
173
|
+
| You want to change | Where it lives |
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| Which app screen is shown | Your capture command (Playwright route, simulator state, deep link). Map it to a slide with `slides[].captures`. |
|
|
176
|
+
| The slide layout / composition | `slides[].templateSlide` — picks one of the template's layouts (hero, tilted, hanging, …). |
|
|
177
|
+
| The copy | `slides[].headline` and `slides[].subtitle`. `*word*` renders as the highlighted accent run; a newline is a hard break. |
|
|
178
|
+
| Colours and fonts | The `template` you pick. Each built-in template ships a fixed palette, background, and heading/body typeface. |
|
|
179
|
+
| App light/dark screenshot | The capture script's stored setting (`settings.theme` in the example's `localStorage` blob). |
|
|
180
|
+
| Which store sizes | `sizes` (or omit it to get every default size of the families you captured). |
|
|
181
|
+
|
|
182
|
+
Manifest-level colour/font overrides are deliberately not exposed: a template is a designed system, and mixing in arbitrary colours usually breaks it. To go beyond the built-ins, copy a template under `vendor/wajha/templates/builtin/`, change its `theme`/`typeset`, and use its `id` in the manifest.
|
|
183
|
+
|
|
184
|
+
### Large screens
|
|
185
|
+
|
|
186
|
+
Capture tables are just another family:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"templateSlide": 0,
|
|
191
|
+
"headline": "…",
|
|
192
|
+
"captures": {
|
|
193
|
+
"ipad": "captures/ipad-01.png",
|
|
194
|
+
"android-tablet": "captures/android-tablet-01.png"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
and export them with `ipad-13` / `play-tablet-10`. Match the frame's screen aspect so nothing is distorted:
|
|
200
|
+
|
|
201
|
+
- **iPad 13″** — 3:4, e.g. capture at 2064×2752 (CSS 768×1024 @2.6875).
|
|
202
|
+
- **Android tablet** — 16:10 inside a 9:16 export, e.g. capture at 1600×2560 (CSS 800×1280 @2), exported at 1440×2560.
|
|
203
|
+
|
|
204
|
+
Templates with native tablet layouts (`mono-swiss`, `noir-gold`, `obsidian`, `panorama-*`, …) compose the tablet canvas directly. Phone-first templates such as `noor` adapt their tall layout and swap the frame to iPad/Android tablet automatically.
|
|
205
|
+
|
|
206
|
+
## Templates
|
|
207
|
+
|
|
208
|
+
| Category | Templates |
|
|
209
|
+
| --- | --- |
|
|
210
|
+
| `elegant` | `desert-dune`, `noir-gold`, `paper-and-ink` |
|
|
211
|
+
| `dark` | `midnight-neon`, `noor`, `obsidian` |
|
|
212
|
+
| `minimal` | `launchpad`, `mono-swiss`, `sage` |
|
|
213
|
+
| `gradient` | `aurora`, `glass-prism`, `sunset-boulevard` |
|
|
214
|
+
| `playful` | `bubblegum`, `clay-garden` |
|
|
215
|
+
| `bold` | `citrus-pop`, `loud-and-clear`, `split-tone` |
|
|
216
|
+
| `panorama` | `panorama-peaks`, `panorama-ribbon`, `panorama-waves` |
|
|
217
|
+
|
|
218
|
+
`noor` (deep emerald, gold geometry, Cairo/Noto Sans Arabic) is the template used by the Tilawah example.
|
|
219
|
+
|
|
220
|
+
## Example: open-tilawa
|
|
221
|
+
|
|
222
|
+
[`examples/open-tilawa`](examples/open-tilawa) captures the deployed [Tilawah](https://tilawah.cc) web app with Playwright and builds Arabic App Store and Google Play sets:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# Playwright lives outside the project in this workspace; point the example at it:
|
|
226
|
+
PLAYWRIGHT_MODULE=/path/to/node_modules/playwright \
|
|
227
|
+
npx capture-flow run --flow examples/open-tilawa/flow.json
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The generated set is checked in at `examples/open-tilawa/store-assets` (20 files: iPhone 6.9″, iPhone 6.3″, iPad 13″, Google Play phone, Google Play tablet). Three details in the example are worth copying:
|
|
231
|
+
|
|
232
|
+
- **Hydration:** the deployed app needs ~20 s to boot. The script waits for the real UI (`getByRole('button', { name: 'تشغيل' })`) rather than for `load`, and navigates with `waitUntil: 'commit'`.
|
|
233
|
+
- **Audio:** headless Chromium cannot use Tilawah's service-worker WebTorrent stream, so the script points `/webtorrent/…` at Tilawah's own documented `/media/` web seed. The player then shows real playback time and an active spectrum instead of a paused 0:00.
|
|
234
|
+
- **Large screens:** the same four screens are captured at tablet viewports (`ipad`, `android-tablet`) and mapped to `ipad-13` / `play-tablet-10` in the manifest.
|
|
235
|
+
|
|
236
|
+
## Library
|
|
237
|
+
|
|
238
|
+
The package ships TypeScript (the CLI runs it through `tsx`), so import it from a `tsx` script or a bundler:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
import { build } from 'capture-flow';
|
|
242
|
+
|
|
243
|
+
const report = await build({ manifestPath: 'shots.json', outDir: 'store-assets' });
|
|
244
|
+
console.log(report.files, report.warnings);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Subpaths are available for the pieces: `capture-flow/build`, `capture-flow/capture`, `capture-flow/manifest`, `capture-flow/verify`. `build` returns the files it wrote and any layout warnings (overflowing text, missing screenshots).
|
|
248
|
+
|
|
249
|
+
## Original idea and credits
|
|
250
|
+
|
|
251
|
+
This project exists because of **[Wajha](https://github.com/M-Alkassem/Wajha)** by [M-Alkassem](https://github.com/M-Alkassem) and contributors — the original store-screenshot designer with the Canvas engine, templates, device frames and export rules.
|
|
252
|
+
|
|
253
|
+
capture-flow is a new orchestration layer around that idea: it adds the capture flow, the manifest/CLI contract, verify, and headless Node rendering. The rendering core is **vendored** from Wajha under `vendor/wajha/` (model, engine, export naming, templates, slide sorting) and is used under the MIT License. The full notice is in [`vendor/wajha/LICENSE`](vendor/wajha/LICENSE) and [`vendor/wajha/NOTICE`](vendor/wajha/NOTICE).
|
|
254
|
+
|
|
255
|
+
Wajha is MIT-licensed, copyright © 2026 Wajha contributors. capture-flow is MIT-licensed too; see [`LICENSE`](LICENSE).
|
|
256
|
+
|
|
257
|
+
If you only need to design screenshots interactively, use Wajha. If you need screenshots produced repeatedly by a pipeline, use capture-flow — and consider upstreaming improvements to the engine.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* npm entry point.
|
|
4
|
+
*
|
|
5
|
+
* The tool ships as TypeScript (its vendored engine is TypeScript too), so the
|
|
6
|
+
* launcher registers tsx and then loads the CLI. Keeping one source of truth
|
|
7
|
+
* beats a separate build output that can drift from the examples.
|
|
8
|
+
*/
|
|
9
|
+
import { register } from 'tsx/esm/api';
|
|
10
|
+
|
|
11
|
+
register();
|
|
12
|
+
await import(new URL('../src/cli.ts', import.meta.url).href);
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capture-flow",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Turn raw simulator and browser captures into App Store and Google Play screenshots.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "adelpro",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"screenshots",
|
|
10
|
+
"app-store",
|
|
11
|
+
"google-play",
|
|
12
|
+
"store-assets",
|
|
13
|
+
"fastlane",
|
|
14
|
+
"playwright",
|
|
15
|
+
"simctl",
|
|
16
|
+
"adb"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"bin": {
|
|
22
|
+
"capture-flow": "./bin/capture-flow.mjs"
|
|
23
|
+
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./src/index.ts",
|
|
26
|
+
"./build": "./src/build.ts",
|
|
27
|
+
"./manifest": "./src/manifest.ts",
|
|
28
|
+
"./capture": "./src/capture.ts",
|
|
29
|
+
"./verify": "./src/verify.ts"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"bin",
|
|
33
|
+
"src",
|
|
34
|
+
"vendor",
|
|
35
|
+
"skills"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsx src/cli.ts build",
|
|
39
|
+
"verify": "tsx src/cli.ts verify",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"example:tilawah": "tsx src/cli.ts run --flow examples/open-tilawa/flow.json"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@fontsource-variable/cairo": "^5.3.0",
|
|
45
|
+
"@fontsource-variable/inter": "^5.3.0",
|
|
46
|
+
"@fontsource-variable/noto-sans-arabic": "^5.3.0",
|
|
47
|
+
"@napi-rs/canvas": "^1.1.0",
|
|
48
|
+
"fflate": "^0.8.3",
|
|
49
|
+
"tsx": "^4.20.6"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^24.19.2",
|
|
53
|
+
"typescript": "^5.9.3"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=22.18"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: capture-flow
|
|
3
|
+
description: Turn raw device or browser captures into App Store and Google Play screenshot sets. Use when asked to generate, polish, resize, frame, or validate store screenshots, to add headlines and device frames to app screens, or to set up a repeatable screenshot pipeline with Playwright, simctl, adb, or Maestro.
|
|
4
|
+
metadata:
|
|
5
|
+
author: adelpro
|
|
6
|
+
version: "0.1.1"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# capture-flow
|
|
10
|
+
|
|
11
|
+
`capture-flow` is a CLI that takes plain screenshots plus a small JSON manifest and
|
|
12
|
+
renders exact-size store assets with device frames and composed copy.
|
|
13
|
+
|
|
14
|
+
It has three stages:
|
|
15
|
+
|
|
16
|
+
1. **capture** — run your own screenshot commands (Playwright, `simctl`, `adb`, Maestro, anything).
|
|
17
|
+
2. **build** — map each raw capture to a slide and render every store size.
|
|
18
|
+
3. **verify** — check that every PNG is the exact size, 8-bit RGB, and has no alpha.
|
|
19
|
+
|
|
20
|
+
Do not invent your own resizing pipeline. Capture at native resolution, let `build` do the rest.
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install --save-dev capture-flow
|
|
26
|
+
npx capture-flow --help
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Node.js **22.18+** is required. Playwright, Xcode, or Android tools are only needed for
|
|
30
|
+
the capture stage; `build` and `verify` run on Node alone.
|
|
31
|
+
|
|
32
|
+
If the project already has `capture-flow` in `package.json`, use `npx capture-flow`
|
|
33
|
+
or the local `node_modules/.bin/capture-flow`.
|
|
34
|
+
|
|
35
|
+
## Workflow
|
|
36
|
+
|
|
37
|
+
1. **Decide the target set** — device families, store sizes, languages, and slide count.
|
|
38
|
+
2. **Write a capture command** that saves full-resolution PNGs into a folder.
|
|
39
|
+
3. **Write `flow.json`** to run that command (or run it manually first).
|
|
40
|
+
4. **Write `shots.json`** mapping captures to slides.
|
|
41
|
+
5. **Run** everything and read the warnings.
|
|
42
|
+
6. **Fix** overflow / missing-screen warnings, rebuild.
|
|
43
|
+
7. **Verify** and inspect the first image of each output folder.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx capture-flow run --flow flow.json
|
|
47
|
+
npx capture-flow verify --dir store-assets
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use `--json` on `build`, `run`, or `verify` when another program consumes the result.
|
|
51
|
+
|
|
52
|
+
## Capture rules
|
|
53
|
+
|
|
54
|
+
These prevent the most common bad output.
|
|
55
|
+
|
|
56
|
+
- Capture at the device's **native pixel size**, never upscale. The renderer can scale
|
|
57
|
+
down but cannot invent detail.
|
|
58
|
+
- Match the frame's screen aspect ratio. A mismatched capture is cropped or distorted.
|
|
59
|
+
- No transparency. `verify` rejects alpha; use an opaque background when capturing.
|
|
60
|
+
- One file per screen per family. Name them predictably (`iphone-01.png`, `android-01.png`, …).
|
|
61
|
+
- Hide dev tooling, cursors, toasts, cookie banners, and consent dialogs before the shot.
|
|
62
|
+
- Wait for real content (fonts, data, animation end) before `screenshot()`. A skeleton
|
|
63
|
+
or empty state is not a store screenshot.
|
|
64
|
+
- Keep text in screenshots readable at thumbnail size; avoid dense tables.
|
|
65
|
+
|
|
66
|
+
Reference dimensions (portrait):
|
|
67
|
+
|
|
68
|
+
| Family | Capture at | Typical CSS viewport |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| `iphone` | 1320 × 2868 | 440 × 956 @3 |
|
|
71
|
+
| `ipad` | 2064 × 2752 | 768 × 1024 @2.6875 |
|
|
72
|
+
| `android-phone` | 1080 × 1920 or higher (9:16) | 360 × 640 @3 |
|
|
73
|
+
| `android-tablet` | 1600 × 2560 (16:10) | 800 × 1280 @2 |
|
|
74
|
+
| `mac` | 2880 × 1800 | — |
|
|
75
|
+
|
|
76
|
+
Native capture recipes for Playwright, iOS Simulator, and Android are in
|
|
77
|
+
[references/capture-recipes.md](references/capture-recipes.md).
|
|
78
|
+
|
|
79
|
+
## flow.json
|
|
80
|
+
|
|
81
|
+
A flow is an ordered list of shell commands. Paths are relative to the flow file.
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"manifest": "./shots.json",
|
|
86
|
+
"out": "./store-assets",
|
|
87
|
+
"captures": [
|
|
88
|
+
{ "name": "web (Playwright)", "command": "node capture.mjs" },
|
|
89
|
+
{ "name": "iOS", "command": "xcrun simctl io booted screenshot captures/ios-01.png" }
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Field | Required | Meaning |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| `captures[].command` | yes | Shell command, run with the system shell. |
|
|
97
|
+
| `captures[].name` | no | Label in the log. |
|
|
98
|
+
| `captures[].cwd` | no | Working directory, relative to the flow file. |
|
|
99
|
+
| `captures[].env` | no | Extra environment variables for the step. |
|
|
100
|
+
| `manifest` | no | Manifest used by `run`. |
|
|
101
|
+
| `out` | no | Output directory used by `run`. |
|
|
102
|
+
|
|
103
|
+
Steps stop at the first non-zero exit code. Use `capture-flow capture --flow flow.json`
|
|
104
|
+
to run only the capture stage.
|
|
105
|
+
|
|
106
|
+
## shots.json
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"app": "My App",
|
|
111
|
+
"template": "noor",
|
|
112
|
+
"languages": ["ar-SA"],
|
|
113
|
+
"sizes": ["ios-6.9", "ios-6.3", "ipad-13", "play-phone", "play-tablet-10"],
|
|
114
|
+
"slides": [
|
|
115
|
+
{
|
|
116
|
+
"templateSlide": 0,
|
|
117
|
+
"headline": "استمع *بخشوع*",
|
|
118
|
+
"subtitle": "تلاوات كاملة ومؤشر طيفي.",
|
|
119
|
+
"captures": {
|
|
120
|
+
"iphone": "captures/iphone-01.png",
|
|
121
|
+
"android-phone": "captures/android-01.png",
|
|
122
|
+
"ipad": "captures/ipad-01.png",
|
|
123
|
+
"android-tablet": "captures/android-tablet-01.png"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
| Field | Required | Meaning |
|
|
131
|
+
| --- | --- | --- |
|
|
132
|
+
| `app` | yes | Project name; also used in the report and fastlane paths. |
|
|
133
|
+
| `template` | yes | Built-in template id. |
|
|
134
|
+
| `languages` | yes | Store locales, e.g. `["en-US", "ar-SA"]`. `ar-SA` exports as `ar` on Google Play. |
|
|
135
|
+
| `slides` | yes | Ordered slides; each maps device family → capture. |
|
|
136
|
+
| `out` | no | Output directory, relative to the manifest. Default `store-assets`. |
|
|
137
|
+
| `structure` | no | `stores` (default), `flat`, or `fastlane`. |
|
|
138
|
+
| `sizes` | no | Subset of size ids; defaults to the default size of every family used. |
|
|
139
|
+
| `slides[].headline` | yes | `*word*` marks the accent run; `\n` is a hard break. |
|
|
140
|
+
| `slides[].subtitle` | no | Secondary copy. |
|
|
141
|
+
| `slides[].templateSlide` | no | Which template layout to use; defaults to the slide index. |
|
|
142
|
+
| `slides[].captures` | yes | Device family → image path (`png`, `jpg`, `jpeg`, `webp`). |
|
|
143
|
+
|
|
144
|
+
Families: `iphone`, `iphone-landscape`, `ipad`, `ipad-landscape`, `android-phone`,
|
|
145
|
+
`android-landscape`, `android-tablet`, `mac`, `watch`, `apple-tv`, `vision`, `play-feature`.
|
|
146
|
+
|
|
147
|
+
Store limits: App Store accepts up to 10 slides per size, Google Play up to 8.
|
|
148
|
+
Respect them before designing a long story.
|
|
149
|
+
|
|
150
|
+
Store sizes: `ios-6.9`, `ios-6.9-b`, `ios-6.5`, `ios-6.5-b`, `ios-6.3`, `ios-6.1`,
|
|
151
|
+
`ipad-13`, `ipad-12.9`, `ipad-11`, `play-phone`, `play-phone-hd`, `play-tablet-7`,
|
|
152
|
+
`play-tablet-10`, plus `-land` variants. Full pixel dimensions are in
|
|
153
|
+
[references/store-sizes.md](references/store-sizes.md).
|
|
154
|
+
|
|
155
|
+
Every slide must supply every family you want in the output. A slide without a family
|
|
156
|
+
produces a placeholder warning and an image with an empty frame — treat that as a bug.
|
|
157
|
+
|
|
158
|
+
## Choosing a template
|
|
159
|
+
|
|
160
|
+
| Category | Templates |
|
|
161
|
+
| --- | --- |
|
|
162
|
+
| `elegant` | `desert-dune`, `noir-gold`, `paper-and-ink` |
|
|
163
|
+
| `dark` | `midnight-neon`, `noor`, `obsidian` |
|
|
164
|
+
| `minimal` | `launchpad`, `mono-swiss`, `sage` |
|
|
165
|
+
| `gradient` | `aurora`, `glass-prism`, `sunset-boulevard` |
|
|
166
|
+
| `playful` | `bubblegum`, `clay-garden` |
|
|
167
|
+
| `bold` | `citrus-pop`, `loud-and-clear`, `split-tone` |
|
|
168
|
+
| `panorama` | `panorama-peaks`, `panorama-ribbon`, `panorama-waves` |
|
|
169
|
+
|
|
170
|
+
- Arabic-first copy: `noor` (Cairo + Noto Sans Arabic, emerald and gold).
|
|
171
|
+
- Native tablet composition: `mono-swiss`, `noir-gold`, `obsidian`, `panorama-*`.
|
|
172
|
+
- Phone-first templates adapt to iPad/Android tablet by swapping the frame and
|
|
173
|
+
reflowing the tall layout. This is acceptable but less deliberate.
|
|
174
|
+
|
|
175
|
+
A template owns the palette and typefaces. There are deliberately no per-manifest colour
|
|
176
|
+
or font overrides — mixing arbitrary colours into a designed system usually breaks it.
|
|
177
|
+
To customize, copy a template from `node_modules/capture-flow/vendor/wajha/templates/builtin/`,
|
|
178
|
+
edit its `theme`/`typeset`, and register it in your own build. Ask the user before
|
|
179
|
+
forking a template.
|
|
180
|
+
|
|
181
|
+
`templateSlide` selects a composition (hero, tilted, pair, hanging, …). Layouts differ
|
|
182
|
+
per template. To see the available layouts and their default copy, read the template
|
|
183
|
+
file:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
sed -n '/slides: \[/,/^ ]/p' node_modules/capture-flow/vendor/wajha/templates/builtin/noor.ts
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Out-of-range values fail with `"<template>" has <n>` — that number is the layout count.
|
|
190
|
+
|
|
191
|
+
## Output
|
|
192
|
+
|
|
193
|
+
The default `stores` structure is store-upload ready:
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
store-assets/
|
|
197
|
+
App Store/
|
|
198
|
+
ar-SA/
|
|
199
|
+
iPhone 6.9/01.png … 10.png
|
|
200
|
+
iPhone 6.3/01.png …
|
|
201
|
+
iPad 13/01.png …
|
|
202
|
+
Google Play/
|
|
203
|
+
ar/
|
|
204
|
+
Phone/01.png …
|
|
205
|
+
Tablet 10/01.png …
|
|
206
|
+
capture-flow-report.json
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`capture-flow-report.json` lists every file with its `width`, `height`, `family`,
|
|
210
|
+
`lang`, and `slide`, plus `warnings`. Read it instead of guessing what was produced.
|
|
211
|
+
|
|
212
|
+
`flat` writes `01-iphone-en-US.png`-style names; `fastlane` writes
|
|
213
|
+
`fastlane/screenshots/<locale>/`.
|
|
214
|
+
|
|
215
|
+
## Warnings and failures
|
|
216
|
+
|
|
217
|
+
| Symptom | Fix |
|
|
218
|
+
| --- | --- |
|
|
219
|
+
| `text overflows at WxH` | Shorten copy, remove a line break, use a different `templateSlide`, or pick a template with more copy room. |
|
|
220
|
+
| `uses a placeholder screen` | That slide has no capture for the family. Add one or drop the family/size. |
|
|
221
|
+
| `has no screenshot` | Slide → family mapping is incomplete for an exported size. |
|
|
222
|
+
| `Unknown device family` | Use a family from the list above. |
|
|
223
|
+
| `Unknown export size` | Use a size id from the table; `-land` is a suffix, not a family. |
|
|
224
|
+
| `capture not found` | Manifest paths are relative to the manifest file, not the terminal's cwd. |
|
|
225
|
+
| `verify` alpha error | Flatten the capture onto an opaque background; PNG must be 8-bit RGB. |
|
|
226
|
+
| Wrong dimensions in `verify` | Do not post-process built files; re-run `build` for that size. |
|
|
227
|
+
|
|
228
|
+
## Library use
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { build, verify, runCaptureFlow } from 'capture-flow';
|
|
232
|
+
|
|
233
|
+
await runCaptureFlow('flow.json');
|
|
234
|
+
const report = await build({ manifestPath: 'shots.json', outDir: 'store-assets' });
|
|
235
|
+
const result = verify('store-assets');
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The package ships TypeScript, so run it through `tsx` or a bundler.
|
|
239
|
+
|
|
240
|
+
## Example
|
|
241
|
+
|
|
242
|
+
A complete, real setup (Playwright capture of a web app, Arabic RTL, five device
|
|
243
|
+
sizes, 20 output files) lives in the repository:
|
|
244
|
+
|
|
245
|
+
<https://github.com/adelpro/capture-flow/tree/main/examples/open-tilawa>
|
|
246
|
+
|
|
247
|
+
It shows consent-dialog dismissal, waiting for hydration, live audio state, and the
|
|
248
|
+
`عرض الكل` reciter flow. Adapt it rather than starting from a blank page.
|