@vosjs/cli 0.69.1 → 0.71.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/dist/cli.js CHANGED
@@ -867,7 +867,7 @@ async function cmdCheck(argv) {
867
867
  return result.ok ? EXIT_OK : EXIT_ERROR;
868
868
  }
869
869
  async function delegate(argv, viaAlias = false) {
870
- const { run } = await import("./run-6VQWD42R.js");
870
+ const { run } = await import("./run-QPHIGP6N.js");
871
871
  if (viaAlias && argv[0]) {
872
872
  process.stderr.write(
873
873
  `note: "vos voila ${argv[0]}" is now "vos ${argv[0]}".
@@ -907,7 +907,7 @@ async function main() {
907
907
  const engine = HELP_ENGINE.split("\n").filter(
908
908
  (l) => l.startsWith(` vos ${cmd} `)
909
909
  );
910
- const { verbHelp } = await import("./run-6VQWD42R.js");
910
+ const { verbHelp } = await import("./run-QPHIGP6N.js");
911
911
  const take = verbHelp(cmd);
912
912
  process.stdout.write(
913
913
  `${engine.join("\n")}
@@ -939,7 +939,7 @@ ${take.includes("no such verb") ? "" : take}`
939
939
  main().then(async (code) => {
940
940
  if (code === EXIT_OK) {
941
941
  const verb = process.argv[2] ?? "";
942
- const { HELP } = await import("./run-6VQWD42R.js");
942
+ const { HELP } = await import("./run-QPHIGP6N.js");
943
943
  const documented = [
944
944
  ...helpFlagsFor(HELP_ENGINE, verb),
945
945
  ...helpFlagsFor(HELP, verb)
package/dist/index.d.ts CHANGED
@@ -213,7 +213,7 @@ interface MotionTemplate {
213
213
  }
214
214
 
215
215
  interface ReuseReport {
216
- /** spans re-timed through an explicit anchor. */
216
+ /** spans re-timed against a step: an explicit anchor, or the step they sat on. */
217
217
  anchored: number;
218
218
  /** spans re-timed through the step map (approximate by nature). */
219
219
  mapped: number;
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  startTakeServer,
15
15
  validateActions,
16
16
  waitForPageDone
17
- } from "./chunk-NHJRD2XC.js";
17
+ } from "./chunk-XB5XRE4Q.js";
18
18
  import {
19
19
  BrowserUnavailableError,
20
20
  configDuration,
@@ -6,7 +6,7 @@ import {
6
6
  takeBackdrop,
7
7
  takeBrowserArgs,
8
8
  verbHelp
9
- } from "./chunk-NHJRD2XC.js";
9
+ } from "./chunk-XB5XRE4Q.js";
10
10
  import "./chunk-YZAM4W7B.js";
11
11
  import "./chunk-Y5E672GT.js";
12
12
  import "./chunk-FL3E27WE.js";
@@ -19,4 +19,4 @@ export {
19
19
  takeBrowserArgs,
20
20
  verbHelp
21
21
  };
22
- //# sourceMappingURL=run-6VQWD42R.js.map
22
+ //# sourceMappingURL=run-QPHIGP6N.js.map
@@ -42,7 +42,9 @@ function beatOf(step) {
42
42
  case "click":
43
43
  return `${named}Click ${sayStepTarget(step)}`;
44
44
  case "type":
45
- return `${named}Type "${step.text}" into ${sayStepTarget(step)}`;
45
+ 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" : ""}`;
46
+ case "press":
47
+ return `${named}Press ${step.key}${step.selector ? ` in ${sayStepTarget(step)}` : ""}`;
46
48
  case "scroll":
47
49
  return `${named}Scroll ${step.dy < 0 ? "up" : "down"} ${step.dy < 0 ? "a little" : "a little"}`;
48
50
  case "move":
@@ -108,4 +110,4 @@ export {
108
110
  sayTarget,
109
111
  shotList
110
112
  };
111
- //# sourceMappingURL=shotList-KH6FTX4L.js.map
113
+ //# sourceMappingURL=shotList-MAQVLZBY.js.map
@@ -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'\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\nfunction beatOf(step: ActionStep): string | null {\n const cap = step.caption\n const named = cap ? `${cap}: ` : ''\n switch (step.do) {\n case 'wait':\n return null\n case 'hover':\n return `${named}Rest the pointer on ${sayStepTarget(step)}`\n case 'click':\n return `${named}Click ${sayStepTarget(step)}`\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 ${step.key}${step.selector ? ` in ${sayStepTarget(step as { selector: string; id?: string })}` : ''}`\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 return `${named}Drag ${step.selector ? sayStepTarget(step as { selector: string; id?: string }) : 'from the start point'} across`\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.69.1",
3
+ "version": "0.71.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",
@@ -51,14 +51,14 @@
51
51
  "acorn-walk": "^8.3.5",
52
52
  "mediabunny": "^1.55.7",
53
53
  "playwright": "^1.49.0",
54
- "@vosjs/editor": "^1.6.0",
55
- "@vosjs/render-core": "^0.3.28",
56
54
  "@vosjs/elements": "^0.12.0",
55
+ "@vosjs/render-core": "^0.4.0",
57
56
  "@vosjs/core": "^0.29.1",
57
+ "@vosjs/editor": "^1.6.0",
58
58
  "@vosjs/shared": "^0.7.0",
59
- "@vosjs/studio-core": "^0.41.1",
60
- "@vosjs/timeline": "^0.5.0",
61
- "@vosjs/tween": "^0.8.3"
59
+ "@vosjs/studio-core": "^0.42.0",
60
+ "@vosjs/tween": "^0.8.3",
61
+ "@vosjs/timeline": "^0.5.0"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/node": "^22",
@@ -41,11 +41,17 @@
41
41
  "type": "string",
42
42
  "enum": ["wait", "click", "type", "press", "goto"]
43
43
  },
44
- "id": { "type": "string" },
45
- "selector": { "type": "string" },
44
+ "id": {
45
+ "type": "string"
46
+ },
47
+ "selector": {
48
+ "type": "string"
49
+ },
46
50
  "text": {
47
51
  "oneOf": [
48
- { "type": "string" },
52
+ {
53
+ "type": "string"
54
+ },
49
55
  {
50
56
  "type": "object",
51
57
  "required": ["env"],
@@ -82,7 +88,9 @@
82
88
  "required": ["selector"],
83
89
  "additionalProperties": false,
84
90
  "properties": {
85
- "selector": { "type": "string" },
91
+ "selector": {
92
+ "type": "string"
93
+ },
86
94
  "as": {
87
95
  "type": "string",
88
96
  "enum": ["blur", "text"],
@@ -122,6 +130,11 @@
122
130
  "type": "string",
123
131
  "maxLength": 80,
124
132
  "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"
133
+ },
134
+ "read": {
135
+ "type": "string",
136
+ "minLength": 1,
137
+ "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"
125
138
  }
126
139
  }
127
140
  },
@@ -151,6 +164,11 @@
151
164
  "type": "string",
152
165
  "maxLength": 80,
153
166
  "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"
167
+ },
168
+ "read": {
169
+ "type": "string",
170
+ "minLength": 1,
171
+ "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"
154
172
  }
155
173
  }
156
174
  },
@@ -179,6 +197,11 @@
179
197
  "type": "string",
180
198
  "maxLength": 80,
181
199
  "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"
200
+ },
201
+ "read": {
202
+ "type": "string",
203
+ "minLength": 1,
204
+ "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"
182
205
  }
183
206
  }
184
207
  },
@@ -221,6 +244,75 @@
221
244
  "type": "string",
222
245
  "maxLength": 80,
223
246
  "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"
247
+ },
248
+ "read": {
249
+ "type": "string",
250
+ "minLength": 1,
251
+ "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"
252
+ },
253
+ "replace": {
254
+ "type": "boolean",
255
+ "default": false,
256
+ "description": "select the field's value before typing, so what is typed replaces it: a form that opens filled in, a search box holding the last query"
257
+ },
258
+ "at": {
259
+ "type": "string",
260
+ "enum": ["end", "start"],
261
+ "description": "where the caret goes after the focusing click, before the first character: a click lands it where the pointer hit, and a code editor line is typed at its end"
262
+ }
263
+ }
264
+ },
265
+ {
266
+ "type": "object",
267
+ "required": ["do", "key"],
268
+ "additionalProperties": false,
269
+ "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.",
270
+ "properties": {
271
+ "do": {
272
+ "const": "press"
273
+ },
274
+ "key": {
275
+ "type": "string",
276
+ "enum": [
277
+ "Escape",
278
+ "Enter",
279
+ "Tab",
280
+ "Backspace",
281
+ "Delete",
282
+ "ArrowUp",
283
+ "ArrowDown",
284
+ "ArrowLeft",
285
+ "ArrowRight",
286
+ "Home",
287
+ "End",
288
+ "PageUp",
289
+ "PageDown",
290
+ "Space"
291
+ ]
292
+ },
293
+ "selector": {
294
+ "type": "string",
295
+ "description": "the element the key is about: the ping and the step's rect are its, with no click"
296
+ },
297
+ "ms": {
298
+ "type": "number",
299
+ "minimum": 0,
300
+ "description": "the read after the key, ms (default 150), held from the page's last visual change like a click's"
301
+ },
302
+ "id": {
303
+ "type": "string",
304
+ "minLength": 1,
305
+ "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."
306
+ },
307
+ "caption": {
308
+ "type": "string",
309
+ "maxLength": 80,
310
+ "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"
311
+ },
312
+ "read": {
313
+ "type": "string",
314
+ "minLength": 1,
315
+ "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"
224
316
  }
225
317
  }
226
318
  },
@@ -249,6 +341,11 @@
249
341
  "type": "string",
250
342
  "maxLength": 80,
251
343
  "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"
344
+ },
345
+ "read": {
346
+ "type": "string",
347
+ "minLength": 1,
348
+ "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"
252
349
  }
253
350
  }
254
351
  },
@@ -277,6 +374,11 @@
277
374
  "type": "string",
278
375
  "maxLength": 80,
279
376
  "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"
377
+ },
378
+ "read": {
379
+ "type": "string",
380
+ "minLength": 1,
381
+ "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"
280
382
  }
281
383
  }
282
384
  },
@@ -322,6 +424,11 @@
322
424
  "type": "string",
323
425
  "maxLength": 80,
324
426
  "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"
427
+ },
428
+ "read": {
429
+ "type": "string",
430
+ "minLength": 1,
431
+ "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"
325
432
  }
326
433
  }
327
434
  }