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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +256 -2
  3. package/bin/capture-flow.mjs +12 -0
  4. package/package.json +56 -4
  5. package/skills/capture-flow/SKILL.md +248 -0
  6. package/skills/capture-flow/references/capture-recipes.md +155 -0
  7. package/skills/capture-flow/references/store-sizes.md +91 -0
  8. package/src/build.ts +186 -0
  9. package/src/capture.ts +77 -0
  10. package/src/cli.ts +139 -0
  11. package/src/index.ts +11 -0
  12. package/src/manifest.ts +98 -0
  13. package/src/render.ts +75 -0
  14. package/src/verify.ts +90 -0
  15. package/vendor/wajha/LICENSE +21 -0
  16. package/vendor/wajha/NOTICE +21 -0
  17. package/vendor/wajha/engine/background.ts +55 -0
  18. package/vendor/wajha/engine/bidi.ts +589 -0
  19. package/vendor/wajha/engine/cache.ts +96 -0
  20. package/vendor/wajha/engine/color.ts +125 -0
  21. package/vendor/wajha/engine/devices/android.ts +53 -0
  22. package/vendor/wajha/engine/devices/browser.ts +170 -0
  23. package/vendor/wajha/engine/devices/finish.ts +154 -0
  24. package/vendor/wajha/engine/devices/geometry.ts +326 -0
  25. package/vendor/wajha/engine/devices/index.ts +143 -0
  26. package/vendor/wajha/engine/devices/ipad.ts +21 -0
  27. package/vendor/wajha/engine/devices/iphone.ts +55 -0
  28. package/vendor/wajha/engine/devices/macbook.ts +187 -0
  29. package/vendor/wajha/engine/devices/mock/apps/calendar.ts +145 -0
  30. package/vendor/wajha/engine/devices/mock/apps/chat.ts +213 -0
  31. package/vendor/wajha/engine/devices/mock/apps/finance.ts +164 -0
  32. package/vendor/wajha/engine/devices/mock/apps/fitness.ts +145 -0
  33. package/vendor/wajha/engine/devices/mock/apps/index.ts +14 -0
  34. package/vendor/wajha/engine/devices/mock/apps/meditation.ts +167 -0
  35. package/vendor/wajha/engine/devices/mock/apps/music.ts +152 -0
  36. package/vendor/wajha/engine/devices/mock/apps/shop.ts +116 -0
  37. package/vendor/wajha/engine/devices/mock/apps/social.ts +152 -0
  38. package/vendor/wajha/engine/devices/mock/apps/tasks.ts +131 -0
  39. package/vendor/wajha/engine/devices/mock/apps/travel.ts +121 -0
  40. package/vendor/wajha/engine/devices/mock/art.ts +557 -0
  41. package/vendor/wajha/engine/devices/mock/chrome.ts +337 -0
  42. package/vendor/wajha/engine/devices/mock/icons.ts +614 -0
  43. package/vendor/wajha/engine/devices/mock/kit.ts +213 -0
  44. package/vendor/wajha/engine/devices/mock/rng.ts +40 -0
  45. package/vendor/wajha/engine/devices/mock/theme.ts +133 -0
  46. package/vendor/wajha/engine/devices/mock/widgets.ts +231 -0
  47. package/vendor/wajha/engine/devices/paths.ts +113 -0
  48. package/vendor/wajha/engine/devices/screen.ts +75 -0
  49. package/vendor/wajha/engine/devices/shadow.ts +124 -0
  50. package/vendor/wajha/engine/devices/slab.ts +200 -0
  51. package/vendor/wajha/engine/devices/vision.ts +43 -0
  52. package/vendor/wajha/engine/devices/watch.ts +14 -0
  53. package/vendor/wajha/engine/geometry.ts +152 -0
  54. package/vendor/wajha/engine/images.ts +96 -0
  55. package/vendor/wajha/engine/index.ts +20 -0
  56. package/vendor/wajha/engine/linebreak.ts +226 -0
  57. package/vendor/wajha/engine/markup.ts +84 -0
  58. package/vendor/wajha/engine/mock.ts +158 -0
  59. package/vendor/wajha/engine/paint.ts +474 -0
  60. package/vendor/wajha/engine/patterns.ts +263 -0
  61. package/vendor/wajha/engine/render.ts +364 -0
  62. package/vendor/wajha/engine/shapes.ts +260 -0
  63. package/vendor/wajha/engine/testing/canvasNode.ts +121 -0
  64. package/vendor/wajha/engine/testing/fakeDevices.ts +85 -0
  65. package/vendor/wajha/engine/text.ts +694 -0
  66. package/vendor/wajha/engine/types.ts +190 -0
  67. package/vendor/wajha/export/paths.ts +197 -0
  68. package/vendor/wajha/export/png.ts +223 -0
  69. package/vendor/wajha/export/types.ts +60 -0
  70. package/vendor/wajha/model/adapt.ts +889 -0
  71. package/vendor/wajha/model/catalog.ts +369 -0
  72. package/vendor/wajha/model/clone.ts +58 -0
  73. package/vendor/wajha/model/colorMath.ts +128 -0
  74. package/vendor/wajha/model/defaults.ts +386 -0
  75. package/vendor/wajha/model/geometry.ts +95 -0
  76. package/vendor/wajha/model/ids.ts +13 -0
  77. package/vendor/wajha/model/instantiate.ts +280 -0
  78. package/vendor/wajha/model/mirror.ts +43 -0
  79. package/vendor/wajha/model/resolve.ts +87 -0
  80. package/vendor/wajha/model/rules.ts +249 -0
  81. package/vendor/wajha/model/stub.ts +4 -0
  82. package/vendor/wajha/model/types.ts +456 -0
  83. package/vendor/wajha/model/typography.ts +28 -0
  84. package/vendor/wajha/model/validate.ts +917 -0
  85. package/vendor/wajha/templates/builtin/aurora.ts +163 -0
  86. package/vendor/wajha/templates/builtin/bubblegum.ts +132 -0
  87. package/vendor/wajha/templates/builtin/citrusPop.ts +124 -0
  88. package/vendor/wajha/templates/builtin/clayGarden.ts +114 -0
  89. package/vendor/wajha/templates/builtin/desertDune.ts +97 -0
  90. package/vendor/wajha/templates/builtin/glassPrism.ts +139 -0
  91. package/vendor/wajha/templates/builtin/launchpad.ts +140 -0
  92. package/vendor/wajha/templates/builtin/loudAndClear.ts +109 -0
  93. package/vendor/wajha/templates/builtin/midnightNeon.ts +108 -0
  94. package/vendor/wajha/templates/builtin/monoSwiss.ts +149 -0
  95. package/vendor/wajha/templates/builtin/noirGold.ts +149 -0
  96. package/vendor/wajha/templates/builtin/noor.ts +107 -0
  97. package/vendor/wajha/templates/builtin/obsidian.ts +137 -0
  98. package/vendor/wajha/templates/builtin/panoramaPeaks.ts +145 -0
  99. package/vendor/wajha/templates/builtin/panoramaRibbon.ts +138 -0
  100. package/vendor/wajha/templates/builtin/panoramaWaves.ts +138 -0
  101. package/vendor/wajha/templates/builtin/paperAndInk.ts +98 -0
  102. package/vendor/wajha/templates/builtin/sage.ts +94 -0
  103. package/vendor/wajha/templates/builtin/splitTone.ts +129 -0
  104. package/vendor/wajha/templates/builtin/sunsetBoulevard.ts +127 -0
  105. package/vendor/wajha/templates/index.ts +55 -0
  106. package/vendor/wajha/templates/kit.ts +761 -0
  107. 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
- # Temporary Holding Version
1
+ # capture-flow
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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.