@vibeo/cli 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +1 -0
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/editor.d.ts +2 -0
- package/dist/commands/editor.d.ts.map +1 -0
- package/dist/commands/editor.js +18 -0
- package/dist/commands/editor.js.map +1 -0
- package/dist/commands/install-skills.d.ts.map +1 -1
- package/dist/commands/install-skills.js +13 -76
- package/dist/commands/install-skills.js.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/skills/vibeo-audio/SKILL.md +283 -0
- package/skills/vibeo-core/SKILL.md +442 -0
- package/skills/vibeo-editor/SKILL.md +348 -0
- package/skills/vibeo-effects/SKILL.md +580 -0
- package/skills/vibeo-extras/SKILL.md +457 -0
- package/skills/vibeo-rendering/SKILL.md +367 -0
- package/skills/vibeo-tiktok/SKILL.md +319 -0
- package/src/commands/create.ts +1 -0
- package/src/commands/editor.ts +23 -0
- package/src/commands/install-skills.ts +16 -75
- package/src/index.ts +15 -0
|
@@ -0,0 +1,442 @@
|
|
|
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
|
+
## Platform Format Presets
|
|
234
|
+
|
|
235
|
+
Use these when the user mentions a platform. **Any time the user says "Short", "Reel", "TikTok", or "vertical" — use 1080x1920 (9:16), not landscape.**
|
|
236
|
+
|
|
237
|
+
| Format | Width | Height | FPS | Max Duration | Aliases |
|
|
238
|
+
|--------|-------|--------|-----|-------------|---------|
|
|
239
|
+
| **YouTube** | 1920 | 1080 | 30 | — | landscape, standard |
|
|
240
|
+
| **YouTube 4K** | 3840 | 2160 | 30-60 | — | 4K |
|
|
241
|
+
| **YouTube Short** | 1080 | 1920 | 30-60 | 3 min | vertical |
|
|
242
|
+
| **TikTok** | 1080 | 1920 | 30 | 10 min | |
|
|
243
|
+
| **Instagram Reel** | 1080 | 1920 | 30 | 20 min | |
|
|
244
|
+
| **Instagram Post** | 1080 | 1080 | 30 | 60s | square |
|
|
245
|
+
| **Twitter/X** | 1920 | 1080 | 30 | 2m 20s | 512MB max |
|
|
246
|
+
| **Twitter/X Short** | 1080 | 1920 | 30 | 2m 20s | vertical tweet |
|
|
247
|
+
|
|
248
|
+
### Vertical video (9:16) layout tips
|
|
249
|
+
|
|
250
|
+
- **Code blocks**: max ~900px wide, font size 24-28 (larger than landscape)
|
|
251
|
+
- **No side-by-side**: use top/bottom stacks, not left/right splits
|
|
252
|
+
- **Text**: minimum 36px body, 64px+ titles
|
|
253
|
+
- **Safe zones**: avoid top 100px (status bar) and bottom 150px (nav gestures)
|
|
254
|
+
- **Single focus**: one idea per screen, no multi-column layouts
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Common Patterns
|
|
259
|
+
|
|
260
|
+
### Multi-file project structure (use for 3+ scenes)
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
src/
|
|
264
|
+
├── index.tsx # Root + Composition registration
|
|
265
|
+
├── Video.tsx # Scene orchestrator (Sequences)
|
|
266
|
+
├── scenes/
|
|
267
|
+
│ ├── Intro.tsx
|
|
268
|
+
│ ├── Problem.tsx
|
|
269
|
+
│ ├── Solution.tsx
|
|
270
|
+
│ └── Outro.tsx
|
|
271
|
+
└── components/
|
|
272
|
+
├── CodeBlock.tsx
|
|
273
|
+
└── AnimatedCard.tsx
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Centralized scene timing (best practice)
|
|
277
|
+
|
|
278
|
+
Define all timing in one place — never hardcode frame numbers in `<Sequence>`:
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
const SCENES = {
|
|
282
|
+
intro: { from: 0, duration: 120 },
|
|
283
|
+
problem: { from: 120, duration: 300 },
|
|
284
|
+
solution: { from: 420, duration: 450 },
|
|
285
|
+
outro: { from: 870, duration: 90 },
|
|
286
|
+
} as const;
|
|
287
|
+
|
|
288
|
+
const TOTAL = SCENES.outro.from + SCENES.outro.duration;
|
|
289
|
+
|
|
290
|
+
function MyVideo() {
|
|
291
|
+
return (
|
|
292
|
+
<>
|
|
293
|
+
<Sequence from={SCENES.intro.from} durationInFrames={SCENES.intro.duration}>
|
|
294
|
+
<IntroScene />
|
|
295
|
+
</Sequence>
|
|
296
|
+
<Sequence from={SCENES.problem.from} durationInFrames={SCENES.problem.duration}>
|
|
297
|
+
<ProblemScene />
|
|
298
|
+
</Sequence>
|
|
299
|
+
{/* ... */}
|
|
300
|
+
</>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Looping/pulsing animation
|
|
306
|
+
|
|
307
|
+
Use `frame % N` for repeating effects (pulse, glow, rotate):
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
const frame = useCurrentFrame();
|
|
311
|
+
const pulse = interpolate(frame % 60, [0, 30, 60], [0.3, 0.6, 0.3]);
|
|
312
|
+
const rotation = (frame % 90) * 4; // 360° every 3 seconds
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Staggered list/card animation
|
|
316
|
+
|
|
317
|
+
Animate N items with increasing delay:
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
const frame = useCurrentFrame();
|
|
321
|
+
const items = ["Feature 1", "Feature 2", "Feature 3"];
|
|
322
|
+
|
|
323
|
+
{items.map((item, i) => {
|
|
324
|
+
const delay = 10 + i * 8;
|
|
325
|
+
const opacity = interpolate(frame, [delay, delay + 20], [0, 1], {
|
|
326
|
+
extrapolateLeft: "clamp", extrapolateRight: "clamp",
|
|
327
|
+
});
|
|
328
|
+
const y = interpolate(frame, [delay, delay + 20], [30, 0], {
|
|
329
|
+
easing: easeOut, extrapolateLeft: "clamp", extrapolateRight: "clamp",
|
|
330
|
+
});
|
|
331
|
+
return <div key={i} style={{ opacity, transform: `translateY(${y}px)` }}>{item}</div>;
|
|
332
|
+
})}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Overlapping Sequences for manual transitions
|
|
336
|
+
|
|
337
|
+
Alternative to `<Transition>` when you need custom per-scene blend control:
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
<Sequence from={0} durationInFrames={90}>
|
|
341
|
+
<SceneA /> {/* fade out in last 15 frames */}
|
|
342
|
+
</Sequence>
|
|
343
|
+
<Sequence from={75} durationInFrames={90}>
|
|
344
|
+
<SceneB /> {/* fade in during first 15 frames */}
|
|
345
|
+
</Sequence>
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Multi-segment interpolation
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
const x = interpolate(frame, [0, 30, 60, 90], [0, 200, 0, 0]);
|
|
352
|
+
const y = interpolate(frame, [0, 30, 60, 90], [0, 0, 0, 200]);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Gotchas and Tips
|
|
358
|
+
|
|
359
|
+
1. **`useCurrentFrame()` is always relative to the nearest `<Sequence>`**. If you need the absolute frame, use `useTimelinePosition()`.
|
|
360
|
+
|
|
361
|
+
2. **`interpolate()` requires at least 2 input range values** and input/output ranges must have the same length.
|
|
362
|
+
|
|
363
|
+
3. **Extrapolation defaults to `"extend"`** (continues the slope beyond the range). Use `{ extrapolateLeft: "clamp", extrapolateRight: "clamp" }` to clamp values at range boundaries.
|
|
364
|
+
|
|
365
|
+
4. **`Sequence` children are only rendered when the absolute frame is within `[from, from + durationInFrames)`**. Outside that range, `Sequence` returns `null`.
|
|
366
|
+
|
|
367
|
+
5. **Loop iterations**: Inside a `<Loop>`, `useCurrentFrame()` resets to 0 each iteration. Use `useLoopContext()` to get the current `iteration` number.
|
|
368
|
+
|
|
369
|
+
6. **`calculateMetadata` is async** — use it for compositions whose duration depends on fetched data (e.g., audio file length).
|
|
370
|
+
|
|
371
|
+
7. **All timing is frame-based, not time-based**. Convert with `frameToTime(frame, fps)` when needed.
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## LLM & Agent Integration
|
|
377
|
+
|
|
378
|
+
Vibeo's CLI is built with [incur](https://github.com/wevm/incur), making it natively discoverable by AI agents and LLMs.
|
|
379
|
+
|
|
380
|
+
### Discovering the API
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
# Get a compact summary of all CLI commands (ideal for LLM system prompts)
|
|
384
|
+
bunx @vibeo/cli --llms
|
|
385
|
+
|
|
386
|
+
# Get the full manifest with schemas, examples, and argument details
|
|
387
|
+
bunx @vibeo/cli --llms-full
|
|
388
|
+
|
|
389
|
+
# Get JSON Schema for a specific command (useful for structured tool calls)
|
|
390
|
+
bunx @vibeo/cli render --schema
|
|
391
|
+
bunx @vibeo/cli create --schema
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Using as an MCP Server
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
# Start Vibeo as an MCP (Model Context Protocol) server
|
|
398
|
+
bunx @vibeo/cli --mcp
|
|
399
|
+
|
|
400
|
+
# Register as a persistent MCP server for your agent
|
|
401
|
+
bunx @vibeo/cli mcp add
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
This lets LLMs call `create`, `render`, `preview`, and `list` as structured tool calls through the MCP protocol.
|
|
405
|
+
|
|
406
|
+
### Generating Skill Files
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
# Sync skill files to your agent's skill directory
|
|
410
|
+
bunx @vibeo/cli skills add
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
This generates markdown skill files that agents like Claude Code can discover and use to write Vibeo code without reading source.
|
|
414
|
+
|
|
415
|
+
### Agent-Friendly Output
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
# Output as JSON for programmatic consumption
|
|
419
|
+
bunx @vibeo/cli list --entry src/index.tsx --format json
|
|
420
|
+
|
|
421
|
+
# Output as YAML
|
|
422
|
+
bunx @vibeo/cli list --entry src/index.tsx --format yaml
|
|
423
|
+
|
|
424
|
+
# Filter output to specific keys
|
|
425
|
+
bunx @vibeo/cli list --entry src/index.tsx --filter-output compositions[0].id
|
|
426
|
+
|
|
427
|
+
# Count tokens in output (useful for context window planning)
|
|
428
|
+
bunx @vibeo/cli render --schema --token-count
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### How LLMs Should Use Vibeo
|
|
432
|
+
|
|
433
|
+
1. **Create a project**: `bunx @vibeo/cli create my-video --template basic`
|
|
434
|
+
2. **Install deps**: `cd my-video && bun install`
|
|
435
|
+
3. **Install Playwright** (required for render/list): `bunx playwright install chromium`
|
|
436
|
+
4. **Edit `src/index.tsx`**: Write React components using `@vibeo/core` hooks and components
|
|
437
|
+
5. **Preview**: `bunx @vibeo/cli preview --entry src/index.tsx`
|
|
438
|
+
6. **Render**: `bunx @vibeo/cli render --entry src/index.tsx --composition MyComp`
|
|
439
|
+
|
|
440
|
+
Step 3 is mandatory — `vibeo render` and `vibeo list` will fail without Playwright browsers installed.
|
|
441
|
+
|
|
442
|
+
All commands accept `--format json` for structured output that LLMs can parse reliably.
|