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 CHANGED
@@ -3,8 +3,7 @@
3
3
  "module": "src/index.ts",
4
4
  "type": "module",
5
5
  "bin": {
6
- "varg": "./src/cli/index.ts",
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-alpha19",
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: "varg init", description: "create hello.tsx starter" },
25
- { command: "varg render hello.tsx", description: "render jsx to video" },
26
- { command: "varg preview hello.tsx", description: "fast preview mode" },
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="varg">
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,4 +1,5 @@
1
1
  export { findCmd, showFindHelp } from "./find.tsx";
2
+ export { helloCmd } from "./hello.ts";
2
3
  export { helpCmd, showHelp } from "./help.tsx";
3
4
  export { initCmd, showInitHelp } from "./init.tsx";
4
5
  export { listCmd, showListHelp } from "./list.tsx";
@@ -1,10 +1,39 @@
1
- /** @jsxImportSource react */
2
-
3
- import { existsSync, mkdirSync } from "node:fs";
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
- import { Box, Text } from "ink";
6
- import { Header, HelpBlock, VargBox, VargText } from "../ui/index.ts";
7
- import { renderStatic } from "../ui/render.ts";
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
- function InitHelpView() {
46
- const examples = [
47
- { command: "varg init", description: "create hello.tsx in current dir" },
48
- { command: "varg init my-project", description: "create in my-project/" },
49
- ];
50
-
51
- return (
52
- <VargBox title="varg init">
53
- <Box marginBottom={1}>
54
- <Text>initialize a new varg project with hello.tsx template.</Text>
55
- </Box>
56
-
57
- <Header>USAGE</Header>
58
- <Box paddingLeft={2} marginBottom={1}>
59
- <VargText variant="accent">varg init [directory]</VargText>
60
- </Box>
61
-
62
- <Header>EXAMPLES</Header>
63
- <Box marginTop={1}>
64
- <HelpBlock examples={examples} />
65
- </Box>
66
- </VargBox>
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
- renderStatic(<InitHelpView />);
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: "initialize project with hello.tsx",
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 outputDir = `${dir}/output`;
89
- const cacheDir = `${dir}/.cache/ai`;
90
- const helloPath = `${dir}/hello.tsx`;
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(dir) && dir !== ".") {
93
- mkdirSync(dir, { recursive: true });
94
- console.log(`created ${dir}/`);
238
+ if (!existsSync(cwd) && dir !== ".") {
239
+ mkdirSync(cwd, { recursive: true });
240
+ log.success(`Created ${dir}/`);
95
241
  }
96
242
 
97
- if (!existsSync(outputDir)) {
98
- mkdirSync(outputDir, { recursive: true });
99
- console.log(`created ${outputDir}/`);
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
- if (!existsSync(cacheDir)) {
103
- mkdirSync(cacheDir, { recursive: true });
104
- console.log(`created ${cacheDir}/`);
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
- if (existsSync(helloPath)) {
108
- console.log(`hello.tsx already exists, skipping`);
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
- await Bun.write(helloPath, HELLO_TEMPLATE);
111
- console.log(`created ${helloPath}`);
356
+ log.info("hello.tsx already exists");
112
357
  }
113
358
 
114
- console.log(`\ndone! run: bunx vargai render hello.tsx`);
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: "varg",
108
+ name: "vargai",
108
109
  version: pkg.version,
109
- description: "ai video infrastructure from your terminal",
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
- });