@vosjs/cli 0.77.0 → 0.78.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/README.md +83 -16
- package/dist/{assetManifest-MQ7HO7RZ.js → assetManifest-S6H5F2VE.js} +2 -2
- package/dist/{chunk-SB622PZM.js → chunk-INRUYQ6J.js} +3228 -328
- package/dist/chunk-INRUYQ6J.js.map +1 -0
- package/dist/{chunk-KDIRW72G.js → chunk-JYCW4EOA.js} +6 -3
- package/dist/chunk-JYCW4EOA.js.map +1 -0
- package/dist/{chunk-2KEQ3LKH.js → chunk-RUDDBHQ7.js} +18 -2
- package/dist/chunk-RUDDBHQ7.js.map +1 -0
- package/dist/{chunk-UY2N42P2.js → chunk-ZSJEISKL.js} +6 -2
- package/dist/{chunk-UY2N42P2.js.map → chunk-ZSJEISKL.js.map} +1 -1
- package/dist/cli.js +6 -6
- package/dist/index.d.ts +29 -26
- package/dist/index.js +4 -4
- package/dist/manifest-6DJSPKVH.js +8 -0
- package/dist/{run-2TY7KEKT.js → run-TIFM3ZHI.js} +6 -4
- package/dist/{shotList-MAQVLZBY.js → shotList-SCA234OS.js} +19 -1
- package/dist/shotList-SCA234OS.js.map +1 -0
- package/package.json +5 -5
- package/schema/actions.schema.json +192 -0
- package/schema/doc.schema.json +26 -2
- package/dist/chunk-2KEQ3LKH.js.map +0 -1
- package/dist/chunk-KDIRW72G.js.map +0 -1
- package/dist/chunk-SB622PZM.js.map +0 -1
- package/dist/manifest-QLQKVSML.js +0 -8
- package/dist/shotList-MAQVLZBY.js.map +0 -1
- /package/dist/{assetManifest-MQ7HO7RZ.js.map → assetManifest-S6H5F2VE.js.map} +0 -0
- /package/dist/{manifest-QLQKVSML.js.map → manifest-6DJSPKVH.js.map} +0 -0
- /package/dist/{run-2TY7KEKT.js.map → run-TIFM3ZHI.js.map} +0 -0
package/dist/index.d.ts
CHANGED
|
@@ -91,32 +91,12 @@ declare class BrowserUnavailableError extends Error {
|
|
|
91
91
|
* `args` are extra Chromium switches (`--browser-arg` on the verbs that
|
|
92
92
|
* record). Some product surfaces cannot be reached without one: a recorder
|
|
93
93
|
* needs a fake capture device to get past a permission prompt, an extension
|
|
94
|
-
* page needs the extension loaded. Passed through verbatim.
|
|
94
|
+
* page needs the extension loaded. Passed through verbatim. `headless:
|
|
95
|
+
* false` opens a window (`vos look --headed`).
|
|
95
96
|
*/
|
|
96
|
-
declare function launchBrowser(args?: string[]
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
every non-engine verb here (`vos <verb>`). The exported `manifest` (index.ts)
|
|
100
|
-
tells the host which verbs exist and how to summarize them in help. */
|
|
101
|
-
declare function run(argv: string[]): Promise<number>;
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* The verb manifest of the take pipeline and the vos.so verbs — what
|
|
105
|
-
* `vos help` lists under the engine verbs. It kept the shape a separately
|
|
106
|
-
* installed plugin once handed the host (name + host range), so a script
|
|
107
|
-
* reading it keeps working.
|
|
108
|
-
*/
|
|
109
|
-
interface PluginVerb {
|
|
110
|
-
name: string;
|
|
111
|
-
summary: string;
|
|
112
|
-
}
|
|
113
|
-
interface PluginManifest {
|
|
114
|
-
name: string;
|
|
115
|
-
/** Host versions this plugin speaks the run(argv) contract with. */
|
|
116
|
-
hostRange: string;
|
|
117
|
-
verbs: PluginVerb[];
|
|
118
|
-
}
|
|
119
|
-
declare const manifest: PluginManifest;
|
|
97
|
+
declare function launchBrowser(args?: string[], launch?: {
|
|
98
|
+
headless?: boolean;
|
|
99
|
+
}): Promise<Browser>;
|
|
120
100
|
|
|
121
101
|
/** The name `record` writes its footage under — its encoder emits WebM. */
|
|
122
102
|
declare const RECORDING_NAME = "recording.webm";
|
|
@@ -143,7 +123,7 @@ interface TakeData {
|
|
|
143
123
|
/** Load a take directory; cursor+meta are required, doc/actions optional. */
|
|
144
124
|
declare function loadTake(dir: string): Promise<TakeData>;
|
|
145
125
|
|
|
146
|
-
type RecordOpts = Omit<RecordOpts$1, 'context' | 'env' | 'platform'> & {
|
|
126
|
+
type RecordOpts = Omit<RecordOpts$1, 'context' | 'env' | 'platform' | 'outputs'> & {
|
|
147
127
|
/**
|
|
148
128
|
* A ready-made context to record IN, instead of one made from `browser`:
|
|
149
129
|
* `--session <name>`, a persistent profile the person signed in to.
|
|
@@ -152,6 +132,29 @@ type RecordOpts = Omit<RecordOpts$1, 'context' | 'env' | 'platform'> & {
|
|
|
152
132
|
};
|
|
153
133
|
declare function recordTake(browser: Browser, url: string, actions: ActionsFile, paths: TakePaths, log: (msg: string) => void, opts?: RecordOpts): Promise<_vosjs_render_core_record.RecordResult>;
|
|
154
134
|
|
|
135
|
+
/** Entry point — the delegation contract for @vosjs/cli: the host forwards
|
|
136
|
+
every non-engine verb here (`vos <verb>`). The exported `manifest` (index.ts)
|
|
137
|
+
tells the host which verbs exist and how to summarize them in help. */
|
|
138
|
+
declare function run(argv: string[]): Promise<number>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The verb manifest of the take pipeline and the vos.so verbs — what
|
|
142
|
+
* `vos help` lists under the engine verbs. It kept the shape a separately
|
|
143
|
+
* installed plugin once handed the host (name + host range), so a script
|
|
144
|
+
* reading it keeps working.
|
|
145
|
+
*/
|
|
146
|
+
interface PluginVerb {
|
|
147
|
+
name: string;
|
|
148
|
+
summary: string;
|
|
149
|
+
}
|
|
150
|
+
interface PluginManifest {
|
|
151
|
+
name: string;
|
|
152
|
+
/** Host versions this plugin speaks the run(argv) contract with. */
|
|
153
|
+
hostRange: string;
|
|
154
|
+
verbs: PluginVerb[];
|
|
155
|
+
}
|
|
156
|
+
declare const manifest: PluginManifest;
|
|
157
|
+
|
|
155
158
|
declare function encodeRecording(browser: Browser, takeDir: string, onProgress: (fraction: number) => void): Promise<{
|
|
156
159
|
bytes: number;
|
|
157
160
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
startTakeServer,
|
|
15
15
|
validateActions,
|
|
16
16
|
waitForPageDone
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-INRUYQ6J.js";
|
|
18
18
|
import {
|
|
19
19
|
BrowserUnavailableError,
|
|
20
20
|
configDuration,
|
|
@@ -23,13 +23,13 @@ import {
|
|
|
23
23
|
previewPages,
|
|
24
24
|
renderStill,
|
|
25
25
|
renderVideo
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-JYCW4EOA.js";
|
|
27
27
|
import {
|
|
28
28
|
RECORDING_NAME,
|
|
29
29
|
loadTake,
|
|
30
30
|
pullMedia,
|
|
31
31
|
takePaths
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-ZSJEISKL.js";
|
|
33
33
|
import {
|
|
34
34
|
apiError,
|
|
35
35
|
apiJson,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
import "./chunk-6XPFWQKP.js";
|
|
43
43
|
import {
|
|
44
44
|
manifest
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-RUDDBHQ7.js";
|
|
46
46
|
export {
|
|
47
47
|
BrowserUnavailableError,
|
|
48
48
|
RECORDING_NAME,
|
|
@@ -3,20 +3,22 @@ import {
|
|
|
3
3
|
HELP,
|
|
4
4
|
MULTI_FLAGS,
|
|
5
5
|
run,
|
|
6
|
+
strictFails,
|
|
6
7
|
takeBackdrop,
|
|
7
8
|
takeBrowserArgs,
|
|
8
9
|
verbHelp
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-INRUYQ6J.js";
|
|
11
|
+
import "./chunk-JYCW4EOA.js";
|
|
12
|
+
import "./chunk-ZSJEISKL.js";
|
|
12
13
|
import "./chunk-B4U4URE3.js";
|
|
13
14
|
import "./chunk-6XPFWQKP.js";
|
|
14
15
|
export {
|
|
15
16
|
HELP,
|
|
16
17
|
MULTI_FLAGS,
|
|
17
18
|
run,
|
|
19
|
+
strictFails,
|
|
18
20
|
takeBackdrop,
|
|
19
21
|
takeBrowserArgs,
|
|
20
22
|
verbHelp
|
|
21
23
|
};
|
|
22
|
-
//# sourceMappingURL=run-
|
|
24
|
+
//# sourceMappingURL=run-TIFM3ZHI.js.map
|
|
@@ -31,10 +31,28 @@ function sayStepTarget(step) {
|
|
|
31
31
|
const byId = sayId(step.id);
|
|
32
32
|
return byId ? `${byId} (${bySelector})` : bySelector;
|
|
33
33
|
}
|
|
34
|
+
function sayCondition(c) {
|
|
35
|
+
if (c.text !== void 0) return `"${c.text}" shows on the page`;
|
|
36
|
+
if (c.gone !== void 0) return `${sayTarget(c.gone)} is gone`;
|
|
37
|
+
const target = sayStepTarget({ selector: c.selector ?? "", id: c.id });
|
|
38
|
+
if (c.enabled === true) return `${target} is enabled`;
|
|
39
|
+
if (c.enabled === false) return `${target} is disabled`;
|
|
40
|
+
return `${target} is showing`;
|
|
41
|
+
}
|
|
34
42
|
function beatOf(step) {
|
|
43
|
+
const line = gestureBeat(step);
|
|
44
|
+
return line && step.guard ? `${line} (this spends something real: do it only if you mean to)` : line;
|
|
45
|
+
}
|
|
46
|
+
function gestureBeat(step) {
|
|
35
47
|
const cap = step.caption;
|
|
36
48
|
const named = cap ? `${cap}: ` : "";
|
|
37
49
|
switch (step.do) {
|
|
50
|
+
case "expect":
|
|
51
|
+
return `${named}Check that ${sayCondition(step)}`;
|
|
52
|
+
case "await":
|
|
53
|
+
return `${named}Wait until ${sayCondition({ ...step.until, id: step.id })} (off camera: pause the recording meanwhile)`;
|
|
54
|
+
case "download":
|
|
55
|
+
return step.selector !== void 0 ? `${named}Download the file ${sayStepTarget({ selector: step.selector, id: step.id })} gives${step.as ? `, as ${step.as}` : ""}` : `${named}Download what ${sayTarget(step.src ?? "")} plays${step.as ? `, as ${step.as}` : ""}`;
|
|
38
56
|
case "wait":
|
|
39
57
|
return null;
|
|
40
58
|
case "hover":
|
|
@@ -110,4 +128,4 @@ export {
|
|
|
110
128
|
sayTarget,
|
|
111
129
|
shotList
|
|
112
130
|
};
|
|
113
|
-
//# sourceMappingURL=shotList-
|
|
131
|
+
//# sourceMappingURL=shotList-SCA234OS.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\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 ${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":";;;;;;AAkCO,SAAS,UAAU,UAA0B;AAClD,QAAM,OAAO,wBAAwB,KAAK,QAAQ;AAClD,MAAI,KAAM,QAAO,IAAI,KAAK,CAAC,CAAC;AAC5B,QAAM,MAAM,6BAA6B,KAAK,QAAQ;AACtD,MAAI,IAAK,QAAO,IAAI,IAAI,CAAC,CAAC;AAC1B,QAAM,OAAO,yDAAyD;AAAA,IACpE;AAAA,EACF;AACA,MAAI,KAAM,QAAO,OAAO,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;AAC3C,QAAM,QAAQ,+BAA+B,KAAK,QAAQ;AAC1D,MAAI,MAAO,QAAO,IAAI,MAAM,CAAC,CAAC;AAC9B,QAAM,KAAK,gCAAgC,KAAK,QAAQ;AACxD,MAAI,GAAI,QAAO,QAAQ,GAAG,CAAC,CAAC;AAC5B,SAAO,KAAK,QAAQ;AACtB;AAEA,IAAM,KAAK,CAAC,OAAe,GAAG,KAAK,MAAM,KAAK,GAAG,IAAI,EAAE,GAAG,QAAQ,QAAQ,EAAE;AAGrE,SAAS,MAAM,IAAuC;AAC3D,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAE,KAAK,GAAG,SAAS,EAAG,QAAO;AACrD,QAAM,QAAQ,GACX,QAAQ,mBAAmB,OAAO,EAClC,QAAQ,UAAU,GAAG,EACrB,YAAY,EACZ,KAAK;AACR,SAAO,MAAM,UAAU,IAAI,QAAQ;AACrC;AAGA,SAAS,cAAc,MAAiD;AACtE,QAAM,aAAa,UAAU,KAAK,QAAQ;AAC1C,MAAI,CAAC,WAAW,WAAW,GAAG,EAAG,QAAO;AACxC,QAAM,OAAO,MAAM,KAAK,EAAE;AAC1B,SAAO,OAAO,GAAG,IAAI,KAAK,UAAU,MAAM;AAC5C;AAGA,SAAS,aAAa,GAMX;AACT,MAAI,EAAE,SAAS,OAAW,QAAO,IAAI,EAAE,IAAI;AAC3C,MAAI,EAAE,SAAS,OAAW,QAAO,GAAG,UAAU,EAAE,IAAI,CAAC;AACrD,QAAM,SAAS,cAAc,EAAE,UAAU,EAAE,YAAY,IAAI,IAAI,EAAE,GAAG,CAAC;AACrE,MAAI,EAAE,YAAY,KAAM,QAAO,GAAG,MAAM;AACxC,MAAI,EAAE,YAAY,MAAO,QAAO,GAAG,MAAM;AACzC,SAAO,GAAG,MAAM;AAClB;AAEA,SAAS,OAAO,MAAiC;AAC/C,QAAM,OAAO,YAAY,IAAI;AAE7B,SAAO,QAAQ,KAAK,QAChB,GAAG,IAAI,6DACP;AACN;AAEA,SAAS,YAAY,MAAiC;AACpD,QAAM,MAAM,KAAK;AACjB,QAAM,QAAQ,MAAM,GAAG,GAAG,OAAO;AACjC,UAAQ,KAAK,IAAI;AAAA,IACf,KAAK;AACH,aAAO,GAAG,KAAK,cAAc,aAAa,IAAI,CAAC;AAAA,IACjD,KAAK;AACH,aAAO,GAAG,KAAK,cAAc,aAAa,EAAE,GAAG,KAAK,OAAO,IAAI,KAAK,GAAG,CAAC,CAAC;AAAA,IAC3E,KAAK;AACH,aAAO,KAAK,aAAa,SACrB,GAAG,KAAK,qBAAqB,cAAc,EAAE,UAAU,KAAK,UAAU,IAAI,KAAK,GAAG,CAAC,CAAC,SAAS,KAAK,KAAK,QAAQ,KAAK,EAAE,KAAK,EAAE,KAC7H,GAAG,KAAK,iBAAiB,UAAU,KAAK,OAAO,EAAE,CAAC,SAAS,KAAK,KAAK,QAAQ,KAAK,EAAE,KAAK,EAAE;AAAA,IACjG,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO,GAAG,KAAK,uBAAuB,cAAc,IAAI,CAAC;AAAA,IAC3D,KAAK;AACH,aAAO,GAAG,KAAK,SAAS,cAAc,IAAI,CAAC;AAAA,IAC7C,KAAK;AACH,aAAO,GAAG,KAAK,GAAG,KAAK,UAAU,uBAAuB,MAAM,GAAG,KAAK,UAAU,IAAI,cAAc,IAAI,CAAC,UAAU,KAAK,IAAI,MAAM,KAAK,KAAK,IAAI,UAAU,cAAc,IAAI,CAAC,EAAE,GAAG,KAAK,OAAO,QAAQ,iBAAiB,KAAK,OAAO,UAAU,mBAAmB,EAAE;AAAA,IAClQ,KAAK;AACH,aAAO,GAAG,KAAK,SAAS,KAAK,GAAG,GAAG,KAAK,WAAW,OAAO,cAAc,IAAyC,CAAC,KAAK,EAAE;AAAA,IAC3H,KAAK;AACH,aAAO,GAAG,KAAK,UAAU,KAAK,KAAK,IAAI,OAAO,MAAM,IAAI,KAAK,KAAK,IAAI,aAAa,UAAU;AAAA,IAC/F,KAAK;AACH,aAAO,GAAG,KAAK,2BAA2B,KAAK,IAAI,MAAM,UAAU,OAAO,IAAI,KAAK,IAAI,MAAM,SAAS,OAAO;AAAA,IAC/G,KAAK;AACH,aAAO,GAAG,KAAK,QAAQ,KAAK,WAAW,cAAc,IAAyC,IAAI,sBAAsB;AAAA,EAC5H;AACF;AAEO,SAAS,SAAS,SAAgC;AACvD,QAAM,QAAoB,CAAC;AAC3B,MAAI,cAAc;AAClB,MAAI,QAAQ;AACZ,aAAW,QAAQ,QAAQ,OAAO;AAChC,aAAS,QAAQ,IAAI;AACrB,QAAI,KAAK,OAAO,QAAQ;AAGtB,UAAI,MAAM,QAAQ;AAChB,cAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,aAAK,SAAS,KAAK,SAAS,KAAK,KAAK,KAAK;AAAA,MAC7C,MAAO,gBAAe,KAAK;AAC3B;AAAA,IACF;AACA,UAAM,OAAO,OAAO,IAAI;AACxB,QAAI,CAAC,KAAM;AACX,UAAM,OAAiB,EAAE,GAAG,MAAM,SAAS,GAAG,KAAK;AACnD,QAAI,KAAK,OAAO,QAAS,MAAK,QAAQ,QAAQ,IAAI,IAAI;AACtD,UAAM,KAAK,IAAI;AAAA,EACjB;AACA,OAAK;AACL,SAAO;AAAA,IACL,KAAK,QAAQ;AAAA,IACb,UAAU,QAAQ;AAAA,IAClB;AAAA;AAAA,IAEA,QAAQ,KAAK,OAAO,QAAQ,MAAM,SAAS,OAAQ,GAAI;AAAA,IACvD,OAAO,QAAQ,OAAO,UAAU;AAAA,EAClC;AACF;AAGO,SAAS,eAAe,MAAwB;AACrD,QAAM,MAAgB,CAAC;AACvB,MAAI;AAAA,IACF,6EAA6E,KAAK,MAAM,WAAW,KAAK,WAAW,kBAAkB,KAAK,SAAS,KAAK,OAAI,KAAK,SAAS,MAAM,KAAK,EAAE;AAAA,EACzL;AACA,MAAI,KAAK;AACP,QAAI,KAAK,YAAY,KAAK,GAAG,qCAAqC;AACpE,MAAI,KAAK;AACP,QAAI;AAAA,MACF,yCAAyC,KAAK,KAAK,cAAc,KAAK,UAAU,IAAI,KAAK,GAAG;AAAA,IAC9F;AACF,MAAI,KAAK,EAAE;AACX,aAAW,KAAK,KAAK,OAAO;AAC1B,UAAM,OACJ,EAAE,SAAS,EAAE,SAAS,MAAM,SAAS,GAAG,EAAE,QAAQ,GAAI,CAAC,QAAQ;AACjE,QAAI,KAAK,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,IAAI,IAAI,EAAE;AAAA,EACtC;AACA,MAAI,KAAK,EAAE;AACX,MAAI;AAAA,IACF;AAAA,EACF;AACA,SAAO,IAAI,KAAK,IAAI;AACtB;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vosjs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.78.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,12 +51,12 @@
|
|
|
51
51
|
"acorn-walk": "^8.3.5",
|
|
52
52
|
"mediabunny": "^1.55.7",
|
|
53
53
|
"playwright": "^1.49.0",
|
|
54
|
-
"@vosjs/
|
|
55
|
-
"@vosjs/render-core": "^0.5.3",
|
|
54
|
+
"@vosjs/core": "^0.31.0",
|
|
56
55
|
"@vosjs/editor": "^1.6.0",
|
|
56
|
+
"@vosjs/elements": "^0.12.1",
|
|
57
|
+
"@vosjs/render-core": "^0.7.0",
|
|
57
58
|
"@vosjs/shared": "^0.7.0",
|
|
58
|
-
"@vosjs/core": "^0.
|
|
59
|
-
"@vosjs/studio-core": "^0.42.2",
|
|
59
|
+
"@vosjs/studio-core": "^0.43.0",
|
|
60
60
|
"@vosjs/timeline": "^0.5.0",
|
|
61
61
|
"@vosjs/tween": "^0.8.3"
|
|
62
62
|
},
|
|
@@ -202,6 +202,11 @@
|
|
|
202
202
|
"type": "string",
|
|
203
203
|
"minLength": 1,
|
|
204
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"
|
|
205
|
+
},
|
|
206
|
+
"guard": {
|
|
207
|
+
"type": "string",
|
|
208
|
+
"enum": ["spend"],
|
|
209
|
+
"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"
|
|
205
210
|
}
|
|
206
211
|
}
|
|
207
212
|
},
|
|
@@ -259,6 +264,11 @@
|
|
|
259
264
|
"type": "string",
|
|
260
265
|
"enum": ["end", "start"],
|
|
261
266
|
"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"
|
|
267
|
+
},
|
|
268
|
+
"guard": {
|
|
269
|
+
"type": "string",
|
|
270
|
+
"enum": ["spend"],
|
|
271
|
+
"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"
|
|
262
272
|
}
|
|
263
273
|
}
|
|
264
274
|
},
|
|
@@ -313,6 +323,11 @@
|
|
|
313
323
|
"type": "string",
|
|
314
324
|
"minLength": 1,
|
|
315
325
|
"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"
|
|
326
|
+
},
|
|
327
|
+
"guard": {
|
|
328
|
+
"type": "string",
|
|
329
|
+
"enum": ["spend"],
|
|
330
|
+
"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"
|
|
316
331
|
}
|
|
317
332
|
}
|
|
318
333
|
},
|
|
@@ -431,6 +446,183 @@
|
|
|
431
446
|
"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"
|
|
432
447
|
}
|
|
433
448
|
}
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"type": "object",
|
|
452
|
+
"required": ["do"],
|
|
453
|
+
"additionalProperties": false,
|
|
454
|
+
"description": "Check the page is as the flow expects, on camera, with no cursor move: exactly one of selector (visible; with enabled, enabled or disabled), text (some visible text contains it) or gone (no longer visible). A condition that never holds is a skip with its reason, which --strict turns into exit 2.",
|
|
455
|
+
"oneOf": [
|
|
456
|
+
{
|
|
457
|
+
"required": ["selector"]
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"required": ["text"]
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
"required": ["gone"]
|
|
464
|
+
}
|
|
465
|
+
],
|
|
466
|
+
"properties": {
|
|
467
|
+
"do": {
|
|
468
|
+
"const": "expect"
|
|
469
|
+
},
|
|
470
|
+
"selector": {
|
|
471
|
+
"type": "string",
|
|
472
|
+
"minLength": 1
|
|
473
|
+
},
|
|
474
|
+
"text": {
|
|
475
|
+
"type": "string",
|
|
476
|
+
"minLength": 1
|
|
477
|
+
},
|
|
478
|
+
"gone": {
|
|
479
|
+
"type": "string",
|
|
480
|
+
"minLength": 1
|
|
481
|
+
},
|
|
482
|
+
"enabled": {
|
|
483
|
+
"type": "boolean",
|
|
484
|
+
"description": "with selector: wait for the element to be enabled (true) or disabled (false)"
|
|
485
|
+
},
|
|
486
|
+
"timeout": {
|
|
487
|
+
"type": "number",
|
|
488
|
+
"exclusiveMinimum": 0,
|
|
489
|
+
"default": 5,
|
|
490
|
+
"description": "seconds to wait for the condition"
|
|
491
|
+
},
|
|
492
|
+
"id": {
|
|
493
|
+
"type": "string",
|
|
494
|
+
"minLength": 1,
|
|
495
|
+
"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."
|
|
496
|
+
},
|
|
497
|
+
"caption": {
|
|
498
|
+
"type": "string",
|
|
499
|
+
"maxLength": 80,
|
|
500
|
+
"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"
|
|
501
|
+
},
|
|
502
|
+
"read": {
|
|
503
|
+
"type": "string",
|
|
504
|
+
"minLength": 1,
|
|
505
|
+
"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"
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"type": "object",
|
|
511
|
+
"required": ["do", "until"],
|
|
512
|
+
"additionalProperties": false,
|
|
513
|
+
"description": "Wait OFF CAMERA for something slow (a render, a generated result): the capture stops, the condition is polled, and the capture resumes when it holds, so the footage has no gap and no frozen stretch. The cut lands in meta.skips[] ({ step, id, at, ms }) and becomes a segment boundary in the planned doc.json. A rehearsal waits for real and prints the time.",
|
|
514
|
+
"properties": {
|
|
515
|
+
"do": {
|
|
516
|
+
"const": "await"
|
|
517
|
+
},
|
|
518
|
+
"until": {
|
|
519
|
+
"type": "object",
|
|
520
|
+
"additionalProperties": false,
|
|
521
|
+
"minProperties": 1,
|
|
522
|
+
"maxProperties": 1,
|
|
523
|
+
"properties": {
|
|
524
|
+
"selector": {
|
|
525
|
+
"type": "string",
|
|
526
|
+
"minLength": 1
|
|
527
|
+
},
|
|
528
|
+
"text": {
|
|
529
|
+
"type": "string",
|
|
530
|
+
"minLength": 1
|
|
531
|
+
},
|
|
532
|
+
"gone": {
|
|
533
|
+
"type": "string",
|
|
534
|
+
"minLength": 1
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
"timeout": {
|
|
539
|
+
"type": "number",
|
|
540
|
+
"exclusiveMinimum": 0,
|
|
541
|
+
"default": 600,
|
|
542
|
+
"description": "the most seconds to wait"
|
|
543
|
+
},
|
|
544
|
+
"poll": {
|
|
545
|
+
"type": "number",
|
|
546
|
+
"exclusiveMinimum": 0,
|
|
547
|
+
"default": 2,
|
|
548
|
+
"description": "seconds between looks"
|
|
549
|
+
},
|
|
550
|
+
"reload": {
|
|
551
|
+
"type": "boolean",
|
|
552
|
+
"default": false,
|
|
553
|
+
"description": "reload the page between looks"
|
|
554
|
+
},
|
|
555
|
+
"onTimeout": {
|
|
556
|
+
"type": "string",
|
|
557
|
+
"enum": ["fail", "continue"],
|
|
558
|
+
"default": "fail",
|
|
559
|
+
"description": "fail: the take stops there, exit 1; continue: the capture resumes and the skip records timedOut"
|
|
560
|
+
},
|
|
561
|
+
"id": {
|
|
562
|
+
"type": "string",
|
|
563
|
+
"minLength": 1,
|
|
564
|
+
"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."
|
|
565
|
+
},
|
|
566
|
+
"caption": {
|
|
567
|
+
"type": "string",
|
|
568
|
+
"maxLength": 80,
|
|
569
|
+
"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"
|
|
570
|
+
},
|
|
571
|
+
"read": {
|
|
572
|
+
"type": "string",
|
|
573
|
+
"minLength": 1,
|
|
574
|
+
"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"
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"type": "object",
|
|
580
|
+
"required": ["do"],
|
|
581
|
+
"additionalProperties": false,
|
|
582
|
+
"description": "Keep a file the page makes, in the take's outputs/: selector clicks a control and waits up to 30 s for the browser's download; src fetches the currentSrc of a video, audio or img element with the page's own cookies. Listed in meta.outputs[] ({ step, id, name, bytes, contentType }). A download that never arrives is a skip with its reason. A hosted take refuses a download.",
|
|
583
|
+
"oneOf": [
|
|
584
|
+
{
|
|
585
|
+
"required": ["selector"]
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
"required": ["src"]
|
|
589
|
+
}
|
|
590
|
+
],
|
|
591
|
+
"properties": {
|
|
592
|
+
"do": {
|
|
593
|
+
"const": "download"
|
|
594
|
+
},
|
|
595
|
+
"selector": {
|
|
596
|
+
"type": "string",
|
|
597
|
+
"minLength": 1
|
|
598
|
+
},
|
|
599
|
+
"src": {
|
|
600
|
+
"type": "string",
|
|
601
|
+
"minLength": 1,
|
|
602
|
+
"description": "a selector (the same grammar as every other step) of a video, audio or img element"
|
|
603
|
+
},
|
|
604
|
+
"as": {
|
|
605
|
+
"type": "string",
|
|
606
|
+
"minLength": 1,
|
|
607
|
+
"pattern": "^(?!\\.)(?!.*\\.\\.)[^/\\\\]+$",
|
|
608
|
+
"description": "the file name inside outputs/ (a plain name: no path separator, no \"..\"); absent, the name the page suggested"
|
|
609
|
+
},
|
|
610
|
+
"id": {
|
|
611
|
+
"type": "string",
|
|
612
|
+
"minLength": 1,
|
|
613
|
+
"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."
|
|
614
|
+
},
|
|
615
|
+
"caption": {
|
|
616
|
+
"type": "string",
|
|
617
|
+
"maxLength": 80,
|
|
618
|
+
"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"
|
|
619
|
+
},
|
|
620
|
+
"read": {
|
|
621
|
+
"type": "string",
|
|
622
|
+
"minLength": 1,
|
|
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"
|
|
624
|
+
}
|
|
625
|
+
}
|
|
434
626
|
}
|
|
435
627
|
]
|
|
436
628
|
}
|
package/schema/doc.schema.json
CHANGED
|
@@ -957,14 +957,38 @@
|
|
|
957
957
|
"start": {
|
|
958
958
|
"type": "number",
|
|
959
959
|
"minimum": 0,
|
|
960
|
-
"description": "OUTPUT seconds"
|
|
960
|
+
"description": "OUTPUT seconds. With an `anchor` it is the fallback, used when the anchor cannot resolve"
|
|
961
|
+
},
|
|
962
|
+
"anchor": {
|
|
963
|
+
"type": "object",
|
|
964
|
+
"required": ["step"],
|
|
965
|
+
"additionalProperties": false,
|
|
966
|
+
"description": "When the layer starts, as a recorder step: the lowering maps the step's moment (meta.steps, SOURCE seconds of its media) through the segments, speed spans and freezes exactly as a source-anchored span is mapped, and adds `offset`. A trim, a speed change or a re-record moves the layer with its step; `start` is the fallback when the step is missing, skipped or cut out (validate says which), and `duration` stays the layer's own. A text layer's {elapsed} fills with the anchored step's off-camera wait (the meta.skips entry naming the step, else its offCameraMs; a step that waited for nothing reads the nearest earlier wait on its media) as a rounded duration, like \"12 minutes\". Not the re-record `anchor` on spans, which the lowering never reads",
|
|
967
|
+
"properties": {
|
|
968
|
+
"step": {
|
|
969
|
+
"type": ["string", "integer"],
|
|
970
|
+
"description": "the step: its actions.json `id` (meta.steps[].id), else its index"
|
|
971
|
+
},
|
|
972
|
+
"media": {
|
|
973
|
+
"type": "string",
|
|
974
|
+
"description": "whose steps: a media[].id (absent = the primary)"
|
|
975
|
+
},
|
|
976
|
+
"at": {
|
|
977
|
+
"enum": ["start", "end"],
|
|
978
|
+
"description": "which edge of the step is its moment (default start)"
|
|
979
|
+
},
|
|
980
|
+
"offset": {
|
|
981
|
+
"type": "number",
|
|
982
|
+
"description": "OUTPUT seconds after the step's moment (negative = before; default 0)"
|
|
983
|
+
}
|
|
984
|
+
}
|
|
961
985
|
},
|
|
962
986
|
"duration": {
|
|
963
987
|
"type": "number",
|
|
964
988
|
"exclusiveMinimum": 0
|
|
965
989
|
},
|
|
966
990
|
"text": {
|
|
967
|
-
"description": "the words; \\n breaks lines. A string, or a list of RUNS when parts of it are set differently: each run is a piece of text and the fields in which it departs from the layer's style. Example: [{ \"text\": \"Ship \" }, { \"text\": \"faster\", \"weight\": 700, \"underline\": true }]. There is no markup in the words: an asterisk is an asterisk. A run may hold \\n, a style may start and end anywhere, and a word-by-word reveal keeps every word in its own style",
|
|
991
|
+
"description": "the words; \\n breaks lines. On an anchored layer, {elapsed} fills with the anchored step's off-camera wait. A string, or a list of RUNS when parts of it are set differently: each run is a piece of text and the fields in which it departs from the layer's style. Example: [{ \"text\": \"Ship \" }, { \"text\": \"faster\", \"weight\": 700, \"underline\": true }]. There is no markup in the words: an asterisk is an asterisk. A run may hold \\n, a style may start and end anywhere, and a word-by-word reveal keeps every word in its own style",
|
|
968
992
|
"oneOf": [
|
|
969
993
|
{ "type": "string" },
|
|
970
994
|
{
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugin/manifest.ts"],"sourcesContent":["/**\n * The verb manifest of the take pipeline and the vos.so verbs — what\n * `vos help` lists under the engine verbs. It kept the shape a separately\n * installed plugin once handed the host (name + host range), so a script\n * reading it keeps working.\n */\nexport interface PluginVerb {\n name: string\n summary: string\n}\n\nexport interface PluginManifest {\n name: string\n /** Host versions this plugin speaks the run(argv) contract with. */\n hostRange: string\n verbs: PluginVerb[]\n}\n\nexport const manifest: PluginManifest = {\n name: '@vosjs/cli',\n hostRange: '>=0.9.0',\n verbs: [\n {\n name: 'create',\n summary: 'record + auto-plan + render, one command (--strict)',\n },\n {\n name: 'record',\n summary: 'drive actions.json into a take (screencast + cursor track)',\n },\n {\n name: 'plan',\n summary:\n 'plan zoom/cursor effects into doc.json (wand contract); --reuse re-times a previous cut onto a re-recording',\n },\n {\n name: 'render',\n summary: 'render a take directory (engine configs render in the host)',\n },\n {\n name: 'frames',\n summary:\n 'PNG stills at output times / zoom apexes / moments / exact sizes',\n },\n {\n name: 'deliver',\n summary:\n 'render a take to release video destinations (YouTube, X, Shorts/LinkedIn, GitHub) + verified kit.json',\n },\n {\n name: 'digest',\n summary:\n 'see a recording before cutting it: moments (clicks, typing, scrolls, idle, scenes) + footage frames + crops, in doc units',\n },\n { name: 'open', summary: 'serve the take into the vos.so studio' },\n {\n name: 'validate',\n summary:\n 'lint actions.json, a take dir (doc.json semantics), or re-measure a kit.json against the channel specs',\n },\n {\n name: 'actions',\n summary:\n 'from-agent-browser: turn an agent-browser walk (steps.jsonl) into actions.json; what cannot follow is named',\n },\n {\n name: 'ingest',\n summary:\n 'a take from a recording someone else made, with its cursor track from a trace beside it (Playwright trace.zip, stamped records, CSV); without one nothing is planned',\n },\n {\n name: 'port',\n summary:\n 'bring a page, a HyperFrames or a Remotion piece: inventory (its words, colours, faces, media, scenes) then scaffold (a data-first program.mjs to translate the motion into)',\n },\n {\n name: 'build',\n summary:\n 'program.mjs (real functions) → config.json, refusing what cannot run on its own (module-scope reads, syntax the page cannot parse, non-JSON data)',\n },\n {\n name: 'compare',\n summary:\n \"a program against the source's own render: per-frame SSIM and source | vos | difference sheets; exits 1 when any frame is under the threshold\",\n },\n {\n name: 'brand',\n summary:\n \"write a product's BRAND.md, witnessed: /design.md, /llms.txt, then the page (palette, faces, marks, the avoid list)\",\n },\n {\n name: 'fetch',\n summary: 'download a hosted program: config.json + vos.json tracking',\n },\n {\n name: 'push',\n summary: 'push a config.json or take to vos.so (private; versioned)',\n },\n {\n name: 'duplicate',\n summary:\n 'a private sibling of your OWN vos (someone else\\u2019s is remixed: fetch + push --remix-of)',\n },\n {\n name: 'pull',\n summary:\n 'sync what changed on vos.so since your base (--media brings a take’s footage home)',\n },\n {\n name: 'folder',\n summary:\n 'list/create/pull folders, move voses and assets into them (pull = the context package on disk)',\n },\n {\n name: 'asset',\n summary:\n 'your files: push or import one, list them (assets ls), see storage (assets usage) and what uses a file (assets why)',\n },\n {\n name: 'recipe',\n summary:\n 'push a recipe .md into a folder, or replace one in place (prior body kept)',\n },\n {\n name: 'login',\n summary: 'sign in via the browser (or --key); stores a content key',\n },\n {\n name: 'setup',\n summary:\n 'ready this machine: the vos skills into your agent, a browser, one rules block, then doctor',\n },\n {\n name: 'doctor',\n summary:\n 'what is ready, in words: node, browser, ffmpeg, skills, a credential (never printed), the dev server',\n },\n {\n name: 'whoami',\n summary: 'the key’s name and the account it belongs to, never the key',\n },\n { name: 'logout', summary: 'remove ~/.config/vos/credentials' },\n ],\n}\n"],"mappings":";;;AAkBO,IAAM,WAA2B;AAAA,EACtC,MAAM;AAAA,EACN,WAAW;AAAA,EACX,OAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA,EAAE,MAAM,QAAQ,SAAS,wCAAwC;AAAA,IACjE;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA,EAAE,MAAM,UAAU,SAAS,mCAAmC;AAAA,EAChE;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loadConfig.ts","../src/browser.ts","../src/render.ts","../src/outputs.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises'\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { basename, dirname, join } from 'node:path'\nimport {\n CURRENT_CONFIG_VERSION,\n migrateConfig,\n vosConfigJsonSchema,\n} from '@vosjs/core'\nimport { UsageError } from './args'\n\nexport interface LoadedConfig {\n config: Record<string, unknown>\n warnings: string[]\n}\n\n/**\n * What a directory holds, by one deterministic sniff and never a flag:\n * a TAKE carries a recording document (`doc.json` with `source`); a PROGRAM\n * directory carries `config.json`, optionally beside a program document\n * (`doc.json` without `source`: the shared layers, the tween overlay, the\n * program's own length, with `program.config` omitted on disk). An unparsable\n * doc.json counts as a take so the take path reports the real error.\n */\nexport type DirectoryKind = 'take' | 'program' | 'none'\n\nexport function directoryKind(target: string): DirectoryKind {\n try {\n if (!statSync(target).isDirectory()) return 'none'\n } catch {\n return 'none'\n }\n const docPath = join(target, 'doc.json')\n if (existsSync(docPath)) {\n try {\n const doc: unknown = JSON.parse(readFileSync(docPath, 'utf8'))\n if (typeof doc === 'object' && doc !== null && 'source' in doc)\n return 'take'\n } catch {\n return 'take'\n }\n }\n return existsSync(join(target, 'config.json')) ? 'program' : 'none'\n}\n\n/** A directory holding a PROGRAM document (`doc.json` without `source`). */\nfunction hasProgramDoc(dir: string): boolean {\n const docPath = join(dir, 'doc.json')\n if (!existsSync(docPath)) return false\n try {\n const doc: unknown = JSON.parse(readFileSync(docPath, 'utf8'))\n return typeof doc === 'object' && doc !== null && !('source' in doc)\n } catch {\n return false\n }\n}\n\n/**\n * A program directory's config: `config.json`, composed with the program\n * document beside it when there is one (the layers ride as the studio stack\n * entry, the tween overlay is baked into `createTimeline`), so a render of\n * the directory is a render of what the studio and vos.so play.\n */\nexport async function loadProgramDirectory(\n dir: string,\n warnings: string[],\n): Promise<unknown> {\n const config: unknown = JSON.parse(\n await readFile(join(dir, 'config.json'), 'utf8'),\n )\n const docPath = join(dir, 'doc.json')\n if (!existsSync(docPath)) return config\n const doc: unknown = JSON.parse(await readFile(docPath, 'utf8'))\n if (typeof doc !== 'object' || doc === null || 'source' in doc) return config\n if (typeof config !== 'object' || config === null) {\n throw new UsageError(`${dir}/config.json does not contain a JSON object`)\n }\n const raw = doc as Record<string, unknown>\n const program =\n raw.program && typeof raw.program === 'object'\n ? (raw.program as Record<string, unknown>)\n : {}\n const { lowerProgramDoc } = await import('@vosjs/studio-core')\n const composed = lowerProgramDoc(\n { ...raw, program: { ...program, config } } as never,\n { bake: true },\n ).config\n warnings.push(\n 'composed config.json with doc.json (a program document: its layers and tween edits ride the render)',\n )\n return composed\n}\n\n/**\n * The text of a config source: an http(s) URL or a file. A directory that is\n * neither a take nor a program directory is refused in words, since the raw\n * read would report a bare EISDIR.\n */\nexport async function readSourceText(source: string): Promise<string> {\n if (/^https?:\\/\\//.test(source)) {\n const res = await fetch(source)\n if (!res.ok) throw new Error(`fetch ${source} → ${res.status}`)\n return await res.text()\n }\n let isDir = false\n try {\n isDir = statSync(source).isDirectory()\n } catch {\n /* a missing path reads below and reports itself */\n }\n if (isDir) {\n throw new UsageError(\n `${source} is a directory with no config.json and no doc.json — pass a config file, a URL, a program directory or a take`,\n )\n }\n return await readFile(source, 'utf8')\n}\n\n/**\n * Load a VosConfigJson from a file path, an http(s) URL, or a program\n * directory (config.json, composed with a program document when one sits\n * beside it); unwrap API `{ config }` envelopes, migrate old versions, and\n * validate against the schema. A take directory is refused here in words:\n * takes render through the take pipeline (`vos render <take>`).\n */\nexport async function loadVosConfig(source: string): Promise<LoadedConfig> {\n const warnings: string[] = []\n let parsed: unknown\n const kind = /^https?:\\/\\//.test(source) ? 'none' : directoryKind(source)\n if (kind === 'take') {\n throw new UsageError(\n `${source} is a take (its doc.json is a recording document). Render it through the take pipeline: vos render ${source} [out]; vos frames ${source} for stills.`,\n )\n }\n if (kind === 'program') {\n parsed = await loadProgramDirectory(source, warnings)\n return finish(source, parsed, warnings)\n }\n // A program's config.json named directly, with its document beside it:\n // read the directory, or the document's sound and layers silently drop\n // out of a render that looks complete.\n if (\n !/^https?:\\/\\//.test(source) &&\n basename(source) === 'config.json' &&\n hasProgramDoc(dirname(source))\n ) {\n parsed = await loadProgramDirectory(dirname(source), warnings)\n return finish(source, parsed, warnings)\n }\n const raw = await readSourceText(source)\n\n try {\n parsed = JSON.parse(raw)\n } catch (e) {\n throw new UsageError(`${source} is not valid JSON: ${(e as Error).message}`)\n }\n return finish(source, parsed, warnings)\n}\n\nfunction finish(\n source: string,\n parsed: unknown,\n warnings: string[],\n): LoadedConfig {\n if (typeof parsed !== 'object' || parsed === null) {\n throw new UsageError(`${source} does not contain a JSON object`)\n }\n\n // API endpoints wrap the config: { config: {...} }.\n let obj = parsed as Record<string, unknown>\n if (\n typeof obj.config === 'object' &&\n obj.config !== null &&\n !('createTimeline' in obj)\n ) {\n obj = obj.config as Record<string, unknown>\n warnings.push('unwrapped { config } envelope')\n }\n\n const version = obj.version\n const migrated = migrateConfig(obj)\n if (version === undefined) {\n // Plays fine (the stamp happens above), but a storer refuses it.\n warnings.push(\n `config declares no \"version\". Add \"version\": ${CURRENT_CONFIG_VERSION} before pushing it.`,\n )\n } else if (version !== CURRENT_CONFIG_VERSION) {\n warnings.push(\n `migrated config v${String(version)} to v${CURRENT_CONFIG_VERSION}`,\n )\n }\n\n const check = vosConfigJsonSchema.safeParse(migrated)\n if (!check.success) {\n const issues = check.error.issues\n .slice(0, 5)\n .map((i) => ` ${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('\\n')\n throw new UsageError(`invalid vos config:\\n${issues}`)\n }\n\n return { config: migrated, warnings }\n}\n\n/** Best-effort duration from the config (seconds). */\nexport function configDuration(\n config: Record<string, unknown>,\n): number | undefined {\n const d = config.duration\n return typeof d === 'number' && Number.isFinite(d) && d > 0 ? d : undefined\n}\n","import { chromium } from 'playwright'\nimport type { Browser } from 'playwright'\n\nexport class BrowserUnavailableError extends Error {\n constructor(cause: string) {\n super(\n `Could not launch a Chromium-family browser (${cause}).\\n` +\n `Fixes, in order of preference:\\n` +\n ` 1. Install Google Chrome (used automatically), or\\n` +\n ` 2. npx playwright install chromium, or\\n` +\n ` 3. Set VOS_BROWSER_PATH to a Chrome/Chromium executable.`,\n )\n }\n}\n\n/**\n * Launch headless Chromium: explicit VOS_BROWSER_PATH → system Chrome\n * (no download needed) → Playwright's bundled Chromium.\n *\n * `args` are extra Chromium switches (`--browser-arg` on the verbs that\n * record). Some product surfaces cannot be reached without one: a recorder\n * needs a fake capture device to get past a permission prompt, an extension\n * page needs the extension loaded. Passed through verbatim.\n */\nexport async function launchBrowser(args: string[] = []): Promise<Browser> {\n const opts = args.length ? { args } : {}\n const explicit = process.env.VOS_BROWSER_PATH\n if (explicit) {\n return chromium.launch({ ...opts, executablePath: explicit }).catch((e) => {\n throw new BrowserUnavailableError(\n `VOS_BROWSER_PATH failed: ${(e as Error).message}`,\n )\n })\n }\n try {\n return await chromium.launch({ ...opts, channel: 'chrome' })\n } catch {\n try {\n return await chromium.launch(opts)\n } catch (e) {\n throw new BrowserUnavailableError(\n (e as Error).message.split('\\n')[0] ?? 'unknown',\n )\n }\n }\n}\n","import { compileVosConfig, programSize } from '@vosjs/core'\nimport { generateRenderTemplate } from '@vosjs/core/runtime'\nimport { elementsBundleCode } from '@vosjs/elements/bundle'\nimport { tweenRuntimeCode } from '@vosjs/tween/bundle'\nimport type { Browser, Page } from 'playwright'\nimport { ASSET_ROUTE, serveFile } from './programAssets'\nimport type { ProgramAssets } from './programAssets'\n\n/** Fake secure origin the render page is served from (WebCodecs needs one). */\nconst RENDER_ORIGIN = 'https://vos-cli.render'\n\nexport interface RenderCommonOptions {\n config: Record<string, unknown>\n width: number\n height: number\n /** Phase callback for progress reporting. */\n onPhase?: (phase: string) => void\n /**\n * The program's declared files made reachable for this page\n * (`programAssets`): handed to the engine as `ctx.assets`, with the local\n * ones served from disk on the page's own origin.\n */\n assets?: ProgramAssets | null\n}\n\nexport interface RenderVideoOptions extends RenderCommonOptions {\n fps: number\n /** Output duration in seconds. */\n duration: number\n format: 'webm' | 'mp4'\n /** The page's audio producer (a program document's sound); absent = silent. */\n audioProducerCode?: string\n}\n\nexport interface RenderStillOptions extends RenderCommonOptions {\n /** Seek time for the captured frame, seconds. */\n time: number\n}\n\nexport interface RenderResult {\n bytes: Uint8Array\n mimeType: string\n /** Uncaught exceptions the page threw while it rendered (it may still have\n * written a frame, so a caller says them instead of hiding them). */\n pageErrors: string[]\n /** The engine's own '[vos] …' warnings: causes a picture cannot show. */\n engineNotes: string[]\n}\n\ninterface RenderComplete {\n success: boolean\n data?: string\n error?: string\n}\n\n/** What a captured still shows, measured from its pixels. */\nexport interface StillPicture {\n /** Every pixel has alpha 0: the frame is empty, whatever the preview showed. */\n transparent: boolean\n /** Every pixel is the same colour. */\n flat: boolean\n}\n\n/**\n * The sentences a still earns before anyone looks at it (pure). A fully\n * transparent frame is the one a custom blend or a cleared alpha leaves, and\n * it reads as black in most viewers while the live preview draws over its\n * page: say so, because nothing else will.\n */\nexport function stillWarnings(\n picture: StillPicture,\n pageErrors: readonly string[],\n engineNotes: readonly string[] = [],\n): string[] {\n const out: string[] = []\n if (picture.transparent) {\n out.push(\n 'the still is fully transparent (every pixel has alpha 0), though the preview may draw it: something wrote alpha 0, most often a custom material blending (blendDst / blendEquation) or a scene with no background. Give the scene a background or keep the destination alpha in the blend.',\n )\n } else if (picture.flat) {\n out.push(\n 'the still is a single flat colour: nothing drew at this time, or everything is hidden; check the time and the program for errors.',\n )\n }\n // The engine's own notes ('[vos] …') name a cause the picture cannot.\n for (const n of new Set(engineNotes)) out.push(n.replace(/^\\[vos\\]\\s*/, ''))\n for (const e of pageErrors.slice(0, 3)) out.push(`the page threw: ${e}`)\n if (pageErrors.length > 3)\n out.push(`the page threw ${pageErrors.length - 3} more errors`)\n return out\n}\n\nfunction compile(\n config: Record<string, unknown>,\n /** Bake these URLs as the program's `ctx.assets` defaults (a preview). */\n assets?: ProgramAssets | null,\n): string {\n if (!assets) return compileVosConfig(config as never, { tweenEngine: 'vos' })\n // The compiler asks per ref; the resolved table is per name, so walk the\n // manifest in the same order to answer.\n const byRef = new Map<string, string>()\n const manifest = (config.assets ?? {}) as Record<\n string,\n { ref: string | string[] }\n >\n for (const [name, decl] of Object.entries(manifest)) {\n const got = assets.assets[name]\n const refs = Array.isArray(decl.ref) ? decl.ref : [decl.ref]\n const urls = Array.isArray(got) ? got : [got]\n refs.forEach((ref, i) => {\n if (typeof urls[i] === 'string') byRef.set(`${name}\\u0000${ref}`, urls[i])\n })\n }\n return compileVosConfig(config as never, {\n tweenEngine: 'vos',\n resolveAssetRef: (ref, name) => byRef.get(`${name}\\u0000${ref}`) ?? ref,\n })\n}\n\n/** A page on the render origin reads a served file by its full URL. */\nfunction captureAssets(assets: ProgramAssets | null | undefined): {\n assets?: Record<string, string | string[]>\n} {\n if (!assets) return {}\n const absolute = (url: string) =>\n url.startsWith(ASSET_ROUTE) ? `${RENDER_ORIGIN}${url}` : url\n const out: Record<string, string | string[]> = {}\n for (const [name, url] of Object.entries(assets.assets)) {\n out[name] = Array.isArray(url) ? url.map(absolute) : absolute(url)\n }\n return { assets: out }\n}\n\nasync function runCapturePage(\n browser: Browser,\n html: string,\n opts: {\n width: number\n height: number\n timeoutMs: number\n /** Served path → file on disk (a program's declared files). */\n files?: Record<string, string>\n },\n): Promise<RenderComplete & { pageErrors: string[]; engineNotes: string[] }> {\n const context = await browser.newContext({\n viewport: { width: opts.width, height: opts.height },\n })\n const page: Page = await context.newPage()\n const errors: string[] = []\n const pageErrors: string[] = []\n const engineNotes: string[] = []\n page.on('console', (m) => {\n if (m.type() === 'error') errors.push(m.text())\n else if (m.type() === 'warning' && m.text().startsWith('[vos] '))\n engineNotes.push(m.text())\n })\n page.on('pageerror', (e) => {\n pageErrors.push(String(e.message || e))\n })\n try {\n await page.route(`${RENDER_ORIGIN}/**`, (route) =>\n route.fulfill({ status: 200, contentType: 'text/html', body: html }),\n )\n // The program's own files, on the page's origin. Registered after the\n // page route on purpose: the newest matching route answers first.\n const files = opts.files ?? {}\n if (Object.keys(files).length > 0) {\n await page.route(`${RENDER_ORIGIN}${ASSET_ROUTE}**`, async (route) => {\n const request = route.request()\n const file = files[new URL(request.url()).pathname]\n if (!file) {\n return route.fulfill({ status: 404, body: 'no such declared file' })\n }\n const answer = serveFile(file, await request.headerValue('range'))\n return route.fulfill(answer)\n })\n }\n await page.goto(`${RENDER_ORIGIN}/render`, {\n waitUntil: 'domcontentloaded',\n })\n const start = Date.now()\n for (;;) {\n const done = (await page.evaluate(\n 'window.__renderComplete ?? null',\n )) as RenderComplete | null\n if (done) return { ...done, pageErrors, engineNotes }\n if (Date.now() - start > opts.timeoutMs) {\n const all = [...pageErrors, ...errors]\n return {\n success: false,\n error: `render timed out after ${Math.round(opts.timeoutMs / 1000)}s${\n all.length ? ` (page errors: ${all.slice(0, 3).join(' | ')})` : ''\n }`,\n pageErrors,\n engineNotes,\n }\n }\n await new Promise((r) => setTimeout(r, 400))\n }\n } finally {\n await context.close()\n }\n}\n\nfunction decodeDataUrl(\n dataUrl: string,\n pageErrors: string[] = [],\n engineNotes: string[] = [],\n): RenderResult {\n const m = /^data:([^;,]+);base64,(.*)$/s.exec(dataUrl)\n if (!m) throw new Error('capture page returned an unexpected payload')\n return {\n bytes: Uint8Array.from(Buffer.from(m[2], 'base64')),\n mimeType: m[1],\n pageErrors,\n engineNotes,\n }\n}\n\n/** Render a vos config to a video (WebM/MP4) in a headless browser. */\nexport async function renderVideo(\n browser: Browser,\n opts: RenderVideoOptions,\n): Promise<RenderResult> {\n opts.onPhase?.('compile')\n const code = compile(opts.config)\n const html = generateRenderTemplate(code, {\n mode: 'capture-video',\n capture: {\n width: opts.width,\n height: opts.height,\n duration: opts.duration,\n fps: opts.fps,\n format: opts.format,\n ...(opts.audioProducerCode\n ? { audioProducerCode: opts.audioProducerCode }\n : {}),\n ...captureAssets(opts.assets),\n },\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n opts.onPhase?.('render')\n // Generous ceiling: startup + CDN module fetch + ~4× realtime per frame batch.\n const timeoutMs = 90_000 + opts.duration * opts.fps * 400\n const done = await runCapturePage(browser, html, {\n width: opts.width,\n height: opts.height,\n timeoutMs,\n files: opts.assets?.files,\n })\n if (!done.success || !done.data)\n throw new Error(done.error ?? 'render failed')\n return decodeDataUrl(done.data, done.pageErrors, done.engineNotes)\n}\n\n/** Render a single frame of a vos config to an image in a headless browser. */\nexport async function renderStill(\n browser: Browser,\n opts: RenderStillOptions,\n): Promise<RenderResult> {\n opts.onPhase?.('compile')\n const code = compile(opts.config)\n const html = generateRenderTemplate(code, {\n mode: 'capture-thumbnail',\n capture: {\n width: opts.width,\n height: opts.height,\n duration: Math.max(1, opts.time + 1),\n fps: 30,\n thumbnailTime: opts.time,\n ...captureAssets(opts.assets),\n },\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n opts.onPhase?.('render')\n const done = await runCapturePage(browser, html, {\n width: opts.width,\n height: opts.height,\n timeoutMs: 120_000,\n files: opts.assets?.files,\n })\n if (!done.success || !done.data)\n throw new Error(done.error ?? 'still render failed')\n return decodeDataUrl(done.data, done.pageErrors, done.engineNotes)\n}\n\n/**\n * Measure a captured still's pixels in a browser page (the canvas is the\n * decoder): whether every pixel is transparent, and whether it is one flat\n * colour. Sampled on a grid, so a large still stays cheap.\n */\nexport async function inspectStill(\n browser: Browser,\n webp: Uint8Array,\n): Promise<StillPicture> {\n const page = await browser.newPage()\n try {\n const src = `data:image/webp;base64,${Buffer.from(webp).toString('base64')}`\n return (await page.evaluate(\n `(async () => {\n const img = new Image()\n img.src = ${JSON.stringify(src)}\n await img.decode()\n const w = img.naturalWidth, h = img.naturalHeight\n const c = document.createElement('canvas')\n c.width = w\n c.height = h\n const g = c.getContext('2d', { willReadFrequently: true })\n g.drawImage(img, 0, 0)\n const d = g.getImageData(0, 0, w, h).data\n const step = Math.max(1, Math.floor(Math.sqrt((w * h) / 40000)))\n let transparent = true, flat = true, first = null\n for (let y = 0; y < h; y += step) {\n for (let x = 0; x < w; x += step) {\n const i = (y * w + x) * 4\n if (d[i + 3] !== 0) transparent = false\n const px = d[i] + ',' + d[i + 1] + ',' + d[i + 2] + ',' + d[i + 3]\n if (first === null) first = px\n else if (px !== first) flat = false\n }\n }\n return { transparent, flat }\n })()`,\n )) as StillPicture\n } finally {\n await page.close()\n }\n}\n\n/**\n * Re-encode a captured WebP still as PNG or JPEG in a browser page, so a\n * still named `.png` holds PNG bytes. The canvas is the encoder; nothing\n * else is installed for it.\n */\nexport async function reencodeStill(\n browser: Browser,\n webp: Uint8Array,\n format: 'png' | 'jpeg',\n): Promise<Uint8Array> {\n const page = await browser.newPage()\n try {\n const src = `data:image/webp;base64,${Buffer.from(webp).toString('base64')}`\n // A string body: a serialized function would carry the bundler's\n // helpers into a page that has none.\n const dataUrl = (await page.evaluate(\n `(async () => {\n const img = new Image()\n img.src = ${JSON.stringify(src)}\n await img.decode()\n const c = document.createElement('canvas')\n c.width = img.naturalWidth\n c.height = img.naturalHeight\n const g = c.getContext('2d')\n ${format === 'jpeg' ? \"g.fillStyle = '#fff'; g.fillRect(0, 0, c.width, c.height)\" : ''}\n g.drawImage(img, 0, 0)\n return c.toDataURL(${JSON.stringify(`image/${format}`)}, 0.92)\n })()`,\n )) as string\n return decodeDataUrl(dataUrl).bytes\n } finally {\n await page.close()\n }\n}\n\nexport interface PreviewPages {\n /** Host page: iframes the player and drives it over the bridge. */\n hostHtml: string\n /** The engine's playback-mode player (waits for a LOAD postMessage). */\n playerHtml: string\n}\n\n/**\n * Pages for `vos preview`. The playback template is the iframe HALF of the\n * player bridge — it renders nothing until a host posts `LOAD`, so the CLI\n * serves a minimal host page that sends `LOAD { code, autoplay }` and shows\n * a transport line (time / duration, click to play-pause).\n */\nexport function previewPages(\n config: Record<string, unknown>,\n /** Declared files, served by the preview server under `ASSET_ROUTE`. */\n assets?: ProgramAssets | null,\n): PreviewPages {\n const code = compile(config, assets)\n const playerHtml = generateRenderTemplate(code, {\n mode: 'playback',\n elementsBundleCode,\n tweenEngine: 'vos',\n tweenBundleCode: tweenRuntimeCode,\n })\n const codeJson = JSON.stringify(code).replace(/<\\//g, '<\\\\/')\n // A program that declares its size plays letterboxed at that aspect, the\n // frame it was designed for; one that declares none fills the window.\n const size = programSize(config)\n const frameCss = size\n ? `iframe{position:absolute;inset:0;margin:auto;border:0;width:min(100vw,calc(100vh * ${size.width} / ${size.height}));height:min(100vh,calc(100vw * ${size.height} / ${size.width}))}`\n : 'iframe{position:absolute;inset:0;width:100%;height:100%;border:0}'\n const hostHtml = `<!doctype html><html><head><meta charset=\"utf-8\"><title>vos preview</title>\n<style>\n html,body{margin:0;height:100%;background:#000;overflow:hidden}\n ${frameCss}\n #cover{position:fixed;inset:0;cursor:pointer}\n #hud{position:fixed;left:12px;bottom:10px;color:#fff;opacity:.75;font:12px/1.4 ui-monospace,monospace;\n background:rgba(0,0,0,.45);padding:4px 8px;border-radius:6px;pointer-events:none}\n</style></head><body>\n<iframe id=\"p\" src=\"/player\" allow=\"autoplay\"></iframe>\n<div id=\"cover\"></div>\n<div id=\"hud\">loading…</div>\n<script>\n const code = ${codeJson}\n const frame = document.getElementById('p')\n const hud = document.getElementById('hud')\n let ready = false\n let playing = true\n const post = (msg) => frame.contentWindow && frame.contentWindow.postMessage(msg, '*')\n let loaded = false\n const loadOnce = () => { if (loaded) return; loaded = true; post({ type: 'LOAD', code, autoplay: true }) }\n window.addEventListener('message', (e) => {\n const msg = e.data || {}\n // LOAD goes exactly once: every LOAD tears the running program down and\n // starts it again, so repeating one restarts a program whose setup is\n // still downloading, and it never finishes.\n if (msg.type === 'BRIDGE_READY' || msg.type === 'STACK_STATE') loadOnce()\n if (msg.type === 'READY') { ready = true; hud.textContent = '0.0 / ' + msg.duration.toFixed(1) + 's — click to pause' }\n if (msg.type === 'UPDATE') hud.textContent = msg.time.toFixed(1) + ' / ' + msg.duration.toFixed(1) + 's — click to ' + (playing ? 'pause' : 'play')\n if (msg.type === 'ERROR') hud.textContent = 'error: ' + msg.error\n })\n // If BRIDGE_READY raced the host's listener, find the bridge with a\n // read-only probe (it answers STACK_STATE) rather than by repeating LOAD.\n let tries = 0\n const iv = setInterval(() => {\n if (loaded || tries++ > 40) return clearInterval(iv)\n post({ type: 'GET_STACK_STATE', requestId: 0 })\n }, 250)\n // The iframe swallows clicks — capture them on an overlay above it.\n document.getElementById('cover').addEventListener('click', () => {\n if (!ready) return\n playing = !playing\n post({ type: playing ? 'PLAY' : 'PAUSE' })\n hud.textContent = hud.textContent.replace(/click to (pause|play)/, 'click to ' + (playing ? 'pause' : 'play'))\n })\n</script></body></html>`\n return { hostHtml, playerHtml }\n}\n","/**\n * What the output is, decided from what the person asked for.\n *\n * Pure, so the engine verbs (a program) and the take verbs agree: a file\n * named `out.mp4` IS an mp4, an explicit `--format` that contradicts the\n * name is refused instead of writing one container under the other's name,\n * and a still named `.png` is a PNG.\n */\nimport { UsageError } from './args'\n\nexport type VideoFormat = 'webm' | 'mp4'\nexport type StillFormat = 'webp' | 'png' | 'jpeg'\n\nfunction extOf(path: string | undefined): string {\n return /\\.([a-z0-9]+)$/i.exec(path ?? '')?.[1]?.toLowerCase() ?? ''\n}\n\n/** The video container: the flag, else the output's name, else WebM. */\nexport function videoFormat(\n flag: string | undefined,\n out: string | undefined,\n): VideoFormat {\n const named = extOf(out)\n const fromName: VideoFormat | null =\n named === 'mp4' ? 'mp4' : named === 'webm' ? 'webm' : null\n if (flag !== undefined) {\n if (flag !== 'webm' && flag !== 'mp4')\n throw new UsageError('--format must be webm or mp4')\n if (fromName && fromName !== flag)\n throw new UsageError(\n `--format ${flag} contradicts the output name ${out}: name it .${flag}, or drop --format and the name decides`,\n )\n return flag\n }\n return fromName ?? 'webm'\n}\n\n/** The still's encoding, from its name. The engine captures WebP. */\nexport function stillFormat(out: string): StillFormat {\n const ext = extOf(out)\n if (ext === 'png') return 'png'\n if (ext === 'jpg' || ext === 'jpeg') return 'jpeg'\n if (ext === 'webp' || ext === '') return 'webp'\n throw new UsageError(\n `vos still writes .webp, .png or .jpg, not .${ext}: rename ${out}`,\n )\n}\n\n/**\n * `--times 0,1.5,50%,100%`: seconds or percentages of the duration, in the\n * order given. 100% is clamped a hair inside the end, where the last frame is.\n */\nexport function parseTimes(raw: string, duration: number): number[] {\n const times = raw\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n .map((s) => {\n const pct = /^(\\d+(?:\\.\\d+)?)%$/.exec(s)\n const t = pct ? (Number(pct[1]) / 100) * duration : Number(s)\n if (!Number.isFinite(t) || t < 0)\n throw new UsageError(\n `--times expects seconds or percentages (0,1.5,50%), got \"${s}\"`,\n )\n return Math.min(t, Math.max(0, duration - 1 / 60))\n })\n if (!times.length) throw new UsageError('--times needs at least one time')\n return times\n}\n\n/** Where the still for one time of several goes: `name-1.50s.ext`. */\nexport function stillOutFor(out: string, time: number, many: boolean): string {\n if (!many) return out\n const m = /^(.*?)(\\.[a-z0-9]+)?$/i.exec(out)\n return `${m?.[1] ?? out}-${time.toFixed(2)}s${m?.[2] ?? ''}`\n}\n\n/** Every `--set` value in argv, in order (the parser keeps only the last). */\nexport function setFlags(argv: string[]): string[] {\n const out: string[] = []\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]\n if (a === '--set' && argv[i + 1] !== undefined) out.push(argv[++i])\n else if (a.startsWith('--set=')) out.push(a.slice('--set='.length))\n }\n return out\n}\n\n/**\n * `--set data.<key>=<value>` on a PROGRAM: the knob-honesty check without a\n * second config. The value reads as JSON when it parses (numbers, booleans,\n * arrays) and as a string otherwise. Patched in memory, never written back.\n */\nexport function applyDataSets(\n config: Record<string, unknown>,\n sets: string[],\n): Record<string, unknown> {\n if (!sets.length) return config\n const data: Record<string, unknown> = {\n ...((config.data as Record<string, unknown> | undefined) ?? {}),\n }\n for (const set of sets) {\n const eq = set.indexOf('=')\n const path = eq === -1 ? set : set.slice(0, eq)\n const m = /^data\\.([A-Za-z0-9_$-]+)$/.exec(path)\n if (eq === -1 || !m)\n throw new UsageError(\n `--set on a program takes data.<key>=<value> (a knob's value), got \"${set}\"`,\n )\n const raw = set.slice(eq + 1)\n let value: unknown = raw\n try {\n value = JSON.parse(raw)\n } catch {\n // a bare word is a string: --set data.name=ROBIN\n }\n data[m[1]] = value\n }\n return { ...config, data }\n}\n"],"mappings":"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","names":[]}
|