@vosjs/cli 0.78.0 → 0.79.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.
Files changed (40) hide show
  1. package/README.md +87 -12
  2. package/dist/{assetManifest-S6H5F2VE.js → assetManifest-U25FJER6.js} +3 -3
  3. package/dist/{chunk-ZSJEISKL.js → chunk-4ZUHEXUX.js} +9 -4
  4. package/dist/chunk-4ZUHEXUX.js.map +1 -0
  5. package/dist/{chunk-B4U4URE3.js → chunk-J3KHIZCH.js} +466 -412
  6. package/dist/chunk-J3KHIZCH.js.map +1 -0
  7. package/dist/{chunk-RUDDBHQ7.js → chunk-OWYOJB7K.js} +5 -1
  8. package/dist/chunk-OWYOJB7K.js.map +1 -0
  9. package/dist/{chunk-INRUYQ6J.js → chunk-TWAP4BKR.js} +2299 -1412
  10. package/dist/chunk-TWAP4BKR.js.map +1 -0
  11. package/dist/{chunk-JYCW4EOA.js → chunk-Z6TLHRFQ.js} +2 -2
  12. package/dist/cli.js +9 -8
  13. package/dist/cli.js.map +1 -1
  14. package/dist/index.d.ts +13 -4
  15. package/dist/index.js +5 -5
  16. package/dist/manifest-VTOIEUHJ.js +8 -0
  17. package/dist/{run-TIFM3ZHI.js → run-UKWKAGDK.js} +5 -5
  18. package/dist/{shotList-SCA234OS.js → shotList-EE3S5LVF.js} +34 -7
  19. package/dist/shotList-EE3S5LVF.js.map +1 -0
  20. package/package.json +3 -3
  21. package/schema/actions.schema.json +298 -13
  22. package/schema/doc.schema.json +37 -1
  23. package/skills/VERSION +1 -1
  24. package/skills/launch-kit/SKILL.md +6 -1
  25. package/skills/product-video/SKILL.md +7 -0
  26. package/skills/vos-authoring/SKILL.md +8 -1
  27. package/skills/vos-create/SKILL.md +5 -0
  28. package/skills/vos-cut/SKILL.md +8 -0
  29. package/skills/vos-footage/SKILL.md +4 -0
  30. package/skills/vos-migrate/SKILL.md +4 -0
  31. package/dist/chunk-B4U4URE3.js.map +0 -1
  32. package/dist/chunk-INRUYQ6J.js.map +0 -1
  33. package/dist/chunk-RUDDBHQ7.js.map +0 -1
  34. package/dist/chunk-ZSJEISKL.js.map +0 -1
  35. package/dist/manifest-6DJSPKVH.js +0 -8
  36. package/dist/shotList-SCA234OS.js.map +0 -1
  37. /package/dist/{assetManifest-S6H5F2VE.js.map → assetManifest-U25FJER6.js.map} +0 -0
  38. /package/dist/{chunk-JYCW4EOA.js.map → chunk-Z6TLHRFQ.js.map} +0 -0
  39. /package/dist/{manifest-6DJSPKVH.js.map → manifest-VTOIEUHJ.js.map} +0 -0
  40. /package/dist/{run-TIFM3ZHI.js.map → run-UKWKAGDK.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin/shotList.ts"],"sourcesContent":["/**\n * The shot list: an `actions.json` said in words, for a person who will\n * record it by hand. The last rung of the session ladder is \"the human\n * records, the agent cuts\", and an agent that falls to it should hand over\n * more than an apology: the flow it worked out, as numbered beats with the\n * pace it wanted, so its knowledge survives the hop to a human. The take\n * comes back through the ordinary handoff and the agent cuts it.\n *\n * Pure: an ActionsFile in, lines out. Selectors are said as what they name\n * where the script says (an id, a caption, a text= locator), and as the\n * selector itself where it does not: a person reading `#filter-late` can\n * find it; a person reading nothing cannot.\n */\nimport type { ActionStep, ActionsFile } from './actions'\nimport { askedMs } from './pace'\nimport { pressSequence, splitChord } from '@vosjs/render-core/record'\n\n/** A key or a chord as a person reads it: `Mod` is ⌘ on a Mac, Ctrl elsewhere. */\nexport function sayChord(chord: string): string {\n const { mods, key } = splitChord(chord)\n const word = (k: string) =>\n k === 'Mod' || k === 'ControlOrMeta'\n ? '⌘/Ctrl'\n : k === 'Control'\n ? 'Ctrl'\n : k === 'Meta'\n ? '⌘'\n : k === 'Alt'\n ? 'Alt/Option'\n : k === ' '\n ? 'Space'\n : k\n return [...mods, key].map(word).join('+')\n}\n\n/** A viewport point as a person finds it: where it is, and the script's numbers. */\nfunction sayPoint(p: { x?: number; y?: number }): string {\n const x = p.x ?? 0\n const y = p.y ?? 0\n return `the spot at ${Math.round(x)}, ${Math.round(y)} (${y < 360 ? 'upper' : 'lower'} ${x < 640 ? 'left' : 'right'})`\n}\n\n/** A list in words: `a`, `a then b`, `a, b then c`. */\nconst thenList = (xs: string[]): string =>\n xs.length <= 1\n ? (xs[0] ?? '')\n : `${xs.slice(0, -1).join(', ')} then ${xs[xs.length - 1]}`\n\nexport interface ShotBeat {\n n: number\n /** what to do, in words */\n line: string\n /** how long to hold after, in seconds, when the script asks for a hold */\n holdS?: number\n}\n\nexport interface ShotList {\n url?: string\n viewport?: { width: number; height: number }\n beats: ShotBeat[]\n /** the take's asked length, from the script's own pacing */\n aboutS: number\n setup: number\n}\n\n/** A selector as a person reads it: the text it names, or the selector itself. */\nexport function sayTarget(selector: string): string {\n const text = /^text=(['\"]?)(.+?)\\1$/.exec(selector)\n if (text) return `\"${text[2]}\"`\n const has = /:has-text\\((['\"])(.+?)\\1\\)/.exec(selector)\n if (has) return `\"${has[2]}\"`\n const role = /getByRole\\(['\"](\\w+)['\"],\\s*\\{\\s*name:\\s*(['\"])(.+?)\\2/.exec(\n selector,\n )\n if (role) return `the ${role[1]} \"${role[3]}\"`\n const label = /\\[aria-label=(['\"])(.+?)\\1\\]/.exec(selector)\n if (label) return `\"${label[2]}\"`\n const ph = /\\[placeholder=(['\"])(.+?)\\1\\]/.exec(selector)\n if (ph) return `the \"${ph[2]}\" field`\n return `\\`${selector}\\``\n}\n\nconst s1 = (ms: number) => `${Math.round(ms / 100) / 10}`.replace(/\\.0$/, '')\n\n/** A step's id, said as a name when it reads as one (`late`, `new-order`, `openBilling`). */\nexport function sayId(id: string | undefined): string | null {\n if (!id || /^\\d+$/.test(id) || id.length < 3) return null\n const words = id\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .toLowerCase()\n .trim()\n return words.length >= 3 ? words : null\n}\n\n/** What a step points at, for a person: the words the selector names, else the id's, else the selector. */\nfunction sayStepTarget(step: { selector: string; id?: string }): string {\n const bySelector = sayTarget(step.selector)\n if (!bySelector.startsWith('`')) return bySelector\n const byId = sayId(step.id)\n return byId ? `${byId} (${bySelector})` : bySelector\n}\n\n/** A page condition, for a person: what they should see before going on. */\nfunction sayCondition(c: {\n selector?: string\n text?: string\n gone?: string\n enabled?: boolean\n id?: string\n}): string {\n if (c.text !== undefined) return `\"${c.text}\" shows on the page`\n if (c.gone !== undefined) return `${sayTarget(c.gone)} is gone`\n const target = sayStepTarget({ selector: c.selector ?? '', id: c.id })\n if (c.enabled === true) return `${target} is enabled`\n if (c.enabled === false) return `${target} is disabled`\n return `${target} is showing`\n}\n\nfunction beatOf(step: ActionStep): string | null {\n const line = gestureBeat(step)\n // A step that spends something real is marked where a person reads it.\n return line && step.guard\n ? `${line} (this spends something real: do it only if you mean to)`\n : line\n}\n\nfunction gestureBeat(step: ActionStep): string | null {\n const cap = step.caption\n const named = cap ? `${cap}: ` : ''\n switch (step.do) {\n case 'expect':\n return `${named}Check that ${sayCondition(step)}`\n case 'await':\n return `${named}Wait until ${sayCondition({ ...step.until, id: step.id })} (off camera: pause the recording meanwhile)`\n case 'download':\n return step.selector !== undefined\n ? `${named}Download the file ${sayStepTarget({ selector: step.selector, id: step.id })} gives${step.as ? `, as ${step.as}` : ''}`\n : `${named}Download what ${sayTarget(step.src ?? '')} plays${step.as ? `, as ${step.as}` : ''}`\n case 'wait':\n return null\n case 'hover':\n return `${named}Rest the pointer on ${step.selector !== undefined ? sayStepTarget({ selector: step.selector, id: step.id }) : sayPoint(step)}`\n case 'click': {\n const verb =\n step.count === 3\n ? 'Triple-click'\n : step.count === 2\n ? 'Double-click'\n : 'Click'\n const button =\n step.button === 'right'\n ? ' with the right button'\n : step.button === 'middle'\n ? ' with the middle button'\n : ''\n const target =\n step.selector !== undefined\n ? sayStepTarget({ selector: step.selector, id: step.id })\n : sayPoint(step)\n const holding = step.modifiers?.length\n ? `Hold ${step.modifiers.map(sayChord).join('+')} and ${verb.toLowerCase()}`\n : verb\n return `${named}${holding} ${target}${button}`\n }\n case 'type':\n return `${named}${step.replace ? 'Replace what is in' : 'Type'}${step.replace ? ` ${sayStepTarget(step)} with \"${step.text}\"` : ` \"${step.text}\" into ${sayStepTarget(step)}`}${step.at === 'end' ? ', at the end' : step.at === 'start' ? ', at the start' : ''}`\n case 'press':\n return `${named}Press ${thenList(pressSequence(step).map(sayChord))}${step.selector ? ` in ${sayStepTarget(step as { selector: string; id?: string })}` : ''}`\n case 'paste': {\n const what =\n step.text !== undefined\n ? `\"${step.text.length > 60 ? `${step.text.slice(0, 57)}...` : step.text}\"`\n : `the file ${step.file}`\n return `${named}Copy ${what}, then paste it${step.selector ? ` into ${sayStepTarget({ selector: step.selector, id: step.id })}` : ''} (⌘/Ctrl+V)`\n }\n case 'scroll':\n return `${named}Scroll ${step.dy < 0 ? 'up' : 'down'} ${step.dy < 0 ? 'a little' : 'a little'}`\n case 'move':\n return `${named}Move the pointer to the ${step.y < 360 ? 'upper' : 'lower'} ${step.x < 640 ? 'left' : 'right'}`\n case 'drag': {\n const holding = step.modifiers?.length\n ? `Hold ${step.modifiers.map(sayChord).join('+')} and `\n : ''\n const verb = holding ? 'drag' : 'Drag'\n if (step.path !== undefined)\n return `${named}${holding}${holding ? 'draw' : 'Draw'} the stroke ${typeof step.path === 'string' ? `in ${step.path}` : `of ${step.path.length} points`}, in one press`\n return `${named}${holding}${verb} ${step.selector ? sayStepTarget(step as { selector: string; id?: string }) : 'from the start point'} across`\n }\n }\n}\n\nexport function shotList(actions: ActionsFile): ShotList {\n const beats: ShotBeat[] = []\n let pendingHold = 0\n let total = 0\n for (const step of actions.steps) {\n total += askedMs(step)\n if (step.do === 'wait') {\n // A wait after a beat is that beat's hold; a leading wait is the\n // settle before the first, which a person does without being told.\n if (beats.length) {\n const last = beats[beats.length - 1]\n last.holdS = (last.holdS ?? 0) + step.ms / 1000\n } else pendingHold += step.ms\n continue\n }\n const line = beatOf(step)\n if (!line) continue\n const beat: ShotBeat = { n: beats.length + 1, line }\n if (step.do === 'hover') beat.holdS = askedMs(step) / 1000\n beats.push(beat)\n }\n void pendingHold\n return {\n url: actions.url,\n viewport: actions.viewport,\n beats,\n // gestures the recorder adds around each ask: about a second a beat\n aboutS: Math.round((total + beats.length * 1000) / 1000),\n setup: actions.setup?.length ?? 0,\n }\n}\n\n/** The list as the person reads it. */\nexport function formatShotList(list: ShotList): string {\n const out: string[] = []\n out.push(\n `Record this with the vosso extension in your own signed-in browser, about ${list.aboutS} seconds${list.viewport ? `, window about ${list.viewport.width}×${list.viewport.height}` : ''}.`,\n )\n if (list.url)\n out.push(`Start on ${list.url}, signed in, with the page settled.`)\n if (list.setup)\n out.push(\n `Before you press record: the script's ${list.setup} setup step${list.setup === 1 ? '' : 's'} (sign in, dismiss banners) are yours to do first; they are not part of the take.`,\n )\n out.push('')\n for (const b of list.beats) {\n const hold =\n b.holdS && b.holdS >= 0.4 ? ` Hold ${s1(b.holdS * 1000)} s.` : ''\n out.push(`${b.n}. ${b.line}.${hold}`)\n }\n out.push('')\n out.push(\n 'Move at a talking pace, let each screen settle before the next beat, and end on the finished state. Press the icon again to stop; the take opens in the studio and saves to your shelf.',\n )\n return out.join('\\n')\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vosjs/cli",
3
- "version": "0.78.0",
3
+ "version": "0.79.0",
4
4
  "description": "The vos CLI: record the real product from a scripted browser flow, auto-zoom from the cursor track, cut as data in doc.json, render deterministic video, deliver a release's video cuts per destination spec, and sync with vos.so. One binary, every verb, MIT.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -53,10 +53,10 @@
53
53
  "playwright": "^1.49.0",
54
54
  "@vosjs/core": "^0.31.0",
55
55
  "@vosjs/editor": "^1.6.0",
56
+ "@vosjs/render-core": "^0.8.0",
57
+ "@vosjs/studio-core": "^0.44.0",
56
58
  "@vosjs/elements": "^0.12.1",
57
- "@vosjs/render-core": "^0.7.0",
58
59
  "@vosjs/shared": "^0.7.0",
59
- "@vosjs/studio-core": "^0.43.0",
60
60
  "@vosjs/timeline": "^0.5.0",
61
61
  "@vosjs/tween": "^0.8.3"
62
62
  },
@@ -80,6 +80,12 @@
80
80
  }
81
81
  }
82
82
  },
83
+ "ready": {
84
+ "type": "string",
85
+ "enum": ["dom", "networkidle"],
86
+ "default": "dom",
87
+ "description": "When the first page counts as ready for step 1. dom: after load, once the page has made no DOM change for 800 ms, at most 8 s. networkidle: once the network has gone quiet, at most 45 s, which a page holding a socket open or an analytics beacon never does. The same mode opens a setup goto, the page after a setup, and an await's reload. `--ready` overrides. The done event's `ready` says how the first load settled ({ mode, ms, capped }); `navTimeout` is true only when networkidle was asked for and never came."
88
+ },
83
89
  "mask": {
84
90
  "type": "array",
85
91
  "description": "Hidden BEFORE the first frame is captured and kept hidden across navigations and re-renders: what a signed-in account shows that must not ship. The real value is never in a frame, never in the recording, never pushed. `vos record` reports what it still sees afterwards (meta.exposures), and a mask whose selector reached nothing fails --strict.",
@@ -104,6 +110,11 @@
104
110
  }
105
111
  }
106
112
  },
113
+ "settle": {
114
+ "type": "boolean",
115
+ "default": false,
116
+ "description": "every gesture step waits on the app after it (a step's own settle overrides): a canvas that falls behind under load finishes the last stroke before the next one starts"
117
+ },
107
118
  "steps": {
108
119
  "type": "array",
109
120
  "minItems": 1,
@@ -140,14 +151,24 @@
140
151
  },
141
152
  {
142
153
  "type": "object",
143
- "required": ["do", "selector"],
154
+ "required": ["do"],
144
155
  "additionalProperties": false,
145
156
  "properties": {
146
157
  "do": {
147
158
  "const": "hover"
148
159
  },
149
160
  "selector": {
150
- "type": "string"
161
+ "type": "string",
162
+ "description": "the element whose centre the pointer goes to; or x and y instead"
163
+ },
164
+ "x": {
165
+ "type": "number",
166
+ "minimum": 0,
167
+ "description": "with y: a viewport point in CSS px, for a spot no selector names (a place on a canvas); takes no selector"
168
+ },
169
+ "y": {
170
+ "type": "number",
171
+ "minimum": 0
151
172
  },
152
173
  "ms": {
153
174
  "type": "number",
@@ -155,6 +176,10 @@
155
176
  "default": 700,
156
177
  "description": "Dwell — parked cursor becomes a zoom signal."
157
178
  },
179
+ "settle": {
180
+ "type": "boolean",
181
+ "description": "after the gesture, wait until the page has made no DOM change and painted no new frame for 300 ms (at most 4 s), so an app that fell behind catches up before the next step; meta.steps[].settleMs says how long it waited. Absent: the script's top-level settle"
182
+ },
158
183
  "id": {
159
184
  "type": "string",
160
185
  "minLength": 1,
@@ -170,11 +195,33 @@
170
195
  "minLength": 1,
171
196
  "description": "a selector whose words the recorder keeps once the step has settled (meta.steps[].text, trimmed, at most 2 KB): the take's own payload, which a layer quotes instead of a replay; on a click, what the click opened"
172
197
  }
173
- }
198
+ },
199
+ "oneOf": [
200
+ {
201
+ "required": ["selector"],
202
+ "not": {
203
+ "anyOf": [
204
+ {
205
+ "required": ["x"]
206
+ },
207
+ {
208
+ "required": ["y"]
209
+ }
210
+ ]
211
+ }
212
+ },
213
+ {
214
+ "required": ["x", "y"],
215
+ "not": {
216
+ "required": ["selector"]
217
+ }
218
+ }
219
+ ],
220
+ "description": "Rest the pointer on an element's centre (selector) or on a viewport point (x, y); ms is the dwell, a zoom signal."
174
221
  },
175
222
  {
176
223
  "type": "object",
177
- "required": ["do", "selector"],
224
+ "required": ["do"],
178
225
  "additionalProperties": false,
179
226
  "properties": {
180
227
  "do": {
@@ -186,7 +233,42 @@
186
233
  "description": "the read after the press, ms (default 150), held from the page's last visual change: the recorder watches the screencast and pays the settle itself (a change within 400 ms says the page is answering, 250 ms of quiet says it settled, 1.2 s bounds a page that never stops), so the same ms reads the same on every run. Size it as a reading beat, about 1000 after a navigation and 600 after a control, never padded for a slow page."
187
234
  },
188
235
  "selector": {
189
- "type": "string"
236
+ "type": "string",
237
+ "description": "the element whose centre the pointer goes to; or x and y instead"
238
+ },
239
+ "x": {
240
+ "type": "number",
241
+ "minimum": 0,
242
+ "description": "with y: a viewport point in CSS px, for a spot no selector names (a place on a canvas); takes no selector"
243
+ },
244
+ "y": {
245
+ "type": "number",
246
+ "minimum": 0
247
+ },
248
+ "count": {
249
+ "type": "integer",
250
+ "enum": [1, 2, 3],
251
+ "default": 1,
252
+ "description": "2 is a real double click, 3 a triple click: that many presses inside the platform's double-click interval, each carrying its click count; every press is a down and an up on the cursor track"
253
+ },
254
+ "button": {
255
+ "type": "string",
256
+ "enum": ["left", "right", "middle"],
257
+ "default": "left",
258
+ "description": "the button pressed: right opens a context menu"
259
+ },
260
+ "modifiers": {
261
+ "type": "array",
262
+ "uniqueItems": true,
263
+ "items": {
264
+ "type": "string",
265
+ "enum": ["Alt", "Shift", "Meta", "Control", "Mod"]
266
+ },
267
+ "description": "held for the press (a Shift-click adds to the selection) and released after it, whatever happens; Mod is Meta on macOS, Control elsewhere"
268
+ },
269
+ "settle": {
270
+ "type": "boolean",
271
+ "description": "after the gesture, wait until the page has made no DOM change and painted no new frame for 300 ms (at most 4 s), so an app that fell behind catches up before the next step; meta.steps[].settleMs says how long it waited. Absent: the script's top-level settle"
190
272
  },
191
273
  "id": {
192
274
  "type": "string",
@@ -208,7 +290,28 @@
208
290
  "enum": ["spend"],
209
291
  "description": "the step costs something real (credits, a purchase, a message sent): `vos record --dry-run` hovers it and never presses it, and `vos record` presses it only with `--confirm <id>`, refusing before any capture otherwise. Needs an id. A hosted take refuses a guarded step"
210
292
  }
211
- }
293
+ },
294
+ "oneOf": [
295
+ {
296
+ "required": ["selector"],
297
+ "not": {
298
+ "anyOf": [
299
+ {
300
+ "required": ["x"]
301
+ },
302
+ {
303
+ "required": ["y"]
304
+ }
305
+ ]
306
+ }
307
+ },
308
+ {
309
+ "required": ["x", "y"],
310
+ "not": {
311
+ "required": ["selector"]
312
+ }
313
+ }
314
+ ]
212
315
  },
213
316
  {
214
317
  "type": "object",
@@ -240,6 +343,10 @@
240
343
  "default": true,
241
344
  "description": "The recorder clicks the field before typing, and that click is what opens the typing zoom on it. false types into the field as it is already focused: for a keystroke that finishes earlier typing (a submitting Enter) rather than starting it, where a second click rings a click effect on empty space beside the text."
242
345
  },
346
+ "settle": {
347
+ "type": "boolean",
348
+ "description": "after the gesture, wait until the page has made no DOM change and painted no new frame for 300 ms (at most 4 s), so an app that fell behind catches up before the next step; meta.steps[].settleMs says how long it waited. Absent: the script's top-level settle"
349
+ },
243
350
  "id": {
244
351
  "type": "string",
245
352
  "minLength": 1,
@@ -274,9 +381,9 @@
274
381
  },
275
382
  {
276
383
  "type": "object",
277
- "required": ["do", "key"],
384
+ "required": ["do"],
278
385
  "additionalProperties": false,
279
- "description": "One named key, recorded as a typing ping where the pointer is (or on selector when given, with no click): Escape closes what a click opened, Enter submits what was typed, Tab moves on, an arrow nudges. Never a character: words are typed with type.",
386
+ "description": "A key, a chord or a sequence, each recorded as a typing ping where the pointer is (or on selector when given, with no click), never as the key itself. key names one control key (Escape closes what a click opened, Enter submits what was typed); keys names a chord (\"Shift+D\", \"Mod+Z\", Mod being Meta on macOS and Control elsewhere), a plain key (\"r\", a tool's shortcut) or a sequence ([\"v\", \"Escape\"]), 150 ms apart. Words are typed with type.",
280
387
  "properties": {
281
388
  "do": {
282
389
  "const": "press"
@@ -300,6 +407,24 @@
300
407
  "Space"
301
408
  ]
302
409
  },
410
+ "keys": {
411
+ "oneOf": [
412
+ {
413
+ "type": "string",
414
+ "minLength": 1
415
+ },
416
+ {
417
+ "type": "array",
418
+ "minItems": 1,
419
+ "maxItems": 50,
420
+ "items": {
421
+ "type": "string",
422
+ "minLength": 1
423
+ }
424
+ }
425
+ ],
426
+ "description": "a key name or chord (modifiers Shift, Control, Alt, Meta, Mod, ControlOrMeta joined with + before the key; a key is a single character, a named key like Escape or ArrowLeft, F1 to F24, or a code like KeyA or Digit1), or a list of them pressed in order"
427
+ },
303
428
  "selector": {
304
429
  "type": "string",
305
430
  "description": "the element the key is about: the ping and the step's rect are its, with no click"
@@ -309,6 +434,10 @@
309
434
  "minimum": 0,
310
435
  "description": "the read after the key, ms (default 150), held from the page's last visual change like a click's"
311
436
  },
437
+ "settle": {
438
+ "type": "boolean",
439
+ "description": "after the gesture, wait until the page has made no DOM change and painted no new frame for 300 ms (at most 4 s), so an app that fell behind catches up before the next step; meta.steps[].settleMs says how long it waited. Absent: the script's top-level settle"
440
+ },
312
441
  "id": {
313
442
  "type": "string",
314
443
  "minLength": 1,
@@ -329,7 +458,15 @@
329
458
  "enum": ["spend"],
330
459
  "description": "the step costs something real (credits, a purchase, a message sent): `vos record --dry-run` hovers it and never presses it, and `vos record` presses it only with `--confirm <id>`, refusing before any capture otherwise. Needs an id. A hosted take refuses a guarded step"
331
460
  }
332
- }
461
+ },
462
+ "oneOf": [
463
+ {
464
+ "required": ["key"]
465
+ },
466
+ {
467
+ "required": ["keys"]
468
+ }
469
+ ]
333
470
  },
334
471
  {
335
472
  "type": "object",
@@ -347,6 +484,10 @@
347
484
  "dy": {
348
485
  "type": "number"
349
486
  },
487
+ "settle": {
488
+ "type": "boolean",
489
+ "description": "after the gesture, wait until the page has made no DOM change and painted no new frame for 300 ms (at most 4 s), so an app that fell behind catches up before the next step; meta.steps[].settleMs says how long it waited. Absent: the script's top-level settle"
490
+ },
350
491
  "id": {
351
492
  "type": "string",
352
493
  "minLength": 1,
@@ -380,6 +521,10 @@
380
521
  "type": "number",
381
522
  "minimum": 0
382
523
  },
524
+ "settle": {
525
+ "type": "boolean",
526
+ "description": "after the gesture, wait until the page has made no DOM change and painted no new frame for 300 ms (at most 4 s), so an app that fell behind catches up before the next step; meta.steps[].settleMs says how long it waited. Absent: the script's top-level settle"
527
+ },
383
528
  "id": {
384
529
  "type": "string",
385
530
  "minLength": 1,
@@ -399,9 +544,9 @@
399
544
  },
400
545
  {
401
546
  "type": "object",
402
- "required": ["do", "tx", "ty"],
547
+ "required": ["do"],
403
548
  "additionalProperties": false,
404
- "description": "Press-move-release: drag an element, slide a range input, move a clip. Start = selector center when given, else (x, y); end = (tx, ty), eased over ms.",
549
+ "description": "Press-move-release: drag an element, slide a range input, move a clip. Start = selector center when given, else (x, y); end = (tx, ty), eased over ms. With path, the drag DRAWS: the press at the path's first point, a move at every later point in order (never dropped, so a page that answers slowly makes the stroke late, reported as meta.steps[].overrunMs, never a different stroke), the release at its last. modifiers are held from the press to the release.",
405
550
  "properties": {
406
551
  "do": {
407
552
  "const": "drag"
@@ -425,10 +570,42 @@
425
570
  "type": "number",
426
571
  "minimum": 0
427
572
  },
573
+ "path": {
574
+ "oneOf": [
575
+ {
576
+ "type": "array",
577
+ "minItems": 2,
578
+ "maxItems": 20000,
579
+ "items": {
580
+ "$ref": "#/$defs/pathPoint"
581
+ }
582
+ },
583
+ {
584
+ "type": "string",
585
+ "pattern": "^(?!\\.)(?!.*\\.\\.)[^/\\\\]+$",
586
+ "description": "a JSON file beside the actions file (a plain name) holding the points, or { \"points\": [...] }"
587
+ }
588
+ ],
589
+ "description": "viewport points to draw through, replayed by their own times (tMs from the first point) or, untimed, evenly over ms. A point that carries pressure draws the whole path with a pen (pointer events with that pressure); without, the mouse draws it. A path takes no selector, x, y, tx or ty"
590
+ },
591
+ "modifiers": {
592
+ "type": "array",
593
+ "uniqueItems": true,
594
+ "items": {
595
+ "type": "string",
596
+ "enum": ["Alt", "Shift", "Meta", "Control", "Mod"]
597
+ },
598
+ "description": "held from the press to the release (Alt-drag to duplicate, Shift to constrain); Mod is Meta on macOS, Control elsewhere"
599
+ },
428
600
  "ms": {
429
601
  "type": "number",
430
602
  "minimum": 0,
431
- "default": 700
603
+ "default": 700,
604
+ "description": "the travel of a straight drag, or of an untimed path (default 700)"
605
+ },
606
+ "settle": {
607
+ "type": "boolean",
608
+ "description": "after the gesture, wait until the page has made no DOM change and painted no new frame for 300 ms (at most 4 s), so an app that fell behind catches up before the next step; meta.steps[].settleMs says how long it waited. Absent: the script's top-level settle"
432
609
  },
433
610
  "id": {
434
611
  "type": "string",
@@ -445,7 +622,18 @@
445
622
  "minLength": 1,
446
623
  "description": "a selector whose words the recorder keeps once the step has settled (meta.steps[].text, trimmed, at most 2 KB): the take's own payload, which a layer quotes instead of a replay; on a click, what the click opened"
447
624
  }
448
- }
625
+ },
626
+ "oneOf": [
627
+ {
628
+ "required": ["tx", "ty"],
629
+ "not": {
630
+ "required": ["path"]
631
+ }
632
+ },
633
+ {
634
+ "required": ["path"]
635
+ }
636
+ ]
449
637
  },
450
638
  {
451
639
  "type": "object",
@@ -623,9 +811,106 @@
623
811
  "description": "a selector whose words the recorder keeps once the step has settled (meta.steps[].text, trimmed, at most 2 KB): the take's own payload, which a layer quotes instead of a replay; on a click, what the click opened"
624
812
  }
625
813
  }
814
+ },
815
+ {
816
+ "type": "object",
817
+ "required": ["do"],
818
+ "additionalProperties": false,
819
+ "description": "Put words or a file on the clipboard and paste it the way a person's ⌘V does: the browser's own clipboard and a trusted paste chord, so the page gets a trusted keydown and paste event and navigator.clipboard.read() sees the item; when the browser refuses the item, a paste event dispatched in the page (untrusted, said in the log). Into selector (clicked first) or where the focus is. Recorded as a typing ping, never its contents. A hosted take refuses a paste.",
820
+ "oneOf": [
821
+ {
822
+ "required": ["text"]
823
+ },
824
+ {
825
+ "required": ["file"]
826
+ }
827
+ ],
828
+ "properties": {
829
+ "do": {
830
+ "const": "paste"
831
+ },
832
+ "selector": {
833
+ "type": "string",
834
+ "minLength": 1,
835
+ "description": "the element to paste into, clicked first"
836
+ },
837
+ "text": {
838
+ "type": "string",
839
+ "description": "the words to paste"
840
+ },
841
+ "file": {
842
+ "type": "string",
843
+ "pattern": "^(?!\\.)(?!.*\\.\\.)[^/\\\\]+\\.(png|jpe?g|webp|gif|svg|txt|md|html?|json|csv)$",
844
+ "description": "a file beside the actions file, by a plain name, at most 10 MB: an image (a picture other than PNG is pasted as PNG), an SVG (also as its markup), or text"
845
+ },
846
+ "ms": {
847
+ "type": "number",
848
+ "minimum": 0,
849
+ "description": "the read after the paste, ms (default 150), held from the page's last visual change like a click's"
850
+ },
851
+ "settle": {
852
+ "type": "boolean",
853
+ "description": "after the gesture, wait until the page has made no DOM change and painted no new frame for 300 ms (at most 4 s), so an app that fell behind catches up before the next step; meta.steps[].settleMs says how long it waited. Absent: the script's top-level settle"
854
+ },
855
+ "id": {
856
+ "type": "string",
857
+ "minLength": 1,
858
+ "description": "Optional stable identity: doc.json anchors name a step by this id (else by index), so a step survives moving or gaining neighbours across script edits. Unique across steps when present."
859
+ },
860
+ "caption": {
861
+ "type": "string",
862
+ "maxLength": 80,
863
+ "description": "a beat's caption: vos deliver lands it as a lower-third at this step's moment on cuts that take words (feed cuts, the demo), never on a loop"
864
+ },
865
+ "read": {
866
+ "type": "string",
867
+ "minLength": 1,
868
+ "description": "a selector whose words the recorder keeps once the step has settled (meta.steps[].text, trimmed, at most 2 KB): the take's own payload, which a layer quotes instead of a replay; on a click, what the click opened"
869
+ }
870
+ }
626
871
  }
627
872
  ]
628
873
  }
629
874
  }
875
+ },
876
+ "$defs": {
877
+ "pathPoint": {
878
+ "oneOf": [
879
+ {
880
+ "type": "array",
881
+ "minItems": 2,
882
+ "maxItems": 4,
883
+ "items": {
884
+ "type": "number"
885
+ },
886
+ "description": "[x, y], [x, y, tMs] or [x, y, tMs, pressure]: every point timed or none"
887
+ },
888
+ {
889
+ "type": "object",
890
+ "required": ["x", "y"],
891
+ "additionalProperties": false,
892
+ "properties": {
893
+ "x": {
894
+ "type": "number",
895
+ "minimum": 0
896
+ },
897
+ "y": {
898
+ "type": "number",
899
+ "minimum": 0
900
+ },
901
+ "t": {
902
+ "type": "number",
903
+ "minimum": 0,
904
+ "description": "ms"
905
+ },
906
+ "pressure": {
907
+ "type": "number",
908
+ "minimum": 0,
909
+ "maximum": 1
910
+ }
911
+ }
912
+ }
913
+ ]
914
+ }
630
915
  }
631
916
  }
@@ -1589,7 +1589,38 @@
1589
1589
  "gain": {
1590
1590
  "type": "number",
1591
1591
  "minimum": 0,
1592
- "maximum": 1
1592
+ "maximum": 1,
1593
+ "description": "linear gain; with a measured `normalize`, a trim on the target"
1594
+ },
1595
+ "normalize": {
1596
+ "type": "number",
1597
+ "minimum": -30,
1598
+ "maximum": -6,
1599
+ "description": "target integrated loudness, LUFS (e.g. -16). Applies only while `loudness.src` is this clip's `key`: the clip plays at 10^((normalize - loudness.lufs) / 20) × gain, which may exceed 1, capped so the file's sample peak stays under -1 dBFS and never lifted more than +24 dB. Unmeasured or measured for another key, it is ignored (vos validate warns: measure it with vos audio measure)"
1600
+ },
1601
+ "loudness": {
1602
+ "type": "object",
1603
+ "description": "what the file measures (BS.1770 integrated, K-weighted, gated, over the whole file), written by vos audio measure, vos score and vos join, never by hand",
1604
+ "required": ["lufs", "peakDb", "at", "src"],
1605
+ "properties": {
1606
+ "lufs": {
1607
+ "type": "number",
1608
+ "description": "integrated loudness, LUFS"
1609
+ },
1610
+ "peakDb": {
1611
+ "type": ["number", "null"],
1612
+ "description": "sample peak, dBFS (not oversampled)"
1613
+ },
1614
+ "at": {
1615
+ "type": "string",
1616
+ "description": "when it was measured (ISO date)"
1617
+ },
1618
+ "src": {
1619
+ "type": "string",
1620
+ "description": "the key it was measured for"
1621
+ }
1622
+ },
1623
+ "additionalProperties": false
1593
1624
  },
1594
1625
  "fadeIn": {
1595
1626
  "type": "number",
@@ -1609,6 +1640,11 @@
1609
1640
  "type": "boolean",
1610
1641
  "description": "duck under the mic while speech is detected"
1611
1642
  },
1643
+ "source": {
1644
+ "type": "string",
1645
+ "enum": ["page"],
1646
+ "description": "page: sound the recorded page itself played (a video or audio element heard during the take, meta.media), placed by the plan at the output time it was heard and keyed to the file fetched into page-sound/. A re-plan replaces only these; absent = a clip someone added"
1647
+ },
1612
1648
  "media": {
1613
1649
  "type": "string",
1614
1650
  "description": "the media this belongs to: a `media[].id`; absent = the primary, `source`"
package/skills/VERSION CHANGED
@@ -1 +1 @@
1
- 0.24.0
1
+ 0.25.0
@@ -180,7 +180,10 @@ not re-run `validate` after a push unless the human asks.
180
180
 
181
181
  ## 6. Push the release
182
182
 
183
- Push the take labelled for the release:
183
+ Set `still` in `doc.json` to the payoff first (OUTPUT seconds): the
184
+ result the release is about, never the opening. Without one the cover is
185
+ derived near the start, usually a zoom on a navigation control under the
186
+ first caption. Then push the take labelled for the release:
184
187
 
185
188
  ```
186
189
  vos push take --folder <project-slug> --label "v2.1 launch" --note "<what shipped, one line>" \
@@ -224,6 +227,8 @@ by data. Never spread before the seed is signed off.
224
227
  anything records. Cost a full re-record once.
225
228
  - A template's own palette on a deliverable: resolve `BRAND.md` (or witness
226
229
  the site) before cutting, and put it beside the take.
230
+ - A push with no `still`: the release's watch page and share card open on
231
+ a frame near the start, not on what shipped.
227
232
  - A cold open (`firstlast`): the first frame is the marketing page's
228
233
  headline with the product nowhere; trim to the story or let the entrance
229
234
  carry it.
@@ -201,6 +201,11 @@ Per-channel dimensions and byte budgets: `references/destinations.md`.
201
201
  serves the take into the studio — your doc.json edits arrive intact and
202
202
  every zoom span is draggable.
203
203
 
204
+ 6c. **Pushing it to vos.so** (`vos push take`)? Set `still` in `doc.json`
205
+ (OUTPUT seconds) to the payoff first, the result the video is about,
206
+ never the opening: without one the cover is derived near the start,
207
+ usually a zoom on a navigation control under the first caption.
208
+
204
209
  7. **Package for the destination**: `references/destinations.md`.
205
210
 
206
211
  ## Launch kit (one take → every cut of a release)
@@ -251,6 +256,8 @@ when the ask is a release, not one video.
251
256
  frame the text, and open the span after the click.
252
257
  - Routing the cursor through a hover-triggered menu between beats.
253
258
  - A first frame or a last frame that cannot stand alone as a poster.
259
+ - A push with no `still`: the shelf, the watch page and the share card
260
+ show a frame near the start instead of the result.
254
261
  - A frozen opening: a static landing page records as freeze-then-bang;
255
262
  trim it or speed it, the story opens near the money shot.
256
263
  - A sign-in in `steps`: every step there is in the footage and a typed
@@ -57,10 +57,15 @@ the first line of `~/.config/vos/credentials`. With one, push:
57
57
 
58
58
  ```bash
59
59
  vos push my-config.json --title "…" --desc "one line" \
60
- --prompt "<the person's prompt, as they wrote it>" --wait
60
+ --prompt "<the person's prompt, as they wrote it>" --still <t> --wait
61
61
  # --wait stays until the cover has rendered, then prints the links
62
62
  ```
63
63
 
64
+ **Set the cover at the payoff.** `--still <t>` (OUTPUT seconds) picks the
65
+ frame the shelf, the thumbnail and the share card show: the result the
66
+ film is about, never the opening. Without it the cover is an early frame,
67
+ before anything has arrived; later pushes keep the one you set.
68
+
64
69
  **Show the prompt that made it.** Pass the person's prompt with `--prompt`
65
70
  on the push that creates the vos: the watch page shows it by default, so
66
71
  whoever watches sees what was asked. Use their words as they wrote them,
@@ -155,6 +160,8 @@ examples of every major type: `references/examples.md`.
155
160
  7. **NO `Date.now()` / `Math.random()`** in frame paths — determinism is the
156
161
  contract; derive everything from the timeline's time and `ctx.data`
157
162
  8. **NO `onFrame`** for new configs — put all per-frame logic in `onUpdate`
163
+ 9. **NO push without `--still`** at the payoff — the default cover is an
164
+ early frame, before the film has arrived
158
165
 
159
166
  ## Params and Looks (make it remixable)
160
167
 
@@ -171,6 +171,11 @@ vos push <file>.json --title "…" --desc "one line" --tags a,b \
171
171
 
172
172
  `--prompt` shows the person's prompt on the watch page by default: their words as they wrote them, never yours, with no file path, key or email they did not mean to publish. Say it is shown, and that they can take it off from the watch page.
173
173
 
174
+ Add `--still <t>` (OUTPUT seconds) on the first push, at the payoff: the
175
+ frame the design is about, never the opening. Without it the cover is an
176
+ early frame, before the composition has arrived; every later push keeps
177
+ the one you set.
178
+
174
179
  Pass `--label` and `--note` on EVERY push, the first one included: a
175
180
  create stamps them on v1, and the history is the conversation the human
176
181
  reads. The push creates a PRIVATE vos on the key owner's shelf, filed