vargai 0.4.0-alpha19 → 0.4.0-alpha20
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/package.json +2 -3
- package/src/cli/commands/hello.ts +84 -0
- package/src/cli/commands/help.tsx +6 -8
- package/src/cli/commands/index.ts +1 -0
- package/src/cli/commands/init.tsx +344 -49
- package/src/cli/index.ts +4 -2
- package/src/init.ts +0 -523
package/package.json
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
"module": "src/index.ts",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
|
-
"
|
|
7
|
-
"vargai": "./src/init.ts"
|
|
6
|
+
"vargai": "./src/cli/index.ts"
|
|
8
7
|
},
|
|
9
8
|
"scripts": {
|
|
10
9
|
"check": "biome check . && tsc --noEmit",
|
|
@@ -66,7 +65,7 @@
|
|
|
66
65
|
"vargai": "^0.4.0-alpha11",
|
|
67
66
|
"zod": "^4.2.1"
|
|
68
67
|
},
|
|
69
|
-
"version": "0.4.0-
|
|
68
|
+
"version": "0.4.0-alpha20",
|
|
70
69
|
"exports": {
|
|
71
70
|
".": "./src/index.ts",
|
|
72
71
|
"./ai": "./src/ai-sdk/index.ts",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
|
|
5
|
+
const HELLO_TEMPLATE = `import { Render, Clip, Image, Video, assets } from "vargai/react";
|
|
6
|
+
import { fal } from "vargai/ai";
|
|
7
|
+
|
|
8
|
+
const girl = Image({
|
|
9
|
+
prompt: {
|
|
10
|
+
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.
|
|
11
|
+
|
|
12
|
+
Framing: Head and shoulders, cropped at upper chest. Direct eye contact with camera.
|
|
13
|
+
|
|
14
|
+
Natural confident expression, relaxed shoulders.
|
|
15
|
+
Preserve the outfit neckline and visible clothing details from reference.
|
|
16
|
+
|
|
17
|
+
Background: Deep black with two contrasting orange gradient accents matching Reference 2. Soft gradient bleed, no hard edges.
|
|
18
|
+
|
|
19
|
+
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.\`,
|
|
20
|
+
images: [assets.characters.orangeGirl, assets.backgrounds.orangeGradient],
|
|
21
|
+
},
|
|
22
|
+
model: fal.imageModel("nano-banana-pro/edit"),
|
|
23
|
+
aspectRatio: "9:16",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export default (
|
|
27
|
+
<Render width={1080} height={1920}>
|
|
28
|
+
<Clip duration={5}>
|
|
29
|
+
<Video
|
|
30
|
+
prompt={{
|
|
31
|
+
text: "She waves hello warmly, natural smile, friendly expression. Studio lighting, authentic confident slightly playful atmosphere. Camera static. Intense orange lighting.",
|
|
32
|
+
images: [girl],
|
|
33
|
+
}}
|
|
34
|
+
model={fal.videoModel("kling-v2.5")}
|
|
35
|
+
/>
|
|
36
|
+
</Clip>
|
|
37
|
+
</Render>
|
|
38
|
+
);
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
export const helloCmd = defineCommand({
|
|
42
|
+
meta: {
|
|
43
|
+
name: "hello",
|
|
44
|
+
description: "create hello.tsx starter video",
|
|
45
|
+
},
|
|
46
|
+
args: {
|
|
47
|
+
directory: {
|
|
48
|
+
type: "positional",
|
|
49
|
+
description: "directory (default: current)",
|
|
50
|
+
required: false,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
async run({ args }) {
|
|
54
|
+
const dir = (args.directory as string) || ".";
|
|
55
|
+
const cwd = dir === "." ? process.cwd() : join(process.cwd(), dir);
|
|
56
|
+
|
|
57
|
+
if (!existsSync(cwd) && dir !== ".") {
|
|
58
|
+
mkdirSync(cwd, { recursive: true });
|
|
59
|
+
console.log(`created ${dir}/`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const outputDir = join(cwd, "output");
|
|
63
|
+
if (!existsSync(outputDir)) {
|
|
64
|
+
mkdirSync(outputDir, { recursive: true });
|
|
65
|
+
console.log(`created output/`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const cacheDir = join(cwd, ".cache/ai");
|
|
69
|
+
if (!existsSync(cacheDir)) {
|
|
70
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
71
|
+
console.log(`created .cache/ai/`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const helloPath = join(cwd, "hello.tsx");
|
|
75
|
+
if (existsSync(helloPath)) {
|
|
76
|
+
console.log(`hello.tsx already exists, skipping`);
|
|
77
|
+
} else {
|
|
78
|
+
writeFileSync(helloPath, HELLO_TEMPLATE);
|
|
79
|
+
console.log(`created hello.tsx`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(`\ndone! run: bunx vargai render hello.tsx`);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
@@ -21,25 +21,23 @@ function CommandRow({ name, description }: CommandRowProps) {
|
|
|
21
21
|
|
|
22
22
|
function HelpView() {
|
|
23
23
|
const examples = [
|
|
24
|
-
{ command: "
|
|
25
|
-
{ command: "
|
|
26
|
-
{ command: "
|
|
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" },
|
|
27
27
|
];
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<VargBox title="
|
|
30
|
+
<VargBox title="vargai">
|
|
31
31
|
<Box marginBottom={1}>
|
|
32
32
|
<Text>ai video generation sdk. jsx for videos.</Text>
|
|
33
33
|
</Box>
|
|
34
34
|
|
|
35
35
|
<Header>COMMANDS</Header>
|
|
36
36
|
<Box flexDirection="column" marginY={1}>
|
|
37
|
-
<CommandRow
|
|
38
|
-
name="init"
|
|
39
|
-
description="create hello.tsx starter project"
|
|
40
|
-
/>
|
|
37
|
+
<CommandRow name="hello" description="create hello.tsx starter video" />
|
|
41
38
|
<CommandRow name="render" description="render jsx component to video" />
|
|
42
39
|
<CommandRow name="preview" description="fast preview (placeholders)" />
|
|
40
|
+
<CommandRow name="init" description="full setup with api keys" />
|
|
43
41
|
<CommandRow
|
|
44
42
|
name="studio"
|
|
45
43
|
description="visual editor at localhost:8282"
|
|
@@ -1,10 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
symlinkSync,
|
|
6
|
+
unlinkSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
4
10
|
import { defineCommand } from "citty";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
+
};
|
|
8
37
|
|
|
9
38
|
const HELLO_TEMPLATE = `import { Render, Clip, Image, Video, assets } from "vargai/react";
|
|
10
39
|
import { fal } from "vargai/ai";
|
|
@@ -42,39 +71,143 @@ export default (
|
|
|
42
71
|
);
|
|
43
72
|
`;
|
|
44
73
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
const SKILL_MD = `---
|
|
75
|
+
name: varg-video-generation
|
|
76
|
+
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.
|
|
77
|
+
license: MIT
|
|
78
|
+
metadata:
|
|
79
|
+
author: vargHQ
|
|
80
|
+
version: "1.0.0"
|
|
81
|
+
compatibility: Requires bun runtime. FAL_API_KEY required. Optional ELEVENLABS_API_KEY, REPLICATE_API_TOKEN, GROQ_API_KEY
|
|
82
|
+
allowed-tools: Bash(bun:*) Bash(cat:*) Read Write Edit
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
# Video Generation with varg React Engine
|
|
86
|
+
|
|
87
|
+
## Overview
|
|
88
|
+
|
|
89
|
+
This rule helps you generate AI videos using the varg SDK's React engine. It provides:
|
|
90
|
+
- Declarative JSX syntax for video composition
|
|
91
|
+
- Automatic caching (same props = instant cache hit)
|
|
92
|
+
- Parallel generation where possible
|
|
93
|
+
- Support for images, video, music, voice, and captions
|
|
94
|
+
|
|
95
|
+
## Step 1: Onboarding (REQUIRED for new users)
|
|
96
|
+
|
|
97
|
+
Before generating videos, ensure the user has the required API keys configured.
|
|
98
|
+
|
|
99
|
+
### Check Current Setup
|
|
100
|
+
|
|
101
|
+
Run this command to check existing configuration:
|
|
102
|
+
|
|
103
|
+
\`\`\`bash
|
|
104
|
+
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"
|
|
105
|
+
\`\`\`
|
|
106
|
+
|
|
107
|
+
### Required: FAL_API_KEY
|
|
108
|
+
|
|
109
|
+
**This is the minimum requirement for video generation.**
|
|
110
|
+
|
|
111
|
+
| Detail | Value |
|
|
112
|
+
|--------|-------|
|
|
113
|
+
| Provider | Fal.ai |
|
|
114
|
+
| Get it | https://fal.ai/dashboard/keys |
|
|
115
|
+
| Free tier | Yes (limited credits) |
|
|
116
|
+
| Used for | Image generation (Flux), Video generation (Wan 2.5, Kling) |
|
|
117
|
+
|
|
118
|
+
If user doesn't have \`FAL_API_KEY\`:
|
|
119
|
+
1. Direct them to https://fal.ai/dashboard/keys
|
|
120
|
+
2. They need to create an account and generate an API key
|
|
121
|
+
3. Add to \`.env\` file in project root
|
|
122
|
+
|
|
123
|
+
### Optional Keys (warn if missing, but continue)
|
|
124
|
+
|
|
125
|
+
| Feature | Required Key | Provider | Get It |
|
|
126
|
+
|---------|-------------|----------|--------|
|
|
127
|
+
| Music generation | \`ELEVENLABS_API_KEY\` | ElevenLabs | https://elevenlabs.io/app/settings/api-keys |
|
|
128
|
+
| Voice/Speech | \`ELEVENLABS_API_KEY\` | ElevenLabs | https://elevenlabs.io/app/settings/api-keys |
|
|
129
|
+
| Lipsync | \`REPLICATE_API_TOKEN\` | Replicate | https://replicate.com/account/api-tokens |
|
|
130
|
+
| Transcription | \`GROQ_API_KEY\` | Groq | https://console.groq.com/keys |
|
|
131
|
+
|
|
132
|
+
**When keys are missing, inform user what features are unavailable.**
|
|
133
|
+
|
|
134
|
+
## Step 2: Running Videos
|
|
135
|
+
|
|
136
|
+
\`\`\`bash
|
|
137
|
+
bunx vargai render video.tsx
|
|
138
|
+
\`\`\`
|
|
139
|
+
|
|
140
|
+
## Key Components
|
|
141
|
+
|
|
142
|
+
| Component | Purpose | Required Key |
|
|
143
|
+
|-----------|---------|--------------|
|
|
144
|
+
| \`<Render>\` | Root container | - |
|
|
145
|
+
| \`<Clip>\` | Sequential segment | - |
|
|
146
|
+
| \`<Image>\` | AI image | FAL |
|
|
147
|
+
| \`<Video>\` | AI video | FAL |
|
|
148
|
+
| \`<Music>\` | Background music | ElevenLabs |
|
|
149
|
+
| \`<Speech>\` | Text-to-speech | ElevenLabs |
|
|
150
|
+
|
|
151
|
+
## Common Patterns
|
|
152
|
+
|
|
153
|
+
### Character Consistency
|
|
154
|
+
\`\`\`tsx
|
|
155
|
+
const character = Image({ prompt: "blue robot" });
|
|
156
|
+
// Reuse same reference for consistent appearance
|
|
157
|
+
<Video prompt={{ text: "waving", images: [character] }} />
|
|
158
|
+
<Video prompt={{ text: "dancing", images: [character] }} />
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
### Transitions
|
|
162
|
+
\`\`\`tsx
|
|
163
|
+
<Clip transition={{ name: "fade", duration: 0.5 }}>
|
|
164
|
+
// Options: fade, crossfade, wipeleft, cube, slideup, etc.
|
|
165
|
+
\`\`\`
|
|
166
|
+
|
|
167
|
+
### Aspect Ratios
|
|
168
|
+
- \`9:16\` - TikTok, Reels, Shorts (vertical)
|
|
169
|
+
- \`16:9\` - YouTube (horizontal)
|
|
170
|
+
- \`1:1\` - Instagram (square)
|
|
171
|
+
`;
|
|
172
|
+
|
|
173
|
+
const ENV_TEMPLATE = `# Varg AI Video Generation - API Keys
|
|
174
|
+
|
|
175
|
+
# REQUIRED - Fal.ai (image & video generation)
|
|
176
|
+
# Get it: https://fal.ai/dashboard/keys
|
|
177
|
+
FAL_API_KEY=
|
|
178
|
+
|
|
179
|
+
# OPTIONAL - ElevenLabs (music & voice)
|
|
180
|
+
# Get it: https://elevenlabs.io/app/settings/api-keys
|
|
181
|
+
ELEVENLABS_API_KEY=
|
|
182
|
+
|
|
183
|
+
# OPTIONAL - Replicate (lipsync)
|
|
184
|
+
# Get it: https://replicate.com/account/api-tokens
|
|
185
|
+
REPLICATE_API_TOKEN=
|
|
186
|
+
|
|
187
|
+
# OPTIONAL - Groq (transcription)
|
|
188
|
+
# Get it: https://console.groq.com/keys
|
|
189
|
+
GROQ_API_KEY=
|
|
190
|
+
`;
|
|
69
191
|
|
|
70
192
|
export function showInitHelp() {
|
|
71
|
-
|
|
193
|
+
console.log(`
|
|
194
|
+
${COLORS.bold}vargai init${COLORS.reset}
|
|
195
|
+
|
|
196
|
+
initialize a new varg project with api key setup and hello.tsx template.
|
|
197
|
+
|
|
198
|
+
${COLORS.bold}USAGE${COLORS.reset}
|
|
199
|
+
vargai init [directory]
|
|
200
|
+
|
|
201
|
+
${COLORS.bold}EXAMPLES${COLORS.reset}
|
|
202
|
+
${COLORS.cyan}vargai init${COLORS.reset} setup in current directory
|
|
203
|
+
${COLORS.cyan}vargai init my-project${COLORS.reset} setup in my-project/
|
|
204
|
+
`);
|
|
72
205
|
}
|
|
73
206
|
|
|
74
207
|
export const initCmd = defineCommand({
|
|
75
208
|
meta: {
|
|
76
209
|
name: "init",
|
|
77
|
-
description: "
|
|
210
|
+
description: "setup project with api keys and hello.tsx",
|
|
78
211
|
},
|
|
79
212
|
args: {
|
|
80
213
|
directory: {
|
|
@@ -85,32 +218,194 @@ export const initCmd = defineCommand({
|
|
|
85
218
|
},
|
|
86
219
|
async run({ args }) {
|
|
87
220
|
const dir = (args.directory as string) || ".";
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
221
|
+
const cwd = dir === "." ? process.cwd() : join(process.cwd(), dir);
|
|
222
|
+
|
|
223
|
+
console.log(`
|
|
224
|
+
${COLORS.bold}${COLORS.cyan}
|
|
225
|
+
██╗ ██╗ █████╗ ██████╗ ██████╗
|
|
226
|
+
██║ ██║██╔══██╗██╔══██╗██╔════╝
|
|
227
|
+
██║ ██║███████║██████╔╝██║ ███╗
|
|
228
|
+
╚██╗ ██╔╝██╔══██║██╔══██╗██║ ██║
|
|
229
|
+
╚████╔╝ ██║ ██║██║ ██║╚██████╔╝
|
|
230
|
+
╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝
|
|
231
|
+
${COLORS.reset}
|
|
232
|
+
${COLORS.bold}AI Video Generation Setup${COLORS.reset}
|
|
233
|
+
`);
|
|
234
|
+
|
|
235
|
+
// Step 1: Create directory structure
|
|
236
|
+
log.step("Setting up project structure");
|
|
91
237
|
|
|
92
|
-
if (!existsSync(
|
|
93
|
-
mkdirSync(
|
|
94
|
-
|
|
238
|
+
if (!existsSync(cwd) && dir !== ".") {
|
|
239
|
+
mkdirSync(cwd, { recursive: true });
|
|
240
|
+
log.success(`Created ${dir}/`);
|
|
95
241
|
}
|
|
96
242
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
243
|
+
const dirs = ["output", ".cache/ai"];
|
|
244
|
+
for (const d of dirs) {
|
|
245
|
+
const path = join(cwd, d);
|
|
246
|
+
if (!existsSync(path)) {
|
|
247
|
+
mkdirSync(path, { recursive: true });
|
|
248
|
+
log.success(`Created ${d}/`);
|
|
249
|
+
} else {
|
|
250
|
+
log.info(`${d}/ already exists`);
|
|
251
|
+
}
|
|
100
252
|
}
|
|
101
253
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
254
|
+
// Step 2: Check/create .env and prompt for FAL_API_KEY
|
|
255
|
+
log.step("Checking API keys");
|
|
256
|
+
|
|
257
|
+
const envPath = join(cwd, ".env");
|
|
258
|
+
let envContent = "";
|
|
259
|
+
let hasFalKey = false;
|
|
260
|
+
|
|
261
|
+
if (existsSync(envPath)) {
|
|
262
|
+
envContent = readFileSync(envPath, "utf8");
|
|
263
|
+
hasFalKey = /^FAL_API_KEY=.+/m.test(envContent);
|
|
264
|
+
|
|
265
|
+
if (hasFalKey) {
|
|
266
|
+
log.success("FAL_API_KEY found in .env");
|
|
267
|
+
} else {
|
|
268
|
+
log.warn("FAL_API_KEY not found in .env");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const hasElevenLabs = /^ELEVENLABS_API_KEY=.+/m.test(envContent);
|
|
272
|
+
const hasReplicate = /^REPLICATE_API_TOKEN=.+/m.test(envContent);
|
|
273
|
+
const hasGroq = /^GROQ_API_KEY=.+/m.test(envContent);
|
|
274
|
+
|
|
275
|
+
if (hasElevenLabs)
|
|
276
|
+
log.info("ELEVENLABS_API_KEY found (music/voice enabled)");
|
|
277
|
+
if (hasReplicate) log.info("REPLICATE_API_TOKEN found (lipsync enabled)");
|
|
278
|
+
if (hasGroq) log.info("GROQ_API_KEY found (transcription enabled)");
|
|
279
|
+
} else {
|
|
280
|
+
log.warn(".env file not found");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!hasFalKey) {
|
|
284
|
+
console.log(`
|
|
285
|
+
${COLORS.yellow}FAL_API_KEY is required for video generation.${COLORS.reset}
|
|
286
|
+
|
|
287
|
+
Get your free API key at: ${COLORS.cyan}https://fal.ai/dashboard/keys${COLORS.reset}
|
|
288
|
+
`);
|
|
289
|
+
|
|
290
|
+
process.stdout.write("Enter your FAL_API_KEY (or press Enter to skip): ");
|
|
291
|
+
|
|
292
|
+
const falKey = await new Promise<string>((resolve) => {
|
|
293
|
+
process.stdin.setEncoding("utf8");
|
|
294
|
+
process.stdin.once("data", (data) => {
|
|
295
|
+
resolve(data.toString().trim());
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (falKey) {
|
|
300
|
+
if (existsSync(envPath)) {
|
|
301
|
+
const newEnvContent = envContent.includes("FAL_API_KEY")
|
|
302
|
+
? envContent.replace(/^FAL_API_KEY=.*/m, `FAL_API_KEY=${falKey}`)
|
|
303
|
+
: `${envContent}\nFAL_API_KEY=${falKey}`;
|
|
304
|
+
writeFileSync(envPath, newEnvContent);
|
|
305
|
+
} else {
|
|
306
|
+
writeFileSync(
|
|
307
|
+
envPath,
|
|
308
|
+
ENV_TEMPLATE.replace("FAL_API_KEY=", `FAL_API_KEY=${falKey}`),
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
log.success("FAL_API_KEY saved to .env");
|
|
312
|
+
hasFalKey = true;
|
|
313
|
+
} else {
|
|
314
|
+
if (!existsSync(envPath)) {
|
|
315
|
+
writeFileSync(envPath, ENV_TEMPLATE);
|
|
316
|
+
log.info("Created .env template - add your keys manually");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Step 3: Install Agent Skills
|
|
322
|
+
log.step("Installing Agent Skills");
|
|
323
|
+
|
|
324
|
+
const skillsDir = join(cwd, ".claude/skills/varg-video-generation");
|
|
325
|
+
const skillPath = join(skillsDir, "SKILL.md");
|
|
326
|
+
|
|
327
|
+
if (!existsSync(skillsDir)) {
|
|
328
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
writeFileSync(skillPath, SKILL_MD);
|
|
332
|
+
log.success("Installed SKILL.md (Agent Skills format)");
|
|
333
|
+
|
|
334
|
+
const rulesDir = join(cwd, ".claude/rules");
|
|
335
|
+
const rulePath = join(rulesDir, "video-generation.md");
|
|
336
|
+
|
|
337
|
+
if (!existsSync(rulesDir)) {
|
|
338
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (existsSync(rulePath)) {
|
|
342
|
+
unlinkSync(rulePath);
|
|
105
343
|
}
|
|
106
344
|
|
|
107
|
-
|
|
108
|
-
|
|
345
|
+
symlinkSync("../skills/varg-video-generation/SKILL.md", rulePath);
|
|
346
|
+
log.success("Created Claude Code rules symlink");
|
|
347
|
+
|
|
348
|
+
// Step 4: Create hello.tsx
|
|
349
|
+
log.step("Creating hello.tsx");
|
|
350
|
+
|
|
351
|
+
const helloPath = join(cwd, "hello.tsx");
|
|
352
|
+
if (!existsSync(helloPath)) {
|
|
353
|
+
writeFileSync(helloPath, HELLO_TEMPLATE);
|
|
354
|
+
log.success("Created hello.tsx");
|
|
109
355
|
} else {
|
|
110
|
-
|
|
111
|
-
console.log(`created ${helloPath}`);
|
|
356
|
+
log.info("hello.tsx already exists");
|
|
112
357
|
}
|
|
113
358
|
|
|
114
|
-
|
|
359
|
+
// Step 5: Update .gitignore
|
|
360
|
+
log.step("Updating .gitignore");
|
|
361
|
+
|
|
362
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
363
|
+
const gitignoreEntries = [".env", ".cache/", "output/"];
|
|
364
|
+
let gitignoreContent = existsSync(gitignorePath)
|
|
365
|
+
? readFileSync(gitignorePath, "utf8")
|
|
366
|
+
: "";
|
|
367
|
+
|
|
368
|
+
let added = false;
|
|
369
|
+
for (const entry of gitignoreEntries) {
|
|
370
|
+
if (!gitignoreContent.includes(entry)) {
|
|
371
|
+
gitignoreContent += `\n${entry}`;
|
|
372
|
+
added = true;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (added) {
|
|
377
|
+
writeFileSync(gitignorePath, gitignoreContent.trim() + "\n");
|
|
378
|
+
log.success("Updated .gitignore");
|
|
379
|
+
} else {
|
|
380
|
+
log.info(".gitignore already configured");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Summary
|
|
384
|
+
console.log(`
|
|
385
|
+
${COLORS.green}${COLORS.bold}Setup complete!${COLORS.reset}
|
|
386
|
+
|
|
387
|
+
${COLORS.bold}What was installed:${COLORS.reset}
|
|
388
|
+
${COLORS.dim}├─${COLORS.reset} hello.tsx ${COLORS.dim}(starter video)${COLORS.reset}
|
|
389
|
+
${COLORS.dim}├─${COLORS.reset} .claude/skills/varg-video-generation/SKILL.md ${COLORS.dim}(Agent Skills)${COLORS.reset}
|
|
390
|
+
${COLORS.dim}├─${COLORS.reset} output/ ${COLORS.dim}(video output folder)${COLORS.reset}
|
|
391
|
+
${COLORS.dim}└─${COLORS.reset} .cache/ai/ ${COLORS.dim}(generation cache)${COLORS.reset}
|
|
392
|
+
|
|
393
|
+
${COLORS.bold}Next steps:${COLORS.reset}
|
|
394
|
+
`);
|
|
395
|
+
|
|
396
|
+
if (!hasFalKey) {
|
|
397
|
+
console.log(` ${COLORS.yellow}1. Add FAL_API_KEY to .env${COLORS.reset}
|
|
398
|
+
Get it at: https://fal.ai/dashboard/keys
|
|
399
|
+
`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
console.log(` ${hasFalKey ? "1" : "2"}. Render your first video:
|
|
403
|
+
${COLORS.cyan}bunx vargai render hello.tsx${COLORS.reset}
|
|
404
|
+
|
|
405
|
+
${hasFalKey ? "2" : "3"}. Or ask Claude to create a video:
|
|
406
|
+
${COLORS.cyan}claude "create a 10 second tiktok video about cats"${COLORS.reset}
|
|
407
|
+
|
|
408
|
+
${COLORS.dim}Documentation: https://github.com/vargHQ/sdk${COLORS.reset}
|
|
409
|
+
`);
|
|
115
410
|
},
|
|
116
411
|
});
|
package/src/cli/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { registry } from "../core/registry";
|
|
|
13
13
|
import { allDefinitions } from "../definitions";
|
|
14
14
|
import {
|
|
15
15
|
findCmd,
|
|
16
|
+
helloCmd,
|
|
16
17
|
helpCmd,
|
|
17
18
|
initCmd,
|
|
18
19
|
listCmd,
|
|
@@ -104,11 +105,12 @@ const pkg = await import("../../package.json");
|
|
|
104
105
|
|
|
105
106
|
const main = defineCommand({
|
|
106
107
|
meta: {
|
|
107
|
-
name: "
|
|
108
|
+
name: "vargai",
|
|
108
109
|
version: pkg.version,
|
|
109
|
-
description: "ai video
|
|
110
|
+
description: "ai video generation sdk",
|
|
110
111
|
},
|
|
111
112
|
subCommands: {
|
|
113
|
+
hello: helloCmd,
|
|
112
114
|
init: initCmd,
|
|
113
115
|
render: renderCmd,
|
|
114
116
|
preview: previewCmd,
|
package/src/init.ts
DELETED
|
@@ -1,523 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* vargai init - Setup script for AI video generation
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* bunx vargai init
|
|
7
|
-
* bun run src/init.ts
|
|
8
|
-
*
|
|
9
|
-
* This script:
|
|
10
|
-
* 1. Checks/creates .env with required API keys
|
|
11
|
-
* 2. Installs Claude Code rules for video generation
|
|
12
|
-
* 3. Creates example files
|
|
13
|
-
* 4. Verifies the setup works
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
existsSync,
|
|
18
|
-
mkdirSync,
|
|
19
|
-
readFileSync,
|
|
20
|
-
symlinkSync,
|
|
21
|
-
unlinkSync,
|
|
22
|
-
writeFileSync,
|
|
23
|
-
} from "node:fs";
|
|
24
|
-
import { join } from "node:path";
|
|
25
|
-
|
|
26
|
-
const COLORS = {
|
|
27
|
-
reset: "\x1b[0m",
|
|
28
|
-
bold: "\x1b[1m",
|
|
29
|
-
dim: "\x1b[2m",
|
|
30
|
-
green: "\x1b[32m",
|
|
31
|
-
yellow: "\x1b[33m",
|
|
32
|
-
blue: "\x1b[34m",
|
|
33
|
-
red: "\x1b[31m",
|
|
34
|
-
cyan: "\x1b[36m",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const log = {
|
|
38
|
-
info: (msg: string) =>
|
|
39
|
-
console.log(`${COLORS.blue}info${COLORS.reset} ${msg}`),
|
|
40
|
-
success: (msg: string) =>
|
|
41
|
-
console.log(`${COLORS.green}done${COLORS.reset} ${msg}`),
|
|
42
|
-
warn: (msg: string) =>
|
|
43
|
-
console.log(`${COLORS.yellow}warn${COLORS.reset} ${msg}`),
|
|
44
|
-
error: (msg: string) =>
|
|
45
|
-
console.log(`${COLORS.red}error${COLORS.reset} ${msg}`),
|
|
46
|
-
step: (msg: string) =>
|
|
47
|
-
console.log(
|
|
48
|
-
`\n${COLORS.bold}${COLORS.cyan}==>${COLORS.reset} ${COLORS.bold}${msg}${COLORS.reset}`,
|
|
49
|
-
),
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// Agent Skills format SKILL.md
|
|
53
|
-
const SKILL_MD = `---
|
|
54
|
-
name: varg-video-generation
|
|
55
|
-
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.
|
|
56
|
-
license: MIT
|
|
57
|
-
metadata:
|
|
58
|
-
author: vargHQ
|
|
59
|
-
version: "1.0.0"
|
|
60
|
-
compatibility: Requires bun runtime. FAL_API_KEY required. Optional ELEVENLABS_API_KEY, REPLICATE_API_TOKEN, GROQ_API_KEY
|
|
61
|
-
allowed-tools: Bash(bun:*) Bash(cat:*) Read Write Edit
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
# Video Generation with varg React Engine
|
|
65
|
-
|
|
66
|
-
## Overview
|
|
67
|
-
|
|
68
|
-
This rule helps you generate AI videos using the varg SDK's React engine. It provides:
|
|
69
|
-
- Declarative JSX syntax for video composition
|
|
70
|
-
- Automatic caching (same props = instant cache hit)
|
|
71
|
-
- Parallel generation where possible
|
|
72
|
-
- Support for images, video, music, voice, and captions
|
|
73
|
-
|
|
74
|
-
## Step 1: Onboarding (REQUIRED for new users)
|
|
75
|
-
|
|
76
|
-
Before generating videos, ensure the user has the required API keys configured.
|
|
77
|
-
|
|
78
|
-
### Check Current Setup
|
|
79
|
-
|
|
80
|
-
Run this command to check existing configuration:
|
|
81
|
-
|
|
82
|
-
\`\`\`bash
|
|
83
|
-
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"
|
|
84
|
-
\`\`\`
|
|
85
|
-
|
|
86
|
-
### Required: FAL_API_KEY
|
|
87
|
-
|
|
88
|
-
**This is the minimum requirement for video generation.**
|
|
89
|
-
|
|
90
|
-
| Detail | Value |
|
|
91
|
-
|--------|-------|
|
|
92
|
-
| Provider | Fal.ai |
|
|
93
|
-
| Get it | https://fal.ai/dashboard/keys |
|
|
94
|
-
| Free tier | Yes (limited credits) |
|
|
95
|
-
| Used for | Image generation (Flux), Video generation (Wan 2.5, Kling) |
|
|
96
|
-
|
|
97
|
-
If user doesn't have \`FAL_API_KEY\`:
|
|
98
|
-
1. Direct them to https://fal.ai/dashboard/keys
|
|
99
|
-
2. They need to create an account and generate an API key
|
|
100
|
-
3. Add to \`.env\` file in project root
|
|
101
|
-
|
|
102
|
-
### Optional Keys (warn if missing, but continue)
|
|
103
|
-
|
|
104
|
-
| Feature | Required Key | Provider | Get It |
|
|
105
|
-
|---------|-------------|----------|--------|
|
|
106
|
-
| Music generation | \`ELEVENLABS_API_KEY\` | ElevenLabs | https://elevenlabs.io/app/settings/api-keys |
|
|
107
|
-
| Voice/Speech | \`ELEVENLABS_API_KEY\` | ElevenLabs | https://elevenlabs.io/app/settings/api-keys |
|
|
108
|
-
| Lipsync | \`REPLICATE_API_TOKEN\` | Replicate | https://replicate.com/account/api-tokens |
|
|
109
|
-
| Transcription | \`GROQ_API_KEY\` | Groq | https://console.groq.com/keys |
|
|
110
|
-
|
|
111
|
-
**When keys are missing, inform user what features are unavailable.**
|
|
112
|
-
|
|
113
|
-
## Step 2: Quick Templates
|
|
114
|
-
|
|
115
|
-
### Simple Slideshow (FAL only)
|
|
116
|
-
|
|
117
|
-
\`\`\`tsx
|
|
118
|
-
import { render, Render, Clip, Image } from "vargai/react";
|
|
119
|
-
|
|
120
|
-
const SCENES = [
|
|
121
|
-
"sunset over ocean, cinematic",
|
|
122
|
-
"mountain peaks at dawn, misty",
|
|
123
|
-
"city skyline at night, neon",
|
|
124
|
-
];
|
|
125
|
-
|
|
126
|
-
await render(
|
|
127
|
-
<Render width={1080} height={1920}>
|
|
128
|
-
{SCENES.map((prompt, i) => (
|
|
129
|
-
<Clip key={i} duration={3} transition={{ name: "fade", duration: 0.5 }}>
|
|
130
|
-
<Image prompt={prompt} zoom="in" />
|
|
131
|
-
</Clip>
|
|
132
|
-
))}
|
|
133
|
-
</Render>,
|
|
134
|
-
{ output: "output/slideshow.mp4" }
|
|
135
|
-
);
|
|
136
|
-
\`\`\`
|
|
137
|
-
|
|
138
|
-
### Animated Video (FAL + ElevenLabs)
|
|
139
|
-
|
|
140
|
-
\`\`\`tsx
|
|
141
|
-
import { render, Render, Clip, Image, Animate, Music } from "vargai/react";
|
|
142
|
-
import { fal, elevenlabs } from "vargai/ai";
|
|
143
|
-
|
|
144
|
-
await render(
|
|
145
|
-
<Render width={1080} height={1920}>
|
|
146
|
-
<Music prompt="upbeat electronic" model={elevenlabs.musicModel()} duration={10} />
|
|
147
|
-
<Clip duration={5}>
|
|
148
|
-
<Animate
|
|
149
|
-
image={Image({ prompt: "cute cat on windowsill" })}
|
|
150
|
-
motion="cat turns head, blinks slowly"
|
|
151
|
-
model={fal.videoModel("wan-2.5")}
|
|
152
|
-
duration={5}
|
|
153
|
-
/>
|
|
154
|
-
</Clip>
|
|
155
|
-
</Render>,
|
|
156
|
-
{ output: "output/video.mp4" }
|
|
157
|
-
);
|
|
158
|
-
\`\`\`
|
|
159
|
-
|
|
160
|
-
### Talking Character
|
|
161
|
-
|
|
162
|
-
\`\`\`tsx
|
|
163
|
-
import { render, Render, Clip, Image, Animate, Speech } from "vargai/react";
|
|
164
|
-
import { fal, elevenlabs } from "vargai/ai";
|
|
165
|
-
|
|
166
|
-
await render(
|
|
167
|
-
<Render width={1080} height={1920}>
|
|
168
|
-
<Clip duration="auto">
|
|
169
|
-
<Animate
|
|
170
|
-
image={Image({ prompt: "friendly robot, blue metallic", aspectRatio: "9:16" })}
|
|
171
|
-
motion="robot talking, subtle head movements"
|
|
172
|
-
model={fal.videoModel("wan-2.5")}
|
|
173
|
-
/>
|
|
174
|
-
<Speech voice="adam" model={elevenlabs.speechModel("turbo")}>
|
|
175
|
-
Hello! I'm your AI assistant. Let's create something amazing!
|
|
176
|
-
</Speech>
|
|
177
|
-
</Clip>
|
|
178
|
-
</Render>,
|
|
179
|
-
{ output: "output/talking-robot.mp4" }
|
|
180
|
-
);
|
|
181
|
-
\`\`\`
|
|
182
|
-
|
|
183
|
-
## Step 3: Running Videos
|
|
184
|
-
|
|
185
|
-
\`\`\`bash
|
|
186
|
-
bun run your-video.tsx
|
|
187
|
-
\`\`\`
|
|
188
|
-
|
|
189
|
-
## Key Components
|
|
190
|
-
|
|
191
|
-
| Component | Purpose | Required Key |
|
|
192
|
-
|-----------|---------|--------------|
|
|
193
|
-
| \`<Render>\` | Root container | - |
|
|
194
|
-
| \`<Clip>\` | Sequential segment | - |
|
|
195
|
-
| \`<Image>\` | AI image | FAL |
|
|
196
|
-
| \`<Animate>\` | Image-to-video | FAL |
|
|
197
|
-
| \`<Music>\` | Background music | ElevenLabs |
|
|
198
|
-
| \`<Speech>\` | Text-to-speech | ElevenLabs |
|
|
199
|
-
|
|
200
|
-
## Common Patterns
|
|
201
|
-
|
|
202
|
-
### Character Consistency
|
|
203
|
-
\`\`\`tsx
|
|
204
|
-
const character = Image({ prompt: "blue robot" });
|
|
205
|
-
// Reuse same reference for consistent appearance
|
|
206
|
-
<Animate image={character} motion="waving" />
|
|
207
|
-
<Animate image={character} motion="dancing" />
|
|
208
|
-
\`\`\`
|
|
209
|
-
|
|
210
|
-
### Transitions
|
|
211
|
-
\`\`\`tsx
|
|
212
|
-
<Clip transition={{ name: "fade", duration: 0.5 }}>
|
|
213
|
-
// Options: fade, crossfade, wipeleft, cube, slideup, etc.
|
|
214
|
-
\`\`\`
|
|
215
|
-
|
|
216
|
-
### Aspect Ratios
|
|
217
|
-
- \`9:16\` - TikTok, Reels, Shorts (vertical)
|
|
218
|
-
- \`16:9\` - YouTube (horizontal)
|
|
219
|
-
- \`1:1\` - Instagram (square)
|
|
220
|
-
|
|
221
|
-
## Troubleshooting
|
|
222
|
-
|
|
223
|
-
### "FAL_API_KEY not found"
|
|
224
|
-
- Check \`.env\` file exists in project root
|
|
225
|
-
- Ensure no spaces around \`=\` sign
|
|
226
|
-
- Restart terminal after adding keys
|
|
227
|
-
|
|
228
|
-
### "Rate limit exceeded"
|
|
229
|
-
- Free tier has limited credits
|
|
230
|
-
- Wait or upgrade plan
|
|
231
|
-
- Use caching to avoid regenerating
|
|
232
|
-
|
|
233
|
-
## Next Steps
|
|
234
|
-
|
|
235
|
-
1. Add your FAL_API_KEY to \`.env\`
|
|
236
|
-
2. Run \`bun run examples/my-first-video.tsx\`
|
|
237
|
-
3. Or ask the agent: "create a 10 second tiktok video about cats"
|
|
238
|
-
`;
|
|
239
|
-
|
|
240
|
-
// Example video file
|
|
241
|
-
const EXAMPLE_VIDEO = `/**
|
|
242
|
-
* Example: Simple animated video
|
|
243
|
-
* Run: bun run examples/my-first-video.tsx
|
|
244
|
-
*/
|
|
245
|
-
import { render, Render, Clip, Image, Animate } from "vargai/react";
|
|
246
|
-
import { fal } from "vargai/ai";
|
|
247
|
-
|
|
248
|
-
async function main() {
|
|
249
|
-
console.log("Creating your first AI video...\\n");
|
|
250
|
-
|
|
251
|
-
await render(
|
|
252
|
-
<Render width={720} height={720}>
|
|
253
|
-
<Clip duration={3}>
|
|
254
|
-
<Animate
|
|
255
|
-
image={Image({
|
|
256
|
-
prompt: "a friendly robot waving hello, cartoon style, blue colors",
|
|
257
|
-
model: fal.imageModel("flux-schnell"),
|
|
258
|
-
aspectRatio: "1:1",
|
|
259
|
-
})}
|
|
260
|
-
motion="robot waves hello, friendly gesture"
|
|
261
|
-
model={fal.videoModel("wan-2.5")}
|
|
262
|
-
duration={3}
|
|
263
|
-
/>
|
|
264
|
-
</Clip>
|
|
265
|
-
</Render>,
|
|
266
|
-
{
|
|
267
|
-
output: "output/my-first-video.mp4",
|
|
268
|
-
cache: ".cache/ai"
|
|
269
|
-
}
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
console.log("\\nDone! Check output/my-first-video.mp4");
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
main().catch(console.error);
|
|
276
|
-
`;
|
|
277
|
-
|
|
278
|
-
// .env template
|
|
279
|
-
const ENV_TEMPLATE = `# Varg AI Video Generation - API Keys
|
|
280
|
-
# Get your keys from the URLs below
|
|
281
|
-
|
|
282
|
-
# REQUIRED - Fal.ai (image & video generation)
|
|
283
|
-
# Get it: https://fal.ai/dashboard/keys
|
|
284
|
-
FAL_API_KEY=
|
|
285
|
-
|
|
286
|
-
# OPTIONAL - ElevenLabs (music & voice)
|
|
287
|
-
# Get it: https://elevenlabs.io/app/settings/api-keys
|
|
288
|
-
ELEVENLABS_API_KEY=
|
|
289
|
-
|
|
290
|
-
# OPTIONAL - Replicate (lipsync)
|
|
291
|
-
# Get it: https://replicate.com/account/api-tokens
|
|
292
|
-
REPLICATE_API_TOKEN=
|
|
293
|
-
|
|
294
|
-
# OPTIONAL - Groq (transcription)
|
|
295
|
-
# Get it: https://console.groq.com/keys
|
|
296
|
-
GROQ_API_KEY=
|
|
297
|
-
`;
|
|
298
|
-
|
|
299
|
-
async function promptForKey(keyName: string, url: string): Promise<string> {
|
|
300
|
-
console.log(`\n${COLORS.cyan}${keyName}${COLORS.reset}`);
|
|
301
|
-
console.log(`${COLORS.dim}Get your key at: ${url}${COLORS.reset}`);
|
|
302
|
-
process.stdout.write(`Enter ${keyName} (or press Enter to skip): `);
|
|
303
|
-
|
|
304
|
-
const response = await new Promise<string>((resolve) => {
|
|
305
|
-
let input = "";
|
|
306
|
-
process.stdin.setEncoding("utf8");
|
|
307
|
-
process.stdin.once("data", (data) => {
|
|
308
|
-
input = data.toString().trim();
|
|
309
|
-
resolve(input);
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
return response;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
async function main() {
|
|
317
|
-
console.log(`
|
|
318
|
-
${COLORS.bold}${COLORS.cyan}
|
|
319
|
-
██╗ ██╗ █████╗ ██████╗ ██████╗
|
|
320
|
-
██║ ██║██╔══██╗██╔══██╗██╔════╝
|
|
321
|
-
██║ ██║███████║██████╔╝██║ ███╗
|
|
322
|
-
╚██╗ ██╔╝██╔══██║██╔══██╗██║ ██║
|
|
323
|
-
╚████╔╝ ██║ ██║██║ ██║╚██████╔╝
|
|
324
|
-
╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝
|
|
325
|
-
${COLORS.reset}
|
|
326
|
-
${COLORS.bold}AI Video Generation Setup${COLORS.reset}
|
|
327
|
-
`);
|
|
328
|
-
|
|
329
|
-
const cwd = process.cwd();
|
|
330
|
-
|
|
331
|
-
// Step 1: Check/create directories
|
|
332
|
-
log.step("Setting up project structure");
|
|
333
|
-
|
|
334
|
-
const dirs = ["output", ".cache/ai", "examples"];
|
|
335
|
-
for (const dir of dirs) {
|
|
336
|
-
const path = join(cwd, dir);
|
|
337
|
-
if (!existsSync(path)) {
|
|
338
|
-
mkdirSync(path, { recursive: true });
|
|
339
|
-
log.success(`Created ${dir}/`);
|
|
340
|
-
} else {
|
|
341
|
-
log.info(`${dir}/ already exists`);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Step 2: Check/create .env
|
|
346
|
-
log.step("Checking API keys");
|
|
347
|
-
|
|
348
|
-
const envPath = join(cwd, ".env");
|
|
349
|
-
let envContent = "";
|
|
350
|
-
let hasFalKey = false;
|
|
351
|
-
|
|
352
|
-
if (existsSync(envPath)) {
|
|
353
|
-
envContent = readFileSync(envPath, "utf8");
|
|
354
|
-
hasFalKey = /^FAL_API_KEY=.+/m.test(envContent);
|
|
355
|
-
|
|
356
|
-
if (hasFalKey) {
|
|
357
|
-
log.success("FAL_API_KEY found in .env");
|
|
358
|
-
} else {
|
|
359
|
-
log.warn("FAL_API_KEY not found in .env");
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Check optional keys
|
|
363
|
-
const hasElevenLabs = /^ELEVENLABS_API_KEY=.+/m.test(envContent);
|
|
364
|
-
const hasReplicate = /^REPLICATE_API_TOKEN=.+/m.test(envContent);
|
|
365
|
-
const hasGroq = /^GROQ_API_KEY=.+/m.test(envContent);
|
|
366
|
-
|
|
367
|
-
if (hasElevenLabs)
|
|
368
|
-
log.info("ELEVENLABS_API_KEY found (music/voice enabled)");
|
|
369
|
-
if (hasReplicate) log.info("REPLICATE_API_TOKEN found (lipsync enabled)");
|
|
370
|
-
if (hasGroq) log.info("GROQ_API_KEY found (transcription enabled)");
|
|
371
|
-
|
|
372
|
-
if (!hasElevenLabs && !hasReplicate && !hasGroq) {
|
|
373
|
-
log.info("No optional keys found (basic video generation only)");
|
|
374
|
-
}
|
|
375
|
-
} else {
|
|
376
|
-
log.warn(".env file not found");
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// If no FAL key, prompt for it
|
|
380
|
-
if (!hasFalKey) {
|
|
381
|
-
console.log(`
|
|
382
|
-
${COLORS.yellow}FAL_API_KEY is required for video generation.${COLORS.reset}
|
|
383
|
-
|
|
384
|
-
Get your free API key at: ${COLORS.cyan}https://fal.ai/dashboard/keys${COLORS.reset}
|
|
385
|
-
`);
|
|
386
|
-
|
|
387
|
-
process.stdout.write("Enter your FAL_API_KEY (or press Enter to skip): ");
|
|
388
|
-
|
|
389
|
-
const falKey = await new Promise<string>((resolve) => {
|
|
390
|
-
let input = "";
|
|
391
|
-
process.stdin.setEncoding("utf8");
|
|
392
|
-
process.stdin.once("data", (data) => {
|
|
393
|
-
input = data.toString().trim();
|
|
394
|
-
resolve(input);
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
if (falKey) {
|
|
399
|
-
if (existsSync(envPath)) {
|
|
400
|
-
// Append to existing .env
|
|
401
|
-
const newEnvContent = envContent.includes("FAL_API_KEY")
|
|
402
|
-
? envContent.replace(/^FAL_API_KEY=.*/m, `FAL_API_KEY=${falKey}`)
|
|
403
|
-
: `${envContent}\nFAL_API_KEY=${falKey}`;
|
|
404
|
-
writeFileSync(envPath, newEnvContent);
|
|
405
|
-
} else {
|
|
406
|
-
// Create new .env from template
|
|
407
|
-
writeFileSync(
|
|
408
|
-
envPath,
|
|
409
|
-
ENV_TEMPLATE.replace("FAL_API_KEY=", `FAL_API_KEY=${falKey}`),
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
log.success("FAL_API_KEY saved to .env");
|
|
413
|
-
hasFalKey = true;
|
|
414
|
-
} else {
|
|
415
|
-
if (!existsSync(envPath)) {
|
|
416
|
-
writeFileSync(envPath, ENV_TEMPLATE);
|
|
417
|
-
log.info("Created .env template - add your keys manually");
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Step 3: Install Agent Skills (SKILL.md) + Claude Code symlink
|
|
423
|
-
log.step("Installing Agent Skills");
|
|
424
|
-
|
|
425
|
-
// Create .claude/skills/varg-video-generation/ (Agent Skills format)
|
|
426
|
-
const skillsDir = join(cwd, ".claude/skills/varg-video-generation");
|
|
427
|
-
const skillPath = join(skillsDir, "SKILL.md");
|
|
428
|
-
|
|
429
|
-
if (!existsSync(skillsDir)) {
|
|
430
|
-
mkdirSync(skillsDir, { recursive: true });
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
writeFileSync(skillPath, SKILL_MD);
|
|
434
|
-
log.success("Installed SKILL.md (Agent Skills format)");
|
|
435
|
-
|
|
436
|
-
// Create .claude/rules/ symlink for Claude Code compatibility
|
|
437
|
-
const rulesDir = join(cwd, ".claude/rules");
|
|
438
|
-
const rulePath = join(rulesDir, "video-generation.md");
|
|
439
|
-
|
|
440
|
-
if (!existsSync(rulesDir)) {
|
|
441
|
-
mkdirSync(rulesDir, { recursive: true });
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// Remove existing file/symlink if present
|
|
445
|
-
if (existsSync(rulePath)) {
|
|
446
|
-
unlinkSync(rulePath);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Create relative symlink
|
|
450
|
-
symlinkSync("../skills/varg-video-generation/SKILL.md", rulePath);
|
|
451
|
-
log.success("Created Claude Code rules symlink");
|
|
452
|
-
|
|
453
|
-
// Step 4: Create example file
|
|
454
|
-
log.step("Creating example files");
|
|
455
|
-
|
|
456
|
-
const examplePath = join(cwd, "examples/my-first-video.tsx");
|
|
457
|
-
if (!existsSync(examplePath)) {
|
|
458
|
-
writeFileSync(examplePath, EXAMPLE_VIDEO);
|
|
459
|
-
log.success("Created examples/my-first-video.tsx");
|
|
460
|
-
} else {
|
|
461
|
-
log.info("examples/my-first-video.tsx already exists");
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Step 5: Add to .gitignore
|
|
465
|
-
log.step("Updating .gitignore");
|
|
466
|
-
|
|
467
|
-
const gitignorePath = join(cwd, ".gitignore");
|
|
468
|
-
const gitignoreEntries = [".env", ".cache/", "output/"];
|
|
469
|
-
let gitignoreContent = existsSync(gitignorePath)
|
|
470
|
-
? readFileSync(gitignorePath, "utf8")
|
|
471
|
-
: "";
|
|
472
|
-
|
|
473
|
-
let added = false;
|
|
474
|
-
for (const entry of gitignoreEntries) {
|
|
475
|
-
if (!gitignoreContent.includes(entry)) {
|
|
476
|
-
gitignoreContent += `\n${entry}`;
|
|
477
|
-
added = true;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (added) {
|
|
482
|
-
writeFileSync(gitignorePath, gitignoreContent.trim() + "\n");
|
|
483
|
-
log.success("Updated .gitignore");
|
|
484
|
-
} else {
|
|
485
|
-
log.info(".gitignore already configured");
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Summary
|
|
489
|
-
console.log(`
|
|
490
|
-
${COLORS.green}${COLORS.bold}Setup complete!${COLORS.reset}
|
|
491
|
-
|
|
492
|
-
${COLORS.bold}What was installed:${COLORS.reset}
|
|
493
|
-
${COLORS.dim}├─${COLORS.reset} .claude/skills/varg-video-generation/SKILL.md ${COLORS.dim}(Agent Skills)${COLORS.reset}
|
|
494
|
-
${COLORS.dim}├─${COLORS.reset} .claude/rules/video-generation.md ${COLORS.dim}(symlink for Claude Code)${COLORS.reset}
|
|
495
|
-
${COLORS.dim}├─${COLORS.reset} examples/my-first-video.tsx ${COLORS.dim}(starter example)${COLORS.reset}
|
|
496
|
-
${COLORS.dim}├─${COLORS.reset} output/ ${COLORS.dim}(video output folder)${COLORS.reset}
|
|
497
|
-
${COLORS.dim}└─${COLORS.reset} .cache/ai/ ${COLORS.dim}(generation cache)${COLORS.reset}
|
|
498
|
-
|
|
499
|
-
${COLORS.bold}Next steps:${COLORS.reset}
|
|
500
|
-
`);
|
|
501
|
-
|
|
502
|
-
if (!hasFalKey) {
|
|
503
|
-
console.log(` ${COLORS.yellow}1. Add FAL_API_KEY to .env${COLORS.reset}
|
|
504
|
-
Get it at: https://fal.ai/dashboard/keys
|
|
505
|
-
`);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
console.log(` ${hasFalKey ? "1" : "2"}. Run your first video:
|
|
509
|
-
${COLORS.cyan}bun run examples/my-first-video.tsx${COLORS.reset}
|
|
510
|
-
|
|
511
|
-
${hasFalKey ? "2" : "3"}. Or ask Claude Code to create a video:
|
|
512
|
-
${COLORS.cyan}claude "create a 10 second tiktok video about cats"${COLORS.reset}
|
|
513
|
-
|
|
514
|
-
${COLORS.dim}Documentation: https://github.com/vargHQ/sdk${COLORS.reset}
|
|
515
|
-
`);
|
|
516
|
-
|
|
517
|
-
process.exit(0);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
main().catch((error) => {
|
|
521
|
-
log.error(error.message);
|
|
522
|
-
process.exit(1);
|
|
523
|
-
});
|