vargai 0.4.0-alpha2 → 0.4.0-alpha21

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 (69) hide show
  1. package/README.md +483 -61
  2. package/launch-videos/06-kawaii-fruits.tsx +93 -0
  3. package/launch-videos/07-ugc-weight-loss.tsx +132 -0
  4. package/launch-videos/08-talking-head-varg.tsx +107 -0
  5. package/launch-videos/09-girl.tsx +160 -0
  6. package/launch-videos/README.md +42 -0
  7. package/package.json +8 -4
  8. package/skills/varg-video-generation/SKILL.md +213 -0
  9. package/skills/varg-video-generation/references/templates.md +380 -0
  10. package/skills/varg-video-generation/scripts/setup.ts +265 -0
  11. package/src/ai-sdk/cache.ts +1 -1
  12. package/src/ai-sdk/middleware/wrap-image-model.ts +4 -21
  13. package/src/ai-sdk/middleware/wrap-music-model.ts +4 -16
  14. package/src/ai-sdk/middleware/wrap-video-model.ts +5 -17
  15. package/src/ai-sdk/providers/editly/index.ts +110 -53
  16. package/src/ai-sdk/providers/editly/types.ts +2 -0
  17. package/src/ai-sdk/providers/elevenlabs.ts +10 -2
  18. package/src/ai-sdk/providers/fal.ts +6 -1
  19. package/src/cli/commands/find.tsx +1 -0
  20. package/src/cli/commands/hello.ts +85 -0
  21. package/src/cli/commands/help.tsx +18 -30
  22. package/src/cli/commands/index.ts +9 -1
  23. package/src/cli/commands/init.tsx +412 -0
  24. package/src/cli/commands/list.tsx +1 -0
  25. package/src/cli/commands/render.tsx +292 -80
  26. package/src/cli/commands/run.tsx +1 -0
  27. package/src/cli/commands/studio.ts +47 -0
  28. package/src/cli/commands/which.tsx +1 -0
  29. package/src/cli/index.ts +20 -5
  30. package/src/cli/ui/components/Badge.tsx +1 -0
  31. package/src/cli/ui/components/DataTable.tsx +1 -0
  32. package/src/cli/ui/components/Header.tsx +1 -0
  33. package/src/cli/ui/components/HelpBlock.tsx +1 -0
  34. package/src/cli/ui/components/KeyValue.tsx +1 -0
  35. package/src/cli/ui/components/OptionRow.tsx +1 -0
  36. package/src/cli/ui/components/Separator.tsx +1 -0
  37. package/src/cli/ui/components/StatusBox.tsx +1 -0
  38. package/src/cli/ui/components/VargBox.tsx +1 -0
  39. package/src/cli/ui/components/VargProgress.tsx +1 -0
  40. package/src/cli/ui/components/VargSpinner.tsx +1 -0
  41. package/src/cli/ui/components/VargText.tsx +1 -0
  42. package/src/react/assets.ts +9 -0
  43. package/src/react/elements.ts +0 -5
  44. package/src/react/examples/branching.tsx +6 -4
  45. package/src/react/examples/character-video.tsx +13 -10
  46. package/src/react/examples/madi.tsx +13 -10
  47. package/src/react/examples/mcmeows.tsx +40 -0
  48. package/src/react/examples/music-defaults.tsx +24 -0
  49. package/src/react/examples/quickstart-test.tsx +97 -0
  50. package/src/react/index.ts +1 -2
  51. package/src/react/react.test.ts +10 -10
  52. package/src/react/renderers/clip.ts +13 -24
  53. package/src/react/renderers/context.ts +3 -0
  54. package/src/react/renderers/image.ts +4 -2
  55. package/src/react/renderers/index.ts +0 -1
  56. package/src/react/renderers/music.ts +3 -3
  57. package/src/react/renderers/progress.ts +1 -3
  58. package/src/react/renderers/render.ts +49 -63
  59. package/src/react/renderers/speech.ts +2 -2
  60. package/src/react/renderers/video.ts +46 -9
  61. package/src/react/types.ts +18 -14
  62. package/src/studio/stages.ts +4 -24
  63. package/src/studio/step-renderer.ts +0 -15
  64. package/test-sync-v2.ts +30 -0
  65. package/test-sync-v2.tsx +29 -0
  66. package/tsconfig.json +5 -3
  67. package/video.tsx +7 -0
  68. package/src/react/cli.ts +0 -52
  69. package/src/react/renderers/animate.ts +0 -59
@@ -0,0 +1,380 @@
1
+ # Video Generation Templates
2
+
3
+ Complete code templates for common video generation patterns.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Simple Slideshow](#simple-slideshow)
8
+ 2. [Animated Video with Music](#animated-video-with-music)
9
+ 3. [Talking Character](#talking-character)
10
+ 4. [TikTok Multi-Clip](#tiktok-multi-clip)
11
+ 5. [Character with Consistent Style](#character-with-consistent-style)
12
+ 6. [Split Screen Comparison](#split-screen-comparison)
13
+
14
+ ---
15
+
16
+ ## Simple Slideshow
17
+
18
+ **Requirements:** FAL_API_KEY only
19
+
20
+ Basic image slideshow with transitions and zoom effects.
21
+
22
+ ```tsx
23
+ // slideshow.tsx
24
+ import { render, Render, Clip, Image } from "vargai/react";
25
+
26
+ const SCENES = [
27
+ "sunset over ocean, cinematic golden hour",
28
+ "mountain peaks at dawn, misty atmosphere",
29
+ "city skyline at night, neon lights",
30
+ "forest path in autumn, fallen leaves",
31
+ ];
32
+
33
+ async function main() {
34
+ await render(
35
+ <Render width={1080} height={1920}>
36
+ {SCENES.map((prompt, i) => (
37
+ <Clip
38
+ key={i}
39
+ duration={3}
40
+ transition={{ name: "fade", duration: 0.5 }}
41
+ >
42
+ <Image prompt={prompt} zoom="in" />
43
+ </Clip>
44
+ ))}
45
+ </Render>,
46
+ { output: "output/slideshow.mp4" }
47
+ );
48
+
49
+ console.log("Done! output/slideshow.mp4");
50
+ }
51
+
52
+ main();
53
+ ```
54
+
55
+ Run: `bun run slideshow.tsx`
56
+
57
+ ---
58
+
59
+ ## Animated Video with Music
60
+
61
+ **Requirements:** FAL_API_KEY + ELEVENLABS_API_KEY
62
+
63
+ Video with AI-generated animation and background music.
64
+
65
+ ```tsx
66
+ // animated-video.tsx
67
+ import { render, Render, Clip, Image, Animate, Music } from "vargai/react";
68
+ import { fal, elevenlabs } from "vargai/ai";
69
+
70
+ async function main() {
71
+ await render(
72
+ <Render width={1080} height={1920}>
73
+ <Music
74
+ prompt="upbeat electronic pop, energetic, modern tiktok vibe"
75
+ model={elevenlabs.musicModel()}
76
+ duration={10}
77
+ volume={0.7}
78
+ />
79
+
80
+ <Clip duration={5}>
81
+ <Animate
82
+ image={Image({
83
+ prompt: "cute cat sitting on windowsill, golden hour lighting",
84
+ model: fal.imageModel("flux-schnell")
85
+ })}
86
+ motion="cat slowly turns head, blinks, tail swishes gently"
87
+ model={fal.videoModel("wan-2.5")}
88
+ duration={5}
89
+ />
90
+ </Clip>
91
+
92
+ <Clip duration={5} transition={{ name: "fade", duration: 0.5 }}>
93
+ <Animate
94
+ image={Image({
95
+ prompt: "same cat stretching on windowsill, yawning"
96
+ })}
97
+ motion="cat stretches, yawns widely, settles back down"
98
+ model={fal.videoModel("wan-2.5")}
99
+ duration={5}
100
+ />
101
+ </Clip>
102
+ </Render>,
103
+ { output: "output/cat-video.mp4" }
104
+ );
105
+ }
106
+
107
+ main();
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Talking Character
113
+
114
+ **Requirements:** FAL_API_KEY + ELEVENLABS_API_KEY
115
+
116
+ Character that speaks with animated movement.
117
+
118
+ ```tsx
119
+ // talking-character.tsx
120
+ import { render, Render, Clip, Image, Animate, Speech } from "vargai/react";
121
+ import { fal, elevenlabs } from "vargai/ai";
122
+
123
+ const CHARACTER = "friendly cartoon robot, blue metallic body, expressive LED eyes, studio background";
124
+
125
+ const SCRIPT = `
126
+ Hello! I'm your AI assistant.
127
+ Today I'm going to show you something amazing.
128
+ Let's create some videos together!
129
+ `;
130
+
131
+ async function main() {
132
+ await render(
133
+ <Render width={1080} height={1920}>
134
+ <Clip duration="auto">
135
+ <Animate
136
+ image={Image({
137
+ prompt: CHARACTER,
138
+ aspectRatio: "9:16",
139
+ model: fal.imageModel("flux-schnell")
140
+ })}
141
+ motion="robot talking naturally, subtle head movements, eyes blink occasionally, friendly gestures"
142
+ model={fal.videoModel("wan-2.5")}
143
+ />
144
+ <Speech
145
+ voice="adam"
146
+ model={elevenlabs.speechModel("turbo")}
147
+ >
148
+ {SCRIPT}
149
+ </Speech>
150
+ </Clip>
151
+ </Render>,
152
+ { output: "output/talking-robot.mp4" }
153
+ );
154
+ }
155
+
156
+ main();
157
+ ```
158
+
159
+ **Available voices:** `adam`, `rachel`, `bella`, `sam`, `josh`
160
+
161
+ ---
162
+
163
+ ## TikTok Multi-Clip
164
+
165
+ **Requirements:** FAL_API_KEY + ELEVENLABS_API_KEY
166
+
167
+ Multi-scene TikTok-style video with varied camera angles.
168
+
169
+ ```tsx
170
+ // tiktok-video.tsx
171
+ import { render, Render, Clip, Image, Animate, Music, Title } from "vargai/react";
172
+ import { fal, elevenlabs } from "vargai/ai";
173
+
174
+ const CHARACTER = "confident young woman, casual style, bright smile, modern apartment";
175
+
176
+ const SCENES = [
177
+ {
178
+ prompt: "extreme close-up face, surprised expression, wide eyes",
179
+ motion: "eyes widen in surprise, eyebrows raise, subtle gasp"
180
+ },
181
+ {
182
+ prompt: "medium shot, laughing genuinely, hand on chest",
183
+ motion: "head tilts back slightly, genuine laugh, shoulders shake"
184
+ },
185
+ {
186
+ prompt: "close-up, knowing smirk, raised eyebrow",
187
+ motion: "slow smile forms, eyebrow raises, subtle head nod"
188
+ },
189
+ {
190
+ prompt: "medium shot, waving at camera, bright smile",
191
+ motion: "waves energetically at camera, bright smile, slight bounce"
192
+ },
193
+ ];
194
+
195
+ async function main() {
196
+ await render(
197
+ <Render width={1080} height={1920}>
198
+ <Music
199
+ prompt="trendy tiktok music, upbeat, catchy hook, viral sound"
200
+ model={elevenlabs.musicModel()}
201
+ duration={8}
202
+ volume={0.6}
203
+ />
204
+
205
+ {SCENES.map((scene, i) => (
206
+ <Clip
207
+ key={i}
208
+ duration={2}
209
+ transition={{ name: "fade", duration: 0.3 }}
210
+ >
211
+ <Animate
212
+ image={Image({
213
+ prompt: `${CHARACTER}, ${scene.prompt}`,
214
+ aspectRatio: "9:16",
215
+ model: fal.imageModel("flux-schnell")
216
+ })}
217
+ motion={scene.motion}
218
+ model={fal.videoModel("wan-2.5")}
219
+ duration={5}
220
+ />
221
+ {i === 0 && (
222
+ <Title position="bottom">POV: When it actually works</Title>
223
+ )}
224
+ </Clip>
225
+ ))}
226
+ </Render>,
227
+ { output: "output/tiktok-video.mp4" }
228
+ );
229
+ }
230
+
231
+ main();
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Character with Consistent Style
237
+
238
+ **Requirements:** FAL_API_KEY
239
+
240
+ Reuse the same character reference for visual consistency across clips.
241
+
242
+ ```tsx
243
+ // consistent-character.tsx
244
+ import { render, Render, Clip, Image, Animate } from "vargai/react";
245
+ import { fal } from "vargai/ai";
246
+
247
+ async function main() {
248
+ // Define character once - same reference = same generated image
249
+ const character = Image({
250
+ prompt: "cute cartoon fox, orange fur, big eyes, friendly expression",
251
+ model: fal.imageModel("flux-schnell"),
252
+ aspectRatio: "9:16"
253
+ });
254
+
255
+ await render(
256
+ <Render width={1080} height={1920}>
257
+ <Clip duration={3}>
258
+ <Animate
259
+ image={character}
260
+ motion="fox waves hello, tail wagging"
261
+ model={fal.videoModel("wan-2.5")}
262
+ duration={3}
263
+ />
264
+ </Clip>
265
+
266
+ <Clip duration={3} transition={{ name: "fade", duration: 0.5 }}>
267
+ <Animate
268
+ image={character}
269
+ motion="fox dances happily, spinning around"
270
+ model={fal.videoModel("wan-2.5")}
271
+ duration={3}
272
+ />
273
+ </Clip>
274
+
275
+ <Clip duration={3} transition={{ name: "fade", duration: 0.5 }}>
276
+ <Animate
277
+ image={character}
278
+ motion="fox blows a kiss at camera, winks"
279
+ model={fal.videoModel("wan-2.5")}
280
+ duration={3}
281
+ />
282
+ </Clip>
283
+ </Render>,
284
+ { output: "output/fox-video.mp4" }
285
+ );
286
+ }
287
+
288
+ main();
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Split Screen Comparison
294
+
295
+ **Requirements:** FAL_API_KEY
296
+
297
+ Side-by-side comparison layout.
298
+
299
+ ```tsx
300
+ // split-screen.tsx
301
+ import { render, Render, Clip, Image, Animate, Split, Title } from "vargai/react";
302
+ import { fal } from "vargai/ai";
303
+
304
+ async function main() {
305
+ const before = Image({
306
+ prompt: "messy room, clothes everywhere, unmade bed",
307
+ aspectRatio: "3:4"
308
+ });
309
+
310
+ const after = Image({
311
+ prompt: "clean organized room, made bed, tidy shelves",
312
+ aspectRatio: "3:4"
313
+ });
314
+
315
+ await render(
316
+ <Render width={1080} height={1920}>
317
+ <Clip duration={5}>
318
+ <Split direction="horizontal">
319
+ <Animate
320
+ image={before}
321
+ motion="camera slowly pans across messy room"
322
+ model={fal.videoModel("wan-2.5")}
323
+ />
324
+ <Animate
325
+ image={after}
326
+ motion="camera slowly pans across clean room"
327
+ model={fal.videoModel("wan-2.5")}
328
+ />
329
+ </Split>
330
+ <Title position="bottom">
331
+ BEFORE AFTER
332
+ </Title>
333
+ </Clip>
334
+ </Render>,
335
+ { output: "output/split-comparison.mp4" }
336
+ );
337
+ }
338
+
339
+ main();
340
+ ```
341
+
342
+ ---
343
+
344
+ ## Component Reference
345
+
346
+ | Component | Props | Description |
347
+ |-----------|-------|-------------|
348
+ | `<Render>` | `width`, `height`, `fps` | Root container |
349
+ | `<Clip>` | `duration`, `transition` | Sequential segment |
350
+ | `<Image>` | `prompt`, `src`, `model`, `aspectRatio`, `zoom` | AI or static image |
351
+ | `<Animate>` | `image`, `motion`, `model`, `duration` | Image-to-video |
352
+ | `<Video>` | `src`, `prompt`, `model`, `keepAudio` | Video file or generated |
353
+ | `<Music>` | `prompt`, `model`, `duration`, `volume`, `loop` | Background music |
354
+ | `<Speech>` | `voice`, `model`, `children` | Text-to-speech |
355
+ | `<Title>` | `position`, `color`, `start`, `end` | Text overlay |
356
+ | `<Split>` | `direction` | Side-by-side layout |
357
+
358
+ ## Transitions
359
+
360
+ Available transition effects:
361
+
362
+ - `fade` - Simple fade
363
+ - `crossfade` - Cross dissolve
364
+ - `wipeleft`, `wiperight`, `wipeup`, `wipedown` - Directional wipes
365
+ - `slideleft`, `slideright`, `slideup`, `slidedown` - Slides
366
+ - `cube` - 3D cube rotation
367
+ - `pixelize` - Pixelation effect
368
+
369
+ ```tsx
370
+ <Clip transition={{ name: "cube", duration: 0.8 }}>
371
+ ```
372
+
373
+ ## Aspect Ratios
374
+
375
+ | Ratio | Use Case | Dimensions |
376
+ |-------|----------|------------|
377
+ | `9:16` | TikTok, Reels, Shorts | 1080x1920 |
378
+ | `16:9` | YouTube, Twitter | 1920x1080 |
379
+ | `1:1` | Instagram posts | 1080x1080 |
380
+ | `4:5` | Instagram portrait | 1080x1350 |
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * varg-video-generation setup script
4
+ *
5
+ * This script initializes a project for AI video generation.
6
+ * Can be run standalone by agents or via bunx vargai.
7
+ *
8
+ * Usage:
9
+ * bun scripts/setup.ts
10
+ */
11
+
12
+ import {
13
+ existsSync,
14
+ mkdirSync,
15
+ readFileSync,
16
+ symlinkSync,
17
+ writeFileSync,
18
+ } from "node:fs";
19
+ import { dirname, join } from "node:path";
20
+
21
+ const COLORS = {
22
+ reset: "\x1b[0m",
23
+ bold: "\x1b[1m",
24
+ dim: "\x1b[2m",
25
+ green: "\x1b[32m",
26
+ yellow: "\x1b[33m",
27
+ blue: "\x1b[34m",
28
+ red: "\x1b[31m",
29
+ cyan: "\x1b[36m",
30
+ };
31
+
32
+ const log = {
33
+ info: (msg: string) =>
34
+ console.log(`${COLORS.blue}info${COLORS.reset} ${msg}`),
35
+ success: (msg: string) =>
36
+ console.log(`${COLORS.green}done${COLORS.reset} ${msg}`),
37
+ warn: (msg: string) =>
38
+ console.log(`${COLORS.yellow}warn${COLORS.reset} ${msg}`),
39
+ error: (msg: string) =>
40
+ console.log(`${COLORS.red}error${COLORS.reset} ${msg}`),
41
+ step: (msg: string) =>
42
+ console.log(
43
+ `\n${COLORS.bold}${COLORS.cyan}==>${COLORS.reset} ${COLORS.bold}${msg}${COLORS.reset}`,
44
+ ),
45
+ };
46
+
47
+ // .env template
48
+ const ENV_TEMPLATE = `# Varg AI Video Generation - API Keys
49
+ # Get your keys from the URLs below
50
+
51
+ # REQUIRED - Fal.ai (image & video generation)
52
+ # Get it: https://fal.ai/dashboard/keys
53
+ FAL_API_KEY=
54
+
55
+ # OPTIONAL - ElevenLabs (music & voice)
56
+ # Get it: https://elevenlabs.io/app/settings/api-keys
57
+ ELEVENLABS_API_KEY=
58
+
59
+ # OPTIONAL - Replicate (lipsync)
60
+ # Get it: https://replicate.com/account/api-tokens
61
+ REPLICATE_API_TOKEN=
62
+
63
+ # OPTIONAL - Groq (transcription)
64
+ # Get it: https://console.groq.com/keys
65
+ GROQ_API_KEY=
66
+ `;
67
+
68
+ // Example video file
69
+ const EXAMPLE_VIDEO = `/**
70
+ * Example: Simple animated video
71
+ * Run: bun run examples/my-first-video.tsx
72
+ */
73
+ import { render, Render, Clip, Image, Animate } from "vargai/react";
74
+ import { fal } from "vargai/ai";
75
+
76
+ async function main() {
77
+ console.log("Creating your first AI video...\\n");
78
+
79
+ await render(
80
+ <Render width={720} height={720}>
81
+ <Clip duration={3}>
82
+ <Animate
83
+ image={Image({
84
+ prompt: "a friendly robot waving hello, cartoon style, blue colors",
85
+ model: fal.imageModel("flux-schnell"),
86
+ aspectRatio: "1:1",
87
+ })}
88
+ motion="robot waves hello, friendly gesture"
89
+ model={fal.videoModel("wan-2.5")}
90
+ duration={3}
91
+ />
92
+ </Clip>
93
+ </Render>,
94
+ {
95
+ output: "output/my-first-video.mp4",
96
+ cache: ".cache/ai"
97
+ }
98
+ );
99
+
100
+ console.log("\\nDone! Check output/my-first-video.mp4");
101
+ }
102
+
103
+ main().catch(console.error);
104
+ `;
105
+
106
+ async function main() {
107
+ console.log(`
108
+ ${COLORS.bold}${COLORS.cyan}varg-video-generation${COLORS.reset} ${COLORS.dim}setup${COLORS.reset}
109
+ `);
110
+
111
+ const cwd = process.cwd();
112
+
113
+ // Step 1: Check/create directories
114
+ log.step("Setting up project structure");
115
+
116
+ const dirs = ["output", ".cache/ai", "examples"];
117
+ for (const dir of dirs) {
118
+ const path = join(cwd, dir);
119
+ if (!existsSync(path)) {
120
+ mkdirSync(path, { recursive: true });
121
+ log.success(`Created ${dir}/`);
122
+ } else {
123
+ log.info(`${dir}/ already exists`);
124
+ }
125
+ }
126
+
127
+ // Step 2: Check/create .env
128
+ log.step("Checking API keys");
129
+
130
+ const envPath = join(cwd, ".env");
131
+ let envContent = "";
132
+ let hasFalKey = false;
133
+
134
+ if (existsSync(envPath)) {
135
+ envContent = readFileSync(envPath, "utf8");
136
+ hasFalKey = /^FAL_API_KEY=.+/m.test(envContent);
137
+
138
+ if (hasFalKey) {
139
+ log.success("FAL_API_KEY found in .env");
140
+ } else {
141
+ log.warn("FAL_API_KEY not found in .env");
142
+ }
143
+
144
+ // Check optional keys
145
+ const hasElevenLabs = /^ELEVENLABS_API_KEY=.+/m.test(envContent);
146
+ const hasReplicate = /^REPLICATE_API_TOKEN=.+/m.test(envContent);
147
+ const hasGroq = /^GROQ_API_KEY=.+/m.test(envContent);
148
+
149
+ if (hasElevenLabs)
150
+ log.info("ELEVENLABS_API_KEY found (music/voice enabled)");
151
+ if (hasReplicate) log.info("REPLICATE_API_TOKEN found (lipsync enabled)");
152
+ if (hasGroq) log.info("GROQ_API_KEY found (transcription enabled)");
153
+
154
+ if (!hasElevenLabs && !hasReplicate && !hasGroq) {
155
+ log.info("No optional keys found (basic video generation only)");
156
+ }
157
+ } else {
158
+ log.warn(".env file not found");
159
+ }
160
+
161
+ // If no FAL key, prompt for it
162
+ if (!hasFalKey) {
163
+ console.log(`
164
+ ${COLORS.yellow}FAL_API_KEY is required for video generation.${COLORS.reset}
165
+
166
+ Get your free API key at: ${COLORS.cyan}https://fal.ai/dashboard/keys${COLORS.reset}
167
+ `);
168
+
169
+ process.stdout.write("Enter your FAL_API_KEY (or press Enter to skip): ");
170
+
171
+ const falKey = await new Promise<string>((resolve) => {
172
+ let input = "";
173
+ process.stdin.setEncoding("utf8");
174
+ process.stdin.once("data", (data) => {
175
+ input = data.toString().trim();
176
+ resolve(input);
177
+ });
178
+ });
179
+
180
+ if (falKey) {
181
+ if (existsSync(envPath)) {
182
+ const newEnvContent = envContent.includes("FAL_API_KEY")
183
+ ? envContent.replace(/^FAL_API_KEY=.*/m, `FAL_API_KEY=${falKey}`)
184
+ : `${envContent}\nFAL_API_KEY=${falKey}`;
185
+ writeFileSync(envPath, newEnvContent);
186
+ } else {
187
+ writeFileSync(
188
+ envPath,
189
+ ENV_TEMPLATE.replace("FAL_API_KEY=", `FAL_API_KEY=${falKey}`),
190
+ );
191
+ }
192
+ log.success("FAL_API_KEY saved to .env");
193
+ hasFalKey = true;
194
+ } else {
195
+ if (!existsSync(envPath)) {
196
+ writeFileSync(envPath, ENV_TEMPLATE);
197
+ log.info("Created .env template - add your keys manually");
198
+ }
199
+ }
200
+ }
201
+
202
+ // Step 3: Create example file
203
+ log.step("Creating example files");
204
+
205
+ const examplePath = join(cwd, "examples/my-first-video.tsx");
206
+ if (!existsSync(examplePath)) {
207
+ writeFileSync(examplePath, EXAMPLE_VIDEO);
208
+ log.success("Created examples/my-first-video.tsx");
209
+ } else {
210
+ log.info("examples/my-first-video.tsx already exists");
211
+ }
212
+
213
+ // Step 4: Add to .gitignore
214
+ log.step("Updating .gitignore");
215
+
216
+ const gitignorePath = join(cwd, ".gitignore");
217
+ const gitignoreEntries = [".env", ".cache/", "output/"];
218
+ let gitignoreContent = existsSync(gitignorePath)
219
+ ? readFileSync(gitignorePath, "utf8")
220
+ : "";
221
+
222
+ let added = false;
223
+ for (const entry of gitignoreEntries) {
224
+ if (!gitignoreContent.includes(entry)) {
225
+ gitignoreContent += `\n${entry}`;
226
+ added = true;
227
+ }
228
+ }
229
+
230
+ if (added) {
231
+ writeFileSync(gitignorePath, gitignoreContent.trim() + "\n");
232
+ log.success("Updated .gitignore");
233
+ } else {
234
+ log.info(".gitignore already configured");
235
+ }
236
+
237
+ // Summary
238
+ console.log(`
239
+ ${COLORS.green}${COLORS.bold}Setup complete!${COLORS.reset}
240
+
241
+ ${COLORS.bold}Next steps:${COLORS.reset}
242
+ `);
243
+
244
+ if (!hasFalKey) {
245
+ console.log(` ${COLORS.yellow}1. Add FAL_API_KEY to .env${COLORS.reset}
246
+ Get it at: https://fal.ai/dashboard/keys
247
+ `);
248
+ }
249
+
250
+ console.log(` ${hasFalKey ? "1" : "2"}. Install vargai package:
251
+ ${COLORS.cyan}bun add vargai${COLORS.reset}
252
+
253
+ ${hasFalKey ? "2" : "3"}. Run your first video:
254
+ ${COLORS.cyan}bun run examples/my-first-video.tsx${COLORS.reset}
255
+
256
+ ${COLORS.dim}Documentation: https://github.com/vargHQ/sdk${COLORS.reset}
257
+ `);
258
+
259
+ process.exit(0);
260
+ }
261
+
262
+ main().catch((error) => {
263
+ log.error(error.message);
264
+ process.exit(1);
265
+ });
@@ -106,7 +106,7 @@ function flatten(value: unknown): unknown {
106
106
  * });
107
107
  * ```
108
108
  */
109
- const DEFAULT_TTL = "1h";
109
+ const DEFAULT_TTL = "7d";
110
110
 
111
111
  export function withCache<T extends object, R>(
112
112
  fn: (options: T) => Promise<R>,
@@ -15,12 +15,12 @@ export interface ImagePlaceholderFallbackOptions {
15
15
  export function imagePlaceholderFallbackMiddleware(
16
16
  options: ImagePlaceholderFallbackOptions,
17
17
  ): ImageModelV3Middleware {
18
- const { mode, onFallback } = options;
18
+ const { mode } = options;
19
19
 
20
20
  return {
21
21
  specificationVersion: "v3",
22
22
  wrapGenerate: async ({ doGenerate, params, model }) => {
23
- const createPlaceholderResult = async () => {
23
+ if (mode === "preview") {
24
24
  const [width, height] = (params.size?.split("x").map(Number) ?? [
25
25
  1024, 1024,
26
26
  ]) as [number, number];
@@ -42,7 +42,7 @@ export function imagePlaceholderFallbackMiddleware(
42
42
  warnings: [
43
43
  {
44
44
  type: "other" as const,
45
- message: "placeholder: provider skipped or failed",
45
+ message: "placeholder: preview mode",
46
46
  },
47
47
  ],
48
48
  response: {
@@ -51,26 +51,9 @@ export function imagePlaceholderFallbackMiddleware(
51
51
  headers: undefined,
52
52
  },
53
53
  };
54
- };
55
-
56
- if (mode === "preview") {
57
- return createPlaceholderResult();
58
54
  }
59
55
 
60
- try {
61
- return await doGenerate();
62
- } catch (e) {
63
- if (mode === "strict") throw e;
64
-
65
- const error = e instanceof Error ? e : new Error(String(e));
66
- const promptText =
67
- typeof params.prompt === "string"
68
- ? params.prompt
69
- : ((params.prompt as { text?: string } | undefined)?.text ??
70
- "placeholder");
71
- onFallback?.(error, promptText);
72
- return createPlaceholderResult();
73
- }
56
+ return doGenerate();
74
57
  },
75
58
  };
76
59
  }