retake-demos 0.2.2 → 0.4.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/skill/SKILL.md CHANGED
@@ -62,10 +62,53 @@ say so in one line and stop.
62
62
  names actually on screen? Anything in shot that shouldn't be (dev
63
63
  badges, leftover data, cut-off text)? Wrong story or failed step →
64
64
  `edit`, run again. More than four rounds → ask the person.
65
- 8. When it is right: `run` with `preview=false` once (full quality), then
65
+ 8. **Before the expensive take, if it is expensive, show them the draft.**
66
+ `run` prints an estimate before capture. If it said this is a long take
67
+ (over ~2.5 minutes), do not go straight to full quality: `ask` them,
68
+ naming the demo and pointing at the draft in the Retake window, and say
69
+ what the full take will cost. Something like *"Draft is ready — 40s, 6
70
+ scenes, in the window. Full quality is about 7 minutes of recording.
71
+ Worth spending, or change something first?"*
72
+ - **Their taste is the thing you cannot check.** Every mechanical
73
+ problem is already caught by `dry` and the take's own checks. What a
74
+ person sees that no checker will is whether the story lands, whether
75
+ the payoff reads, whether it looks right for their product. Getting
76
+ that verdict on a cheap draft is the whole point.
77
+ - **Short take?** Skip the gate. Asking someone to approve a 30-second
78
+ recording costs them more than just recording it.
79
+ - **Nobody there to ask?** Never block and never guess. Leave the draft,
80
+ `done` with what you have and what it would cost, and let them start
81
+ the expensive one when they are back.
82
+ 9. When they say go: `run` with `preview=false` once (full quality), then
66
83
  `done` with one sentence. Outputs: `demo.mp4`, `stills/` (one PNG per
67
84
  scene), `proof-log.md`.
68
85
 
86
+ ## What a change costs — read this before re-recording anything
87
+
88
+ The one mistake that wastes real time is reaching for a new recording when a
89
+ re-render would do. Almost nothing needs the camera twice.
90
+
91
+ | You want to change | Do this | Cost |
92
+ |---|---|---|
93
+ | size, shape, format (landscape → square → vertical) | `render` with another preset | seconds |
94
+ | a caption's words, the speed, a zoom, cards, music | edit the manifest, `render` | seconds |
95
+ | **where** a caption or still sits in time | `nudge: <ms>` on the scene, then `render` | seconds |
96
+ | which frame is the poster | click a frame in the window, or `--scene` | seconds |
97
+ | the ENDING of a demo | `run --from <scene>` | the tail only |
98
+ | one beat, iterating | `run --until <scene>` | the head only |
99
+ | the steps themselves — what the demo DOES | full `run` | the whole thing |
100
+
101
+ Only the last row needs the camera for the whole demo. If you are about to
102
+ re-record and your reason is not in that last row, you are paying for
103
+ something you already have.
104
+
105
+ `nudge` and `--from` are the two most recently added and the two most often
106
+ missed. `nudge: -1500` moves a scene's marker 1.5s earlier in the finished
107
+ video — caption, still and thumbnail follow it. `--from <scene>` runs the
108
+ earlier steps at full speed off camera so the app reaches the right state,
109
+ then starts recording there; a seven-minute demo whose last third changed
110
+ costs ninety seconds instead of seven minutes.
111
+
69
112
  ## Style — how the person wants their videos to look
70
113
 
71
114
  Do not ask about knobs before the first take — nobody has opinions about
@@ -77,23 +120,244 @@ zooms or caption tone until they have seen one. Two moves instead:
77
120
  → landscape, still camera, a hold on the payoff. Skip the question if
78
121
  the project's style note already answers it.
79
122
  2. **The first take is the menu.** Record the calm default as a preview,
80
- let them watch it, then offer two or three concrete alternatives as
81
- sentences ("Square for a post? Captions off? Zoom on the result?").
82
- Whatever they choose, save it with `style` so it never comes up again.
123
+ let them watch it, then one line — no list, no questions before it:
124
+ "Want it different? A zoom on the result, cursor off, square for a post —
125
+ say so and I'll re-render." Whatever they choose, save it with `style`
126
+ so it never comes up again in this project. Never ask about zoom or
127
+ cursor before they have seen a take.
128
+
129
+ **Which kind of take is this? Get it from the destination, never from the
130
+ subject.** Write it into the manifest as `mode:`, and default to `demo`.
131
+
132
+ - **`mode: demo`** — it proves how the thing works. A client walkthrough IS
133
+ a demo. So is a PR review, a bug repro, a docs clip, a lesson, "show the
134
+ team", "send it to the customer", "so they can see the flow". Nothing in
135
+ the frame but the product doing the thing. No title card, no music, no
136
+ callouts. Most takes are this.
137
+ - **`mode: launch`** — it presents the product to strangers, in public: a
138
+ launch, a post, an ad, a landing page, Product Hunt, "for X", a trailer.
139
+ Only then are a title card, music, emphasis and a branded ending on the
140
+ table, and each still has to earn its second.
141
+
142
+ The tell is the audience, not the polish: *someone who already wants to
143
+ know how it works* → demo. *Someone who has to be given a reason to care*
144
+ → launch. If the person has not said, ask the one destination question and
145
+ take `demo` if they shrug. **Never upgrade a demo to a launch on your own**
146
+ — `validate` warns when a demo carries launch furniture, and that warning
147
+ means you guessed.
148
+
149
+ A launch cut ends with THEIR product, never with Retake. Retake branding
150
+ belongs only in a video that presents Retake.
151
+
152
+ **Two kinds of take, one file — and the destination decides.** The default
153
+ is the plain cut: still camera, cursor, captions, nothing else. That is
154
+ correct for PRs, clients, docs, lessons — most takes. When the person's
155
+ answer to "where is this going?" is a launch, a post, or anything public
156
+ and promotional, offer the produced cut in ONE sentence: "Want the launch
157
+ treatment — title card, a callout on the payoff, the waits compressed,
158
+ the captions read aloud, square or vertical for the feed, and music if
159
+ you give me a track?" Never apply it unasked, and
160
+ never ask about its parts one by one.
161
+
162
+ The produced cut, when they say yes (all render-time except typing):
163
+ - `intro: { title, subtitle }` and usually `outro:` (the product name or URL)
164
+ - one `callout` on the payoff moment, not more
165
+ - the destination's shape: `post-square` or `post-vertical`
166
+ - `compressIdle: true` — the app's waits shown as ~1.5s each; the person's
167
+ pacing `wait`s are never touched
168
+ - **voiceover: leave it off.** It needs `script:` (one continuous script,
169
+ synthesized as one performance) AND `approved: true`, which only a person
170
+ who has HEARD it can set. Reading the captions line by line resets prosody
171
+ at every scene — that is a defect in the shape, not the voice. A silent cut
172
+ beats an unapproved synthetic voice. Offer narration only if they ask for
173
+ it, and let them listen before it ships.
174
+ - `typing: brisk` — fast keys, the pauses carry the meaning (re-record)
175
+ - `music: <file>` only if they hand you a file or point at one on disk.
176
+ Retake bundles no tracks (licensing) — say: "give me an mp3 you have the
177
+ rights to; CC0 tracks live at freepd.com or pixabay.com/music". Skip
178
+ music without complaint if they shrug.
83
179
 
84
180
  Anything they would tell a videographer maps to a knob, in plain English:
85
181
 
182
+ - "add a title card" / "give it a cover" → `intro: { title, subtitle }`
183
+ (and `outro:`) — render-time, and it also writes two poster candidates:
184
+ cover.png (the card) and cover-titled.png (a real frame with the title on
185
+ it, which is what stops a scroll)
186
+ - "the thumbnail is wrong" → they pick the poster themselves in the window
187
+ (any frame, any still, a cover, or their own image). Point them at it
188
+ rather than re-rendering; `outputs.thumbnail: { scene }` only sets the
189
+ starting guess.
190
+ - "point at it" / "highlight the button" → a `callout` step with a label —
191
+ at most one per scene, and only where the caption alone is not enough
192
+
86
193
  - "no zooms" / "keep the camera still" → `camera: static` on scenes
87
194
  - "hide the cursor" → `cursor: false`
88
195
  - "no captions" / "shorter captions" → captions off, or rewrite them
89
196
  - "slower" / "hold the ending" → `pauseAfter`, scene `holdMs`, end waits
90
- - "square" / "vertical" / "for X" → preset `post-square` / `post-vertical`
197
+ - "square" / "vertical" / "for X" → preset `post-square` / `post-vertical`,
198
+ and this is a RE-RENDER of the take you already have, never a re-record:
199
+ `retake render outputs/<name> --preset post-square`. Size is not recorded.
200
+ - **Never set `viewport`.** The preset owns the size — one preset, one
201
+ output shape, every time. A manifest that sets its own viewport comes out
202
+ a different shape from every other demo of the same app, and players
203
+ letterbox it. `validate` warns.
91
204
 
92
- The default is already calm: still camera, cursor shown, plain captions.
205
+ The default is calm and real: still camera (`camera: static` is the
206
+ manifest default — write `auto` only when asked), cursor shown, plain
207
+ captions.
93
208
  The first time the person expresses taste, save it with the `style` tool
94
209
  (or write `demos/style.md`) — every later draft in the project reads it, so
95
210
  they never have to say it twice. Their stated taste always beats defaults.
96
211
 
212
+ ## The person may never have seen Retake — walk them
213
+
214
+ Assume a first-timer: your agent installed Retake for them and they know
215
+ nothing about a window, a workspace or a `.env`. So, every time:
216
+
217
+ - **Say where to look, with the link.** At the start of a demo: "I'll record
218
+ this with Retake. You can watch at http://localhost:4310 (run `retake ui`
219
+ if it isn't open) — the video, stills and a step-by-step log land there."
220
+ Never assume the window is open or that they know the address.
221
+ - **One thing at a time, in their words.** When you need them to act (type a
222
+ demo account, log in by hand, start their app), say exactly what to do,
223
+ where, and how you'll know it's done: "…press Save to .env, then tell me
224
+ it's done." Then wait for them to say so. Do not go quiet — a tool that
225
+ waits on them is your cue to speak, not to sit.
226
+ - **Close the loop.** When the take is done: the one-line result, where the
227
+ file is, and the one thing they can change without a re-record (speed,
228
+ captions, format). Offer, don't pile on.
229
+
230
+ ## Logins — most real demos start behind one
231
+
232
+ You will never see a password, and you never need to. The whole method:
233
+
234
+ 1. **Notice it early.** `read_project` reports sign-in fields; `scout` shows a
235
+ password input; or the person says so. Do not draft a manifest that
236
+ pretends the app is open.
237
+ 2. **Call `secrets` with the NAMES** you will use — `APP_USER`, `APP_PASSWORD`,
238
+ and `APP_TOTP_SECRET` if the site asks for an authenticator code — and one
239
+ sentence of why. It returns immediately with the words to say: a form is
240
+ open in the window at a link, or a one-line terminal command. **Relay
241
+ those words in full, link included**, then stop and wait for "done". Call
242
+ `secrets` again; it answers "set" and you carry on. The values went
243
+ straight into the workspace `.env` — you never saw them.
244
+ 3. **Write the sign-in under `auth.setup`**, referencing `${APP_USER}` and
245
+ `${APP_PASSWORD}` with `secret: true`, and set `auth.storageState` to
246
+ `.auth/<name>.json`. The login runs before the camera and is trimmed off;
247
+ the session is saved and later takes skip the login entirely. A failed
248
+ sign-in is never saved as a session, so a wrong password stops the take
249
+ instead of recording a logged-out one.
250
+ 4. **Authenticator codes:** `text: "${TOTP:APP_TOTP_SECRET}"` — Retake
251
+ computes the six-digit code at the moment it fills the field.
252
+ 5. **SMS codes, SSO, captchas, anything only a human can do:** do not try.
253
+ Tell the person: "run `retake signin demos/<name>.yaml` and log in once by
254
+ hand — Retake keeps the session." Then run as normal. The manifest still
255
+ needs `auth.storageState`; `auth.setup` can be empty.
256
+ 6. **Firebase-style apps** keep the session in IndexedDB, which a saved
257
+ session cannot restore: put the login under plain `setup` instead, so it
258
+ runs every take (still before the camera, still trimmed).
259
+
260
+ Never ask for a credential in chat. Never write a literal one into a
261
+ manifest. Never invent one. Tell the person to use a demo account — the
262
+ output is a video they may publish.
263
+
264
+ ## Hard-won rules from real sessions
265
+
266
+ - **Scout again after ANY server restart** — not just the first time. A dev
267
+ server that restarts can come back serving a different build or mode on
268
+ the same port, and the take fails 20 seconds in with a selector timeout
269
+ that looks like your manifest's fault. Five takes were lost to this once.
270
+ If the run aborts saying "the app at <url> is not what this manifest
271
+ expects", believe it: look at not-the-app.png, fix the server, re-run.
272
+ - **A failed step leaves failed-step.png next to the take** — a full-page
273
+ picture of the exact moment. Read it BEFORE re-running or editing
274
+ anything; it turns a five-round guess loop into one round.
275
+ - **`waitFor` can match a STALE element.** After an action, the previous
276
+ capture's Undo button (or any leftover) may still satisfy your selector,
277
+ and the click lands on the wrong thing. Wait for something unique to the
278
+ NEW state (a count, a heading, a row with the new text), never for a
279
+ control that also existed before the action.
280
+ - **What `compressIdle` compresses:** the app's own delays (`waitFor`,
281
+ `navigate`) and long typing. It NEVER touches your `wait` steps — those
282
+ are your pacing. Do not re-render hoping a `wait` gets shorter; shorten
283
+ the wait.
284
+ - **Streaming content:** `waitFor` on a container resolves when the shell
285
+ exists, not when the text has streamed in. Wait on the signal the app
286
+ itself uses — a button flipping from busy to ready
287
+ (`button:has-text("Send")`), a count, a class change.
288
+
289
+ ## Waits that do not lie
290
+
291
+ A bare `waitFor` resolves the instant the element exists. Three times that
292
+ is the wrong moment, and each has cost a take:
293
+
294
+ - the element is the **previous** action's success banner, still on screen →
295
+ `{ action: waitFor, selector: ".landed", gone: true }`
296
+ - the element is an empty shell that **streams** its text in →
297
+ `minChars: 40`
298
+ - the element is there but still **changing** → `stableMs: 500`
299
+
300
+ Prefer these to a flat `wait`. A timer that happens to work today is the
301
+ thing that breaks when the app gets slower.
302
+
303
+ ## Rules the manifest enforces, so you do not have to remember them
304
+
305
+ `validate` and `dry` refuse these outright rather than letting a take discover
306
+ them:
307
+
308
+ - **Scene labels are unique.** `thumbnail`, `--until`, the stills and the proof
309
+ log all address a scene by its label.
310
+ - **The action has to fit the element.** `type` on a `<select>`, `select` on a
311
+ `<div>`, `upload` on something that is not a file input, `click` on a button
312
+ that is disabled at that moment — `dry` names the fix.
313
+ - **A navigate that changes only the `#fragment` is flagged.** It does not
314
+ reload the document, so an SPA keeps its state and a stub armed just before
315
+ it never fires. Add a throwaway query param, or click the app's own link.
316
+ - **A stub that never answers a request fails `check`.** Armed but unmatched
317
+ means that screen showed the app's real data.
318
+ - **Seeds may not leave anything running** — no `&`, `nohup`, `pm2`. Starting
319
+ the app is `start_app`'s job, which the person authorises.
320
+
321
+ ## Cheap iteration — a demo should not cost five takes
322
+
323
+ A take is real time (capture is ~2.3× the finished video) and the field
324
+ average is three to five takes per demo. Most of that is avoidable:
325
+
326
+ 1. **Draft everything.** `--preset draft` has the same layout and timing at
327
+ a quarter of the pixels. Judge the story there; switch to a post preset
328
+ once, at the end.
329
+ 2. **`--until <scene>` for the head, `--from <scene>` for the tail.** These
330
+ are the two halves of the same idea and `--from` is the one people miss:
331
+ when the ending changed, everything before it still runs (the app has to
332
+ reach that state) but at full speed and off camera. Re-recording a 60s
333
+ demo to change its last 8 seconds is paying for 50 seconds you already
334
+ have.
335
+ 3. **Get a person's eyes on a cheap draft before buying an expensive take.**
336
+ A draft costs a quarter of the pixels and answers the only question a
337
+ checker cannot: is this the right video? Spending seven minutes of
338
+ capture to find out the story was wrong is the most expensive mistake
339
+ available in this tool.
340
+ 4. **A caption in the wrong place is a nudge, not a take.** `nudge: -1500` on
341
+ a scene moves its marker 1.5s earlier in the finished video; the caption,
342
+ the still and the thumbnail all follow. Re-render and look — it is seconds.
343
+ 5. **Never re-record for a size or a format.** `render outputs/<name>
344
+ --preset post-vertical` re-frames the take you have. Captions, camera,
345
+ speed, cards and posters are all render-time too.
346
+ 6. **`dry` before every `run`** — including after the app restarts. It is
347
+ ~10s and it catches the wrong-build-on-the-port failure that otherwise
348
+ costs a take and a confusing video.
349
+ 7. **Fix everything `dry` and the proof log report in ONE pass.** Reading
350
+ the whole failure list before editing turns three rounds into one.
351
+
352
+ ## Judging the cut
353
+
354
+ `check: pass` proves the file is sound, not that the demo is good. Before
355
+ `done`, look: `retake contact outputs/<name>` writes one timestamped grid
356
+ of the whole video (launch cuts get it automatically). Read it for the
357
+ things checks cannot see — a caption that is true when its scene starts and
358
+ false by the end, a payoff nobody can find, four seconds where nothing
359
+ changes. `look` at the payoff scene is the other half.
360
+
97
361
  ## When a take fails
98
362
 
99
363
  The camera stops at the first failed step, so a failed take is short and
@@ -136,8 +400,8 @@ up to the end of that scene.
136
400
 
137
401
  - Real interactions read as real: type actual text, scroll to what you use,
138
402
  hold on the result. No zoom tricks unless asked.
139
- - Secrets never go in a manifest. `${ENV_VAR}` + `secret: true`, and tell
140
- the person which variable to set.
403
+ - Secrets never go in a manifest and never into chat: the `secrets` tool
404
+ gets them into `.env` by name; you reference `${NAME}` with `secret: true`.
141
405
  - Never invent selectors — only what scout/read_project returned.
142
406
  - If the backend is down, prefer a `stub:` block over giving up.
143
407
  - Starting the person's app needs their explicit say-so, every time.
@@ -1,65 +0,0 @@
1
- # A template. Copy it, point it at your app, replace the selectors.
2
- #
3
- # retake validate demos/example.yaml schema + warnings, instant
4
- # retake dry demos/example.yaml every selector and wait, no camera
5
- # retake run demos/example.yaml record + render → outputs/example/
6
- #
7
- # Or don't write it at all: once `retake install` has run, ask your coding
8
- # agent for "a demo of my app at localhost:3000 showing …" and it drafts,
9
- # dry-runs, records and checks this file for you. `retake ui` is the window
10
- # to watch it in.
11
-
12
- name: example
13
- title: "Example — the shape of a demo"
14
- url: http://localhost:3000
15
-
16
- preset: post-landscape # 1080p @ 30fps · see `retake presets`
17
- viewport: { width: 1440, height: 1080 } # match your app's natural shape
18
- scale: 1.8 # page drawn at 1.8× so text stays crisp at 1080p
19
- reducedMotion: true # calms infinite spinners and entrance animations
20
- camera: auto # each scene eases toward the last thing touched
21
-
22
- # Put the app in a known state before the camera rolls. Pick what fits:
23
- # - { kind: file, path: /tmp/app/state.json, from: seeds/empty.json }
24
- # - { kind: evaluate, script: "localStorage.clear()" }
25
- # - { kind: command, run: "curl -X POST http://localhost:3000/api/test/reset" }
26
- seed: []
27
-
28
- # Runs before recording starts and is trimmed off the front of the video.
29
- # Sign-ins belong here — see the "Signing in" section of the README.
30
- setup: []
31
-
32
- steps:
33
- - action: scene
34
- label: start
35
- caption: "The problem, before anything happens."
36
- camera: static
37
- - action: wait
38
- ms: 1800
39
-
40
- - action: click
41
- selector: "button:has-text('Get started')"
42
- pauseAfter: 900
43
-
44
- - action: type
45
- selector: "input[name=title]"
46
- text: "A realistic thing a person would type"
47
- delay: 40
48
- pauseAfter: 600
49
-
50
- - action: click
51
- selector: "button[type=submit]"
52
- - action: waitFor # wait for the RESULT, never a fixed sleep
53
- selector: ".result"
54
- timeout: 30000
55
-
56
- - action: scene
57
- label: result
58
- caption: "And the result, which is the whole point."
59
- - action: wait
60
- ms: 2600
61
-
62
- outputs:
63
- mp4: true
64
- gif: false # opt-in; gifski is used when installed
65
- thumbnail: { scene: result }