@vosjs/cli 0.82.0 → 0.83.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/package.json +4 -4
- package/schema/actions.schema.json +3 -2
- package/skills/VERSION +1 -1
- package/skills/product-video/SKILL.md +55 -3
- package/skills/product-video/references/how-to.md +7 -0
- package/skills/product-video/references/sessions.md +20 -0
- package/skills/vos-cut/SKILL.md +3 -1
package/README.md
CHANGED
|
@@ -190,7 +190,7 @@ vos look https://app.acme.com/projects --session acme --click '[data-testid="new
|
|
|
190
190
|
|
|
191
191
|
It writes `<out>/look.png` (the viewport; `--out look` by default) and lists every control in view (buttons, links, tabs, menu items, checkboxes, switches, options, fields, editable regions, and anything with a test id) with its tag, role, accessible name, test id, link path and query, rect in viewport px, whether it is disabled, inside an open dialog or covered by something on top of it, and ONE proposed selector: its test id; else its `aria-label`, inside the open dialog it sits in; else a link's own `href`, query kept (two tiles to `?vos=a` and `?vos=b` are two links); else role and name (`role=button[name="Continue"]`, what `page.locator` reads), narrowed by its dialog or landmark when that is what makes it one; else a field's own `name` or `placeholder`, a written id, or its words inside its dialog or landmark. Never a position in the tree. Each proposal is counted with the recorder's own selector engine and marked `UNIQUE` (it reaches this element and no other) or `AMBIGUOUS` (it reaches others too, and the recorder would take the first). A control whose words hold an address, a key or a card number is listed with those words withheld, and the frame's exposures are said the way a rehearsal says them. Looking never spends or destroys: a `--click` whose target's words begin with a stem that does (delet, remov, revok, eras, discard, publish, deploy, purchas, subscri, unsubscri, pay, buy, generat, submit, send, transfer, upgrad, confirm, leav, destroy, so `Allow deleting` and `Revoke` are caught with `Delete`), or say move to trash, empty trash or cancel subscription, is refused in words before it is pressed (exit 2), and `--type` fills a field without pressing Enter. A look cannot know every spend: `Make a video` or `Create` on a primary button may start paid work and passes, because refusing every Make would refuse half of every app. The list is a floor under looking; in a recorded flow, a step's `guard: "spend"` in `actions.json` is the fence. A sign-in in front of the page is the wall check's verdict, exit 4; the sign-in page is still looked at, since it is what a `setup` step scripts past. `--wait <ms>` adds a pause before the look for a page that animates in; `--json` prints the whole look as one done event (`url`, `screenshot`, `controls`, `exposures`, `wall`).
|
|
192
192
|
|
|
193
|
-
**`setup`: the steps that run before the camera rolls.** A sign-in form, a cookie banner, the "choose your editor" modal, an onboarding tour: things a take must get past and must not show. `actions.json` takes `setup: [...]` beside `steps`, with the verbs `goto`, `click`, `type`, `press`, `wait`. They run after the first navigation and before a frame is captured, as plain actions with no cursor, no frames, no pace and nothing in `meta.steps`; then the recorder opens `url` again and the take begins where the setup left it. A `type` step's `text` may be `{ "env": "DEMO_PASSWORD" }`, read from the shell at run time and never logged or stored (the log names the field, never the value; a literal typed into a password field is refused by `validate`, because `actions.json` is committed and pushed with the take). A selector that never appears fails the take before anything is recorded (exit 2), because a take that begins at a half-finished sign-in is the wall by another name; rehearse the setup with `--dry-run` like everything else. This is rung 2 of the session ladder made scriptable: a local or self-hosted instance with a seeded user, no state file needed.
|
|
193
|
+
**`setup`: the steps that run before the camera rolls.** A sign-in form, a cookie banner, the "choose your editor" modal, an onboarding tour: things a take must get past and must not show. `actions.json` takes `setup: [...]` beside `steps`, with the verbs `goto`, `click`, `type`, `press`, `wait`, and the guard `if`. They run after the first navigation and before a frame is captured, as plain actions with no cursor, no frames, no pace and nothing in `meta.steps`; then the recorder opens `url` again and the take begins where the setup left it. A `type` step's `text` may be `{ "env": "DEMO_PASSWORD" }`, read from the shell at run time and never logged or stored (the log names the field, never the value; a literal typed into a password field is refused by `validate`, because `actions.json` is committed and pushed with the take). A selector that never appears fails the take before anything is recorded (exit 2), because a take that begins at a half-finished sign-in is the wall by another name; rehearse the setup with `--dry-run` like everything else. This is rung 2 of the session ladder made scriptable: a local or self-hosted instance with a seeded user, no state file needed.
|
|
194
194
|
|
|
195
195
|
```json
|
|
196
196
|
"setup": [
|
|
@@ -203,9 +203,12 @@ It writes `<out>/look.png` (the viewport; `--out look` by default) and lists eve
|
|
|
203
203
|
|
|
204
204
|
**`teardown`: the steps that run after it.** A flow that CREATES something in the account (an API key, a project, an invite) creates it on every rehearsal and every take, since `--dry-run` clicks for real. `teardown: [...]` beside `setup` removes it again: the same verbs and the same rules (a `{ "env": "NAME" }` text, no literal in a password field, http and https only), run off camera once the screencast has stopped, on the take's own page and session, so nothing of it is captured or lands in `meta.steps`. It runs after a rehearsal too, and when the take stops early (a failed `await`, `--strict` on a page that stopped answering, the cap) or throws once its first step ran; not when the take never began (a setup that failed, a wall). The steps together get 60 s. A teardown never fails the take or changes the exit code: it stops at the first step that fails, the done event carries `teardown: { ran, of, failed?: { step, do, selector?, error } }`, and the summary says `teardown: 3 step(s) ran` or `TEARDOWN FAILED at teardown[1] (click #confirm): <reason>. What the take created may still be in the product; remove it by hand.` A `{ env }` the shell does not hold refuses the take before it begins, since the take would create what its teardown cannot remove.
|
|
205
205
|
|
|
206
|
+
**`if`: a guard for either list.** `{ "do": "if", "selector": "…" }` looks for its selector (3 s, or its `ms`): on the page, the steps after it run; not on the page, they are skipped and the list ends there, which is never a failure. A take that deletes on camera what it created, and keeps a teardown as the net for a take that stops early, starts the undoing with one, so a good take says `teardown: nothing to undo, <selector> is not on the page` instead of a failure; the done event carries `skipped: { step, selector, rest }`. A setup that signs in guards its sign-in the same way (`if` the email field), so a session already signed in passes. A step that fails after a guard still fails.
|
|
207
|
+
|
|
206
208
|
```json
|
|
207
209
|
"teardown": [
|
|
208
210
|
{ "do": "goto", "url": "http://localhost:3000/settings/keys" },
|
|
211
|
+
{ "do": "if", "selector": "[data-testid=key-row]:first-child" },
|
|
209
212
|
{ "do": "click", "selector": "[data-testid=key-row]:first-child button[aria-label=Revoke]" },
|
|
210
213
|
{ "do": "click", "selector": "role=button[name=\"Revoke key\"]" }
|
|
211
214
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vosjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.83.1",
|
|
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",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"mediabunny": "^1.55.7",
|
|
53
53
|
"playwright": "^1.49.0",
|
|
54
54
|
"@vosjs/core": "^0.31.0",
|
|
55
|
-
"@vosjs/editor": "^1.6.0",
|
|
56
|
-
"@vosjs/elements": "^0.12.1",
|
|
57
|
-
"@vosjs/render-core": "^0.10.0",
|
|
58
55
|
"@vosjs/shared": "^0.7.0",
|
|
56
|
+
"@vosjs/elements": "^0.12.1",
|
|
57
|
+
"@vosjs/editor": "^1.6.0",
|
|
58
|
+
"@vosjs/render-core": "^0.11.0",
|
|
59
59
|
"@vosjs/studio-core": "^0.46.0",
|
|
60
60
|
"@vosjs/tween": "^0.8.3",
|
|
61
61
|
"@vosjs/timeline": "^0.5.0"
|
|
@@ -1078,7 +1078,8 @@
|
|
|
1078
1078
|
"properties": {
|
|
1079
1079
|
"do": {
|
|
1080
1080
|
"type": "string",
|
|
1081
|
-
"enum": ["wait", "click", "type", "press", "goto"]
|
|
1081
|
+
"enum": ["wait", "click", "type", "press", "goto", "if"],
|
|
1082
|
+
"description": "if: a guard; when its selector is not on the page, the steps after it are skipped and the list ends there, which is not a failure"
|
|
1082
1083
|
},
|
|
1083
1084
|
"id": {
|
|
1084
1085
|
"type": "string"
|
|
@@ -1114,7 +1115,7 @@
|
|
|
1114
1115
|
},
|
|
1115
1116
|
"ms": {
|
|
1116
1117
|
"type": "number",
|
|
1117
|
-
"description": "wait: the pause; click: the read after the page settled, held from its last visual change (default 150); type and scroll: the settle after (defaults 150/200)"
|
|
1118
|
+
"description": "wait: the pause; click: the read after the page settled, held from its last visual change (default 150); type and scroll: the settle after (defaults 150/200); if: how long to look for its selector (default 3000)"
|
|
1118
1119
|
}
|
|
1119
1120
|
},
|
|
1120
1121
|
"description": "One setup or teardown step: a plain action, with no cursor and no frames."
|
package/skills/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.28.0
|
|
@@ -89,6 +89,25 @@ person before recording**; an answer that does neither was not an answer.
|
|
|
89
89
|
the product is a real account, and report it at the end so they can
|
|
90
90
|
delete what was made. A product where creating is cheap (a sandbox, a
|
|
91
91
|
seeded local instance) needs the ledger written, not asked.
|
|
92
|
+
**Undo it in `teardown`** (`@vosjs/cli` 0.81 and later): the setup verbs,
|
|
93
|
+
run off camera after every rehearsal and take, and when one stops
|
|
94
|
+
early, so no rehearsal leaves a draft behind. When the video itself
|
|
95
|
+
deletes on camera what it made (a key created, then revoked), the
|
|
96
|
+
teardown is the net for a take that stopped before the delete: open it
|
|
97
|
+
with a guard on what the take created (`@vosjs/cli` 0.83 and later),
|
|
98
|
+
so a good take says `teardown: nothing to undo` instead of a failure.
|
|
99
|
+
```json
|
|
100
|
+
"teardown": [
|
|
101
|
+
{ "do": "press", "key": "Escape" },
|
|
102
|
+
{ "do": "if", "selector": "[aria-label='Actions for CI deploy']" },
|
|
103
|
+
{ "do": "click", "selector": "[aria-label='Actions for CI deploy']" },
|
|
104
|
+
{ "do": "click", "selector": "role=menuitem[name='Revoke']" },
|
|
105
|
+
{ "do": "click", "selector": "role=alertdialog >> role=button[name='Revoke']" }
|
|
106
|
+
]
|
|
107
|
+
```
|
|
108
|
+
A step that fails AFTER the guard is a real leftover: say it to the
|
|
109
|
+
person. Read the account back before you report (a `vos session exec`
|
|
110
|
+
script lists it) and say it holds what it held before.
|
|
92
111
|
4. **What it SPENDS.** A step that costs something real (a generation, a
|
|
93
112
|
purchase, a message sent, a deploy) carries `"guard": "spend"` and an
|
|
94
113
|
`id`. A rehearsal hovers it and never presses it; `vos record` refuses
|
|
@@ -154,6 +173,17 @@ person before recording**; an answer that does neither was not an answer.
|
|
|
154
173
|
steps as they are. Then the side-effect ledger (pre-flight, 3), then
|
|
155
174
|
rehearse.
|
|
156
175
|
|
|
176
|
+
**Past what `look` reaches, script inside the session.** A state no
|
|
177
|
+
step reaches yet, what the account holds, a check after a flow: write
|
|
178
|
+
an ES module and run `vos session exec <app> script.mjs --url <page>`
|
|
179
|
+
(`@vosjs/cli` 0.81 and later). Its default export gets
|
|
180
|
+
`{ context, page, url, name }` on the profile exactly as `record` opens
|
|
181
|
+
it, and what it returns is printed. **Never open the session's profile
|
|
182
|
+
yourself** (Playwright's `launchPersistentContext`, a bundled Chromium, a
|
|
183
|
+
keychain flag): a browser that cannot read the profile's cookies starts
|
|
184
|
+
them over, the session is signed out for good, and the person signs in
|
|
185
|
+
again. Run one verb at a time on a session; two at once lock the profile.
|
|
186
|
+
|
|
157
187
|
**Verified the feature with agent-browser already?** Keep that walk and
|
|
158
188
|
skip the second script. agent-browser's `--json` result does not say
|
|
159
189
|
what ran (`scroll` answers `{scrolled:true}`), so wrap each call so the
|
|
@@ -250,6 +280,12 @@ person before recording**; an answer that does neither was not an answer.
|
|
|
250
280
|
flow is broken — fix it, never ship around it. A guarded step records
|
|
251
281
|
only with `--confirm <id>` (pre-flight, 4). The take auto-encodes and
|
|
252
282
|
auto-plans.
|
|
283
|
+
**Recording again into a take you already cut** (a fixed step, a longer
|
|
284
|
+
hold, the product changed): add `--reuse` (`@vosjs/cli` 0.81 and later).
|
|
285
|
+
The new footage replaces the old and your cut (zooms, captions, layers,
|
|
286
|
+
cover, sound) is re-timed onto it in the same command; the last line
|
|
287
|
+
names what it kept and what could not follow. Without it the take is
|
|
288
|
+
planned fresh and the cut waits in `doc.prev.json`.
|
|
253
289
|
(`vos create --actions actions.json out.webm --strict` is the one-shot
|
|
254
290
|
record+render verb — fine for a quick first pass, but THIS skill's loop
|
|
255
291
|
reviews frames before rendering, so prefer the separate verbs here.)
|
|
@@ -302,9 +338,14 @@ person before recording**; an answer that does neither was not an answer.
|
|
|
302
338
|
every zoom span is draggable.
|
|
303
339
|
|
|
304
340
|
6c. **Pushing it to vos.so** (`vos push take`)? Set `still` in `doc.json`
|
|
305
|
-
(OUTPUT seconds) to the payoff first, the result the
|
|
306
|
-
never the opening: without one the cover is derived near
|
|
307
|
-
usually a zoom on a navigation control
|
|
341
|
+
(OUTPUT seconds) to the payoff BEFORE the first push, the result the
|
|
342
|
+
video is about, never the opening: without one the cover is derived near
|
|
343
|
+
the start, usually a zoom on a navigation control or an empty field
|
|
344
|
+
under the first caption, and moving it after costs a second version.
|
|
345
|
+
`vos plan take --still <t>` writes it and `vos frames take --at-still`
|
|
346
|
+
shows it: look before the push. The push writes
|
|
347
|
+
`take/vos.json`: its `vosId` is the link (`https://vos.so/vos/<vosId>`).
|
|
348
|
+
Read the link there, never from filtered logs or a lookup script.
|
|
308
349
|
|
|
309
350
|
7. **Package for the destination**: `references/destinations.md`.
|
|
310
351
|
|
|
@@ -383,3 +424,14 @@ when the ask is a release, not one video.
|
|
|
383
424
|
- Scripting a signed-in product blind: `vos look` first, then the ledger,
|
|
384
425
|
then the rehearsal. Guessing selectors from a public page costs a take
|
|
385
426
|
per guess.
|
|
427
|
+
- Opening a session's profile with your own Playwright: it signed a
|
|
428
|
+
founder's session out minutes after `vos look` had used it. Script
|
|
429
|
+
inside it with `vos session exec`.
|
|
430
|
+
- Recording again into a cut take without `--reuse`: the fresh plan takes
|
|
431
|
+
the cut's place.
|
|
432
|
+
- A teardown that undoes an on-camera delete with no `if` in front of its
|
|
433
|
+
undoing: every good take reports `TEARDOWN FAILED`, and a person learns
|
|
434
|
+
to ignore the line that one day is true.
|
|
435
|
+
- Cutting a caption's `duration` because the plan said it outlasts its
|
|
436
|
+
step: the words then cannot be read. Shorten the words, or give the step
|
|
437
|
+
more `ms` and record again.
|
|
@@ -26,6 +26,13 @@ how-tos and the cuts a person had to redo on them.
|
|
|
26
26
|
the step in `actions.json`; `vos plan` and `vos join --captions` land
|
|
27
27
|
each one anchored to its step, so a re-record carries it). One caption
|
|
28
28
|
is one beat; a step with no caption is part of the beat before it.
|
|
29
|
+
It leaves when its step ends, or once its words are read when that is
|
|
30
|
+
later (17 characters a second, 1.2 to 3 s; `@vosjs/cli` 0.82 and later),
|
|
31
|
+
so a short caption on a quick step never lingers over the next one. A
|
|
32
|
+
caption the plan names as staying past its step has words that outrun
|
|
33
|
+
the beat: shorten them (an edit) or give the step more `ms` (a
|
|
34
|
+
re-record); never cut its `duration`, which takes the time its words
|
|
35
|
+
need to be read.
|
|
29
36
|
|
|
30
37
|
3. **The result is the payoff, and the cold open.** Open on the finished
|
|
31
38
|
result for a beat (a repeated segment at camera rest,
|
|
@@ -129,6 +129,19 @@ vos record --actions actions.json --out take --session acme --dry-run
|
|
|
129
129
|
vos record --actions actions.json --out take --session acme --strict --json
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
Anything else you need from the signed-in browser (what the account holds,
|
|
133
|
+
a state no step reaches yet, a check that a flow cleaned up after itself)
|
|
134
|
+
runs INSIDE the session (`@vosjs/cli` 0.81 and later):
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
vos session exec acme list-keys.mjs --url https://app.example.com/settings/keys
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`list-keys.mjs` is an ES module whose default export receives
|
|
141
|
+
`{ context, page, url, name }`, opened exactly as `record` opens the
|
|
142
|
+
profile; what it returns is printed (`result` in `--json`). Never open the
|
|
143
|
+
profile any other way: see the rules below.
|
|
144
|
+
|
|
132
145
|
`--session` and `--storage-state` are two doors to one take; pass one.
|
|
133
146
|
No file to mint, nothing in the take, nothing to delete: the profile lives
|
|
134
147
|
under `~/.config/vos/sessions/` and `vos push` refuses a take that holds a
|
|
@@ -191,6 +204,13 @@ show, before anything is pushed further or handed over.
|
|
|
191
204
|
mint again and it is a live credential for as long as it sits there.
|
|
192
205
|
- **A session expires.** When a re-record that worked last week skips its
|
|
193
206
|
first selector, re-walk the ladder before touching the script.
|
|
207
|
+
- **Never open a session's profile yourself.** Not with Playwright's
|
|
208
|
+
`launchPersistentContext`, not with a bundled Chromium, not with a
|
|
209
|
+
keychain flag patched in. A browser that cannot decrypt the profile's
|
|
210
|
+
cookies starts the store over: measured, one such launch signed a session
|
|
211
|
+
out for the system Chrome too, and the person had to sign in again. Every
|
|
212
|
+
read goes through a vos verb (`look`, `record`, `session exec`), one at a
|
|
213
|
+
time: two verbs on one profile at once lock it.
|
|
194
214
|
- **A person signing in will use their REAL account**, whatever you asked
|
|
195
215
|
for: it is the one they have. The recorder looks for you (`@vosjs/cli`
|
|
196
216
|
0.42 and later): the rehearsal ends with `EXPOSED in the frame`, naming
|
package/skills/vos-cut/SKILL.md
CHANGED
|
@@ -232,7 +232,9 @@ shows it): the result the cut is about, never the opening. Without one the
|
|
|
232
232
|
cover is derived near the start, usually a zoom on a navigation control
|
|
233
233
|
under the first caption.
|
|
234
234
|
|
|
235
|
-
The
|
|
235
|
+
The push writes `<take>/vos.json`: its `vosId` is the link
|
|
236
|
+
(`https://vos.so/vos/<vosId>`); read it there, never from filtered logs or
|
|
237
|
+
a lookup script. The base comes from `vos.json`. `409 stale_base` → `vos pull`, re-apply on
|
|
236
238
|
top, push again. `409 protected_conflict` → keep the human's values unless
|
|
237
239
|
the ask named that node (`--override <id>` only then). A 400 prints the
|
|
238
240
|
field and its limit. Then `GET /api/vos/<id>`, follow
|