varg.ai-sdk 0.1.1 → 0.4.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/.claude/settings.local.json +1 -1
  2. package/.env.example +3 -0
  3. package/.github/workflows/ci.yml +23 -0
  4. package/.husky/README.md +102 -0
  5. package/.husky/commit-msg +6 -0
  6. package/.husky/pre-commit +9 -0
  7. package/.husky/pre-push +6 -0
  8. package/.size-limit.json +8 -0
  9. package/.test-hooks.ts +5 -0
  10. package/CLAUDE.md +10 -3
  11. package/CONTRIBUTING.md +150 -0
  12. package/LICENSE.md +53 -0
  13. package/README.md +56 -209
  14. package/SKILLS.md +26 -10
  15. package/biome.json +7 -1
  16. package/bun.lock +1286 -0
  17. package/commitlint.config.js +22 -0
  18. package/docs/index.html +1130 -0
  19. package/docs/prompting.md +326 -0
  20. package/docs/react.md +834 -0
  21. package/docs/sdk.md +812 -0
  22. package/ffmpeg/CLAUDE.md +68 -0
  23. package/package.json +43 -10
  24. package/pipeline/cookbooks/scripts/animate-frames-parallel.ts +84 -0
  25. package/pipeline/cookbooks/scripts/combine-scenes.sh +53 -0
  26. package/pipeline/cookbooks/scripts/generate-frames-parallel.ts +99 -0
  27. package/pipeline/cookbooks/scripts/still-to-video.sh +37 -0
  28. package/pipeline/cookbooks/text-to-tiktok.md +669 -0
  29. package/pipeline/cookbooks/trendwatching.md +156 -0
  30. package/plan.md +281 -0
  31. package/scripts/.gitkeep +0 -0
  32. package/src/ai-sdk/cache.ts +142 -0
  33. package/src/ai-sdk/examples/cached-generation.ts +53 -0
  34. package/src/ai-sdk/examples/duet-scene-4.ts +53 -0
  35. package/src/ai-sdk/examples/duet-scene-5-audio.ts +32 -0
  36. package/src/ai-sdk/examples/duet-video.ts +56 -0
  37. package/src/ai-sdk/examples/editly-composition.ts +63 -0
  38. package/src/ai-sdk/examples/editly-test.ts +57 -0
  39. package/src/ai-sdk/examples/editly-video-test.ts +52 -0
  40. package/src/ai-sdk/examples/fal-lipsync.ts +43 -0
  41. package/src/ai-sdk/examples/higgsfield-image.ts +61 -0
  42. package/src/ai-sdk/examples/music-generation.ts +19 -0
  43. package/src/ai-sdk/examples/openai-sora.ts +34 -0
  44. package/src/ai-sdk/examples/replicate-bg-removal.ts +52 -0
  45. package/src/ai-sdk/examples/simpsons-scene.ts +61 -0
  46. package/src/ai-sdk/examples/talking-lion.ts +55 -0
  47. package/src/ai-sdk/examples/video-generation.ts +39 -0
  48. package/src/ai-sdk/examples/workflow-animated-girl.ts +104 -0
  49. package/src/ai-sdk/examples/workflow-before-after.ts +114 -0
  50. package/src/ai-sdk/examples/workflow-character-grid.ts +112 -0
  51. package/src/ai-sdk/examples/workflow-slideshow.ts +161 -0
  52. package/src/ai-sdk/file-cache.ts +112 -0
  53. package/src/ai-sdk/file.ts +238 -0
  54. package/src/ai-sdk/generate-element.ts +92 -0
  55. package/src/ai-sdk/generate-music.ts +46 -0
  56. package/src/ai-sdk/generate-video.ts +165 -0
  57. package/src/ai-sdk/index.ts +72 -0
  58. package/src/ai-sdk/music-model.ts +110 -0
  59. package/src/ai-sdk/providers/editly/editly.test.ts +1108 -0
  60. package/src/ai-sdk/providers/editly/ffmpeg.ts +60 -0
  61. package/src/ai-sdk/providers/editly/index.ts +817 -0
  62. package/src/ai-sdk/providers/editly/layers.ts +776 -0
  63. package/src/ai-sdk/providers/editly/plan.md +144 -0
  64. package/src/ai-sdk/providers/editly/types.ts +328 -0
  65. package/src/ai-sdk/providers/elevenlabs-provider.ts +255 -0
  66. package/src/ai-sdk/providers/fal-provider.ts +512 -0
  67. package/src/ai-sdk/providers/higgsfield.ts +379 -0
  68. package/src/ai-sdk/providers/openai.ts +251 -0
  69. package/src/ai-sdk/providers/replicate.ts +16 -0
  70. package/src/ai-sdk/video-model.ts +185 -0
  71. package/src/cli/commands/find.tsx +137 -0
  72. package/src/cli/commands/help.tsx +85 -0
  73. package/src/cli/commands/index.ts +6 -0
  74. package/src/cli/commands/list.tsx +238 -0
  75. package/src/cli/commands/render.tsx +71 -0
  76. package/src/cli/commands/run.tsx +511 -0
  77. package/src/cli/commands/which.tsx +253 -0
  78. package/src/cli/index.ts +114 -0
  79. package/src/cli/quiet.ts +44 -0
  80. package/src/cli/types.ts +32 -0
  81. package/src/cli/ui/components/Badge.tsx +29 -0
  82. package/src/cli/ui/components/DataTable.tsx +51 -0
  83. package/src/cli/ui/components/Header.tsx +23 -0
  84. package/src/cli/ui/components/HelpBlock.tsx +44 -0
  85. package/src/cli/ui/components/KeyValue.tsx +33 -0
  86. package/src/cli/ui/components/OptionRow.tsx +81 -0
  87. package/src/cli/ui/components/Separator.tsx +23 -0
  88. package/src/cli/ui/components/StatusBox.tsx +108 -0
  89. package/src/cli/ui/components/VargBox.tsx +51 -0
  90. package/src/cli/ui/components/VargProgress.tsx +36 -0
  91. package/src/cli/ui/components/VargSpinner.tsx +34 -0
  92. package/src/cli/ui/components/VargText.tsx +56 -0
  93. package/src/cli/ui/components/index.ts +19 -0
  94. package/src/cli/ui/index.ts +12 -0
  95. package/src/cli/ui/render.ts +35 -0
  96. package/src/cli/ui/theme.ts +63 -0
  97. package/src/cli/utils.ts +78 -0
  98. package/src/core/executor/executor.ts +201 -0
  99. package/src/core/executor/index.ts +13 -0
  100. package/src/core/executor/job.ts +214 -0
  101. package/src/core/executor/pipeline.ts +222 -0
  102. package/src/core/index.ts +11 -0
  103. package/src/core/registry/index.ts +9 -0
  104. package/src/core/registry/loader.ts +149 -0
  105. package/src/core/registry/registry.ts +221 -0
  106. package/src/core/registry/resolver.ts +206 -0
  107. package/src/core/schema/helpers.ts +134 -0
  108. package/src/core/schema/index.ts +8 -0
  109. package/src/core/schema/shared.ts +102 -0
  110. package/src/core/schema/types.ts +279 -0
  111. package/src/core/schema/validator.ts +92 -0
  112. package/src/definitions/actions/captions.ts +261 -0
  113. package/src/definitions/actions/edit.ts +298 -0
  114. package/src/definitions/actions/image.ts +125 -0
  115. package/src/definitions/actions/index.ts +114 -0
  116. package/src/definitions/actions/music.ts +205 -0
  117. package/src/definitions/actions/sync.ts +128 -0
  118. package/{action/transcribe/index.ts → src/definitions/actions/transcribe.ts} +58 -68
  119. package/src/definitions/actions/upload.ts +111 -0
  120. package/src/definitions/actions/video.ts +163 -0
  121. package/src/definitions/actions/voice.ts +119 -0
  122. package/src/definitions/index.ts +23 -0
  123. package/src/definitions/models/elevenlabs.ts +50 -0
  124. package/src/definitions/models/flux.ts +56 -0
  125. package/src/definitions/models/index.ts +36 -0
  126. package/src/definitions/models/kling.ts +56 -0
  127. package/src/definitions/models/llama.ts +54 -0
  128. package/src/definitions/models/nano-banana-pro.ts +102 -0
  129. package/src/definitions/models/sonauto.ts +68 -0
  130. package/src/definitions/models/soul.ts +65 -0
  131. package/src/definitions/models/wan.ts +54 -0
  132. package/src/definitions/models/whisper.ts +44 -0
  133. package/src/definitions/skills/index.ts +12 -0
  134. package/src/definitions/skills/talking-character.ts +87 -0
  135. package/src/definitions/skills/text-to-tiktok.ts +97 -0
  136. package/src/index.ts +118 -0
  137. package/src/providers/apify.ts +269 -0
  138. package/src/providers/base.ts +264 -0
  139. package/src/providers/elevenlabs.ts +217 -0
  140. package/src/providers/fal.ts +392 -0
  141. package/src/providers/ffmpeg.ts +544 -0
  142. package/src/providers/fireworks.ts +193 -0
  143. package/src/providers/groq.ts +149 -0
  144. package/src/providers/higgsfield.ts +145 -0
  145. package/src/providers/index.ts +143 -0
  146. package/src/providers/replicate.ts +147 -0
  147. package/src/providers/storage.ts +206 -0
  148. package/src/react/cli.ts +52 -0
  149. package/src/react/elements.ts +146 -0
  150. package/src/react/examples/branching.tsx +66 -0
  151. package/src/react/examples/captions-demo.tsx +37 -0
  152. package/src/react/examples/character-video.tsx +84 -0
  153. package/src/react/examples/grid.tsx +53 -0
  154. package/src/react/examples/layouts-demo.tsx +57 -0
  155. package/src/react/examples/madi.tsx +60 -0
  156. package/src/react/examples/music-test.tsx +35 -0
  157. package/src/react/examples/onlyfans-1m/workflow.tsx +88 -0
  158. package/src/react/examples/orange-portrait.tsx +41 -0
  159. package/src/react/examples/split-element-demo.tsx +60 -0
  160. package/src/react/examples/split-layout-demo.tsx +60 -0
  161. package/src/react/examples/split.tsx +41 -0
  162. package/src/react/examples/video-grid.tsx +46 -0
  163. package/src/react/index.ts +43 -0
  164. package/src/react/layouts/grid.tsx +28 -0
  165. package/src/react/layouts/index.ts +2 -0
  166. package/src/react/layouts/split.tsx +20 -0
  167. package/src/react/react.test.ts +309 -0
  168. package/src/react/render.ts +21 -0
  169. package/src/react/renderers/animate.ts +59 -0
  170. package/src/react/renderers/captions.ts +297 -0
  171. package/src/react/renderers/clip.ts +248 -0
  172. package/src/react/renderers/context.ts +17 -0
  173. package/src/react/renderers/image.ts +109 -0
  174. package/src/react/renderers/index.ts +22 -0
  175. package/src/react/renderers/music.ts +60 -0
  176. package/src/react/renderers/packshot.ts +84 -0
  177. package/src/react/renderers/progress.ts +173 -0
  178. package/src/react/renderers/render.ts +243 -0
  179. package/src/react/renderers/slider.ts +69 -0
  180. package/src/react/renderers/speech.ts +53 -0
  181. package/src/react/renderers/split.ts +91 -0
  182. package/src/react/renderers/subtitle.ts +16 -0
  183. package/src/react/renderers/swipe.ts +75 -0
  184. package/src/react/renderers/title.ts +17 -0
  185. package/src/react/renderers/utils.ts +124 -0
  186. package/src/react/renderers/video.ts +127 -0
  187. package/src/react/runtime/jsx-dev-runtime.ts +43 -0
  188. package/src/react/runtime/jsx-runtime.ts +35 -0
  189. package/src/react/types.ts +232 -0
  190. package/src/studio/index.ts +26 -0
  191. package/src/studio/scanner.ts +102 -0
  192. package/src/studio/server.ts +554 -0
  193. package/src/studio/stages.ts +251 -0
  194. package/src/studio/step-renderer.ts +279 -0
  195. package/src/studio/types.ts +60 -0
  196. package/src/studio/ui/cache.html +303 -0
  197. package/src/studio/ui/index.html +1820 -0
  198. package/src/tests/all.test.ts +509 -0
  199. package/src/tests/index.ts +33 -0
  200. package/src/tests/unit.test.ts +403 -0
  201. package/tsconfig.cli.json +8 -0
  202. package/tsconfig.json +21 -3
  203. package/TEST_RESULTS.md +0 -122
  204. package/action/captions/SKILL.md +0 -170
  205. package/action/captions/index.ts +0 -169
  206. package/action/edit/SKILL.md +0 -235
  207. package/action/edit/index.ts +0 -437
  208. package/action/image/SKILL.md +0 -140
  209. package/action/image/index.ts +0 -105
  210. package/action/sync/SKILL.md +0 -136
  211. package/action/sync/index.ts +0 -145
  212. package/action/transcribe/SKILL.md +0 -179
  213. package/action/video/SKILL.md +0 -116
  214. package/action/video/index.ts +0 -125
  215. package/action/voice/SKILL.md +0 -125
  216. package/action/voice/index.ts +0 -136
  217. package/cli/commands/find.ts +0 -58
  218. package/cli/commands/help.ts +0 -70
  219. package/cli/commands/list.ts +0 -49
  220. package/cli/commands/run.ts +0 -237
  221. package/cli/commands/which.ts +0 -66
  222. package/cli/discover.ts +0 -66
  223. package/cli/index.ts +0 -33
  224. package/cli/runner.ts +0 -65
  225. package/cli/types.ts +0 -49
  226. package/cli/ui.ts +0 -185
  227. package/index.ts +0 -75
  228. package/lib/README.md +0 -144
  229. package/lib/ai-sdk/fal.ts +0 -106
  230. package/lib/ai-sdk/replicate.ts +0 -107
  231. package/lib/elevenlabs.ts +0 -382
  232. package/lib/fal.ts +0 -467
  233. package/lib/ffmpeg.ts +0 -467
  234. package/lib/fireworks.ts +0 -235
  235. package/lib/groq.ts +0 -246
  236. package/lib/higgsfield.ts +0 -176
  237. package/lib/remotion/SKILL.md +0 -823
  238. package/lib/remotion/cli.ts +0 -115
  239. package/lib/remotion/functions.ts +0 -283
  240. package/lib/remotion/index.ts +0 -19
  241. package/lib/remotion/templates.ts +0 -73
  242. package/lib/replicate.ts +0 -304
  243. package/output.txt +0 -1
  244. package/test-import.ts +0 -7
  245. package/test-services.ts +0 -97
  246. package/utilities/s3.ts +0 -147
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Replicate provider for video and image generation
3
+ * Supports Minimax, Kling, Luma, Flux, and other models
4
+ */
5
+
6
+ import Replicate from "replicate";
7
+ import type { JobStatusUpdate, ProviderConfig } from "../core/schema/types";
8
+ import { BaseProvider } from "./base";
9
+
10
+ export class ReplicateProvider extends BaseProvider {
11
+ readonly name = "replicate";
12
+ private client: Replicate;
13
+
14
+ constructor(config?: ProviderConfig) {
15
+ super(config);
16
+ this.client = new Replicate({
17
+ auth: config?.apiKey || process.env.REPLICATE_API_TOKEN || "",
18
+ });
19
+ }
20
+
21
+ async submit(
22
+ model: string,
23
+ inputs: Record<string, unknown>,
24
+ _config?: ProviderConfig,
25
+ ): Promise<string> {
26
+ // Transform inputs for provider-specific field names
27
+ const transformedInputs = this.transformInputs(model, inputs);
28
+
29
+ const prediction = await this.client.predictions.create({
30
+ model: model as `${string}/${string}`,
31
+ input: transformedInputs,
32
+ });
33
+
34
+ console.log(`[replicate] job submitted: ${prediction.id}`);
35
+ return prediction.id;
36
+ }
37
+
38
+ /**
39
+ * Transform inputs for provider-specific field names
40
+ */
41
+ private transformInputs(
42
+ model: string,
43
+ inputs: Record<string, unknown>,
44
+ ): Record<string, unknown> {
45
+ // Nano Banana Pro: Replicate uses 'image_input' instead of 'image_urls'
46
+ if (model === "google/nano-banana-pro" && inputs.image_urls) {
47
+ const { image_urls, ...rest } = inputs;
48
+ return {
49
+ ...rest,
50
+ image_input: image_urls,
51
+ };
52
+ }
53
+ return inputs;
54
+ }
55
+
56
+ async getStatus(jobId: string): Promise<JobStatusUpdate> {
57
+ const prediction = await this.client.predictions.get(jobId);
58
+
59
+ const statusMap: Record<string, JobStatusUpdate["status"]> = {
60
+ starting: "queued",
61
+ processing: "processing",
62
+ succeeded: "completed",
63
+ failed: "failed",
64
+ canceled: "cancelled",
65
+ };
66
+
67
+ return {
68
+ status: statusMap[prediction.status] ?? "processing",
69
+ output: prediction.output,
70
+ error:
71
+ typeof prediction.error === "string" ? prediction.error : undefined,
72
+ };
73
+ }
74
+
75
+ async getResult(jobId: string): Promise<unknown> {
76
+ const prediction = await this.client.predictions.get(jobId);
77
+ return prediction.output;
78
+ }
79
+
80
+ override async cancel(jobId: string): Promise<void> {
81
+ await this.client.predictions.cancel(jobId);
82
+ }
83
+
84
+ // ============================================================================
85
+ // High-level convenience methods
86
+ // ============================================================================
87
+
88
+ async runModel(model: string, input: Record<string, unknown>) {
89
+ console.log(`[replicate] running ${model}...`);
90
+
91
+ const output = await this.client.run(model as `${string}/${string}`, {
92
+ input,
93
+ });
94
+
95
+ console.log(`[replicate] completed`);
96
+ return output;
97
+ }
98
+
99
+ async runVideo(options: { model: string; input: Record<string, unknown> }) {
100
+ return this.runModel(options.model, options.input);
101
+ }
102
+
103
+ async runImage(options: { model: string; input: Record<string, unknown> }) {
104
+ return this.runModel(options.model, options.input);
105
+ }
106
+
107
+ async listPredictions() {
108
+ return this.client.predictions.list();
109
+ }
110
+
111
+ async getPrediction(id: string) {
112
+ return this.client.predictions.get(id);
113
+ }
114
+ }
115
+
116
+ // Popular models registry
117
+ export const MODELS = {
118
+ VIDEO: {
119
+ MINIMAX: "minimax/video-01",
120
+ KLING: "fofr/kling-v1.5",
121
+ LUMA: "fofr/ltx-video",
122
+ RUNWAY_GEN3: "replicate/runway-gen3-turbo",
123
+ WAN_2_5: "wan-video/wan-2.5-i2v",
124
+ },
125
+ IMAGE: {
126
+ FLUX_PRO: "black-forest-labs/flux-1.1-pro",
127
+ FLUX_DEV: "black-forest-labs/flux-dev",
128
+ FLUX_SCHNELL: "black-forest-labs/flux-schnell",
129
+ STABLE_DIFFUSION: "stability-ai/sdxl",
130
+ NANO_BANANA_PRO: "google/nano-banana-pro",
131
+ },
132
+ };
133
+
134
+ // Export singleton instance
135
+ export const replicateProvider = new ReplicateProvider();
136
+
137
+ // Re-export convenience functions for backward compatibility
138
+ export const runModel = (model: string, input: Record<string, unknown>) =>
139
+ replicateProvider.runModel(model, input);
140
+ export const runVideo = (options: {
141
+ model: string;
142
+ input: Record<string, unknown>;
143
+ }) => replicateProvider.runVideo(options);
144
+ export const runImage = (options: {
145
+ model: string;
146
+ input: Record<string, unknown>;
147
+ }) => replicateProvider.runImage(options);
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Storage provider for Cloudflare R2 / S3 compatible storage
3
+ */
4
+
5
+ import {
6
+ GetObjectCommand,
7
+ PutObjectCommand,
8
+ S3Client,
9
+ } from "@aws-sdk/client-s3";
10
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
11
+ import type { JobStatusUpdate, ProviderConfig } from "../core/schema/types";
12
+ import { BaseProvider } from "./base";
13
+
14
+ export interface StorageConfig extends ProviderConfig {
15
+ endpoint?: string;
16
+ accessKeyId?: string;
17
+ secretAccessKey?: string;
18
+ bucket?: string;
19
+ publicUrl?: string;
20
+ }
21
+
22
+ export class StorageProvider extends BaseProvider {
23
+ readonly name = "storage";
24
+ private client: S3Client;
25
+ private bucket: string;
26
+ private publicUrl: string;
27
+
28
+ constructor(config?: StorageConfig) {
29
+ super(config);
30
+
31
+ this.client = new S3Client({
32
+ region: "auto",
33
+ endpoint: config?.endpoint || process.env.CLOUDFLARE_R2_API_URL,
34
+ credentials: {
35
+ accessKeyId:
36
+ config?.accessKeyId || process.env.CLOUDFLARE_ACCESS_KEY_ID || "",
37
+ secretAccessKey:
38
+ config?.secretAccessKey || process.env.CLOUDFLARE_ACCESS_SECRET || "",
39
+ },
40
+ });
41
+
42
+ this.bucket = config?.bucket || process.env.CLOUDFLARE_R2_BUCKET || "m";
43
+ this.publicUrl = config?.publicUrl || "https://s3.varg.ai";
44
+ }
45
+
46
+ async submit(
47
+ _model: string,
48
+ _inputs: Record<string, unknown>,
49
+ _config?: ProviderConfig,
50
+ ): Promise<string> {
51
+ const jobId = `storage_${Date.now()}_${Math.random().toString(36).slice(2)}`;
52
+ return jobId;
53
+ }
54
+
55
+ async getStatus(_jobId: string): Promise<JobStatusUpdate> {
56
+ return { status: "completed" };
57
+ }
58
+
59
+ async getResult(_jobId: string): Promise<unknown> {
60
+ return null;
61
+ }
62
+
63
+ // ============================================================================
64
+ // Storage Operations
65
+ // ============================================================================
66
+
67
+ /**
68
+ * Upload a file from local path to storage
69
+ */
70
+ async uploadLocalFile(filePath: string, objectKey: string): Promise<string> {
71
+ console.log(`[storage] uploading ${filePath} to ${objectKey}`);
72
+
73
+ const file = Bun.file(filePath);
74
+ const buffer = await file.arrayBuffer();
75
+
76
+ await this.client.send(
77
+ new PutObjectCommand({
78
+ Bucket: this.bucket,
79
+ Key: objectKey,
80
+ Body: new Uint8Array(buffer),
81
+ }),
82
+ );
83
+
84
+ return this.getPublicUrl(objectKey);
85
+ }
86
+
87
+ /**
88
+ * Upload content from a URL to storage
89
+ */
90
+ async uploadFromUrl(url: string, objectKey: string): Promise<string> {
91
+ console.log(`[storage] uploading from URL to ${objectKey}`);
92
+
93
+ const response = await fetch(url);
94
+ const buffer = await response.arrayBuffer();
95
+
96
+ await this.client.send(
97
+ new PutObjectCommand({
98
+ Bucket: this.bucket,
99
+ Key: objectKey,
100
+ Body: new Uint8Array(buffer),
101
+ }),
102
+ );
103
+
104
+ return this.getPublicUrl(objectKey);
105
+ }
106
+
107
+ /**
108
+ * Upload raw buffer to storage
109
+ */
110
+ async uploadBuffer(
111
+ buffer: ArrayBuffer | Uint8Array,
112
+ objectKey: string,
113
+ contentType?: string,
114
+ ): Promise<string> {
115
+ console.log(`[storage] uploading buffer to ${objectKey}`);
116
+
117
+ await this.client.send(
118
+ new PutObjectCommand({
119
+ Bucket: this.bucket,
120
+ Key: objectKey,
121
+ Body: buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer),
122
+ ContentType: contentType,
123
+ }),
124
+ );
125
+
126
+ return this.getPublicUrl(objectKey);
127
+ }
128
+
129
+ /**
130
+ * Generate a presigned URL for temporary access
131
+ */
132
+ async generatePresignedUrl(
133
+ objectKey: string,
134
+ expiresIn = 3600,
135
+ ): Promise<string> {
136
+ const command = new GetObjectCommand({
137
+ Bucket: this.bucket,
138
+ Key: objectKey,
139
+ });
140
+
141
+ return getSignedUrl(this.client, command, { expiresIn });
142
+ }
143
+
144
+ /**
145
+ * Get the public URL for an object
146
+ */
147
+ getPublicUrl(objectKey: string): string {
148
+ const endpoint = process.env.CLOUDFLARE_R2_API_URL || "";
149
+
150
+ if (endpoint.includes("localhost")) {
151
+ return `${endpoint}/${objectKey}`;
152
+ }
153
+
154
+ return `${this.publicUrl}/${objectKey}`;
155
+ }
156
+
157
+ /**
158
+ * Download a file from storage
159
+ */
160
+ async downloadToFile(objectKey: string, outputPath: string): Promise<string> {
161
+ console.log(`[storage] downloading ${objectKey} to ${outputPath}`);
162
+
163
+ const command = new GetObjectCommand({
164
+ Bucket: this.bucket,
165
+ Key: objectKey,
166
+ });
167
+
168
+ const response = await this.client.send(command);
169
+ const body = response.Body;
170
+
171
+ if (!body) {
172
+ throw new Error("Empty response body");
173
+ }
174
+
175
+ // Convert stream to buffer
176
+ const chunks: Uint8Array[] = [];
177
+ // @ts-expect-error - body is a readable stream
178
+ for await (const chunk of body) {
179
+ chunks.push(chunk);
180
+ }
181
+ const buffer = Buffer.concat(chunks);
182
+
183
+ await Bun.write(outputPath, buffer);
184
+ console.log(`[storage] saved to ${outputPath}`);
185
+
186
+ return outputPath;
187
+ }
188
+ }
189
+
190
+ // Export singleton instance
191
+ export const storageProvider = new StorageProvider();
192
+
193
+ // Re-export convenience functions for backward compatibility
194
+ export const uploadFile = (filePath: string, objectKey: string) =>
195
+ storageProvider.uploadLocalFile(filePath, objectKey);
196
+ export const uploadFromUrl = (url: string, objectKey: string) =>
197
+ storageProvider.uploadFromUrl(url, objectKey);
198
+ export const uploadBuffer = (
199
+ buffer: ArrayBuffer | Uint8Array,
200
+ objectKey: string,
201
+ contentType?: string,
202
+ ) => storageProvider.uploadBuffer(buffer, objectKey, contentType);
203
+ export const generatePresignedUrl = (objectKey: string, expiresIn?: number) =>
204
+ storageProvider.generatePresignedUrl(objectKey, expiresIn);
205
+ export const getPublicUrl = (objectKey: string) =>
206
+ storageProvider.getPublicUrl(objectKey);
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { parseArgs } from "node:util";
4
+ import { render } from "./render";
5
+ import type { VargElement } from "./types";
6
+
7
+ const { values, positionals } = parseArgs({
8
+ args: Bun.argv.slice(2),
9
+ options: {
10
+ output: { type: "string", short: "o" },
11
+ cache: { type: "string", short: "c", default: ".cache/ai" },
12
+ quiet: { type: "boolean", short: "q", default: false },
13
+ },
14
+ allowPositionals: true,
15
+ });
16
+
17
+ const [file] = positionals;
18
+
19
+ if (!file) {
20
+ console.error("usage: bun react/cli.ts <component.tsx> [-o output.mp4]");
21
+ process.exit(1);
22
+ }
23
+
24
+ const resolvedPath = Bun.resolveSync(file, process.cwd());
25
+ const mod = await import(resolvedPath);
26
+ const component: VargElement = mod.default;
27
+
28
+ if (!component || component.type !== "render") {
29
+ console.error("error: default export must be a <Render> element");
30
+ process.exit(1);
31
+ }
32
+
33
+ const outputPath =
34
+ values.output ??
35
+ `output/${file
36
+ .replace(/\.tsx?$/, "")
37
+ .split("/")
38
+ .pop()}.mp4`;
39
+
40
+ if (!values.quiet) {
41
+ console.log(`rendering ${file} → ${outputPath}`);
42
+ }
43
+
44
+ const buffer = await render(component, {
45
+ output: outputPath,
46
+ cache: values.cache,
47
+ quiet: values.quiet,
48
+ });
49
+
50
+ if (!values.quiet) {
51
+ console.log(`done! ${buffer.byteLength} bytes → ${outputPath}`);
52
+ }
@@ -0,0 +1,146 @@
1
+ import type {
2
+ AnimateProps,
3
+ CaptionsProps,
4
+ ClipProps,
5
+ ImageProps,
6
+ MusicProps,
7
+ OverlayProps,
8
+ PackshotProps,
9
+ RenderProps,
10
+ SliderProps,
11
+ SpeechProps,
12
+ SplitProps,
13
+ SubtitleProps,
14
+ SwipeProps,
15
+ TalkingHeadProps,
16
+ TitleProps,
17
+ VargElement,
18
+ VargNode,
19
+ VideoProps,
20
+ } from "./types";
21
+
22
+ function normalizeChildren(children: unknown): VargNode[] {
23
+ if (children === null || children === undefined) return [];
24
+ if (Array.isArray(children))
25
+ return children.flat().flatMap(normalizeChildren);
26
+ return [children as VargNode];
27
+ }
28
+
29
+ function createElement<T extends VargElement["type"]>(
30
+ type: T,
31
+ props: Record<string, unknown>,
32
+ children: unknown,
33
+ ): VargElement<T> {
34
+ const { children: _, ...restProps } = props;
35
+ return {
36
+ type,
37
+ props: restProps,
38
+ children: normalizeChildren(children ?? props.children),
39
+ };
40
+ }
41
+
42
+ export function Render(props: RenderProps): VargElement<"render"> {
43
+ return createElement(
44
+ "render",
45
+ props as Record<string, unknown>,
46
+ props.children,
47
+ );
48
+ }
49
+
50
+ export function Clip(props: ClipProps): VargElement<"clip"> {
51
+ return createElement(
52
+ "clip",
53
+ props as Record<string, unknown>,
54
+ props.children,
55
+ );
56
+ }
57
+
58
+ export function Overlay(props: OverlayProps): VargElement<"overlay"> {
59
+ return createElement(
60
+ "overlay",
61
+ props as Record<string, unknown>,
62
+ props.children,
63
+ );
64
+ }
65
+
66
+ export function Image(props: ImageProps): VargElement<"image"> {
67
+ return createElement("image", props as Record<string, unknown>, undefined);
68
+ }
69
+
70
+ export function Video(props: VideoProps): VargElement<"video"> {
71
+ return createElement("video", props as Record<string, unknown>, undefined);
72
+ }
73
+
74
+ export function Animate(props: AnimateProps): VargElement<"animate"> {
75
+ return createElement("animate", props as Record<string, unknown>, undefined);
76
+ }
77
+
78
+ export function Speech(props: SpeechProps): VargElement<"speech"> {
79
+ return createElement(
80
+ "speech",
81
+ props as Record<string, unknown>,
82
+ props.children,
83
+ );
84
+ }
85
+
86
+ export function TalkingHead(
87
+ props: TalkingHeadProps,
88
+ ): VargElement<"talking-head"> {
89
+ return createElement(
90
+ "talking-head",
91
+ props as Record<string, unknown>,
92
+ props.children,
93
+ );
94
+ }
95
+
96
+ export function Title(props: TitleProps): VargElement<"title"> {
97
+ return createElement(
98
+ "title",
99
+ props as Record<string, unknown>,
100
+ props.children,
101
+ );
102
+ }
103
+
104
+ export function Subtitle(props: SubtitleProps): VargElement<"subtitle"> {
105
+ return createElement(
106
+ "subtitle",
107
+ props as Record<string, unknown>,
108
+ props.children,
109
+ );
110
+ }
111
+
112
+ export function Music(props: MusicProps): VargElement<"music"> {
113
+ return createElement("music", props as Record<string, unknown>, undefined);
114
+ }
115
+
116
+ export function Captions(props: CaptionsProps): VargElement<"captions"> {
117
+ return createElement("captions", props as Record<string, unknown>, undefined);
118
+ }
119
+
120
+ export function Split(props: SplitProps): VargElement<"split"> {
121
+ return createElement(
122
+ "split",
123
+ props as Record<string, unknown>,
124
+ props.children,
125
+ );
126
+ }
127
+
128
+ export function Slider(props: SliderProps): VargElement<"slider"> {
129
+ return createElement(
130
+ "slider",
131
+ props as Record<string, unknown>,
132
+ props.children,
133
+ );
134
+ }
135
+
136
+ export function Swipe(props: SwipeProps): VargElement<"swipe"> {
137
+ return createElement(
138
+ "swipe",
139
+ props as Record<string, unknown>,
140
+ props.children,
141
+ );
142
+ }
143
+
144
+ export function Packshot(props: PackshotProps): VargElement<"packshot"> {
145
+ return createElement("packshot", props as Record<string, unknown>, undefined);
146
+ }
@@ -0,0 +1,66 @@
1
+ import { elevenlabs } from "../../ai-sdk/providers/elevenlabs-provider";
2
+ import { fal } from "../../ai-sdk/providers/fal-provider";
3
+ import { Animate, Clip, Image, Render, Speech, Title } from "..";
4
+
5
+ // Non-linear tree: multiple clips with independent branches
6
+ // Clip 1: TalkingHead (Image -> Animate + Speech)
7
+ // Clip 2: Split comparison (2 independent Images)
8
+ // Clip 3: Product shot with music
9
+
10
+ const character = Image({
11
+ prompt:
12
+ "friendly tech reviewer, young man with glasses, studio lighting, professional headshot",
13
+ model: fal.imageModel("flux-schnell"),
14
+ });
15
+
16
+ const _productBefore = Image({
17
+ prompt:
18
+ "old smartphone, cracked screen, slow, outdated design, on white background",
19
+ model: fal.imageModel("flux-schnell"),
20
+ });
21
+
22
+ const _productAfter = Image({
23
+ prompt:
24
+ "sleek new smartphone, edge-to-edge display, premium design, on white background",
25
+ model: fal.imageModel("flux-schnell"),
26
+ });
27
+
28
+ const packshot = Image({
29
+ prompt:
30
+ "smartphone floating with gradient background, product photography, premium feel",
31
+ model: fal.imageModel("flux-schnell"),
32
+ });
33
+
34
+ export default (
35
+ <Render width={1080} height={1920}>
36
+ {/* Clip 1: Talking head intro */}
37
+ <Clip duration={5}>
38
+ <Animate
39
+ image={character}
40
+ model={fal.videoModel("wan-2.5")}
41
+ motion="talking naturally, slight head movements, friendly expression"
42
+ />
43
+ <Speech voice="adam" model={elevenlabs.speechModel("turbo")}>
44
+ Hey everyone! Today we're looking at the biggest smartphone upgrade of
45
+ the year.
46
+ </Speech>
47
+ </Clip>
48
+
49
+ {/* Clip 2: Before/after comparison - branches into 2 images */}
50
+ <Clip duration={4} transition={{ name: "fade", duration: 0.5 }}>
51
+ <Image
52
+ prompt="split screen comparison layout"
53
+ model={fal.imageModel("flux-schnell")}
54
+ />
55
+ <Title position="top">Before vs After</Title>
56
+ </Clip>
57
+
58
+ {/* Clip 3: Product packshot */}
59
+ <Clip duration={3} transition={{ name: "fade", duration: 0.5 }}>
60
+ {packshot}
61
+ <Title position="bottom" color="#ffffff">
62
+ Available Now
63
+ </Title>
64
+ </Clip>
65
+ </Render>
66
+ );
@@ -0,0 +1,37 @@
1
+ import { elevenlabs } from "../../ai-sdk/providers/elevenlabs-provider";
2
+ import { Captions, Clip, Image, Render, render, Speech } from "..";
3
+
4
+ async function main() {
5
+ const speech = Speech({
6
+ model: elevenlabs.speechModel("eleven_multilingual_v2"),
7
+ voice: "adam",
8
+ children:
9
+ "Hello world! This is a test of the captions system with word level timestamps.",
10
+ });
11
+
12
+ const video = (
13
+ <Render width={1080} height={1920}>
14
+ <Clip duration={5}>
15
+ <Image src="media/cyberpunk-street.png" resize="contain" />
16
+ </Clip>
17
+ <Clip duration={5}>
18
+ <Image src="media/cyberpunk-street.png" resize="cover" />
19
+ </Clip>
20
+ <Clip duration={5}>
21
+ <Image src="media/cyberpunk-street.png" />
22
+ </Clip>
23
+ <Captions src={speech} style="tiktok" />
24
+ </Render>
25
+ );
26
+
27
+ console.log("rendering captions demo with speech transcription...\n");
28
+
29
+ await render(video, {
30
+ output: "output/captions-demo.mp4",
31
+ cache: ".cache/ai",
32
+ });
33
+
34
+ console.log("\ndone! check output/captions-demo.mp4");
35
+ }
36
+
37
+ main().catch(console.error);