clipwise 0.1.0
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 +359 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +2150 -0
- package/dist/index.d.ts +1631 -0
- package/dist/index.js +1704 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 kwakseongjae
|
|
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
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# Clipwise
|
|
2
|
+
|
|
3
|
+
Scriptable cinematic screen recorder for product demos — YAML in, polished MP4 out. Powered by Playwright CDP.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install
|
|
9
|
+
npm install -D clipwise
|
|
10
|
+
|
|
11
|
+
# Try the built-in demo instantly
|
|
12
|
+
npx clipwise demo
|
|
13
|
+
|
|
14
|
+
# Or create your own scenario
|
|
15
|
+
npx clipwise init # Creates clipwise.yaml template
|
|
16
|
+
# Edit clipwise.yaml — change URL to your site
|
|
17
|
+
npx clipwise record clipwise.yaml -f mp4 # Record!
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- **Node.js** >= 18
|
|
23
|
+
- **ffmpeg** (for MP4 output)
|
|
24
|
+
- **Chromium** (auto-installed on first run via Playwright)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# macOS
|
|
28
|
+
brew install ffmpeg
|
|
29
|
+
|
|
30
|
+
# Ubuntu
|
|
31
|
+
sudo apt install ffmpeg
|
|
32
|
+
|
|
33
|
+
# Windows
|
|
34
|
+
choco install ffmpeg
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### CLI Commands
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Instant demo — records the built-in dashboard showcase
|
|
43
|
+
npx clipwise demo # Browser frame, MP4
|
|
44
|
+
npx clipwise demo --device iphone # iPhone mockup
|
|
45
|
+
npx clipwise demo --device android # Android mockup
|
|
46
|
+
npx clipwise demo --device ipad # iPad mockup
|
|
47
|
+
npx clipwise demo --url https://my-app.com # Your deployed site
|
|
48
|
+
|
|
49
|
+
# Record from YAML scenario
|
|
50
|
+
npx clipwise record <scenario.yaml> -f mp4 -o ./output
|
|
51
|
+
npx clipwise record <scenario.yaml> -f gif -o ./output
|
|
52
|
+
|
|
53
|
+
# Initialize a template
|
|
54
|
+
npx clipwise init
|
|
55
|
+
|
|
56
|
+
# Validate without recording
|
|
57
|
+
npx clipwise validate <scenario.yaml>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Programmatic API
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { ClipwiseRecorder, CanvasRenderer, encodeMp4, loadScenario } from "clipwise";
|
|
64
|
+
|
|
65
|
+
const scenario = await loadScenario("my-scenario.yaml");
|
|
66
|
+
const recorder = new ClipwiseRecorder();
|
|
67
|
+
const session = await recorder.record(scenario);
|
|
68
|
+
|
|
69
|
+
const renderer = new CanvasRenderer(scenario.effects, scenario.output, scenario.steps);
|
|
70
|
+
const frames = await renderer.composeAll(session.frames);
|
|
71
|
+
|
|
72
|
+
const mp4 = await encodeMp4(frames, scenario.output);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## YAML Scenario Format
|
|
76
|
+
|
|
77
|
+
A scenario has 4 sections: metadata, effects, output, and steps.
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
name: "My Demo"
|
|
81
|
+
description: "Optional description"
|
|
82
|
+
|
|
83
|
+
viewport:
|
|
84
|
+
width: 1280 # Browser width (default: 1280)
|
|
85
|
+
height: 800 # Browser height (default: 800)
|
|
86
|
+
|
|
87
|
+
effects:
|
|
88
|
+
# See "Effects" section below
|
|
89
|
+
|
|
90
|
+
output:
|
|
91
|
+
format: mp4 # gif | mp4 | png-sequence
|
|
92
|
+
width: 1280
|
|
93
|
+
height: 800
|
|
94
|
+
fps: 30 # 1-60
|
|
95
|
+
quality: 80 # 1-100 (MP4: maps to CRF)
|
|
96
|
+
|
|
97
|
+
steps:
|
|
98
|
+
- name: "Step name"
|
|
99
|
+
actions:
|
|
100
|
+
- action: navigate
|
|
101
|
+
url: "https://example.com"
|
|
102
|
+
captureDelay: 200 # ms to wait after actions
|
|
103
|
+
holdDuration: 800 # ms to hold on result
|
|
104
|
+
transition: none # none | fade
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Actions
|
|
108
|
+
|
|
109
|
+
| Action | Parameters | Description |
|
|
110
|
+
|--------|-----------|-------------|
|
|
111
|
+
| `navigate` | `url`, `waitUntil?` | Navigate to URL |
|
|
112
|
+
| `click` | `selector`, `delay?` | Click an element |
|
|
113
|
+
| `type` | `selector`, `text`, `delay?` | Type text (char-by-char) |
|
|
114
|
+
| `hover` | `selector` | Hover over element |
|
|
115
|
+
| `scroll` | `y?`, `x?`, `selector?`, `smooth?` | Scroll by offset |
|
|
116
|
+
| `wait` | `duration` | Wait (ms) |
|
|
117
|
+
| `screenshot` | `name?`, `fullPage?` | Capture marker |
|
|
118
|
+
|
|
119
|
+
**`waitUntil`** options: `"load"`, `"domcontentloaded"`, `"networkidle"` (default)
|
|
120
|
+
|
|
121
|
+
### Timing Tips
|
|
122
|
+
|
|
123
|
+
For snappy demos (~30 seconds):
|
|
124
|
+
- `captureDelay: 50-100` ms
|
|
125
|
+
- `holdDuration: 500-800` ms
|
|
126
|
+
- `type.delay: 15-25` ms per character
|
|
127
|
+
|
|
128
|
+
For slower, cinematic demos:
|
|
129
|
+
- `captureDelay: 200-400` ms
|
|
130
|
+
- `holdDuration: 1500-2500` ms
|
|
131
|
+
- `type.delay: 40-60` ms per character
|
|
132
|
+
|
|
133
|
+
## Effects
|
|
134
|
+
|
|
135
|
+
All effects are optional and have sensible defaults.
|
|
136
|
+
|
|
137
|
+
### Zoom
|
|
138
|
+
|
|
139
|
+
Adaptive zoom follows cursor and zooms in on click targets.
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
zoom:
|
|
143
|
+
enabled: true
|
|
144
|
+
scale: 1.8 # Peak zoom level (1-5)
|
|
145
|
+
duration: 500 # Zoom animation ms
|
|
146
|
+
autoZoom:
|
|
147
|
+
followCursor: true
|
|
148
|
+
maxScale: 2.0
|
|
149
|
+
transitionDuration: 300
|
|
150
|
+
padding: 200
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Cursor
|
|
154
|
+
|
|
155
|
+
Custom cursor with click ripple, trail, glow highlight, and speed control.
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
cursor:
|
|
159
|
+
enabled: true
|
|
160
|
+
size: 20
|
|
161
|
+
speed: "fast" # fast (~72ms) | normal (~144ms) | slow (~288ms)
|
|
162
|
+
clickEffect: true
|
|
163
|
+
clickColor: "rgba(59, 130, 246, 0.3)"
|
|
164
|
+
trail: true
|
|
165
|
+
trailLength: 6
|
|
166
|
+
highlight: true
|
|
167
|
+
highlightRadius: 35
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Background
|
|
171
|
+
|
|
172
|
+
Gradient/solid padding with rounded corners and shadow.
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
background:
|
|
176
|
+
type: gradient # gradient | solid | image
|
|
177
|
+
value: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
|
178
|
+
padding: 48
|
|
179
|
+
borderRadius: 14
|
|
180
|
+
shadow: true
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Device Frame
|
|
184
|
+
|
|
185
|
+
Wraps the recording in a device mockup.
|
|
186
|
+
|
|
187
|
+
```yaml
|
|
188
|
+
deviceFrame:
|
|
189
|
+
enabled: true
|
|
190
|
+
type: browser # browser | iphone | ipad | android | none
|
|
191
|
+
darkMode: true
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
| Type | Description |
|
|
195
|
+
|------|-------------|
|
|
196
|
+
| `browser` | macOS browser chrome with traffic lights |
|
|
197
|
+
| `iphone` | iPhone 15 Pro with Dynamic Island + home bar |
|
|
198
|
+
| `ipad` | iPad Pro with front camera dot |
|
|
199
|
+
| `android` | Android generic with punch-hole camera |
|
|
200
|
+
|
|
201
|
+
### Keystroke HUD
|
|
202
|
+
|
|
203
|
+
Displays typed keys at the bottom of the screen.
|
|
204
|
+
|
|
205
|
+
```yaml
|
|
206
|
+
keystroke:
|
|
207
|
+
enabled: true
|
|
208
|
+
position: bottom-center
|
|
209
|
+
fontSize: 16
|
|
210
|
+
fadeAfter: 1500
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Watermark
|
|
214
|
+
|
|
215
|
+
Text overlay at a corner.
|
|
216
|
+
|
|
217
|
+
```yaml
|
|
218
|
+
watermark:
|
|
219
|
+
enabled: true
|
|
220
|
+
text: "Clipwise"
|
|
221
|
+
position: bottom-right
|
|
222
|
+
opacity: 0.5
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Speed Ramp
|
|
226
|
+
|
|
227
|
+
Automatically slows down near clicks and speeds up idle sections.
|
|
228
|
+
|
|
229
|
+
```yaml
|
|
230
|
+
speedRamp:
|
|
231
|
+
enabled: true
|
|
232
|
+
idleSpeed: 3.0 # Skip factor for idle frames
|
|
233
|
+
actionSpeed: 0.8 # Slow factor near clicks
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Output Compression
|
|
237
|
+
|
|
238
|
+
MP4 output uses `libx264` with `slow` preset and `animation` tuning for best quality-per-byte. The `quality` field maps to CRF:
|
|
239
|
+
|
|
240
|
+
| quality | CRF | Use case |
|
|
241
|
+
|---------|-----|----------|
|
|
242
|
+
| 90-100 | 0-5 | Lossless / archival |
|
|
243
|
+
| 70-85 | 8-15 | High quality demos |
|
|
244
|
+
| 50-70 | 15-26 | Web sharing |
|
|
245
|
+
| 30-50 | 26-36 | Small file size |
|
|
246
|
+
|
|
247
|
+
**Recommended**: `quality: 80` for most demos (CRF ~10, good visual quality, ~3-7 MB for 30s).
|
|
248
|
+
|
|
249
|
+
For further compression after export:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# Re-encode with tighter CRF
|
|
253
|
+
ffmpeg -i input.mp4 -c:v libx264 -crf 26 -preset slow -movflags +faststart output.mp4
|
|
254
|
+
|
|
255
|
+
# Convert to WebM (smaller, web-native)
|
|
256
|
+
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 output.webm
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Writing Your Own Scenario
|
|
260
|
+
|
|
261
|
+
1. **Create your target page** — any URL (localhost, file://, or remote)
|
|
262
|
+
|
|
263
|
+
2. **Create a YAML file** with your steps:
|
|
264
|
+
|
|
265
|
+
```yaml
|
|
266
|
+
name: "My App Demo"
|
|
267
|
+
viewport:
|
|
268
|
+
width: 1280
|
|
269
|
+
height: 800
|
|
270
|
+
|
|
271
|
+
effects:
|
|
272
|
+
deviceFrame:
|
|
273
|
+
enabled: true
|
|
274
|
+
type: browser
|
|
275
|
+
background:
|
|
276
|
+
padding: 48
|
|
277
|
+
borderRadius: 14
|
|
278
|
+
|
|
279
|
+
output:
|
|
280
|
+
format: mp4
|
|
281
|
+
fps: 30
|
|
282
|
+
quality: 80
|
|
283
|
+
|
|
284
|
+
steps:
|
|
285
|
+
- name: "Open app"
|
|
286
|
+
captureDelay: 100
|
|
287
|
+
holdDuration: 1000
|
|
288
|
+
actions:
|
|
289
|
+
- action: navigate
|
|
290
|
+
url: "http://localhost:3000"
|
|
291
|
+
waitUntil: networkidle
|
|
292
|
+
|
|
293
|
+
- name: "Click login"
|
|
294
|
+
captureDelay: 50
|
|
295
|
+
holdDuration: 800
|
|
296
|
+
actions:
|
|
297
|
+
- action: click
|
|
298
|
+
selector: "#login-btn"
|
|
299
|
+
|
|
300
|
+
- name: "Type email"
|
|
301
|
+
captureDelay: 50
|
|
302
|
+
holdDuration: 600
|
|
303
|
+
actions:
|
|
304
|
+
- action: click
|
|
305
|
+
selector: "input[name=email]"
|
|
306
|
+
- action: type
|
|
307
|
+
selector: "input[name=email]"
|
|
308
|
+
text: "demo@example.com"
|
|
309
|
+
delay: 20
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
3. **Record**:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
npx clipwise record my-scenario.yaml -f mp4 -o ./output
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Tips
|
|
319
|
+
|
|
320
|
+
- Use CSS selectors (`#id`, `.class`, `[data-testid=...]`) for reliable targeting
|
|
321
|
+
- Start each interaction with enough scroll to make the target element visible
|
|
322
|
+
- Use `waitUntil: "networkidle"` for pages with API calls
|
|
323
|
+
- Keep `type.delay` at 15-25ms for a fast but readable typing effect
|
|
324
|
+
- Use `transition: fade` between major sections for cinematic cuts
|
|
325
|
+
|
|
326
|
+
## Hosting the Demo Site (GitHub Pages)
|
|
327
|
+
|
|
328
|
+
Clipwise includes a demo dashboard in `docs/index.html`. To host it:
|
|
329
|
+
|
|
330
|
+
1. Push to GitHub: `git push origin main`
|
|
331
|
+
2. Go to **Settings > Pages**
|
|
332
|
+
3. Set source to **Deploy from a branch**, select `main`, folder `/docs`
|
|
333
|
+
4. Demo goes live at `https://kwakseongjae.github.io/clipwise/`
|
|
334
|
+
|
|
335
|
+
Then anyone can record the demo site:
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
npx clipwise demo --url https://kwakseongjae.github.io/clipwise/
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Security
|
|
342
|
+
|
|
343
|
+
- **Selector validation**: All CSS selectors in YAML are validated against a safe character allowlist
|
|
344
|
+
- **URL handling**: Only `http://`, `https://`, and `file://` schemes are accepted
|
|
345
|
+
- **Chromium sandbox**: Playwright runs Chromium with default sandboxing
|
|
346
|
+
- **Local processing**: Recordings are processed locally — frames never leave your machine
|
|
347
|
+
|
|
348
|
+
## Development
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
npm install # Install dependencies
|
|
352
|
+
npm run build # Build with tsup
|
|
353
|
+
npm run typecheck # Type check
|
|
354
|
+
npm test # Run tests (vitest)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## License
|
|
358
|
+
|
|
359
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|