@vibeo/cli 0.3.4 → 0.3.5

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.
@@ -0,0 +1,380 @@
1
+ # Vibeo Core (`@vibeo/core`)
2
+
3
+ ## Overview
4
+
5
+ `@vibeo/core` is the foundation of the Vibeo video framework. It provides the timing engine, React context providers, hooks for frame-based animation, the interpolation engine, and the core components (`Composition`, `Sequence`, `Loop`) used to structure video timelines.
6
+
7
+ **When to use**: Any time you are building a Vibeo video project. This package is always required.
8
+
9
+ ---
10
+
11
+ ## API Reference
12
+
13
+ ### Components
14
+
15
+ #### `Composition<T>`
16
+ Registers a video composition with the framework.
17
+
18
+ ```tsx
19
+ <Composition
20
+ id="MyVideo"
21
+ component={MyScene}
22
+ width={1920}
23
+ height={1080}
24
+ fps={30}
25
+ durationInFrames={300}
26
+ defaultProps={{ title: "Hello" }}
27
+ calculateMetadata={async (props) => ({ durationInFrames: 600 })}
28
+ />
29
+ ```
30
+
31
+ **Props** (`CompositionProps<T>`):
32
+ | Prop | Type | Description |
33
+ |------|------|-------------|
34
+ | `id` | `string` | Unique identifier for the composition |
35
+ | `component` | `ComponentType<T>` | The React component to render |
36
+ | `width` | `number` | Width in pixels |
37
+ | `height` | `number` | Height in pixels |
38
+ | `fps` | `number` | Frames per second |
39
+ | `durationInFrames` | `number` | Total duration in frames |
40
+ | `defaultProps?` | `T` | Default props passed to the component |
41
+ | `calculateMetadata?` | `(props: T) => Promise<Partial<VideoConfig>>` | Async function to compute metadata dynamically |
42
+
43
+ #### `Sequence`
44
+ Time-shifts its children on the timeline. Supports arbitrary nesting with cumulative offsets.
45
+
46
+ ```tsx
47
+ <Sequence from={30} durationInFrames={60} name="Scene2">
48
+ <MyComponent />
49
+ </Sequence>
50
+ ```
51
+
52
+ **Props**:
53
+ | Prop | Type | Default | Description |
54
+ |------|------|---------|-------------|
55
+ | `from?` | `number` | `0` | Frame offset where this sequence begins |
56
+ | `durationInFrames?` | `number` | `Infinity` | How many frames this sequence lasts |
57
+ | `name?` | `string` | — | Debug label |
58
+ | `layout?` | `"none" \| "absolute-fill"` | `"absolute-fill"` | CSS layout mode |
59
+
60
+ Inside a Sequence, `useCurrentFrame()` returns the **relative** frame (i.e., frame 0 is when the Sequence starts).
61
+
62
+ #### `Loop`
63
+ Repeats its children for a given duration.
64
+
65
+ ```tsx
66
+ <Loop durationInFrames={30} times={4}>
67
+ <PulsingDot />
68
+ </Loop>
69
+ ```
70
+
71
+ **Props**:
72
+ | Prop | Type | Default | Description |
73
+ |------|------|---------|-------------|
74
+ | `durationInFrames` | `number` | — | Duration of one loop iteration |
75
+ | `times?` | `number` | `Infinity` | Number of loop iterations |
76
+ | `layout?` | `"none" \| "absolute-fill"` | — | CSS layout mode |
77
+
78
+ #### `VibeoRoot`
79
+ Top-level context provider tree. Wrap your composition registration in this.
80
+
81
+ ```tsx
82
+ <VibeoRoot>
83
+ <Composition id="MyVideo" ... />
84
+ </VibeoRoot>
85
+ ```
86
+
87
+ ### Hooks
88
+
89
+ #### `useCurrentFrame(): number`
90
+ Returns the current frame number **relative to the nearest parent Sequence**. This is the primary hook for animation.
91
+
92
+ #### `useVideoConfig(): VideoConfig`
93
+ Returns `{ width, height, fps, durationInFrames }` for the current composition.
94
+
95
+ #### `useTimelinePosition(): number`
96
+ Returns the **absolute** frame number (ignores Sequence offsets). Rarely needed directly.
97
+
98
+ #### `useSequenceContext(): SequenceContextType | null`
99
+ Returns the current Sequence context, or `null` if not inside a Sequence.
100
+
101
+ #### `useLoopContext(): LoopContextType | null`
102
+ Returns `{ iteration, durationInFrames }` for the current loop, or `null`.
103
+
104
+ ### Interpolation
105
+
106
+ #### `interpolate(input, inputRange, outputRange, options?): number`
107
+ The core animation primitive. Maps an input value through input/output ranges with easing.
108
+
109
+ ```ts
110
+ const opacity = interpolate(frame, [0, 30], [0, 1]);
111
+ const x = interpolate(frame, [0, 15, 30], [0, 200, 0], { easing: easeInOut });
112
+ ```
113
+
114
+ **Parameters**:
115
+ | Param | Type | Description |
116
+ |-------|------|-------------|
117
+ | `input` | `number` | The current value (usually the frame) |
118
+ | `inputRange` | `readonly number[]` | Breakpoints for the input |
119
+ | `outputRange` | `readonly number[]` | Corresponding output values |
120
+ | `options?` | `InterpolateOptions` | Easing and extrapolation config |
121
+
122
+ **`InterpolateOptions`**:
123
+ | Field | Type | Default | Description |
124
+ |-------|------|---------|-------------|
125
+ | `easing?` | `(t: number) => number` | `linear` | Easing function |
126
+ | `extrapolateLeft?` | `ExtrapolateType` | `"extend"` | Behavior below input range |
127
+ | `extrapolateRight?` | `ExtrapolateType` | `"extend"` | Behavior above input range |
128
+
129
+ **`ExtrapolateType`**: `"clamp" | "extend" | "identity"`
130
+
131
+ ### Easing Functions
132
+
133
+ ```ts
134
+ import { linear, easeIn, easeOut, easeInOut, bezier, steps } from "@vibeo/core";
135
+ ```
136
+
137
+ | Function | Description |
138
+ |----------|-------------|
139
+ | `linear(t)` | No easing, linear progression |
140
+ | `easeIn(t)` | Cubic ease-in (t^3) |
141
+ | `easeOut(t)` | Cubic ease-out |
142
+ | `easeInOut(t)` | Cubic ease-in-out |
143
+ | `bezier(x1, y1, x2, y2)` | Returns a custom cubic bezier easing function |
144
+ | `steps(n)` | Returns a step function with `n` discrete steps |
145
+
146
+ ### Timing Utilities
147
+
148
+ ```ts
149
+ import { msPerFrame, frameToTime, timeToFrame, getMediaTime } from "@vibeo/core";
150
+ ```
151
+
152
+ | Function | Signature | Description |
153
+ |----------|-----------|-------------|
154
+ | `msPerFrame` | `(fps) => number` | Milliseconds per frame: `1000 / fps` |
155
+ | `frameToTime` | `(frame, fps) => number` | Frame to seconds: `frame * msPerFrame(fps) / 1000` |
156
+ | `timeToFrame` | `(time, fps) => number` | Seconds to frame: `floor(time * fps)` |
157
+ | `getMediaTime` | `(frame, fps, playbackRate, startFrom) => number` | Compute media time accounting for playback rate |
158
+
159
+ ### Other Utilities
160
+
161
+ #### `calculateMediaDuration(options): number`
162
+ Computes the actual duration of media in frames, accounting for trim and playback rate.
163
+
164
+ #### `validateVideoConfig(config): void`
165
+ Validates a `VideoConfig` object, throwing on invalid values.
166
+
167
+ ### Types
168
+
169
+ ```ts
170
+ import type {
171
+ VideoConfig,
172
+ CompositionProps,
173
+ SequenceContextType,
174
+ LoopContextType,
175
+ ExtrapolateType,
176
+ InterpolateOptions,
177
+ } from "@vibeo/core";
178
+ ```
179
+
180
+ ### Context Providers (advanced)
181
+
182
+ - `TimelineProvider` / `TimelineContext` / `useTimelineContext` — manages the global frame state
183
+ - `CompositionProvider` / `CompositionContext` / `useCompositionContext` — manages composition registration
184
+ - `SequenceContext` / `useSequenceContext` — tracks cumulative Sequence offsets
185
+ - `LoopContext` / `useLoopContext` — tracks loop iteration state
186
+
187
+ ---
188
+
189
+ ## Key Timing Math
190
+
191
+ ### Frame <-> Time
192
+ ```
193
+ msPerFrame = 1000 / fps
194
+ timeInSeconds = frame * msPerFrame / 1000
195
+ frameFromTime = floor(timeInSeconds * fps)
196
+ ```
197
+
198
+ ### Media Time with Playback Rate
199
+ ```
200
+ mediaTime(frame, fps, playbackRate, startFrom) =
201
+ interpolate(frame, [-1, startFrom, startFrom+1], [-1, startFrom, startFrom+playbackRate]) * (1000/fps) / 1000
202
+ ```
203
+
204
+ ### Sequence Relative Frame
205
+ ```
206
+ relativeFrame = absoluteFrame - (cumulatedFrom + relativeFrom)
207
+ ```
208
+
209
+ ### Loop Iteration
210
+ ```
211
+ iteration = floor(currentFrame / durationInFrames)
212
+ loopFrame = currentFrame % durationInFrames
213
+ ```
214
+
215
+ ### Interpolation Engine
216
+ ```
217
+ 1. Find segment: which adjacent pair in inputRange brackets input
218
+ 2. Normalize: t = (input - inputRange[i]) / (inputRange[i+1] - inputRange[i])
219
+ 3. Ease: t = easing(t)
220
+ 4. Scale: output = t * (outputRange[i+1] - outputRange[i]) + outputRange[i]
221
+ 5. Extrapolate: clamp | extend | identity beyond range ends
222
+ ```
223
+
224
+ ### Media Duration with Trim & Playback Rate
225
+ ```
226
+ duration = trimAfter ?? totalDurationInFrames
227
+ duration -= trimBefore ?? 0
228
+ actualDuration = floor(duration / playbackRate)
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Common Patterns
234
+
235
+ ### Creating a basic composition
236
+
237
+ ```tsx
238
+ import { Composition, Sequence, useCurrentFrame, interpolate, easeInOut } from "@vibeo/core";
239
+
240
+ function MyScene() {
241
+ const frame = useCurrentFrame();
242
+ const opacity = interpolate(frame, [0, 30], [0, 1], { easing: easeInOut });
243
+ return <div style={{ opacity }}>Hello Vibeo</div>;
244
+ }
245
+
246
+ // Register in a VibeoRoot
247
+ <Composition
248
+ id="MyScene"
249
+ component={MyScene}
250
+ width={1920}
251
+ height={1080}
252
+ fps={30}
253
+ durationInFrames={150}
254
+ />
255
+ ```
256
+
257
+ ### Using Sequence for scene structure
258
+
259
+ ```tsx
260
+ function MyVideo() {
261
+ return (
262
+ <>
263
+ <Sequence from={0} durationInFrames={60}>
264
+ <IntroScene />
265
+ </Sequence>
266
+ <Sequence from={60} durationInFrames={90}>
267
+ <MainScene />
268
+ </Sequence>
269
+ <Sequence from={150} durationInFrames={60}>
270
+ <OutroScene />
271
+ </Sequence>
272
+ </>
273
+ );
274
+ }
275
+ ```
276
+
277
+ ### Nesting Loops inside Sequences
278
+
279
+ ```tsx
280
+ <Sequence from={0} durationInFrames={120}>
281
+ <Loop durationInFrames={30} times={4}>
282
+ <BouncingBall />
283
+ </Loop>
284
+ </Sequence>
285
+ ```
286
+
287
+ ### Multi-segment interpolation
288
+
289
+ ```tsx
290
+ const frame = useCurrentFrame();
291
+ // Move right, then back, then down
292
+ const x = interpolate(frame, [0, 30, 60, 90], [0, 200, 0, 0]);
293
+ const y = interpolate(frame, [0, 30, 60, 90], [0, 0, 0, 200]);
294
+ ```
295
+
296
+ ---
297
+
298
+ ## Gotchas and Tips
299
+
300
+ 1. **`useCurrentFrame()` is always relative to the nearest `<Sequence>`**. If you need the absolute frame, use `useTimelinePosition()`.
301
+
302
+ 2. **`interpolate()` requires at least 2 input range values** and input/output ranges must have the same length.
303
+
304
+ 3. **Extrapolation defaults to `"extend"`** (continues the slope beyond the range). Use `{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }` to clamp values at range boundaries.
305
+
306
+ 4. **`Sequence` children are only rendered when the absolute frame is within `[from, from + durationInFrames)`**. Outside that range, `Sequence` returns `null`.
307
+
308
+ 5. **Loop iterations**: Inside a `<Loop>`, `useCurrentFrame()` resets to 0 each iteration. Use `useLoopContext()` to get the current `iteration` number.
309
+
310
+ 6. **`calculateMetadata` is async** — use it for compositions whose duration depends on fetched data (e.g., audio file length).
311
+
312
+ 7. **All timing is frame-based, not time-based**. Convert with `frameToTime(frame, fps)` when needed.
313
+
314
+
315
+ ---
316
+
317
+ ## LLM & Agent Integration
318
+
319
+ Vibeo's CLI is built with [incur](https://github.com/wevm/incur), making it natively discoverable by AI agents and LLMs.
320
+
321
+ ### Discovering the API
322
+
323
+ ```bash
324
+ # Get a compact summary of all CLI commands (ideal for LLM system prompts)
325
+ bunx @vibeo/cli --llms
326
+
327
+ # Get the full manifest with schemas, examples, and argument details
328
+ bunx @vibeo/cli --llms-full
329
+
330
+ # Get JSON Schema for a specific command (useful for structured tool calls)
331
+ bunx @vibeo/cli render --schema
332
+ bunx @vibeo/cli create --schema
333
+ ```
334
+
335
+ ### Using as an MCP Server
336
+
337
+ ```bash
338
+ # Start Vibeo as an MCP (Model Context Protocol) server
339
+ bunx @vibeo/cli --mcp
340
+
341
+ # Register as a persistent MCP server for your agent
342
+ bunx @vibeo/cli mcp add
343
+ ```
344
+
345
+ This lets LLMs call `create`, `render`, `preview`, and `list` as structured tool calls through the MCP protocol.
346
+
347
+ ### Generating Skill Files
348
+
349
+ ```bash
350
+ # Sync skill files to your agent's skill directory
351
+ bunx @vibeo/cli skills add
352
+ ```
353
+
354
+ This generates markdown skill files that agents like Claude Code can discover and use to write Vibeo code without reading source.
355
+
356
+ ### Agent-Friendly Output
357
+
358
+ ```bash
359
+ # Output as JSON for programmatic consumption
360
+ bunx @vibeo/cli list --entry src/index.tsx --format json
361
+
362
+ # Output as YAML
363
+ bunx @vibeo/cli list --entry src/index.tsx --format yaml
364
+
365
+ # Filter output to specific keys
366
+ bunx @vibeo/cli list --entry src/index.tsx --filter-output compositions[0].id
367
+
368
+ # Count tokens in output (useful for context window planning)
369
+ bunx @vibeo/cli render --schema --token-count
370
+ ```
371
+
372
+ ### How LLMs Should Use Vibeo
373
+
374
+ 1. **Discover commands**: Run `bunx @vibeo/cli --llms` to get the command manifest
375
+ 2. **Create a project**: `bunx @vibeo/cli create my-video --template basic`
376
+ 3. **Edit `src/index.tsx`**: Write React components using `@vibeo/core` hooks and components
377
+ 4. **Preview**: `bunx @vibeo/cli preview --entry src/index.tsx`
378
+ 5. **Render**: `bunx @vibeo/cli render --entry src/index.tsx --composition MyComp`
379
+
380
+ All commands accept `--format json` for structured output that LLMs can parse reliably.