@scenerok/cli 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +173 -0
  2. package/dist/commands/auth.d.ts +3 -0
  3. package/dist/commands/auth.d.ts.map +1 -0
  4. package/dist/commands/auth.js +94 -0
  5. package/dist/commands/cache.d.ts +3 -0
  6. package/dist/commands/cache.d.ts.map +1 -0
  7. package/dist/commands/cache.js +65 -0
  8. package/dist/commands/lint-video.d.ts +21 -0
  9. package/dist/commands/lint-video.d.ts.map +1 -0
  10. package/dist/commands/lint-video.js +122 -0
  11. package/dist/commands/project.d.ts +3 -0
  12. package/dist/commands/project.d.ts.map +1 -0
  13. package/dist/commands/project.js +173 -0
  14. package/dist/commands/render.d.ts +4 -0
  15. package/dist/commands/render.d.ts.map +1 -0
  16. package/dist/commands/render.js +242 -0
  17. package/dist/commands/skills.d.ts +3 -0
  18. package/dist/commands/skills.d.ts.map +1 -0
  19. package/dist/commands/skills.js +208 -0
  20. package/dist/commands/validate.d.ts +3 -0
  21. package/dist/commands/validate.d.ts.map +1 -0
  22. package/dist/commands/validate.js +254 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +51 -0
  26. package/dist/lib/api.d.ts +268 -0
  27. package/dist/lib/api.d.ts.map +1 -0
  28. package/dist/lib/api.js +160 -0
  29. package/dist/lib/config.d.ts +13 -0
  30. package/dist/lib/config.d.ts.map +1 -0
  31. package/dist/lib/config.js +53 -0
  32. package/dist/lib/format-api-error.d.ts +2 -0
  33. package/dist/lib/format-api-error.d.ts.map +1 -0
  34. package/dist/lib/format-api-error.js +24 -0
  35. package/dist/lib/language.d.ts +3 -0
  36. package/dist/lib/language.d.ts.map +1 -0
  37. package/dist/lib/language.js +9 -0
  38. package/dist/lib/logger.d.ts +8 -0
  39. package/dist/lib/logger.d.ts.map +1 -0
  40. package/dist/lib/logger.js +51 -0
  41. package/dist/lib/project.d.ts +29 -0
  42. package/dist/lib/project.d.ts.map +1 -0
  43. package/dist/lib/project.js +156 -0
  44. package/dist/lib/sdk-scene-modules.d.ts +7 -0
  45. package/dist/lib/sdk-scene-modules.d.ts.map +1 -0
  46. package/dist/lib/sdk-scene-modules.js +35 -0
  47. package/dist/utils/skillsManifest.d.ts +42 -0
  48. package/dist/utils/skillsManifest.d.ts.map +1 -0
  49. package/dist/utils/skillsManifest.js +122 -0
  50. package/examples/system/3-tips-fitness.vid +45 -0
  51. package/examples/system/ecom-product-launch.vid +31 -0
  52. package/examples/system/glitch-title.vid +17 -0
  53. package/examples/system/meme-remix.vid +15 -0
  54. package/examples/system/minimal-text-reel.vid +11 -0
  55. package/examples/system/product-launch.vid +18 -0
  56. package/examples/system/rokmilk-chocolate-promo.vid +25 -0
  57. package/examples/system/testimonial-cut.vid +16 -0
  58. package/examples/system/ugc-testimonial-voiceover.vid +33 -0
  59. package/examples/system/voice-visual-promo.vid +21 -0
  60. package/package.json +51 -0
  61. package/skills/remote/ai-models.md +65 -0
  62. package/skills/remote/animations-catalog.md +89 -0
  63. package/skills/remote/animations-picker.md +77 -0
  64. package/skills/remote/brand-setup.md +69 -0
  65. package/skills/remote/catalog-index.json +1810 -0
  66. package/skills/remote/environments.md +73 -0
  67. package/skills/remote/intent-router.md +81 -0
  68. package/skills/remote/media-stdlib.md +66 -0
  69. package/skills/remote/music.md +69 -0
  70. package/skills/remote/sdk-guide.md +201 -0
  71. package/skills/remote/shaders-filters.md +97 -0
  72. package/skills/remote/text-styles.md +108 -0
  73. package/skills/remote/transitions-catalog.md +55 -0
  74. package/skills/remote/tts.md +82 -0
  75. package/skills/remote/vidscript-guide.md +574 -0
  76. package/skills/remote/vidscript-sample.md +28 -0
  77. package/skills/remote/vidscript-strict.md +251 -0
  78. package/skills/remote/workflow.md +72 -0
  79. package/skills/shared/SKILL.md +157 -0
@@ -0,0 +1,574 @@
1
+ # VidScript Language Reference (v2)
2
+
3
+ VidScript is a declarative DSL for composing short-form videos. Write a script, render an MP4.
4
+
5
+ ## Agent quick start (read before composing)
6
+
7
+ 1. **Import every package function** — use `import xai from "@scenerok/xai"`, `import cf from "@scenerok/cloudflare"`, `import eleven from "@elevenlabs/music"`, and `import motion from "@scenerok/basic-animations"` before calling `xai.*`, `cf.*`, `eleven.*`, or `motion.*`.
8
+ 2. **Validate early** — `scenerok validate file.vid` (errors show as `Line N:C — message`).
9
+ 3. **Music package syntax** — `import eleven from "@elevenlabs/music"`, then call `eleven.music(...)`, `eleven.generateMusic(...)`, or `eleven.composeMusic(...)`.
10
+ 4. **Copy working examples** — see `examples/system/` in this skill folder (e.g. `minimal-text-reel.vid`, `ecom-product-launch.vid`, `rokmilk-chocolate-promo.vid`).
11
+ 5. **Use URL assets selectively** — if the user gives a website or product URL and browser screenshot tools are available, you can capture above-fold screenshots, product images, app screenshots, and logos for VidScript `input` assets. Do not treat screenshots as mandatory or as the only visual source.
12
+ 6. **Probe asset type and dimensions** — before placing real assets, identify image vs video with `file`; inspect width, height, and duration with `ffprobe`, `sips`, or `identify`; then scale from the measured aspect ratio and set explicit `width`, `height`, `x`, and `y`.
13
+ 7. **Use still images directly** — `input` supports videos and images. Place still screenshots, logos, and product photos with `video image_input`; the renderer treats images as static clips for the block duration.
14
+ 8. **Use the full media plugin toolkit** — choose still generation, text-to-video, image-to-video, reference-to-video, video extension, TTS, and music deliberately. Do not default to only `xai.imagine`.
15
+ 9. **Include audio when useful** — use `xai.tts` / `xai.textToSpeech` for narration and `eleven.music` / `eleven.generateMusic` / `eleven.composeMusic` for music beds.
16
+ 10. **Generated media has no text** — prompts for AI-generated images/videos must explicitly ask for no text, no words, no letters, no captions, no logos, no watermarks, and no readable UI copy. Use VidScript `text` primitives for all final copy.
17
+ 11. **Channel-aware `[-]` timing** — audio and visual auto blocks have separate playheads. A music bed or TTS sequence does not delay the next `[-] = video ...` block.
18
+ 12. **Never guess asset durations** — assign plugin results with `let` and reference the measured duration: `let vo = xai.tts(...)` then `[0 .. vo.duration] = audio vo`. Guessed windows like `[0s .. 3s] = audio xai.tts(...)` clip or overlap when the real audio runs longer.
19
+ 13. **Never guess text coordinates** — place text with `area:` (`title`, `stage`, `lower-third`, `caption` built in) instead of hand-picked `x`/`y`. See Grid Areas below.
20
+ 14. **Deep-validate before rendering** — `scenerok validate script.vid --generate-assets --report-json` runs the real compile (billed, assets cached for the render) and returns measured text geometry, collisions, and audio overlaps to fix before rendering.
21
+ 15. **Strict rules** — see `vidscript-strict.md` for invalid patterns (bare `xai.imagine`, bare `cf.video(...)`, bare `eleven.music(...)` without import, trailing params after direct plugin calls, nested quotes in prompts).
22
+
23
+ ### Common validation failures
24
+
25
+ | Symptom | Fix |
26
+ |---------|-----|
27
+ | `Unknown function 'xai.imagine'` | Add `import xai from "@scenerok/xai"` at top |
28
+ | `Unknown function 'cf.video'` | Add `import cf from "@scenerok/cloudflare"` at top |
29
+ | `Unknown function 'fadeIn'` | Use `import motion from "@scenerok/basic-animations"` and call `motion.fadeIn(...)` |
30
+ | `Expected ... but "," found` | Do not put `, volume:` after a direct plugin call. Use `let bed = eleven.music(...)`, then `audio bed, volume: ...` |
31
+ | `Unknown function 'eleven.generateMusic'` | Add `import eleven from "@elevenlabs/music"` before calling `eleven.generateMusic(...)` |
32
+ | Prompt needs quoted words | Escape quotes inside strings, e.g. `"Package labeled \"AER-01\""` |
33
+
34
+ ## Program Structure
35
+
36
+ A VidScript program is a sequence of statements separated by newlines.
37
+
38
+ Strings support common backslash escapes. Use `\n` inside a `text` string for an intentional rendered line break, and use `\"` or `\'` for quote characters inside the matching string delimiter.
39
+
40
+ ```vidscript
41
+ [0s .. 3s] = text "RUN LIGHTER\nDAILY", line_height: 1.05
42
+ let prompt = "Package labeled \"AER-01\", no text, no watermark"
43
+ ```
44
+
45
+ ```vidscript
46
+ # Single-line comment
47
+ /* Multi-line
48
+ comment */
49
+ ```
50
+
51
+ ## Statements
52
+
53
+ ### Input Declaration
54
+
55
+ ```vidscript
56
+ input hero = "https://cdn.example.com/hero.mp4"
57
+ input logo = "./assets/logo.png"
58
+ ```
59
+
60
+ Supports HTTP(S) URLs, `/uploads/` paths, and local paths for video/image assets.
61
+
62
+ For ads based on a supplied URL, browser tools can capture real website visuals and save product images or logos when useful. Use screenshots sparingly: favor above-fold, cropped, or focused captures that remain legible at video size. Avoid full-page or long scrolling screenshots as backgrounds because their text will usually become unreadable.
63
+
64
+ Website captures are optional grounding assets, not a ban on generated video. A strong ad may combine product/logo/app screenshots with generated lifestyle clips, motion backgrounds, transitions, and VidScript text primitives. Use generative clips when they improve storytelling, show context, or make the ad feel more polished.
65
+
66
+ After declaring or downloading assets, probe file type and dimensions before placing them:
67
+
68
+ ```bash
69
+ file asset.png
70
+ ffprobe -v error -select_streams v:0 -show_entries stream=width,height,duration -of csv=p=0 asset.mp4
71
+ sips -g pixelWidth -g pixelHeight asset.png
72
+ identify asset.webp
73
+ ```
74
+
75
+ Use real videos as motion footage and still images as static clips. The `video` primitive accepts both; the renderer detects image vs video:
76
+
77
+ ```vidscript
78
+ input screenshot = "assets/screenshots/homepage.png"
79
+ [0s .. 4.5s] = video screenshot, width: 1080, height: 1920, x: 0, y: 0, animate: motion.fadeIn(0.35s)
80
+ ```
81
+
82
+ For URL-based ads, still assets can also drive generated motion: use `xai.imageToVideo(screenshot, "...")` for one strong source image, or `xai.referenceToVideo([product, logo, person], "...")` to guide a new scene with extracted references.
83
+
84
+ Use the measured width and height to preserve aspect ratio. For a 1080x1920 vertical ad, a common centered fit is:
85
+
86
+ ```text
87
+ scaledWidth = min(assetWidth * scale, 900)
88
+ scaledHeight = scaledWidth / (assetWidth / assetHeight)
89
+ x = (1080 - scaledWidth) / 2
90
+ y = chosen safe-area top offset
91
+ ```
92
+
93
+ Do not guess asset size. Tiny logos, wide screenshots, and tall app screenshots need different scale and placement.
94
+
95
+ ### Output Declaration
96
+
97
+ ```vidscript
98
+ output to "video.mp4", resolution: "1080x1920", fps: 30
99
+ output to "reel.mp4", resolution: "720x1280", format: "mp4", codec: "h264", bitrate: "5M", background: "#0D0D0D"
100
+ ```
101
+
102
+ | Option | Type | Default | Description |
103
+ |--------|------|---------|-------------|
104
+ | `resolution` | string | `"1080x1920"` | Width×Height or single number |
105
+ | `fps` | number | `30` | Frames per second |
106
+ | `format` | string | `"mp4"` | Container format |
107
+ | `codec` | string | `"h264"` | Video codec |
108
+ | `bitrate` | string | `"5M"` | Encoding bitrate |
109
+ | `background` | string | `"#000000"` | Background color hex |
110
+
111
+ ### Variable Assignment
112
+
113
+ ```vidscript
114
+ let brandColor = "#6366F1"
115
+ let fadeTime = 0.5s
116
+ let titleSize = 72
117
+ let clipName = "hero"
118
+ ```
119
+
120
+ Variables can hold strings, numbers, time literals, and object expressions. Variables are evaluated at compile time.
121
+
122
+ ### Package Imports
123
+
124
+ Only default package imports are supported for callable packages. Call functions through the imported alias.
125
+
126
+ ```vidscript
127
+ import xai from "@scenerok/xai"
128
+ import cf from "@scenerok/cloudflare"
129
+ import eleven from "@elevenlabs/music"
130
+ import motion from "@scenerok/basic-animations"
131
+ ```
132
+
133
+ Named imports (`import { fn } ...`) and namespace imports (`import * as ...`) are not supported.
134
+
135
+ ## Time Blocks
136
+
137
+ Time blocks are the core of VidScript. They define when instructions execute on the timeline.
138
+
139
+ ### Dynamic Playhead (recommended)
140
+
141
+ ```vidscript
142
+ [-] = hero # auto-append: starts at visual cursor
143
+ [- 3s] = text "Title", size: 72 # auto-start, last 3 seconds
144
+ [- 2.5s] = filter "glow", intensity: 0.8
145
+ ```
146
+
147
+ VidScript keeps separate auto-playhead cursors for visual and audio content. Visual blocks (`video`, `text`, filters, shaders, visual plugin calls, and bare visual inputs) advance the visual cursor. Audio blocks (`audio`, `xai.tts`, `eleven.music`, and other audio plugin calls) advance the audio cursor. `[- duration]` advances the relevant cursor by the explicit duration.
148
+
149
+ This means a long audio bed or voiceover sequence does not push a following auto video block to the end of the audio:
150
+
151
+ ```vidscript
152
+ import xai from "@scenerok/xai"
153
+ import eleven from "@elevenlabs/music"
154
+
155
+ input product = "https://cdn.example.com/product.png"
156
+ let bed = eleven.music("Warm music bed", duration: 15, instrumental: true)
157
+ [0s .. 15s] = audio bed, volume: 0.35
158
+ [-] = video xai.imageToVideo(product, "Slow premium camera move", aspect_ratio: "9:16", duration: 6)
159
+ ```
160
+
161
+ The generated video above starts at the current visual cursor, usually `0s`, even though the audio cursor already reaches `15s`. Mixed blocks containing both audio and visual instructions advance both cursors. The `prev` keyword resolves against the cursor for the block being compiled; for mixed timing it uses the current combined timeline position.
162
+
163
+ ### Explicit Range
164
+
165
+ ```vidscript
166
+ [0s .. 5s] = hero # absolute range
167
+ [0.5s .. 3s] = text "Hello" # explicit times
168
+ [prev + 1s .. prev + 4s] = filter "glow" # expression-based
169
+ ```
170
+
171
+ Use `..` (not `-`) as the range separator to avoid ambiguity with subtraction.
172
+
173
+ ### Time Formats
174
+
175
+ ```vidscript
176
+ 5s # 5 seconds
177
+ 300ms # 300 milliseconds
178
+ frame 90 # 90 frames at 30fps = 3 seconds
179
+ 0:30 # 30 seconds
180
+ 1:30:00 # 1 hour 30 minutes
181
+ ```
182
+
183
+ ### The `prev` Keyword
184
+
185
+ `prev` refers to the current channel-aware playhead position for the block being compiled. In a visual block it follows the visual cursor; in an audio block it follows the audio cursor.
186
+
187
+ ```vidscript
188
+ [- 2s] = text "Hello" # plays from cursor to cursor+2s
189
+ [prev + 1s .. prev + 3s] = filter "glow" # 1s after text ends, for 2s
190
+ ```
191
+
192
+ ### Symbolic Durations (`asset.duration`)
193
+
194
+ Generated assets (TTS, music, video) have durations you cannot know while writing the script. Assign the plugin result with `let` and reference its **measured** duration in any time expression — the compiler probes the generated file, so the value is real, not an estimate:
195
+
196
+ ```vidscript
197
+ import xai from "@scenerok/xai"
198
+
199
+ let vo1 = xai.tts("Welcome to the show", voice: "eve")
200
+ let vo2 = xai.tts("Let's dive in", voice: "eve")
201
+
202
+ [0 .. vo1.duration] = audio vo1
203
+ [vo1.duration + 0.3 .. vo1.duration + 0.3 + vo2.duration] = audio vo2
204
+ [0 .. vo1.duration] = text "Welcome", area: "title" # visual spans the narration exactly
205
+ ```
206
+
207
+ - Bare numbers are seconds; `2s` / `300ms` literals and `+ - * /` arithmetic work anywhere a time appears.
208
+ - Exported timelines expose `.duration` too: `use scene1 at 0s .. 0s with {}` then `[scene1.duration .. scene1.duration + 4] = ...`. A `use` also advances the playhead past its content, so a following `[-]` block never overlaps it.
209
+ - This replaces guessing: `[0s .. 5s] = audio xai.tts(...)` clips or overlaps when the real audio is longer. For simple back-to-back narration, consecutive `[-] = audio ...` blocks also never overlap.
210
+
211
+ ## Include TypeScript / JavaScript scenes
212
+
213
+ Import a `@scenerok/sdk` scene (`.ts` / `.js`) into VidScript with the same default-import form as plugins, then `use` it like a timeline:
214
+
215
+ ```vidscript
216
+ import intro from "./intro.ts"
217
+ import bump from "./bump.js"
218
+
219
+ use intro at 0s .. 0s with {}
220
+ use bump at intro.duration .. intro.duration with {}
221
+ [intro.duration + bump.duration .. intro.duration + bump.duration + 2s] = text "Thanks", area: "caption"
222
+
223
+ output to "reel.mp4", resolution: "1080x1920"
224
+ ```
225
+
226
+ Rules:
227
+ - The SDK file must export `default function main()` (or `module.exports = main` / `exports.main`) that returns `t.toIR()` or an `IRTimeline`.
228
+ - `with { ... }` params are not supported on SDK scenes (no timeline parameters).
229
+ - Relative paths only (`./` / `../`). The CLI uploads companion scene files automatically when you `scenerok validate` / `scenerok render` a `.vid`.
230
+ - Prefer this when a complex loop/branch scene should sit inside a mostly-VidScript reel. For a fully TypeScript project, author the entry as `.ts` instead.
231
+
232
+ ## Instructions (inside time blocks)
233
+
234
+ Multiple instructions in the same time block go on separate lines:
235
+
236
+ ```vidscript
237
+ [0s .. 5s] = hero
238
+ hero.Trim(start: 0s, end: 5s)
239
+ hero.Speed(factor: 1.5)
240
+ ```
241
+
242
+ ### Clip Reference
243
+
244
+ ```vidscript
245
+ [-] = hero # just play the input clip
246
+ ```
247
+
248
+ ### Text Overlay
249
+
250
+ ```vidscript
251
+ [- 3s] = text "Hello World", font: "Bebas Neue", x: "50%", y: "45%", align: center, color: "#FFFFFF", size: 72, stroke: "#000000", stroke_width: 3, letter_spacing: 4, shadow_color: "#FF0055", shadow_blur: 8
252
+ ```
253
+
254
+ | Param | Type | Description |
255
+ |-------|------|-------------|
256
+ | `style` | string | `title` (64px), `subtitle` (54px), `caption` (36px), or `default` (48px) |
257
+ | `area` | string | Named grid region (preferred over `x`/`y`). Built-in: `title`, `stage`, `lower-third`, `caption`. Centers text in the cell and caps `max_width`/`max_height` to it. See Grid Areas. |
258
+ | `position` | string | `center`, `top`, `bottom`, `top-left`, `top-right`, `bottom-left`, `bottom-right` |
259
+ | `x` | number \| string | Horizontal position. Number = px from left. `"50%"` = percent of width. Overrides `position` and `area`. |
260
+ | `y` | number \| string | Vertical position. Number = px from top. `"50%"` = percent of height. Overrides `position` and `area`. |
261
+ | `max_width` | number \| string | Wrap/shrink boundary, px or `"90%"` |
262
+ | `max_height` | number \| string | Vertical fit boundary, px or `"20%"` |
263
+ | `fit` | string | `wrap-shrink` (default), `wrap`, `shrink`, `none` |
264
+ | `overflow` | string | `contain` (default, clamps to safe area), `crop`, `allow` |
265
+ | `color` | string | Hex color |
266
+ | `size` | number \| string | Font size in output canvas pixels. `size: 72` is shorthand for `size: "72px"`. |
267
+ | `font` | string | Font family. System fonts or Google Fonts: `"Inter"`, `"Bebas Neue"`, `"Space Grotesk"`, `"Outfit"`, etc. |
268
+ | `align` | string | `left`, `center`, `right`. Default: `center` |
269
+ | `line_height` | number | Line height multiplier for multi-line text. Default: 1.2 |
270
+ | `letter_spacing` | number | Extra letter spacing in pixels. Default: 0 |
271
+ | `rotation` | number | Rotation in degrees. Positive = clockwise. Default: 0 |
272
+ | `opacity` | number | Opacity 0–1. Default: 1 |
273
+ | `stroke` | string | Outline color |
274
+ | `stroke_width` | number | Outline thickness |
275
+ | `shadow_color` | string | Drop shadow color |
276
+ | `shadow_blur` | number | Shadow blur radius |
277
+ | `shadow_offset_x` | number | Shadow horizontal offset |
278
+ | `shadow_offset_y` | number | Shadow vertical offset |
279
+ | `animation` | string | Parsed but not yet rendered |
280
+
281
+ Params are optional and comma-separated. Params can be on the same line as the content.
282
+
283
+ ### Grid Areas
284
+
285
+ Place text in named regions instead of guessing coordinates. Without any declaration, a built-in grid provides `title`, `stage`, `lower-third`, and `caption`:
286
+
287
+ ```vidscript
288
+ [0 .. 3] = text "BIG SALE", area: "title"
289
+ [0 .. 3] = text "50% off this week", area: "lower-third"
290
+ ```
291
+
292
+ Declare a custom grid with CSS Grid semantics — `rows`/`cols` are track lists (`%` and `fr` units only), `areas` is grid-template-areas (one string per row, `.` for empty cells, repeated names span):
293
+
294
+ ```vidscript
295
+ grid rows: "20% 1fr 30%", cols: "1fr 1fr", areas: ["title title", "left right", "caption caption"]
296
+ [0 .. 3] = text "Left column", area: "left"
297
+ ```
298
+
299
+ The area centers the text in its cell and sets `max_width`/`max_height` to the cell size, so long copy wraps and shrinks to fit the region. Explicit `x`/`y`/`max_width`/`max_height` override the area. Everything resolves to percentages, so grids work at any output resolution.
300
+
301
+ **TypeScript SDK equivalent** (`sdk-guide.md`):
302
+
303
+ ```ts
304
+ t.grid({
305
+ rows: '20% 1fr 30%',
306
+ cols: '1fr 1fr',
307
+ areas: ['title title', 'left right', 'caption caption'],
308
+ })
309
+ t[0][s(3)] = text('Left column', { area: 'left' })
310
+ ```
311
+
312
+ ### Video Methods
313
+
314
+ ```vidscript
315
+ hero.Trim(start: 0s, end: 5s) # trim clip (positional: hero.Trim(0s, 5s))
316
+ hero.Resize(width: 720, height: 1280) # resize (positional: hero.Resize(720, 1280))
317
+ hero.Speed(factor: 1.5) # playback rate
318
+ hero.Loop(count: 3) # loop count
319
+ hero.Opacity(value: 0.5, duration: 2s) # opacity with duration
320
+ ```
321
+
322
+ Arguments can be positional or named using `:`.
323
+
324
+ ### Compositing
325
+
326
+ ```vidscript
327
+ hero.Overlay(logo, x: 50, y: 100, opacity: 0.8)
328
+ hero.Composite(overlay, x: 0, y: 0, opacity: 0.5, mode: "screen")
329
+ ```
330
+
331
+ | Param | Type | Description |
332
+ |-------|------|-------------|
333
+ | `x` | number | Horizontal offset |
334
+ | `y` | number | Vertical offset |
335
+ | `opacity` | number | 0–1 transparency |
336
+ | `mode` | string | Blend mode for Composite |
337
+
338
+ ### Filters
339
+
340
+ ```vidscript
341
+ [- 2s] = filter "glow", intensity: 0.8
342
+ [0s .. 5s] = filter "vignette", intensity: 0.3
343
+ ```
344
+
345
+ Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignette`, `contrast`, `saturation`, `brightness`.
346
+
347
+ ### Shaders
348
+
349
+ Author fragment GLSL in a separate file, import it, then apply with `shader` (not `filter`):
350
+
351
+ ```vidscript
352
+ import shader glow from "./shaders/glow.glsl"
353
+ # or: import shader glow from "shader-pack/shaders/glow.glsl"
354
+
355
+ [0s .. 5s] = shader glow, intensity: 0.8
356
+ ```
357
+
358
+ Fragment must sample `tDiffuse`, write `gl_FragColor`, and declare uniforms matching param names. See `shaders-filters.md` → Custom shaders.
359
+ ### Audio
360
+
361
+ ```vidscript
362
+ audio music, volume: 0.5, fade_in: 1s, fade_out: 2s
363
+ ```
364
+
365
+ Audio sources can be MP3 inputs, audio plugin results, or the embedded audio stream from an input video. Video inputs are visually silent unless you explicitly add `audio clip`. Plugin-generated audio (TTS, music, and probed native audio on generated video clips) is EBU R128 loudness-normalized during render to about -16 LUFS; `volume` is a relative gain applied after that. Ordinary imported audio/video is not auto-normalized.
366
+
367
+ | Param | Type | Description |
368
+ |-------|------|-------------|
369
+ | `volume` | number | Relative gain (0–1+). For generated audio, applied after loudness normalization. |
370
+ | `fade_in` | number | Fade-in duration (seconds) |
371
+ | `fade_out` | number | Fade-out duration (seconds) |
372
+
373
+ ### Plugin Calls
374
+
375
+ ```vidscript
376
+ import xai from "@scenerok/xai"
377
+ import cf from "@scenerok/cloudflare"
378
+ import eleven from "@elevenlabs/music"
379
+ import motion from "@scenerok/basic-animations"
380
+
381
+ [- 4s] = video xai.image("Premium product still on seamless studio background, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", aspect_ratio: "9:16", resolution: "2k")
382
+ [-] = video xai.imagine("Cinematic product video, premium lighting, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", aspect_ratio: "9:16", resolution: "720p", duration: 5)
383
+ [-] = video xai.imageToVideo("https://cdn.example.com/product.png", "Slow premium camera move around the product, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", aspect_ratio: "9:16", duration: 6)
384
+ [-] = video xai.referenceToVideo(["https://cdn.example.com/product.png", "https://cdn.example.com/person.png"], "Lifestyle ad shot featuring the referenced product and person, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", aspect_ratio: "9:16", duration: 8)
385
+ [-] = video xai.extendVideo("https://cdn.example.com/clip.mp4", "Camera pulls back to reveal the full premium setup, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", duration: 5)
386
+ [-] = audio xai.tts("Welcome to SceneRok", voice: "eve")
387
+
388
+ # Cloudflare AI Gateway package.
389
+ [-] = video cf.video("High-energy fashion product shot, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", model: "pixverse/v6", aspect_ratio: "9:16", duration: 5, generate_audio: false)
390
+ [-] = video cf.imageToVideo("https://cdn.example.com/product.png", "Animate the product with realistic parallax, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", model: "runwayml/gen-4.5", aspect_ratio: "9:16", duration: 5)
391
+ [- 4s] = video cf.image("Clean editorial product still, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy", model: "black-forest-labs/flux-2-pro-preview", aspect_ratio: "9:16")
392
+
393
+ # ElevenLabs music package functions require an import alias.
394
+ [0s .. 15s] = audio eleven.music("Warm premium launch bed", duration: 15, instrumental: true)
395
+
396
+ # If you need volume or fades, store the generated audio first.
397
+ let bed = eleven.music("Warm premium launch bed", duration: 15, instrumental: true)
398
+ [0s .. 15s] = audio bed, volume: 0.35, fade_out: 2s
399
+ ```
400
+
401
+ `@scenerok/xai`, `@scenerok/cloudflare`, and `@elevenlabs/music` require explicit default imports. ElevenLabs music validates as `eleven.music(...)`, `eleven.generateMusic(...)`, or `eleven.composeMusic(...)` after `import eleven from "@elevenlabs/music"`. Plugin calls run at compile time; direct media plugin instructions cannot have trailing audio params, so use a `let` binding when you need `volume`, `fade_in`, or `fade_out`.
402
+
403
+ ### Media Plugin API Reference
404
+
405
+ | Package | Function | Use when |
406
+ |---------|----------|----------|
407
+ | `@scenerok/xai` | `xai.image(prompt, ...)`, `xai.generateImage(prompt, ...)` | Generate a still image clip. Use `resolution: "1k"` or `"2k"` and `duration` for how long the still stays on the timeline. |
408
+ | `@scenerok/xai` | `xai.imagine(prompt, ...)`, `xai.genVideo(prompt, ...)` | Generate video from text. Use video resolutions such as `"480p"` or `"720p"`. |
409
+ | `@scenerok/xai` | `xai.imageToVideo(image, prompt, ...)` | Animate one source image, screenshot, logo, product photo, data URI, file id, or `{ url }` object. |
410
+ | `@scenerok/xai` | `xai.referenceToVideo([images...], prompt, ...)` | Guide a generated scene with 1-7 reference images. Requires a prompt; keep duration <= 10s. |
411
+ | `@scenerok/xai` | `xai.extendVideo(video, prompt, ...)` | Continue an existing video clip by up to 10s. |
412
+ | `@scenerok/xai` | `xai.tts(text, ...)`, `xai.textToSpeech(text, ...)` | Generate spoken narration. Voices: `eve`, `ara`, `rex`, `sal`, `leo`. |
413
+ | `@scenerok/cloudflare` | `cf.image(prompt, ...)`, `cf.generateImage(prompt, ...)` | Generate stills through Cloudflare AI Gateway image models such as `black-forest-labs/flux-2-pro-preview`, `openai/gpt-image-2`, `xai/grok-imagine-image-quality`, or `recraft/recraftv4-pro`. |
414
+ | `@scenerok/cloudflare` | `cf.video(prompt, ...)`, `cf.imagine(prompt, ...)`, `cf.genVideo(prompt, ...)`, `cf.generateVideo(prompt, ...)` | Generate text-to-video through models such as `pixverse/v6`, `vidu/q3-turbo`, `runwayml/gen-4.5`, `minimax/hailuo-2.3`, `bytedance/seedance-2.0`, `google/veo-3.1`, `google/veo-3.1-fast`, or `google/veo-3-fast`. |
415
+ | `@scenerok/cloudflare` | `cf.imageToVideo(image, prompt, ...)`, `cf.referenceToVideo([images...], prompt, ...)`, `cf.extendVideo(video, prompt, ...)` | Use Cloudflare-backed image-guided, reference-guided, or extension modes when the selected model supports them. |
416
+ | `@scenerok/cloudflare` | `cf.listModels()`, `cf.models()` | Return model metadata as data; useful for exploration, not for timeline media. |
417
+ | `@elevenlabs/music` | `eleven.music(prompt, ...)`, `eleven.generateMusic(prompt, ...)`, `eleven.composeMusic(prompt, ...)` | Generate background music. Use a `let` binding if you need `volume`, `fade_in`, or `fade_out`. |
418
+
419
+ xAI TTS voices: `eve` for upbeat demos/announcements, `ara` for warm conversational narration, `rex` for business/tutorial content, `sal` for balanced general delivery, and `leo` for authoritative instruction.
420
+
421
+ Do not call `eleven.tts(...)`; the registered ElevenLabs package is `@elevenlabs/music` for music beds only. Use `xai.tts(...)` or `xai.textToSpeech(...)` for voiceover.
422
+
423
+ Cloudflare video calls accept these common signatures:
424
+
425
+ | Function | Signature |
426
+ |----------|-----------|
427
+ | `cf.video` / `cf.generateVideo` / `cf.imagine` / `cf.genVideo` | `(prompt, model?, aspect_ratio?, duration?, resolution?/quality?, generate_audio?, negative_prompt?, seed?, input?)` |
428
+ | `cf.imageToVideo` | `(image, prompt, model?, aspect_ratio?, duration?, resolution?/quality?, generate_audio?, negative_prompt?, seed?, input?)` |
429
+ | `cf.referenceToVideo` | `([images...], prompt, model?, aspect_ratio?, duration?, resolution?/quality?, generate_audio?, input?)` |
430
+ | `cf.extendVideo` | `(video, prompt, model?, duration?, aspect_ratio?, resolution?/quality?, input?)` |
431
+
432
+ Use `aspect_ratio: "9:16"` for vertical output, `"16:9"` for wide, `"1:1"` for square, or model-supported values such as `"4:3"`, `"3:4"`, and `"21:9"`. For Runway models, keep writing friendly `aspect_ratio` values; SceneRok translates them to Cloudflare's required `ratio` values such as `"720:1280"` for vertical Gen-4.5. Prefer explicit `model:` when choosing Cloudflare so the intended provider is clear.
433
+
434
+ **Invalid (will not validate):**
435
+
436
+ ```vidscript
437
+ [-] = video xai.imagine("...") # missing xAI import
438
+ [0s .. 5s] = video cf.video("...") # missing Cloudflare import
439
+ [0s .. 15s] = audio eleven.generateMusic("...") # missing `import eleven from "@elevenlabs/music"`
440
+ [0s .. 5s] = audio eleven.tts("...") # ElevenLabs TTS is not registered; use xai.tts
441
+ [0s .. 15s] = audio eleven.music("..."), volume: 0.5 # trailing params after direct plugin call
442
+ ```
443
+
444
+ ## Animations, Effects & Rendering (v2)
445
+
446
+ VidScript v2 uses a **minimal-grammar, plugin-first** architecture. The compiler lowers VidScript into `IRTimeline` — the single contract shared by browser preview and final render.
447
+
448
+ User-facing extension uses two parameters on any text or video surface:
449
+
450
+ - `animate:` — motion over time (plugin calls or object descriptors)
451
+ - `effects:` — post-processing filters with optional animated params
452
+
453
+ ### animate: parameter
454
+
455
+ ```vidscript
456
+ import motion from "@scenerok/basic-animations"
457
+
458
+ [0s .. 3s] = text "Hello", x: "50%", y: "50%", animate: motion.fadeIn(0.8s)
459
+
460
+ [-] = video hero, animate: [motion.fadeIn(0.5s), motion.slideY(-40, 0, 1.2s)]
461
+
462
+ # Static asset with measured 4:3-ish aspect ratio, centered in a vertical 1080x1920 frame.
463
+ [0s .. 3.4s] = video app_screen, width: 760, height: 615, x: 160, y: 300, animate: [motion.riseIn(0.6s, 70), motion.float(2.4s, 8)]
464
+ [3.4s .. 4s] = video app_screen, width: 760, height: 615, x: 160, y: 300, animate: motion.fadeOut(0.6s)
465
+
466
+ [0s .. 2s] = text "Title", animate: {
467
+ type: "fade",
468
+ from: { opacity: 0 },
469
+ to: { opacity: 1 },
470
+ start: 0,
471
+ end: 1.5,
472
+ easing: "easeOutQuad"
473
+ }
474
+ ```
475
+
476
+ Compiler lowers `animate:` into `IRMotionTrack[]` (property-path keyframes). Legacy `IRAnimation` descriptors are adapted at runtime for backward compatibility.
477
+
478
+ ### effects: parameter
479
+
480
+ ```vidscript
481
+ [0s .. 5s] = filter "vignette", intensity: 0.4
482
+
483
+ [0s .. 5s] = filter "glitch", intensity: 0.5, animate: motion.fadeIn(1s)
484
+ ```
485
+
486
+ Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignette`, `contrast`, `saturation`, `brightness`. Filters are optional finishing tools; use them only when they improve the composition.
487
+
488
+ ### Animation functions (`@scenerok/basic-animations`)
489
+
490
+ | Function | Description |
491
+ |----------|-------------|
492
+ | `motion.fadeIn(duration?)` | Opacity 0 → 1 |
493
+ | `motion.fadeOut(duration?)` | Opacity 1 → 0 |
494
+ | `motion.slideX(from, to, dur?)` | Horizontal slide |
495
+ | `motion.slideY(from, to, dur?)` | Vertical slide |
496
+ | `motion.popIn(duration?)` | Scale + fade entrance |
497
+ | `motion.riseIn(duration?, dist?)` | Upward fade entrance |
498
+ | `motion.swingIn(duration?)` | Rotating slide/fade |
499
+ | `motion.glitchIn(duration?)` | Jitter + flash entrance |
500
+ | `motion.float(duration?, amp?)` | Gentle vertical bob |
501
+ | `motion.typewriter(duration?)` | Character reveal |
502
+
503
+ ### Render architecture (contributors)
504
+
505
+ Implemented registry-based rendering. See `plan-v1/35-render-subsystem-architecture.md`.
506
+
507
+ | Registry | Path | Add via |
508
+ |----------|------|---------|
509
+ | Motion | `src/lib/motion/registry.ts` | `registerEasing`, `registerMotionBehavior` |
510
+ | Surface | `src/render/surfaces/registry.ts` | `registerSurfaceRenderer` |
511
+ | Effect | `src/render/effects/registry.ts` | `registerEffect` or `src/shaders/library.ts` |
512
+ | Engine | `src/render/engine/index.ts` | `createRenderEngine` backends |
513
+
514
+ For SceneRok platform engineering, load the **`scenerok-development`** skill (`.agents/skills/scenerok-development/`).
515
+
516
+ ## Expressions
517
+
518
+ Used in time specs, `let` values, and function arguments:
519
+
520
+ ```vidscript
521
+ let x = 5s # Time literals
522
+ let y = x * 2 # Arithmetic
523
+ let prompt = "Scene: " + title_text # String concatenation
524
+ let z = prev + 0.5s # Playhead reference
525
+ let a = { primary: "#FF5733" } # Objects
526
+ ```
527
+
528
+ Operators: `+`, `-`, `*`, `/` with standard precedence.
529
+
530
+ ## Template Placeholders
531
+
532
+ ```vidscript
533
+ [- 3s] = text "{{headline | Default Text}}", color: "{{color | #FFFFFF}}"
534
+ ```
535
+
536
+ Placeholders are filled before parsing via `fillPlaceholders()`.
537
+
538
+ ## CLI Commands
539
+
540
+ ```bash
541
+ # Authenticate
542
+ scenerok auth login
543
+
544
+ # Validate a script
545
+ scenerok validate my-video.vid
546
+
547
+ # Render a video
548
+ scenerok project upload my-video.vid --assets ./assets --render --watch --download ./renders
549
+ scenerok project render <project-id> --watch --download ./renders
550
+ scenerok render my-video.vid --project-id <project-id> --watch
551
+ scenerok cache pull
552
+
553
+ # Check render status
554
+ scenerok status <render-id>
555
+
556
+ # Install agent skills
557
+ scenerok skills install <platform>
558
+
559
+ # Set API secrets for plugins
560
+ scenerok secrets set ELEVENLABS_API_KEY=your-key
561
+ ```
562
+
563
+ ## Best Practices
564
+
565
+ 1. **Use dynamic timeblocks** — `[-]` auto-advances the relevant audio or visual cursor, reducing calculation errors
566
+ 2. **Use `prev` for offsets** — `[prev + 0.5s .. prev + 2s]` for gaps after previous content in the same channel
567
+ 3. **1080×1920 for vertical** (TikTok, Reels, Shorts), **1920×1080 for horizontal** (YouTube)
568
+ 4. **Hook viewers in the first 3 seconds** — place the most compelling content early
569
+ 5. **High-contrast text** — use `stroke` and `stroke_width` on text overlays over video
570
+ 6. **Test with `scenerok validate` first** — catch syntax errors before spending render credits
571
+ 7. **Keep text content short** — text disappears when the time block ends
572
+ 8. **One instruction per line** — each instruction inside a timeblock goes on its own line
573
+ 9. **Text-only auto blocks** — `[-] = text ...` lasts 2 seconds by default; use `[- 3s]` for custom timing
574
+ 10. **Named arguments are clearer** — `hero.Trim(start: 0s, end: 5s)` over `hero.Trim(0s, 5s)`
@@ -0,0 +1,28 @@
1
+ # VidScript v2 Sample — Product promo with generated video
2
+
3
+ Copy this shape for promos that use xAI-generated clips. Run `scenerok validate` before `scenerok render`.
4
+
5
+ ```vidscript
6
+ import xai from "@scenerok/xai"
7
+ import motion from "@scenerok/basic-animations"
8
+
9
+ input product_name = "{{product_name | RokMilk}}"
10
+ input hook_text = "{{hook_text | Chocolate Milkshake}}"
11
+ input cta_text = "{{cta_text | visit your nearest grocery store}}"
12
+
13
+ [0s .. 2.5s] = text product_name, font: "Bebas Neue", size: 80, color: "#FFD700", x: "50%", y: "20%", align: center, stroke: "#3E1C00", stroke_width: 4, animate: motion.fadeIn(0.6s)
14
+
15
+ [2s .. 4.5s] = text hook_text, font: "Outfit", size: 52, color: "#FFFFFF", x: "50%", y: "45%", align: center, stroke: "#3E1C00", stroke_width: 3, animate: motion.riseIn(0.8s)
16
+
17
+ [-] = video xai.imagine(
18
+ "Premium lifestyle product shot of " + product_name + " chocolate milk in 250ml carton, cinematic lighting, 9:16 vertical, no text, no words, no letters, no captions, no logos, no watermark, no readable UI copy",
19
+ aspect_ratio: "9:16",
20
+ duration: 6
21
+ )
22
+
23
+ [8s .. 11s] = text cta_text, font: "Bebas Neue", size: 48, color: "#FFD700", x: "50%", y: "80%", align: center, stroke: "#3E1C00", stroke_width: 4, animate: motion.fadeIn(0.8s)
24
+
25
+ output to "product-promo.mp4", resolution: "1080x1920", fps: 30
26
+ ```
27
+
28
+ For clips you already have (no xAI), start from `examples/system/product-launch.vid` instead.