vargai 0.4.0-alpha4 → 0.4.0-alpha40
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/.env.example +6 -0
- package/README.md +483 -61
- package/assets/fonts/TikTokSans-Bold.ttf +0 -0
- package/examples/grok-imagine-test.tsx +155 -0
- package/launch-videos/06-kawaii-fruits.tsx +93 -0
- package/launch-videos/07-ugc-weight-loss.tsx +132 -0
- package/launch-videos/08-talking-head-varg.tsx +107 -0
- package/launch-videos/09-girl.tsx +160 -0
- package/launch-videos/README.md +42 -0
- package/package.json +10 -4
- package/pipeline/cookbooks/round-video-character.md +1 -1
- package/skills/varg-video-generation/SKILL.md +224 -0
- package/skills/varg-video-generation/references/templates.md +380 -0
- package/skills/varg-video-generation/scripts/setup.ts +265 -0
- package/src/ai-sdk/cache.ts +1 -3
- package/src/ai-sdk/examples/google-image.ts +62 -0
- package/src/ai-sdk/index.ts +10 -0
- package/src/ai-sdk/middleware/wrap-image-model.ts +4 -21
- package/src/ai-sdk/middleware/wrap-music-model.ts +4 -16
- package/src/ai-sdk/middleware/wrap-video-model.ts +5 -17
- package/src/ai-sdk/providers/CONTRIBUTING.md +457 -0
- package/src/ai-sdk/providers/editly/backends/index.ts +8 -0
- package/src/ai-sdk/providers/editly/backends/local.ts +94 -0
- package/src/ai-sdk/providers/editly/backends/types.ts +74 -0
- package/src/ai-sdk/providers/editly/editly.test.ts +49 -1
- package/src/ai-sdk/providers/editly/index.ts +164 -80
- package/src/ai-sdk/providers/editly/layers.ts +58 -6
- package/src/ai-sdk/providers/editly/rendi/editly-with-rendi-backend.test.ts +335 -0
- package/src/ai-sdk/providers/editly/rendi/index.ts +289 -0
- package/src/ai-sdk/providers/editly/rendi/rendi.test.ts +35 -0
- package/src/ai-sdk/providers/editly/types.ts +30 -0
- package/src/ai-sdk/providers/elevenlabs.ts +10 -2
- package/src/ai-sdk/providers/fal.test.ts +214 -0
- package/src/ai-sdk/providers/fal.ts +435 -40
- package/src/ai-sdk/providers/google.ts +423 -0
- package/src/ai-sdk/providers/together.ts +191 -0
- package/src/cli/commands/find.tsx +1 -0
- package/src/cli/commands/frame.tsx +616 -0
- package/src/cli/commands/hello.ts +85 -0
- package/src/cli/commands/help.tsx +18 -30
- package/src/cli/commands/index.ts +11 -2
- package/src/cli/commands/init.tsx +570 -0
- package/src/cli/commands/list.tsx +1 -0
- package/src/cli/commands/render.tsx +322 -76
- package/src/cli/commands/run.tsx +1 -0
- package/src/cli/commands/storyboard.tsx +1714 -0
- package/src/cli/commands/which.tsx +1 -0
- package/src/cli/index.ts +23 -4
- package/src/cli/ui/components/Badge.tsx +1 -0
- package/src/cli/ui/components/DataTable.tsx +1 -0
- package/src/cli/ui/components/Header.tsx +1 -0
- package/src/cli/ui/components/HelpBlock.tsx +1 -0
- package/src/cli/ui/components/KeyValue.tsx +1 -0
- package/src/cli/ui/components/OptionRow.tsx +1 -0
- package/src/cli/ui/components/Separator.tsx +1 -0
- package/src/cli/ui/components/StatusBox.tsx +1 -0
- package/src/cli/ui/components/VargBox.tsx +1 -0
- package/src/cli/ui/components/VargProgress.tsx +1 -0
- package/src/cli/ui/components/VargSpinner.tsx +1 -0
- package/src/cli/ui/components/VargText.tsx +1 -0
- package/src/definitions/actions/grok-edit.ts +133 -0
- package/src/definitions/actions/index.ts +16 -0
- package/src/definitions/actions/qwen-angles.ts +218 -0
- package/src/index.ts +1 -0
- package/src/providers/fal.ts +196 -0
- package/src/react/assets.ts +9 -0
- package/src/react/elements.ts +0 -5
- package/src/react/examples/branching.tsx +6 -4
- package/src/react/examples/character-video.tsx +13 -10
- package/src/react/examples/local-files-test.tsx +19 -0
- package/src/react/examples/ltx2-test.tsx +25 -0
- package/src/react/examples/madi.tsx +13 -10
- package/src/react/examples/mcmeows.tsx +40 -0
- package/src/react/examples/music-defaults.tsx +24 -0
- package/src/react/examples/quickstart-test.tsx +101 -0
- package/src/react/examples/qwen-angles-test.tsx +72 -0
- package/src/react/index.ts +3 -3
- package/src/react/layouts/grid.tsx +1 -1
- package/src/react/layouts/index.ts +2 -1
- package/src/react/layouts/slot.tsx +85 -0
- package/src/react/layouts/split.tsx +18 -0
- package/src/react/react.test.ts +60 -11
- package/src/react/renderers/burn-captions.ts +95 -0
- package/src/react/renderers/cache.test.ts +182 -0
- package/src/react/renderers/captions.ts +25 -6
- package/src/react/renderers/clip.ts +56 -25
- package/src/react/renderers/context.ts +5 -2
- package/src/react/renderers/image.ts +5 -2
- package/src/react/renderers/index.ts +0 -1
- package/src/react/renderers/music.ts +8 -3
- package/src/react/renderers/packshot/blinking-button.ts +413 -0
- package/src/react/renderers/packshot.ts +170 -8
- package/src/react/renderers/progress.ts +4 -3
- package/src/react/renderers/render.ts +127 -71
- package/src/react/renderers/speech.ts +2 -2
- package/src/react/renderers/split.ts +34 -13
- package/src/react/renderers/utils.test.ts +80 -0
- package/src/react/renderers/utils.ts +37 -1
- package/src/react/renderers/video.ts +47 -9
- package/src/react/types.ts +70 -17
- package/src/studio/stages.ts +40 -39
- package/src/studio/step-renderer.ts +14 -24
- package/src/studio/ui/index.html +2 -2
- package/src/tests/all.test.ts +4 -4
- package/src/tests/index.ts +1 -1
- package/test-slot-grid.tsx +19 -0
- package/test-slot-userland.tsx +30 -0
- package/test-sync-v2.ts +30 -0
- package/test-sync-v2.tsx +29 -0
- package/tsconfig.json +1 -1
- package/video.tsx +7 -0
- package/src/ai-sdk/providers/editly/ffmpeg.ts +0 -60
- package/src/react/renderers/animate.ts +0 -59
- /package/src/cli/commands/{studio.tsx → studio.ts} +0 -0
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* varg help command
|
|
3
|
-
* Ink-based help display
|
|
4
|
-
*/
|
|
1
|
+
/** @jsxImportSource react */
|
|
5
2
|
|
|
6
3
|
import { defineCommand } from "citty";
|
|
7
4
|
import { Box, Text } from "ink";
|
|
@@ -24,44 +21,35 @@ function CommandRow({ name, description }: CommandRowProps) {
|
|
|
24
21
|
|
|
25
22
|
function HelpView() {
|
|
26
23
|
const examples = [
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
command: 'varg run video --prompt "person talking" --image photo.jpg',
|
|
33
|
-
description: "generate video from image",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
command: 'varg run voice --text "hello world" --voice sam',
|
|
37
|
-
description: "text to speech",
|
|
38
|
-
},
|
|
39
|
-
{ command: "varg list", description: "see all available" },
|
|
40
|
-
{
|
|
41
|
-
command: "varg which video",
|
|
42
|
-
description: "inspect an action or model",
|
|
43
|
-
},
|
|
24
|
+
{ command: "vargai hello", description: "create hello.tsx starter" },
|
|
25
|
+
{ command: "vargai render hello.tsx", description: "render jsx to video" },
|
|
26
|
+
{ command: "vargai init", description: "full setup with api keys" },
|
|
44
27
|
];
|
|
45
28
|
|
|
46
29
|
return (
|
|
47
|
-
<VargBox title="
|
|
30
|
+
<VargBox title="vargai">
|
|
48
31
|
<Box marginBottom={1}>
|
|
49
|
-
<Text>ai video
|
|
32
|
+
<Text>ai video generation sdk. jsx for videos.</Text>
|
|
50
33
|
</Box>
|
|
51
34
|
|
|
52
35
|
<Header>COMMANDS</Header>
|
|
53
36
|
<Box flexDirection="column" marginY={1}>
|
|
54
|
-
<CommandRow name="
|
|
55
|
-
<CommandRow name="
|
|
37
|
+
<CommandRow name="hello" description="create hello.tsx starter video" />
|
|
38
|
+
<CommandRow name="render" description="render jsx component to video" />
|
|
39
|
+
<CommandRow name="preview" description="fast preview (placeholders)" />
|
|
40
|
+
<CommandRow name="init" description="full setup with api keys" />
|
|
56
41
|
<CommandRow
|
|
57
|
-
name="
|
|
58
|
-
description="
|
|
42
|
+
name="studio"
|
|
43
|
+
description="visual editor at localhost:8282"
|
|
44
|
+
/>
|
|
45
|
+
<CommandRow name="run" description="run a single model or action" />
|
|
46
|
+
<CommandRow
|
|
47
|
+
name="list"
|
|
48
|
+
description="discover models, actions, skills"
|
|
59
49
|
/>
|
|
60
|
-
<CommandRow name="which" description="inspect a specific item" />
|
|
61
|
-
<CommandRow name="help" description="show this help" />
|
|
62
50
|
</Box>
|
|
63
51
|
|
|
64
|
-
<Header>
|
|
52
|
+
<Header>QUICKSTART</Header>
|
|
65
53
|
<Box marginTop={1}>
|
|
66
54
|
<HelpBlock examples={examples} />
|
|
67
55
|
</Box>
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
export { findCmd, showFindHelp } from "./find.tsx";
|
|
2
|
+
export { frameCmd, showFrameHelp } from "./frame.tsx";
|
|
3
|
+
export { helloCmd } from "./hello.ts";
|
|
2
4
|
export { helpCmd, showHelp } from "./help.tsx";
|
|
5
|
+
export { initCmd, showInitHelp } from "./init.tsx";
|
|
3
6
|
export { listCmd, showListHelp } from "./list.tsx";
|
|
4
|
-
export {
|
|
7
|
+
export {
|
|
8
|
+
previewCmd,
|
|
9
|
+
renderCmd,
|
|
10
|
+
showPreviewHelp,
|
|
11
|
+
showRenderHelp,
|
|
12
|
+
} from "./render.tsx";
|
|
5
13
|
export { runCmd, showRunHelp, showTargetHelp } from "./run.tsx";
|
|
6
|
-
export {
|
|
14
|
+
export { showStoryboardHelp, storyboardCmd } from "./storyboard.tsx";
|
|
15
|
+
export { studioCmd } from "./studio.ts";
|
|
7
16
|
export { showWhichHelp, whichCmd } from "./which.tsx";
|
|
@@ -0,0 +1,570 @@
|
|
|
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_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_KEY|ELEVENLABS_API_KEY|REPLICATE_API_TOKEN|GROQ_API_KEY)=" || echo "No API keys found in .env"
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
### Required: FAL_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_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 GUIDE_MD = `## vargai - AI Video Generation
|
|
175
|
+
|
|
176
|
+
### Setup
|
|
177
|
+
|
|
178
|
+
\`\`\`bash
|
|
179
|
+
bunx vargai init
|
|
180
|
+
bun install vargai ai
|
|
181
|
+
\`\`\`
|
|
182
|
+
|
|
183
|
+
Required: \`FAL_KEY\` in \`.env\`
|
|
184
|
+
Optional: \`ELEVENLABS_API_KEY\` (voice/music), \`REPLICATE_API_TOKEN\` (lipsync), \`GROQ_API_KEY\` (transcription)
|
|
185
|
+
|
|
186
|
+
### Render videos
|
|
187
|
+
|
|
188
|
+
\`\`\`bash
|
|
189
|
+
bunx vargai render video.tsx
|
|
190
|
+
\`\`\`
|
|
191
|
+
|
|
192
|
+
### Basic structure
|
|
193
|
+
|
|
194
|
+
Every file needs the JSX pragma and exports a default \`<Render>\`:
|
|
195
|
+
|
|
196
|
+
\`\`\`tsx
|
|
197
|
+
/** @jsxImportSource vargai */
|
|
198
|
+
import { Render, Clip, Image, Video, Speech, Captions, Music } from "vargai/react";
|
|
199
|
+
import { fal, elevenlabs } from "vargai/ai";
|
|
200
|
+
|
|
201
|
+
export default (
|
|
202
|
+
<Render width={1080} height={1920}>
|
|
203
|
+
<Clip duration={5}>
|
|
204
|
+
<Image prompt="..." model={fal.imageModel("flux-pro")} aspectRatio="9:16" />
|
|
205
|
+
</Clip>
|
|
206
|
+
</Render>
|
|
207
|
+
);
|
|
208
|
+
\`\`\`
|
|
209
|
+
|
|
210
|
+
### Quirks and gotchas
|
|
211
|
+
|
|
212
|
+
1. **Reusable character pattern** - Export character as named export so it can be imported by other files, then use \`<Image src={character} />\` in the Clip:
|
|
213
|
+
\`\`\`tsx
|
|
214
|
+
/** @jsxImportSource vargai */
|
|
215
|
+
import { Captions, Clip, Image, Render, Speech } from "vargai/react";
|
|
216
|
+
import { elevenlabs, fal } from "vargai/ai";
|
|
217
|
+
|
|
218
|
+
export const character = Image({
|
|
219
|
+
prompt: "character description",
|
|
220
|
+
model: fal.imageModel("nano-banana-pro"),
|
|
221
|
+
aspectRatio: "9:16",
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
export default (
|
|
225
|
+
<Render width={1080} height={1920}>
|
|
226
|
+
<Clip duration={21}>
|
|
227
|
+
<Image src={character} />
|
|
228
|
+
</Clip>
|
|
229
|
+
<Captions src={voiceover} style="tiktok" color="#ffffff" activeColor="#FFD700" />
|
|
230
|
+
</Render>
|
|
231
|
+
);
|
|
232
|
+
\`\`\`
|
|
233
|
+
This file can be both rendered directly (\`bunx vargai render file.tsx\`) and imported by other files (\`import { character } from "./file.tsx"\`).
|
|
234
|
+
|
|
235
|
+
2. **Captions include audio** - \`<Captions src={voiceover} />\` already plays the audio. No need for separate \`<Speech>\` in the clip.
|
|
236
|
+
|
|
237
|
+
3. **Clip duration** - Omit \`duration\` to auto-fit content. Set explicit \`duration={N}\` to lock length. If duration is shorter than content, you get black screen while audio continues.
|
|
238
|
+
|
|
239
|
+
4. **Model names must be exact**:
|
|
240
|
+
- Images: \`flux-pro\`, \`nano-banana-pro\`, \`nano-banana-pro/edit\`
|
|
241
|
+
- Videos: \`kling-v2.5\`, \`wan-2.5\`
|
|
242
|
+
- Lipsync: \`sync-v2-pro\` (NOT \`sync-lipsync\`)
|
|
243
|
+
- Speech: \`eleven_multilingual_v2\`
|
|
244
|
+
|
|
245
|
+
5. **Speech function syntax**:
|
|
246
|
+
\`\`\`tsx
|
|
247
|
+
// Correct - use for voiceover/captions source
|
|
248
|
+
const voiceover = Speech({
|
|
249
|
+
model: elevenlabs.speechModel("eleven_multilingual_v2"),
|
|
250
|
+
voice: "21m00Tcm4TlvDq8ikWAM",
|
|
251
|
+
children: "Text to speak",
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Inside Clip - use JSX with children
|
|
255
|
+
<Speech voice="21m00Tcm4TlvDq8ikWAM" model={elevenlabs.speechModel("eleven_multilingual_v2")}>
|
|
256
|
+
Text to speak
|
|
257
|
+
</Speech>
|
|
258
|
+
\`\`\`
|
|
259
|
+
|
|
260
|
+
6. **Image-to-image editing** - Use \`nano-banana-pro/edit\` with prompt object:
|
|
261
|
+
\`\`\`tsx
|
|
262
|
+
const edited = Image({
|
|
263
|
+
prompt: {
|
|
264
|
+
text: "new description",
|
|
265
|
+
images: [baseImage],
|
|
266
|
+
},
|
|
267
|
+
model: fal.imageModel("nano-banana-pro/edit"),
|
|
268
|
+
});
|
|
269
|
+
\`\`\`
|
|
270
|
+
|
|
271
|
+
7. **Lipsync videos** - Pass video and audio to sync:
|
|
272
|
+
\`\`\`tsx
|
|
273
|
+
<Video
|
|
274
|
+
prompt={{
|
|
275
|
+
video: generatedVideo,
|
|
276
|
+
audio: voiceover,
|
|
277
|
+
}}
|
|
278
|
+
model={fal.videoModel("sync-v2-pro")}
|
|
279
|
+
/>
|
|
280
|
+
\`\`\`
|
|
281
|
+
|
|
282
|
+
8. **Caching** - Same prompts/params hit cache automatically. Regenerate by changing prompts.
|
|
283
|
+
|
|
284
|
+
### Simple template (still image + voiceover + captions)
|
|
285
|
+
|
|
286
|
+
\`\`\`tsx
|
|
287
|
+
/** @jsxImportSource vargai */
|
|
288
|
+
import { Captions, Clip, Image, Render, Speech } from "vargai/react";
|
|
289
|
+
import { elevenlabs, fal } from "vargai/ai";
|
|
290
|
+
|
|
291
|
+
const SCRIPT = \\\`Your script here.\\\`;
|
|
292
|
+
|
|
293
|
+
const voiceover = Speech({
|
|
294
|
+
model: elevenlabs.speechModel("eleven_multilingual_v2"),
|
|
295
|
+
voice: "21m00Tcm4TlvDq8ikWAM",
|
|
296
|
+
children: SCRIPT,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
export default (
|
|
300
|
+
<Render width={1080} height={1920}>
|
|
301
|
+
<Clip duration={21}>
|
|
302
|
+
<Image
|
|
303
|
+
prompt="character description"
|
|
304
|
+
model={fal.imageModel("nano-banana-pro")}
|
|
305
|
+
aspectRatio="9:16"
|
|
306
|
+
/>
|
|
307
|
+
</Clip>
|
|
308
|
+
<Captions src={voiceover} style="tiktok" color="#ffffff" activeColor="#FFD700" />
|
|
309
|
+
</Render>
|
|
310
|
+
);
|
|
311
|
+
\`\`\`
|
|
312
|
+
|
|
313
|
+
### Aspect ratios
|
|
314
|
+
|
|
315
|
+
- \`9:16\` - TikTok, Reels, Shorts (vertical)
|
|
316
|
+
- \`16:9\` - YouTube (horizontal)
|
|
317
|
+
- \`1:1\` - Instagram (square)
|
|
318
|
+
|
|
319
|
+
### Workflow
|
|
320
|
+
|
|
321
|
+
1. **Commit every change** - After each successful render or code change, commit to preserve progress.
|
|
322
|
+
2. **Test incrementally** - Start simple (still image + audio), add complexity (video, lipsync) step by step.
|
|
323
|
+
3. **Check duration** - Use \`ffprobe -v error -show_entries format=duration -of csv=p=0 output/file.mp4\` to verify video length.
|
|
324
|
+
4. **Open to preview** - Use \`open output/file.mp4\` to view rendered videos.
|
|
325
|
+
`;
|
|
326
|
+
|
|
327
|
+
const ENV_TEMPLATE = `# Varg AI Video Generation - API Keys
|
|
328
|
+
|
|
329
|
+
# REQUIRED - Fal.ai (image & video generation)
|
|
330
|
+
# Get it: https://fal.ai/dashboard/keys
|
|
331
|
+
FAL_KEY=
|
|
332
|
+
|
|
333
|
+
# OPTIONAL - ElevenLabs (music & voice)
|
|
334
|
+
# Get it: https://elevenlabs.io/app/settings/api-keys
|
|
335
|
+
ELEVENLABS_API_KEY=
|
|
336
|
+
|
|
337
|
+
# OPTIONAL - Replicate (lipsync)
|
|
338
|
+
# Get it: https://replicate.com/account/api-tokens
|
|
339
|
+
REPLICATE_API_TOKEN=
|
|
340
|
+
|
|
341
|
+
# OPTIONAL - Groq (transcription)
|
|
342
|
+
# Get it: https://console.groq.com/keys
|
|
343
|
+
GROQ_API_KEY=
|
|
344
|
+
`;
|
|
345
|
+
|
|
346
|
+
export function showInitHelp() {
|
|
347
|
+
console.log(`
|
|
348
|
+
${COLORS.bold}vargai init${COLORS.reset}
|
|
349
|
+
|
|
350
|
+
initialize a new varg project with api key setup and hello.tsx template.
|
|
351
|
+
|
|
352
|
+
${COLORS.bold}USAGE${COLORS.reset}
|
|
353
|
+
vargai init [directory]
|
|
354
|
+
|
|
355
|
+
${COLORS.bold}EXAMPLES${COLORS.reset}
|
|
356
|
+
${COLORS.cyan}vargai init${COLORS.reset} setup in current directory
|
|
357
|
+
${COLORS.cyan}vargai init my-project${COLORS.reset} setup in my-project/
|
|
358
|
+
`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export const initCmd = defineCommand({
|
|
362
|
+
meta: {
|
|
363
|
+
name: "init",
|
|
364
|
+
description: "setup project with api keys and hello.tsx",
|
|
365
|
+
},
|
|
366
|
+
args: {
|
|
367
|
+
directory: {
|
|
368
|
+
type: "positional",
|
|
369
|
+
description: "project directory (default: current)",
|
|
370
|
+
required: false,
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
async run({ args }) {
|
|
374
|
+
const dir = (args.directory as string) || ".";
|
|
375
|
+
const cwd = dir === "." ? process.cwd() : join(process.cwd(), dir);
|
|
376
|
+
|
|
377
|
+
console.log(`
|
|
378
|
+
${COLORS.bold}${COLORS.cyan}
|
|
379
|
+
██╗ ██╗ █████╗ ██████╗ ██████╗
|
|
380
|
+
██║ ██║██╔══██╗██╔══██╗██╔════╝
|
|
381
|
+
██║ ██║███████║██████╔╝██║ ███╗
|
|
382
|
+
╚██╗ ██╔╝██╔══██║██╔══██╗██║ ██║
|
|
383
|
+
╚████╔╝ ██║ ██║██║ ██║╚██████╔╝
|
|
384
|
+
╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝
|
|
385
|
+
${COLORS.reset}
|
|
386
|
+
${COLORS.bold}AI Video Generation Setup${COLORS.reset}
|
|
387
|
+
`);
|
|
388
|
+
|
|
389
|
+
// Step 1: Create directory structure
|
|
390
|
+
log.step("Setting up project structure");
|
|
391
|
+
|
|
392
|
+
if (!existsSync(cwd) && dir !== ".") {
|
|
393
|
+
mkdirSync(cwd, { recursive: true });
|
|
394
|
+
log.success(`Created ${dir}/`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const dirs = ["output", ".cache/ai"];
|
|
398
|
+
for (const d of dirs) {
|
|
399
|
+
const path = join(cwd, d);
|
|
400
|
+
if (!existsSync(path)) {
|
|
401
|
+
mkdirSync(path, { recursive: true });
|
|
402
|
+
log.success(`Created ${d}/`);
|
|
403
|
+
} else {
|
|
404
|
+
log.info(`${d}/ already exists`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Step 2: Check/create .env and prompt for FAL_KEY
|
|
409
|
+
log.step("Checking API keys");
|
|
410
|
+
|
|
411
|
+
const envPath = join(cwd, ".env");
|
|
412
|
+
let envContent = "";
|
|
413
|
+
let hasFalKey = false;
|
|
414
|
+
|
|
415
|
+
if (existsSync(envPath)) {
|
|
416
|
+
envContent = readFileSync(envPath, "utf8");
|
|
417
|
+
hasFalKey = /^FAL_KEY=.+/m.test(envContent);
|
|
418
|
+
|
|
419
|
+
if (hasFalKey) {
|
|
420
|
+
log.success("FAL_KEY found in .env");
|
|
421
|
+
} else {
|
|
422
|
+
log.warn("FAL_KEY not found in .env");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const hasElevenLabs = /^ELEVENLABS_API_KEY=.+/m.test(envContent);
|
|
426
|
+
const hasReplicate = /^REPLICATE_API_TOKEN=.+/m.test(envContent);
|
|
427
|
+
const hasGroq = /^GROQ_API_KEY=.+/m.test(envContent);
|
|
428
|
+
|
|
429
|
+
if (hasElevenLabs)
|
|
430
|
+
log.info("ELEVENLABS_API_KEY found (music/voice enabled)");
|
|
431
|
+
if (hasReplicate) log.info("REPLICATE_API_TOKEN found (lipsync enabled)");
|
|
432
|
+
if (hasGroq) log.info("GROQ_API_KEY found (transcription enabled)");
|
|
433
|
+
} else {
|
|
434
|
+
log.warn(".env file not found");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (!hasFalKey) {
|
|
438
|
+
console.log(`
|
|
439
|
+
${COLORS.yellow}FAL_KEY is required for video generation.${COLORS.reset}
|
|
440
|
+
|
|
441
|
+
Get your free API key at: ${COLORS.cyan}https://fal.ai/dashboard/keys${COLORS.reset}
|
|
442
|
+
`);
|
|
443
|
+
|
|
444
|
+
process.stdout.write("Enter your FAL_KEY (or press Enter to skip): ");
|
|
445
|
+
|
|
446
|
+
const falKey = await new Promise<string>((resolve) => {
|
|
447
|
+
process.stdin.setEncoding("utf8");
|
|
448
|
+
process.stdin.once("data", (data) => {
|
|
449
|
+
resolve(data.toString().trim());
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
if (falKey) {
|
|
454
|
+
if (existsSync(envPath)) {
|
|
455
|
+
const newEnvContent = envContent.includes("FAL_KEY")
|
|
456
|
+
? envContent.replace(/^FAL_KEY=.*/m, `FAL_KEY=${falKey}`)
|
|
457
|
+
: `${envContent}\nFAL_KEY=${falKey}`;
|
|
458
|
+
writeFileSync(envPath, newEnvContent);
|
|
459
|
+
} else {
|
|
460
|
+
writeFileSync(
|
|
461
|
+
envPath,
|
|
462
|
+
ENV_TEMPLATE.replace("FAL_KEY=", `FAL_KEY=${falKey}`),
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
log.success("FAL_KEY saved to .env");
|
|
466
|
+
hasFalKey = true;
|
|
467
|
+
} else {
|
|
468
|
+
if (!existsSync(envPath)) {
|
|
469
|
+
writeFileSync(envPath, ENV_TEMPLATE);
|
|
470
|
+
log.info("Created .env template - add your keys manually");
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Step 3: Install Agent Skills
|
|
476
|
+
log.step("Installing Agent Skills");
|
|
477
|
+
|
|
478
|
+
const skillsDir = join(cwd, ".claude/skills/varg-video-generation");
|
|
479
|
+
const skillPath = join(skillsDir, "SKILL.md");
|
|
480
|
+
|
|
481
|
+
if (!existsSync(skillsDir)) {
|
|
482
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
writeFileSync(skillPath, SKILL_MD);
|
|
486
|
+
log.success("Installed SKILL.md (Agent Skills format)");
|
|
487
|
+
|
|
488
|
+
const guidePath = join(skillsDir, "GUIDE.md");
|
|
489
|
+
writeFileSync(guidePath, GUIDE_MD);
|
|
490
|
+
log.success("Installed GUIDE.md (usage guide)");
|
|
491
|
+
|
|
492
|
+
const rulesDir = join(cwd, ".claude/rules");
|
|
493
|
+
const rulePath = join(rulesDir, "video-generation.md");
|
|
494
|
+
|
|
495
|
+
if (!existsSync(rulesDir)) {
|
|
496
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (existsSync(rulePath)) {
|
|
500
|
+
unlinkSync(rulePath);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
symlinkSync("../skills/varg-video-generation/SKILL.md", rulePath);
|
|
504
|
+
log.success("Created Claude Code rules symlink");
|
|
505
|
+
|
|
506
|
+
// Step 4: Create hello.tsx
|
|
507
|
+
log.step("Creating hello.tsx");
|
|
508
|
+
|
|
509
|
+
const helloPath = join(cwd, "hello.tsx");
|
|
510
|
+
if (!existsSync(helloPath)) {
|
|
511
|
+
writeFileSync(helloPath, HELLO_TEMPLATE);
|
|
512
|
+
log.success("Created hello.tsx");
|
|
513
|
+
} else {
|
|
514
|
+
log.info("hello.tsx already exists");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Step 5: Update .gitignore
|
|
518
|
+
log.step("Updating .gitignore");
|
|
519
|
+
|
|
520
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
521
|
+
const gitignoreEntries = [".env", ".cache/", "output/"];
|
|
522
|
+
let gitignoreContent = existsSync(gitignorePath)
|
|
523
|
+
? readFileSync(gitignorePath, "utf8")
|
|
524
|
+
: "";
|
|
525
|
+
|
|
526
|
+
let added = false;
|
|
527
|
+
for (const entry of gitignoreEntries) {
|
|
528
|
+
if (!gitignoreContent.includes(entry)) {
|
|
529
|
+
gitignoreContent += `\n${entry}`;
|
|
530
|
+
added = true;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (added) {
|
|
535
|
+
writeFileSync(gitignorePath, gitignoreContent.trim() + "\n");
|
|
536
|
+
log.success("Updated .gitignore");
|
|
537
|
+
} else {
|
|
538
|
+
log.info(".gitignore already configured");
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Summary
|
|
542
|
+
console.log(`
|
|
543
|
+
${COLORS.green}${COLORS.bold}Setup complete!${COLORS.reset}
|
|
544
|
+
|
|
545
|
+
${COLORS.bold}What was installed:${COLORS.reset}
|
|
546
|
+
${COLORS.dim}├─${COLORS.reset} hello.tsx ${COLORS.dim}(starter video)${COLORS.reset}
|
|
547
|
+
${COLORS.dim}├─${COLORS.reset} .claude/skills/varg-video-generation/SKILL.md ${COLORS.dim}(Agent Skills)${COLORS.reset}
|
|
548
|
+
${COLORS.dim}├─${COLORS.reset} .claude/skills/varg-video-generation/GUIDE.md ${COLORS.dim}(usage guide)${COLORS.reset}
|
|
549
|
+
${COLORS.dim}├─${COLORS.reset} output/ ${COLORS.dim}(video output folder)${COLORS.reset}
|
|
550
|
+
${COLORS.dim}└─${COLORS.reset} .cache/ai/ ${COLORS.dim}(generation cache)${COLORS.reset}
|
|
551
|
+
|
|
552
|
+
${COLORS.bold}Next steps:${COLORS.reset}
|
|
553
|
+
`);
|
|
554
|
+
|
|
555
|
+
if (!hasFalKey) {
|
|
556
|
+
console.log(` ${COLORS.yellow}1. Add FAL_KEY to .env${COLORS.reset}
|
|
557
|
+
Get it at: https://fal.ai/dashboard/keys
|
|
558
|
+
`);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
console.log(` ${hasFalKey ? "1" : "2"}. Render your first video:
|
|
562
|
+
${COLORS.cyan}bunx vargai render hello.tsx${COLORS.reset}
|
|
563
|
+
|
|
564
|
+
${hasFalKey ? "2" : "3"}. Or ask Claude to create a video:
|
|
565
|
+
${COLORS.cyan}claude "create a 10 second tiktok video about cats"${COLORS.reset}
|
|
566
|
+
|
|
567
|
+
${COLORS.dim}Documentation: https://github.com/vargHQ/sdk${COLORS.reset}
|
|
568
|
+
`);
|
|
569
|
+
},
|
|
570
|
+
});
|