@vibeo/cli 0.3.3 → 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.
- package/dist/commands/install-skills.d.ts.map +1 -1
- package/dist/commands/install-skills.js +22 -83
- package/dist/commands/install-skills.js.map +1 -1
- package/package.json +2 -1
- package/skills/vibeo-audio/SKILL.md +283 -0
- package/skills/vibeo-core/SKILL.md +380 -0
- package/skills/vibeo-effects/SKILL.md +432 -0
- package/skills/vibeo-extras/SKILL.md +457 -0
- package/skills/vibeo-rendering/SKILL.md +364 -0
- package/src/commands/install-skills.ts +25 -82
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# Vibeo Effects (`@vibeo/effects`)
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`@vibeo/effects` provides advanced animation primitives that go beyond basic interpolation: declarative keyframes, spring physics, scene transitions, and audio-reactive animation hooks.
|
|
6
|
+
|
|
7
|
+
**When to use**: When you need keyframe animation, physics-based springs, transitions between scenes, or audio-driven visuals.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## API Reference
|
|
12
|
+
|
|
13
|
+
### `useKeyframes(frame, keyframes, options?): number`
|
|
14
|
+
|
|
15
|
+
Declarative keyframe animation. Automatically interpolates between keyframe stops.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { useKeyframes } from "@vibeo/effects";
|
|
19
|
+
import { useCurrentFrame, easeInOut } from "@vibeo/core";
|
|
20
|
+
|
|
21
|
+
const frame = useCurrentFrame();
|
|
22
|
+
const y = useKeyframes(frame, { 0: 0, 30: 100, 60: 0 }, { easing: easeInOut });
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Parameters**:
|
|
26
|
+
| Param | Type | Description |
|
|
27
|
+
|-------|------|-------------|
|
|
28
|
+
| `frame` | `number` | Current frame (pass `useCurrentFrame()`) |
|
|
29
|
+
| `keyframes` | `KeyframeMap` | `Record<number, KeyframeStop>` — frame-to-value mapping |
|
|
30
|
+
| `options?` | `KeyframeOptions` | Default easing function |
|
|
31
|
+
|
|
32
|
+
**`KeyframeStop`**: `number | { value: number; easing?: (t: number) => number }`
|
|
33
|
+
|
|
34
|
+
Per-segment easing uses the **starting** stop's easing function for that segment.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const scale = useKeyframes(frame, {
|
|
38
|
+
0: { value: 0, easing: easeIn },
|
|
39
|
+
20: { value: 1.2, easing: easeOut },
|
|
40
|
+
30: 1,
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `useSpring(options): number`
|
|
45
|
+
|
|
46
|
+
Physics-based spring animation. Simulates mass-spring-damper system per frame.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { useSpring } from "@vibeo/effects";
|
|
50
|
+
|
|
51
|
+
const x = useSpring({ from: 0, to: 100 });
|
|
52
|
+
const bouncy = useSpring({ from: 0, to: 1, config: { mass: 1, stiffness: 300, damping: 10 } });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**`SpringOptions`**:
|
|
56
|
+
| Field | Type | Default | Description |
|
|
57
|
+
|-------|------|---------|-------------|
|
|
58
|
+
| `from` | `number` | — | Start value |
|
|
59
|
+
| `to` | `number` | — | Target value |
|
|
60
|
+
| `frame?` | `number` | `useCurrentFrame()` | Frame override |
|
|
61
|
+
| `fps?` | `number` | `useVideoConfig().fps` | FPS override |
|
|
62
|
+
| `config?` | `SpringConfig` | see below | Physics parameters |
|
|
63
|
+
|
|
64
|
+
**`SpringConfig`** (defaults):
|
|
65
|
+
| Field | Default | Description |
|
|
66
|
+
|-------|---------|-------------|
|
|
67
|
+
| `mass` | `1` | Mass of the object |
|
|
68
|
+
| `stiffness` | `170` | Spring stiffness |
|
|
69
|
+
| `damping` | `26` | Damping coefficient |
|
|
70
|
+
|
|
71
|
+
### `springDuration(options): number`
|
|
72
|
+
|
|
73
|
+
Compute how many frames a spring takes to settle.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const dur = springDuration({ fps: 30, config: { stiffness: 300, damping: 10 } });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `Transition`
|
|
80
|
+
|
|
81
|
+
Component for transitioning between exactly two children.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { Transition } from "@vibeo/effects";
|
|
85
|
+
import { Sequence } from "@vibeo/core";
|
|
86
|
+
|
|
87
|
+
<Sequence from={55} durationInFrames={20}>
|
|
88
|
+
<Transition type="fade" durationInFrames={20}>
|
|
89
|
+
<SceneA />
|
|
90
|
+
<SceneB />
|
|
91
|
+
</Transition>
|
|
92
|
+
</Sequence>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**`TransitionProps`**:
|
|
96
|
+
| Prop | Type | Default | Description |
|
|
97
|
+
|------|------|---------|-------------|
|
|
98
|
+
| `type` | `TransitionType` | — | `"fade" \| "wipe" \| "slide" \| "dissolve"` |
|
|
99
|
+
| `durationInFrames` | `number` | — | Frames the transition lasts |
|
|
100
|
+
| `timing?` | `TransitionTiming` | `"in-and-out"` | `"in-and-out" \| "in" \| "out"` |
|
|
101
|
+
| `direction?` | `TransitionDirection` | `"left"` | `"left" \| "right" \| "up" \| "down"` (for wipe/slide) |
|
|
102
|
+
| `children` | `[ReactNode, ReactNode]` | — | Exactly 2 children |
|
|
103
|
+
|
|
104
|
+
### Transition Strategies
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { fade, wipe, slide, dissolve } from "@vibeo/effects";
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Each returns `{ childA: CSSProperties; childB: CSSProperties }` for a given `TransitionState`.
|
|
111
|
+
|
|
112
|
+
| Strategy | Effect |
|
|
113
|
+
|----------|--------|
|
|
114
|
+
| `fade` | Opacity crossfade |
|
|
115
|
+
| `wipe` | Clip-path reveal |
|
|
116
|
+
| `slide` | Transform slide in/out |
|
|
117
|
+
| `dissolve` | Mix-blend + opacity |
|
|
118
|
+
|
|
119
|
+
### `useAudioData(audioSrc, options?): AudioAnalysis | null`
|
|
120
|
+
|
|
121
|
+
Pre-analyzes an audio file and returns per-frame frequency/amplitude data. Returns `null` while loading.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { useAudioData } from "@vibeo/effects";
|
|
125
|
+
|
|
126
|
+
const data = useAudioData("/music.mp3", { fftSize: 2048 });
|
|
127
|
+
if (data) {
|
|
128
|
+
// data.amplitude — overall RMS amplitude (0-1)
|
|
129
|
+
// data.bass — average energy in 20-250 Hz
|
|
130
|
+
// data.mid — average energy in 250-4000 Hz
|
|
131
|
+
// data.treble — average energy in 4000-20000 Hz
|
|
132
|
+
// data.frequencies — Float32Array of FFT magnitude data (dB)
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**`AudioDataOptions`**:
|
|
137
|
+
| Field | Type | Default | Description |
|
|
138
|
+
|-------|------|---------|-------------|
|
|
139
|
+
| `fftSize?` | `number` | `2048` | FFT window size (power of 2) |
|
|
140
|
+
|
|
141
|
+
**`AudioAnalysis`**:
|
|
142
|
+
| Field | Type | Description |
|
|
143
|
+
|-------|------|-------------|
|
|
144
|
+
| `amplitude` | `number` | Overall RMS amplitude (0-1 range) |
|
|
145
|
+
| `frequencies` | `Float32Array` | Full FFT magnitude data in dB |
|
|
146
|
+
| `bass` | `number` | Average energy 20-250 Hz |
|
|
147
|
+
| `mid` | `number` | Average energy 250-4000 Hz |
|
|
148
|
+
| `treble` | `number` | Average energy 4000-20000 Hz |
|
|
149
|
+
|
|
150
|
+
### `useTransitionProgress(durationInFrames): number | null`
|
|
151
|
+
|
|
152
|
+
Returns the transition progress (0 to 1) within a transition window, or `null` outside.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
const progress = useTransitionProgress(20);
|
|
156
|
+
if (progress !== null) {
|
|
157
|
+
// 0 at start of transition, 1 at end
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Types
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import type {
|
|
165
|
+
KeyframeStop,
|
|
166
|
+
KeyframeMap,
|
|
167
|
+
KeyframeOptions,
|
|
168
|
+
SpringConfig,
|
|
169
|
+
SpringOptions,
|
|
170
|
+
TransitionTiming,
|
|
171
|
+
TransitionDirection,
|
|
172
|
+
TransitionType,
|
|
173
|
+
TransitionProps,
|
|
174
|
+
TransitionState,
|
|
175
|
+
AudioAnalysis,
|
|
176
|
+
AudioDataOptions,
|
|
177
|
+
} from "@vibeo/effects";
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Keyframe Animation Recipes
|
|
183
|
+
|
|
184
|
+
### Fade in
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const opacity = useKeyframes(frame, { 0: 0, 30: 1 });
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Bounce
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
const y = useKeyframes(frame, {
|
|
194
|
+
0: 0,
|
|
195
|
+
15: { value: -100, easing: easeOut },
|
|
196
|
+
30: { value: 0, easing: easeIn },
|
|
197
|
+
45: { value: -40, easing: easeOut },
|
|
198
|
+
60: 0,
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Slide in from left
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
const x = useKeyframes(frame, {
|
|
206
|
+
0: -1920,
|
|
207
|
+
30: { value: 0, easing: easeOut },
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Scale pulse
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const scale = useKeyframes(frame, { 0: 1, 15: 1.2, 30: 1 }, { easing: easeInOut });
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Spring Animation Guide
|
|
220
|
+
|
|
221
|
+
### Snappy UI motion
|
|
222
|
+
```ts
|
|
223
|
+
useSpring({ from: 0, to: 1, config: { stiffness: 300, damping: 30 } })
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Bouncy entrance
|
|
227
|
+
```ts
|
|
228
|
+
useSpring({ from: 0, to: 1, config: { stiffness: 400, damping: 10, mass: 1 } })
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Slow, heavy drag
|
|
232
|
+
```ts
|
|
233
|
+
useSpring({ from: 0, to: 1, config: { stiffness: 50, damping: 20, mass: 3 } })
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Gentle ease
|
|
237
|
+
```ts
|
|
238
|
+
useSpring({ from: 0, to: 1, config: { stiffness: 100, damping: 26 } })
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Tuning guide**:
|
|
242
|
+
- Higher `stiffness` = faster motion, more overshoot
|
|
243
|
+
- Higher `damping` = less bounce, settles faster
|
|
244
|
+
- Higher `mass` = slower, more momentum
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Transition Usage Between Scenes
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
function MyVideo() {
|
|
252
|
+
return (
|
|
253
|
+
<>
|
|
254
|
+
{/* Scene A plays frames 0-59 */}
|
|
255
|
+
<Sequence from={0} durationInFrames={75}>
|
|
256
|
+
<SceneA />
|
|
257
|
+
</Sequence>
|
|
258
|
+
|
|
259
|
+
{/* 15-frame fade transition overlapping scenes */}
|
|
260
|
+
<Sequence from={55} durationInFrames={20}>
|
|
261
|
+
<Transition type="fade" durationInFrames={20}>
|
|
262
|
+
<SceneA />
|
|
263
|
+
<SceneB />
|
|
264
|
+
</Transition>
|
|
265
|
+
</Sequence>
|
|
266
|
+
|
|
267
|
+
{/* Scene B continues */}
|
|
268
|
+
<Sequence from={75} durationInFrames={90}>
|
|
269
|
+
<SceneB />
|
|
270
|
+
</Sequence>
|
|
271
|
+
</>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
For directional transitions:
|
|
277
|
+
```tsx
|
|
278
|
+
<Transition type="slide" durationInFrames={20} direction="right">
|
|
279
|
+
<OldScene />
|
|
280
|
+
<NewScene />
|
|
281
|
+
</Transition>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Audio-Reactive Animation Patterns
|
|
287
|
+
|
|
288
|
+
### Beat-reactive scale
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
function BeatCircle() {
|
|
292
|
+
const frame = useCurrentFrame();
|
|
293
|
+
const audio = useAudioData("/track.mp3");
|
|
294
|
+
const baseScale = audio ? 1 + audio.bass * 2 : 1;
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<div style={{
|
|
298
|
+
width: 100,
|
|
299
|
+
height: 100,
|
|
300
|
+
borderRadius: "50%",
|
|
301
|
+
background: "white",
|
|
302
|
+
transform: `scale(${baseScale})`,
|
|
303
|
+
}} />
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Audio visualizer bars
|
|
309
|
+
|
|
310
|
+
```tsx
|
|
311
|
+
function Visualizer() {
|
|
312
|
+
const audio = useAudioData("/music.mp3", { fftSize: 512 });
|
|
313
|
+
if (!audio) return null;
|
|
314
|
+
|
|
315
|
+
const barCount = 32;
|
|
316
|
+
const step = Math.floor(audio.frequencies.length / barCount);
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<div style={{ display: "flex", alignItems: "flex-end", height: 200 }}>
|
|
320
|
+
{Array.from({ length: barCount }, (_, i) => {
|
|
321
|
+
const db = audio.frequencies[i * step];
|
|
322
|
+
const height = Math.max(2, ((db + 100) / 100) * 200);
|
|
323
|
+
return <div key={i} style={{ width: 8, height, margin: 1, background: "cyan" }} />;
|
|
324
|
+
})}
|
|
325
|
+
</div>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Background color shift with amplitude
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
function ReactiveBackground({ children }: { children: React.ReactNode }) {
|
|
334
|
+
const audio = useAudioData("/music.mp3");
|
|
335
|
+
const hue = audio ? Math.round(audio.amplitude * 360) : 200;
|
|
336
|
+
const lightness = audio ? 20 + audio.amplitude * 30 : 20;
|
|
337
|
+
|
|
338
|
+
return (
|
|
339
|
+
<div style={{
|
|
340
|
+
width: "100%",
|
|
341
|
+
height: "100%",
|
|
342
|
+
background: `hsl(${hue}, 70%, ${lightness}%)`,
|
|
343
|
+
}}>
|
|
344
|
+
{children}
|
|
345
|
+
</div>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Gotchas and Tips
|
|
353
|
+
|
|
354
|
+
1. **`useAudioData` returns `null` initially** — always handle the loading state.
|
|
355
|
+
|
|
356
|
+
2. **`useKeyframes` clamps at boundaries** — it won't extrapolate beyond the first/last keyframe.
|
|
357
|
+
|
|
358
|
+
3. **`<Transition>` must have exactly 2 children** — it throws if you pass more or fewer.
|
|
359
|
+
|
|
360
|
+
4. **Spring simulations are cached** at the module level for identical parameters, so repeated renders are cheap.
|
|
361
|
+
|
|
362
|
+
5. **`useKeyframes` requires you to pass `frame` explicitly** — it doesn't call `useCurrentFrame()` internally. This gives you flexibility to use any frame value.
|
|
363
|
+
|
|
364
|
+
6. **`springDuration()` is useful for sizing `<Sequence>` wrappers** to match how long a spring animation takes to settle.
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## LLM & Agent Integration
|
|
370
|
+
|
|
371
|
+
Vibeo's CLI is built with [incur](https://github.com/wevm/incur), making it natively discoverable by AI agents and LLMs.
|
|
372
|
+
|
|
373
|
+
### Discovering the API
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
# Get a compact summary of all CLI commands (ideal for LLM system prompts)
|
|
377
|
+
bunx @vibeo/cli --llms
|
|
378
|
+
|
|
379
|
+
# Get the full manifest with schemas, examples, and argument details
|
|
380
|
+
bunx @vibeo/cli --llms-full
|
|
381
|
+
|
|
382
|
+
# Get JSON Schema for a specific command (useful for structured tool calls)
|
|
383
|
+
bunx @vibeo/cli render --schema
|
|
384
|
+
bunx @vibeo/cli create --schema
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Using as an MCP Server
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
# Start Vibeo as an MCP (Model Context Protocol) server
|
|
391
|
+
bunx @vibeo/cli --mcp
|
|
392
|
+
|
|
393
|
+
# Register as a persistent MCP server for your agent
|
|
394
|
+
bunx @vibeo/cli mcp add
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
This lets LLMs call `create`, `render`, `preview`, and `list` as structured tool calls through the MCP protocol.
|
|
398
|
+
|
|
399
|
+
### Generating Skill Files
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
# Sync skill files to your agent's skill directory
|
|
403
|
+
bunx @vibeo/cli skills add
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
This generates markdown skill files that agents like Claude Code can discover and use to write Vibeo code without reading source.
|
|
407
|
+
|
|
408
|
+
### Agent-Friendly Output
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
# Output as JSON for programmatic consumption
|
|
412
|
+
bunx @vibeo/cli list --entry src/index.tsx --format json
|
|
413
|
+
|
|
414
|
+
# Output as YAML
|
|
415
|
+
bunx @vibeo/cli list --entry src/index.tsx --format yaml
|
|
416
|
+
|
|
417
|
+
# Filter output to specific keys
|
|
418
|
+
bunx @vibeo/cli list --entry src/index.tsx --filter-output compositions[0].id
|
|
419
|
+
|
|
420
|
+
# Count tokens in output (useful for context window planning)
|
|
421
|
+
bunx @vibeo/cli render --schema --token-count
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### How LLMs Should Use Vibeo
|
|
425
|
+
|
|
426
|
+
1. **Discover commands**: Run `bunx @vibeo/cli --llms` to get the command manifest
|
|
427
|
+
2. **Create a project**: `bunx @vibeo/cli create my-video --template basic`
|
|
428
|
+
3. **Edit `src/index.tsx`**: Write React components using `@vibeo/core` hooks and components
|
|
429
|
+
4. **Preview**: `bunx @vibeo/cli preview --entry src/index.tsx`
|
|
430
|
+
5. **Render**: `bunx @vibeo/cli render --entry src/index.tsx --composition MyComp`
|
|
431
|
+
|
|
432
|
+
All commands accept `--format json` for structured output that LLMs can parse reliably.
|