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,412 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ symlinkSync,
6
+ unlinkSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { defineCommand } from "citty";
11
+
12
+ const COLORS = {
13
+ reset: "\x1b[0m",
14
+ bold: "\x1b[1m",
15
+ dim: "\x1b[2m",
16
+ green: "\x1b[32m",
17
+ yellow: "\x1b[33m",
18
+ blue: "\x1b[34m",
19
+ red: "\x1b[31m",
20
+ cyan: "\x1b[36m",
21
+ };
22
+
23
+ const log = {
24
+ info: (msg: string) =>
25
+ console.log(`${COLORS.blue}info${COLORS.reset} ${msg}`),
26
+ success: (msg: string) =>
27
+ console.log(`${COLORS.green}done${COLORS.reset} ${msg}`),
28
+ warn: (msg: string) =>
29
+ console.log(`${COLORS.yellow}warn${COLORS.reset} ${msg}`),
30
+ error: (msg: string) =>
31
+ console.log(`${COLORS.red}error${COLORS.reset} ${msg}`),
32
+ step: (msg: string) =>
33
+ console.log(
34
+ `\n${COLORS.bold}${COLORS.cyan}==>${COLORS.reset} ${COLORS.bold}${msg}${COLORS.reset}`,
35
+ ),
36
+ };
37
+
38
+ const HELLO_TEMPLATE = `/** @jsxImportSource vargai */
39
+ import { Render, Clip, Image, Video, assets } from "vargai/react";
40
+ import { fal } from "vargai/ai";
41
+
42
+ const girl = Image({
43
+ prompt: {
44
+ text: \`Using the attached reference images, generate a photorealistic three-quarter editorial portrait of the exact same character — maintain identical face, hairstyle, and proportions from Image 1.
45
+
46
+ Framing: Head and shoulders, cropped at upper chest. Direct eye contact with camera.
47
+
48
+ Natural confident expression, relaxed shoulders.
49
+ Preserve the outfit neckline and visible clothing details from reference.
50
+
51
+ Background: Deep black with two contrasting orange gradient accents matching Reference 2. Soft gradient bleed, no hard edges.
52
+
53
+ Shot on 85mm f/1.4 lens, shallow depth of field. Clean studio lighting — soft key light on face, subtle rim light on hair and shoulders for separation. High-end fashion editorial aesthetic.\`,
54
+ images: [assets.characters.orangeGirl, assets.backgrounds.orangeGradient],
55
+ },
56
+ model: fal.imageModel("nano-banana-pro/edit"),
57
+ aspectRatio: "9:16",
58
+ });
59
+
60
+ export default (
61
+ <Render width={1080} height={1920}>
62
+ <Clip duration={5}>
63
+ <Video
64
+ prompt={{
65
+ text: "She waves hello warmly, natural smile, friendly expression. Studio lighting, authentic confident slightly playful atmosphere. Camera static. Intense orange lighting.",
66
+ images: [girl],
67
+ }}
68
+ model={fal.videoModel("kling-v2.5")}
69
+ />
70
+ </Clip>
71
+ </Render>
72
+ );
73
+ `;
74
+
75
+ const SKILL_MD = `---
76
+ name: varg-video-generation
77
+ description: Generate AI videos using varg SDK React engine. Use when creating videos, animations, talking characters, slideshows, or social media content. Always run onboarding first to check API keys.
78
+ license: MIT
79
+ metadata:
80
+ author: vargHQ
81
+ version: "1.0.0"
82
+ compatibility: Requires bun runtime. FAL_API_KEY required. Optional ELEVENLABS_API_KEY, REPLICATE_API_TOKEN, GROQ_API_KEY
83
+ allowed-tools: Bash(bun:*) Bash(cat:*) Read Write Edit
84
+ ---
85
+
86
+ # Video Generation with varg React Engine
87
+
88
+ ## Overview
89
+
90
+ This rule helps you generate AI videos using the varg SDK's React engine. It provides:
91
+ - Declarative JSX syntax for video composition
92
+ - Automatic caching (same props = instant cache hit)
93
+ - Parallel generation where possible
94
+ - Support for images, video, music, voice, and captions
95
+
96
+ ## Step 1: Onboarding (REQUIRED for new users)
97
+
98
+ Before generating videos, ensure the user has the required API keys configured.
99
+
100
+ ### Check Current Setup
101
+
102
+ Run this command to check existing configuration:
103
+
104
+ \`\`\`bash
105
+ cat .env 2>/dev/null | grep -E "^(FAL_API_KEY|ELEVENLABS_API_KEY|REPLICATE_API_TOKEN|GROQ_API_KEY)=" || echo "No API keys found in .env"
106
+ \`\`\`
107
+
108
+ ### Required: FAL_API_KEY
109
+
110
+ **This is the minimum requirement for video generation.**
111
+
112
+ | Detail | Value |
113
+ |--------|-------|
114
+ | Provider | Fal.ai |
115
+ | Get it | https://fal.ai/dashboard/keys |
116
+ | Free tier | Yes (limited credits) |
117
+ | Used for | Image generation (Flux), Video generation (Wan 2.5, Kling) |
118
+
119
+ If user doesn't have \`FAL_API_KEY\`:
120
+ 1. Direct them to https://fal.ai/dashboard/keys
121
+ 2. They need to create an account and generate an API key
122
+ 3. Add to \`.env\` file in project root
123
+
124
+ ### Optional Keys (warn if missing, but continue)
125
+
126
+ | Feature | Required Key | Provider | Get It |
127
+ |---------|-------------|----------|--------|
128
+ | Music generation | \`ELEVENLABS_API_KEY\` | ElevenLabs | https://elevenlabs.io/app/settings/api-keys |
129
+ | Voice/Speech | \`ELEVENLABS_API_KEY\` | ElevenLabs | https://elevenlabs.io/app/settings/api-keys |
130
+ | Lipsync | \`REPLICATE_API_TOKEN\` | Replicate | https://replicate.com/account/api-tokens |
131
+ | Transcription | \`GROQ_API_KEY\` | Groq | https://console.groq.com/keys |
132
+
133
+ **When keys are missing, inform user what features are unavailable.**
134
+
135
+ ## Step 2: Running Videos
136
+
137
+ \`\`\`bash
138
+ bunx vargai render video.tsx
139
+ \`\`\`
140
+
141
+ ## Key Components
142
+
143
+ | Component | Purpose | Required Key |
144
+ |-----------|---------|--------------|
145
+ | \`<Render>\` | Root container | - |
146
+ | \`<Clip>\` | Sequential segment | - |
147
+ | \`<Image>\` | AI image | FAL |
148
+ | \`<Video>\` | AI video | FAL |
149
+ | \`<Music>\` | Background music | ElevenLabs |
150
+ | \`<Speech>\` | Text-to-speech | ElevenLabs |
151
+
152
+ ## Common Patterns
153
+
154
+ ### Character Consistency
155
+ \`\`\`tsx
156
+ const character = Image({ prompt: "blue robot" });
157
+ // Reuse same reference for consistent appearance
158
+ <Video prompt={{ text: "waving", images: [character] }} />
159
+ <Video prompt={{ text: "dancing", images: [character] }} />
160
+ \`\`\`
161
+
162
+ ### Transitions
163
+ \`\`\`tsx
164
+ <Clip transition={{ name: "fade", duration: 0.5 }}>
165
+ // Options: fade, crossfade, wipeleft, cube, slideup, etc.
166
+ \`\`\`
167
+
168
+ ### Aspect Ratios
169
+ - \`9:16\` - TikTok, Reels, Shorts (vertical)
170
+ - \`16:9\` - YouTube (horizontal)
171
+ - \`1:1\` - Instagram (square)
172
+ `;
173
+
174
+ const ENV_TEMPLATE = `# Varg AI Video Generation - API Keys
175
+
176
+ # REQUIRED - Fal.ai (image & video generation)
177
+ # Get it: https://fal.ai/dashboard/keys
178
+ FAL_API_KEY=
179
+
180
+ # OPTIONAL - ElevenLabs (music & voice)
181
+ # Get it: https://elevenlabs.io/app/settings/api-keys
182
+ ELEVENLABS_API_KEY=
183
+
184
+ # OPTIONAL - Replicate (lipsync)
185
+ # Get it: https://replicate.com/account/api-tokens
186
+ REPLICATE_API_TOKEN=
187
+
188
+ # OPTIONAL - Groq (transcription)
189
+ # Get it: https://console.groq.com/keys
190
+ GROQ_API_KEY=
191
+ `;
192
+
193
+ export function showInitHelp() {
194
+ console.log(`
195
+ ${COLORS.bold}vargai init${COLORS.reset}
196
+
197
+ initialize a new varg project with api key setup and hello.tsx template.
198
+
199
+ ${COLORS.bold}USAGE${COLORS.reset}
200
+ vargai init [directory]
201
+
202
+ ${COLORS.bold}EXAMPLES${COLORS.reset}
203
+ ${COLORS.cyan}vargai init${COLORS.reset} setup in current directory
204
+ ${COLORS.cyan}vargai init my-project${COLORS.reset} setup in my-project/
205
+ `);
206
+ }
207
+
208
+ export const initCmd = defineCommand({
209
+ meta: {
210
+ name: "init",
211
+ description: "setup project with api keys and hello.tsx",
212
+ },
213
+ args: {
214
+ directory: {
215
+ type: "positional",
216
+ description: "project directory (default: current)",
217
+ required: false,
218
+ },
219
+ },
220
+ async run({ args }) {
221
+ const dir = (args.directory as string) || ".";
222
+ const cwd = dir === "." ? process.cwd() : join(process.cwd(), dir);
223
+
224
+ console.log(`
225
+ ${COLORS.bold}${COLORS.cyan}
226
+ ██╗ ██╗ █████╗ ██████╗ ██████╗
227
+ ██║ ██║██╔══██╗██╔══██╗██╔════╝
228
+ ██║ ██║███████║██████╔╝██║ ███╗
229
+ ╚██╗ ██╔╝██╔══██║██╔══██╗██║ ██║
230
+ ╚████╔╝ ██║ ██║██║ ██║╚██████╔╝
231
+ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝
232
+ ${COLORS.reset}
233
+ ${COLORS.bold}AI Video Generation Setup${COLORS.reset}
234
+ `);
235
+
236
+ // Step 1: Create directory structure
237
+ log.step("Setting up project structure");
238
+
239
+ if (!existsSync(cwd) && dir !== ".") {
240
+ mkdirSync(cwd, { recursive: true });
241
+ log.success(`Created ${dir}/`);
242
+ }
243
+
244
+ const dirs = ["output", ".cache/ai"];
245
+ for (const d of dirs) {
246
+ const path = join(cwd, d);
247
+ if (!existsSync(path)) {
248
+ mkdirSync(path, { recursive: true });
249
+ log.success(`Created ${d}/`);
250
+ } else {
251
+ log.info(`${d}/ already exists`);
252
+ }
253
+ }
254
+
255
+ // Step 2: Check/create .env and prompt for FAL_API_KEY
256
+ log.step("Checking API keys");
257
+
258
+ const envPath = join(cwd, ".env");
259
+ let envContent = "";
260
+ let hasFalKey = false;
261
+
262
+ if (existsSync(envPath)) {
263
+ envContent = readFileSync(envPath, "utf8");
264
+ hasFalKey = /^FAL_API_KEY=.+/m.test(envContent);
265
+
266
+ if (hasFalKey) {
267
+ log.success("FAL_API_KEY found in .env");
268
+ } else {
269
+ log.warn("FAL_API_KEY not found in .env");
270
+ }
271
+
272
+ const hasElevenLabs = /^ELEVENLABS_API_KEY=.+/m.test(envContent);
273
+ const hasReplicate = /^REPLICATE_API_TOKEN=.+/m.test(envContent);
274
+ const hasGroq = /^GROQ_API_KEY=.+/m.test(envContent);
275
+
276
+ if (hasElevenLabs)
277
+ log.info("ELEVENLABS_API_KEY found (music/voice enabled)");
278
+ if (hasReplicate) log.info("REPLICATE_API_TOKEN found (lipsync enabled)");
279
+ if (hasGroq) log.info("GROQ_API_KEY found (transcription enabled)");
280
+ } else {
281
+ log.warn(".env file not found");
282
+ }
283
+
284
+ if (!hasFalKey) {
285
+ console.log(`
286
+ ${COLORS.yellow}FAL_API_KEY is required for video generation.${COLORS.reset}
287
+
288
+ Get your free API key at: ${COLORS.cyan}https://fal.ai/dashboard/keys${COLORS.reset}
289
+ `);
290
+
291
+ process.stdout.write("Enter your FAL_API_KEY (or press Enter to skip): ");
292
+
293
+ const falKey = await new Promise<string>((resolve) => {
294
+ process.stdin.setEncoding("utf8");
295
+ process.stdin.once("data", (data) => {
296
+ resolve(data.toString().trim());
297
+ });
298
+ });
299
+
300
+ if (falKey) {
301
+ if (existsSync(envPath)) {
302
+ const newEnvContent = envContent.includes("FAL_API_KEY")
303
+ ? envContent.replace(/^FAL_API_KEY=.*/m, `FAL_API_KEY=${falKey}`)
304
+ : `${envContent}\nFAL_API_KEY=${falKey}`;
305
+ writeFileSync(envPath, newEnvContent);
306
+ } else {
307
+ writeFileSync(
308
+ envPath,
309
+ ENV_TEMPLATE.replace("FAL_API_KEY=", `FAL_API_KEY=${falKey}`),
310
+ );
311
+ }
312
+ log.success("FAL_API_KEY saved to .env");
313
+ hasFalKey = true;
314
+ } else {
315
+ if (!existsSync(envPath)) {
316
+ writeFileSync(envPath, ENV_TEMPLATE);
317
+ log.info("Created .env template - add your keys manually");
318
+ }
319
+ }
320
+ }
321
+
322
+ // Step 3: Install Agent Skills
323
+ log.step("Installing Agent Skills");
324
+
325
+ const skillsDir = join(cwd, ".claude/skills/varg-video-generation");
326
+ const skillPath = join(skillsDir, "SKILL.md");
327
+
328
+ if (!existsSync(skillsDir)) {
329
+ mkdirSync(skillsDir, { recursive: true });
330
+ }
331
+
332
+ writeFileSync(skillPath, SKILL_MD);
333
+ log.success("Installed SKILL.md (Agent Skills format)");
334
+
335
+ const rulesDir = join(cwd, ".claude/rules");
336
+ const rulePath = join(rulesDir, "video-generation.md");
337
+
338
+ if (!existsSync(rulesDir)) {
339
+ mkdirSync(rulesDir, { recursive: true });
340
+ }
341
+
342
+ if (existsSync(rulePath)) {
343
+ unlinkSync(rulePath);
344
+ }
345
+
346
+ symlinkSync("../skills/varg-video-generation/SKILL.md", rulePath);
347
+ log.success("Created Claude Code rules symlink");
348
+
349
+ // Step 4: Create hello.tsx
350
+ log.step("Creating hello.tsx");
351
+
352
+ const helloPath = join(cwd, "hello.tsx");
353
+ if (!existsSync(helloPath)) {
354
+ writeFileSync(helloPath, HELLO_TEMPLATE);
355
+ log.success("Created hello.tsx");
356
+ } else {
357
+ log.info("hello.tsx already exists");
358
+ }
359
+
360
+ // Step 5: Update .gitignore
361
+ log.step("Updating .gitignore");
362
+
363
+ const gitignorePath = join(cwd, ".gitignore");
364
+ const gitignoreEntries = [".env", ".cache/", "output/"];
365
+ let gitignoreContent = existsSync(gitignorePath)
366
+ ? readFileSync(gitignorePath, "utf8")
367
+ : "";
368
+
369
+ let added = false;
370
+ for (const entry of gitignoreEntries) {
371
+ if (!gitignoreContent.includes(entry)) {
372
+ gitignoreContent += `\n${entry}`;
373
+ added = true;
374
+ }
375
+ }
376
+
377
+ if (added) {
378
+ writeFileSync(gitignorePath, gitignoreContent.trim() + "\n");
379
+ log.success("Updated .gitignore");
380
+ } else {
381
+ log.info(".gitignore already configured");
382
+ }
383
+
384
+ // Summary
385
+ console.log(`
386
+ ${COLORS.green}${COLORS.bold}Setup complete!${COLORS.reset}
387
+
388
+ ${COLORS.bold}What was installed:${COLORS.reset}
389
+ ${COLORS.dim}├─${COLORS.reset} hello.tsx ${COLORS.dim}(starter video)${COLORS.reset}
390
+ ${COLORS.dim}├─${COLORS.reset} .claude/skills/varg-video-generation/SKILL.md ${COLORS.dim}(Agent Skills)${COLORS.reset}
391
+ ${COLORS.dim}├─${COLORS.reset} output/ ${COLORS.dim}(video output folder)${COLORS.reset}
392
+ ${COLORS.dim}└─${COLORS.reset} .cache/ai/ ${COLORS.dim}(generation cache)${COLORS.reset}
393
+
394
+ ${COLORS.bold}Next steps:${COLORS.reset}
395
+ `);
396
+
397
+ if (!hasFalKey) {
398
+ console.log(` ${COLORS.yellow}1. Add FAL_API_KEY to .env${COLORS.reset}
399
+ Get it at: https://fal.ai/dashboard/keys
400
+ `);
401
+ }
402
+
403
+ console.log(` ${hasFalKey ? "1" : "2"}. Render your first video:
404
+ ${COLORS.cyan}bunx vargai render hello.tsx${COLORS.reset}
405
+
406
+ ${hasFalKey ? "2" : "3"}. Or ask Claude to create a video:
407
+ ${COLORS.cyan}claude "create a 10 second tiktok video about cats"${COLORS.reset}
408
+
409
+ ${COLORS.dim}Documentation: https://github.com/vargHQ/sdk${COLORS.reset}
410
+ `);
411
+ },
412
+ });
@@ -1,3 +1,4 @@
1
+ /** @jsxImportSource react */
1
2
  /**
2
3
  * varg list command
3
4
  * Ink-based discovery view