vargai 0.4.0-alpha2 → 0.4.0-alpha21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +483 -61
- package/launch-videos/06-kawaii-fruits.tsx +93 -0
- package/launch-videos/07-ugc-weight-loss.tsx +132 -0
- package/launch-videos/08-talking-head-varg.tsx +107 -0
- package/launch-videos/09-girl.tsx +160 -0
- package/launch-videos/README.md +42 -0
- package/package.json +8 -4
- package/skills/varg-video-generation/SKILL.md +213 -0
- package/skills/varg-video-generation/references/templates.md +380 -0
- package/skills/varg-video-generation/scripts/setup.ts +265 -0
- package/src/ai-sdk/cache.ts +1 -1
- package/src/ai-sdk/middleware/wrap-image-model.ts +4 -21
- package/src/ai-sdk/middleware/wrap-music-model.ts +4 -16
- package/src/ai-sdk/middleware/wrap-video-model.ts +5 -17
- package/src/ai-sdk/providers/editly/index.ts +110 -53
- package/src/ai-sdk/providers/editly/types.ts +2 -0
- package/src/ai-sdk/providers/elevenlabs.ts +10 -2
- package/src/ai-sdk/providers/fal.ts +6 -1
- package/src/cli/commands/find.tsx +1 -0
- package/src/cli/commands/hello.ts +85 -0
- package/src/cli/commands/help.tsx +18 -30
- package/src/cli/commands/index.ts +9 -1
- package/src/cli/commands/init.tsx +412 -0
- package/src/cli/commands/list.tsx +1 -0
- package/src/cli/commands/render.tsx +292 -80
- package/src/cli/commands/run.tsx +1 -0
- package/src/cli/commands/studio.ts +47 -0
- package/src/cli/commands/which.tsx +1 -0
- package/src/cli/index.ts +20 -5
- package/src/cli/ui/components/Badge.tsx +1 -0
- package/src/cli/ui/components/DataTable.tsx +1 -0
- package/src/cli/ui/components/Header.tsx +1 -0
- package/src/cli/ui/components/HelpBlock.tsx +1 -0
- package/src/cli/ui/components/KeyValue.tsx +1 -0
- package/src/cli/ui/components/OptionRow.tsx +1 -0
- package/src/cli/ui/components/Separator.tsx +1 -0
- package/src/cli/ui/components/StatusBox.tsx +1 -0
- package/src/cli/ui/components/VargBox.tsx +1 -0
- package/src/cli/ui/components/VargProgress.tsx +1 -0
- package/src/cli/ui/components/VargSpinner.tsx +1 -0
- package/src/cli/ui/components/VargText.tsx +1 -0
- package/src/react/assets.ts +9 -0
- package/src/react/elements.ts +0 -5
- package/src/react/examples/branching.tsx +6 -4
- package/src/react/examples/character-video.tsx +13 -10
- package/src/react/examples/madi.tsx +13 -10
- package/src/react/examples/mcmeows.tsx +40 -0
- package/src/react/examples/music-defaults.tsx +24 -0
- package/src/react/examples/quickstart-test.tsx +97 -0
- package/src/react/index.ts +1 -2
- package/src/react/react.test.ts +10 -10
- package/src/react/renderers/clip.ts +13 -24
- package/src/react/renderers/context.ts +3 -0
- package/src/react/renderers/image.ts +4 -2
- package/src/react/renderers/index.ts +0 -1
- package/src/react/renderers/music.ts +3 -3
- package/src/react/renderers/progress.ts +1 -3
- package/src/react/renderers/render.ts +49 -63
- package/src/react/renderers/speech.ts +2 -2
- package/src/react/renderers/video.ts +46 -9
- package/src/react/types.ts +18 -14
- package/src/studio/stages.ts +4 -24
- package/src/studio/step-renderer.ts +0 -15
- package/test-sync-v2.ts +30 -0
- package/test-sync-v2.tsx +29 -0
- package/tsconfig.json +5 -3
- package/video.tsx +7 -0
- package/src/react/cli.ts +0 -52
- package/src/react/renderers/animate.ts +0 -59
|
@@ -53,11 +53,11 @@ export interface MusicPlaceholderFallbackOptions {
|
|
|
53
53
|
export function musicPlaceholderFallbackMiddleware(
|
|
54
54
|
options: MusicPlaceholderFallbackOptions,
|
|
55
55
|
): MusicModelMiddleware {
|
|
56
|
-
const { mode
|
|
56
|
+
const { mode } = options;
|
|
57
57
|
|
|
58
58
|
return {
|
|
59
59
|
wrapGenerate: async ({ doGenerate, params, model }) => {
|
|
60
|
-
|
|
60
|
+
if (mode === "preview") {
|
|
61
61
|
const placeholder = await generatePlaceholder({
|
|
62
62
|
type: "audio",
|
|
63
63
|
prompt: params.prompt,
|
|
@@ -69,7 +69,7 @@ export function musicPlaceholderFallbackMiddleware(
|
|
|
69
69
|
warnings: [
|
|
70
70
|
{
|
|
71
71
|
type: "other" as const,
|
|
72
|
-
message: "placeholder:
|
|
72
|
+
message: "placeholder: preview mode",
|
|
73
73
|
},
|
|
74
74
|
],
|
|
75
75
|
response: {
|
|
@@ -78,21 +78,9 @@ export function musicPlaceholderFallbackMiddleware(
|
|
|
78
78
|
headers: undefined,
|
|
79
79
|
},
|
|
80
80
|
};
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
if (mode === "preview") {
|
|
84
|
-
return createPlaceholderResult();
|
|
85
81
|
}
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
return await doGenerate();
|
|
89
|
-
} catch (e) {
|
|
90
|
-
if (mode === "strict") throw e;
|
|
91
|
-
|
|
92
|
-
const error = e instanceof Error ? e : new Error(String(e));
|
|
93
|
-
onFallback?.(error, params.prompt);
|
|
94
|
-
return createPlaceholderResult();
|
|
95
|
-
}
|
|
83
|
+
return doGenerate();
|
|
96
84
|
},
|
|
97
85
|
};
|
|
98
86
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { VideoModelV3, VideoModelV3CallOptions } from "../video-model";
|
|
2
2
|
import { generatePlaceholder } from "./placeholder";
|
|
3
3
|
|
|
4
|
-
export type RenderMode = "strict" | "
|
|
4
|
+
export type RenderMode = "strict" | "preview";
|
|
5
5
|
|
|
6
6
|
export interface VideoModelMiddleware {
|
|
7
7
|
transformParams?: (options: {
|
|
@@ -55,11 +55,11 @@ export interface PlaceholderFallbackOptions {
|
|
|
55
55
|
export function placeholderFallbackMiddleware(
|
|
56
56
|
options: PlaceholderFallbackOptions,
|
|
57
57
|
): VideoModelMiddleware {
|
|
58
|
-
const { mode
|
|
58
|
+
const { mode } = options;
|
|
59
59
|
|
|
60
60
|
return {
|
|
61
61
|
wrapGenerate: async ({ doGenerate, params, model }) => {
|
|
62
|
-
|
|
62
|
+
if (mode === "preview") {
|
|
63
63
|
const [width, height] = (params.resolution?.split("x").map(Number) ?? [
|
|
64
64
|
1080, 1920,
|
|
65
65
|
]) as [number, number];
|
|
@@ -76,7 +76,7 @@ export function placeholderFallbackMiddleware(
|
|
|
76
76
|
warnings: [
|
|
77
77
|
{
|
|
78
78
|
type: "other" as const,
|
|
79
|
-
message: "placeholder:
|
|
79
|
+
message: "placeholder: preview mode",
|
|
80
80
|
},
|
|
81
81
|
],
|
|
82
82
|
response: {
|
|
@@ -85,21 +85,9 @@ export function placeholderFallbackMiddleware(
|
|
|
85
85
|
headers: undefined,
|
|
86
86
|
},
|
|
87
87
|
};
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
if (mode === "preview") {
|
|
91
|
-
return createPlaceholderResult();
|
|
92
88
|
}
|
|
93
89
|
|
|
94
|
-
|
|
95
|
-
return await doGenerate();
|
|
96
|
-
} catch (e) {
|
|
97
|
-
if (mode === "strict") throw e;
|
|
98
|
-
|
|
99
|
-
const error = e instanceof Error ? e : new Error(String(e));
|
|
100
|
-
onFallback?.(error, params.prompt);
|
|
101
|
-
return createPlaceholderResult();
|
|
102
|
-
}
|
|
90
|
+
return doGenerate();
|
|
103
91
|
},
|
|
104
92
|
};
|
|
105
93
|
}
|
|
@@ -137,6 +137,15 @@ function isOverlayLayer(layer: Layer): boolean {
|
|
|
137
137
|
return isVideoOverlayLayer(layer) || isImageOverlayLayer(layer);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
function isTextOverlayLayer(layer: Layer): boolean {
|
|
141
|
+
return (
|
|
142
|
+
layer.type === "title" ||
|
|
143
|
+
layer.type === "subtitle" ||
|
|
144
|
+
layer.type === "news-title" ||
|
|
145
|
+
layer.type === "slide-in-text"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
140
149
|
function buildBaseClipFilter(
|
|
141
150
|
clip: ProcessedClip,
|
|
142
151
|
clipIndex: number,
|
|
@@ -164,7 +173,10 @@ function buildBaseClipFilter(
|
|
|
164
173
|
let baseLabel = "";
|
|
165
174
|
let inputIdx = inputOffset;
|
|
166
175
|
|
|
167
|
-
|
|
176
|
+
// Filter out overlay layers AND text overlay layers (text will be applied after image overlays)
|
|
177
|
+
const baseLayers = clip.layers.filter(
|
|
178
|
+
(l) => l && !isOverlayLayer(l) && !isTextOverlayLayer(l),
|
|
179
|
+
);
|
|
168
180
|
|
|
169
181
|
for (let i = 0; i < baseLayers.length; i++) {
|
|
170
182
|
const layer = baseLayers[i];
|
|
@@ -201,58 +213,6 @@ function buildBaseClipFilter(
|
|
|
201
213
|
inputIdx++;
|
|
202
214
|
}
|
|
203
215
|
}
|
|
204
|
-
|
|
205
|
-
if (layer.type === "title") {
|
|
206
|
-
const titleFilter = getTitleFilter(
|
|
207
|
-
layer as TitleLayer,
|
|
208
|
-
baseLabel,
|
|
209
|
-
width,
|
|
210
|
-
height,
|
|
211
|
-
clip.duration,
|
|
212
|
-
);
|
|
213
|
-
const newLabel = `title${clipIndex}_${i}`;
|
|
214
|
-
filters.push(`${titleFilter}[${newLabel}]`);
|
|
215
|
-
baseLabel = newLabel;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (layer.type === "subtitle") {
|
|
219
|
-
const subtitleFilter = getSubtitleFilter(
|
|
220
|
-
layer as SubtitleLayer,
|
|
221
|
-
baseLabel,
|
|
222
|
-
width,
|
|
223
|
-
height,
|
|
224
|
-
clip.duration,
|
|
225
|
-
);
|
|
226
|
-
const newLabel = `sub${clipIndex}_${i}`;
|
|
227
|
-
filters.push(`${subtitleFilter}[${newLabel}]`);
|
|
228
|
-
baseLabel = newLabel;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (layer.type === "news-title") {
|
|
232
|
-
const newsFilter = getNewsTitleFilter(
|
|
233
|
-
layer as NewsTitleLayer,
|
|
234
|
-
baseLabel,
|
|
235
|
-
width,
|
|
236
|
-
height,
|
|
237
|
-
clip.duration,
|
|
238
|
-
);
|
|
239
|
-
const newLabel = `news${clipIndex}_${i}`;
|
|
240
|
-
filters.push(`${newsFilter}[${newLabel}]`);
|
|
241
|
-
baseLabel = newLabel;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (layer.type === "slide-in-text") {
|
|
245
|
-
const slideFilter = getSlideInTextFilter(
|
|
246
|
-
layer as SlideInTextLayer,
|
|
247
|
-
baseLabel,
|
|
248
|
-
width,
|
|
249
|
-
height,
|
|
250
|
-
clip.duration,
|
|
251
|
-
);
|
|
252
|
-
const newLabel = `slide${clipIndex}_${i}`;
|
|
253
|
-
filters.push(`${slideFilter}[${newLabel}]`);
|
|
254
|
-
baseLabel = newLabel;
|
|
255
|
-
}
|
|
256
216
|
}
|
|
257
217
|
|
|
258
218
|
return {
|
|
@@ -358,6 +318,41 @@ function collectAudioLayers(
|
|
|
358
318
|
return audioLayers;
|
|
359
319
|
}
|
|
360
320
|
|
|
321
|
+
type TextLayer = TitleLayer | SubtitleLayer | NewsTitleLayer | SlideInTextLayer;
|
|
322
|
+
|
|
323
|
+
interface TimedTextLayer {
|
|
324
|
+
layer: TextLayer;
|
|
325
|
+
startTime: number;
|
|
326
|
+
duration: number;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function collectTextLayers(clips: ProcessedClip[]): TimedTextLayer[] {
|
|
330
|
+
const textLayers: TimedTextLayer[] = [];
|
|
331
|
+
let currentTime = 0;
|
|
332
|
+
|
|
333
|
+
for (let i = 0; i < clips.length; i++) {
|
|
334
|
+
const clip = clips[i];
|
|
335
|
+
if (!clip) continue;
|
|
336
|
+
|
|
337
|
+
for (const layer of clip.layers) {
|
|
338
|
+
if (layer && isTextOverlayLayer(layer)) {
|
|
339
|
+
textLayers.push({
|
|
340
|
+
layer: layer as TextLayer,
|
|
341
|
+
startTime: currentTime,
|
|
342
|
+
duration: clip.duration,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
currentTime += clip.duration;
|
|
348
|
+
if (i < clips.length - 1) {
|
|
349
|
+
currentTime -= clip.transition.duration;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return textLayers;
|
|
354
|
+
}
|
|
355
|
+
|
|
361
356
|
function buildTransitionFilter(
|
|
362
357
|
fromLabel: string,
|
|
363
358
|
toLabel: string,
|
|
@@ -744,6 +739,67 @@ export async function editly(config: EditlyConfig): Promise<void> {
|
|
|
744
739
|
finalVideoLabel = currentBase;
|
|
745
740
|
}
|
|
746
741
|
|
|
742
|
+
const textLayers = collectTextLayers(clips);
|
|
743
|
+
if (textLayers.length > 0) {
|
|
744
|
+
let currentBase = finalVideoLabel;
|
|
745
|
+
|
|
746
|
+
for (let i = 0; i < textLayers.length; i++) {
|
|
747
|
+
const timedLayer = textLayers[i];
|
|
748
|
+
if (!timedLayer) continue;
|
|
749
|
+
|
|
750
|
+
const { layer, startTime, duration } = timedLayer;
|
|
751
|
+
const outputLabel = `vwithtext${i}`;
|
|
752
|
+
|
|
753
|
+
const timedLayerWithEnable = {
|
|
754
|
+
...layer,
|
|
755
|
+
start: layer.start ?? startTime,
|
|
756
|
+
stop: layer.stop ?? startTime + duration,
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
if (layer.type === "title") {
|
|
760
|
+
const titleFilter = getTitleFilter(
|
|
761
|
+
timedLayerWithEnable as TitleLayer,
|
|
762
|
+
currentBase,
|
|
763
|
+
width,
|
|
764
|
+
height,
|
|
765
|
+
totalDuration,
|
|
766
|
+
);
|
|
767
|
+
allFilters.push(`${titleFilter}[${outputLabel}]`);
|
|
768
|
+
} else if (layer.type === "subtitle") {
|
|
769
|
+
const subtitleFilter = getSubtitleFilter(
|
|
770
|
+
timedLayerWithEnable as SubtitleLayer,
|
|
771
|
+
currentBase,
|
|
772
|
+
width,
|
|
773
|
+
height,
|
|
774
|
+
totalDuration,
|
|
775
|
+
);
|
|
776
|
+
allFilters.push(`${subtitleFilter}[${outputLabel}]`);
|
|
777
|
+
} else if (layer.type === "news-title") {
|
|
778
|
+
const newsFilter = getNewsTitleFilter(
|
|
779
|
+
timedLayerWithEnable as NewsTitleLayer,
|
|
780
|
+
currentBase,
|
|
781
|
+
width,
|
|
782
|
+
height,
|
|
783
|
+
totalDuration,
|
|
784
|
+
);
|
|
785
|
+
allFilters.push(`${newsFilter}[${outputLabel}]`);
|
|
786
|
+
} else if (layer.type === "slide-in-text") {
|
|
787
|
+
const slideFilter = getSlideInTextFilter(
|
|
788
|
+
timedLayerWithEnable as SlideInTextLayer,
|
|
789
|
+
currentBase,
|
|
790
|
+
width,
|
|
791
|
+
height,
|
|
792
|
+
totalDuration,
|
|
793
|
+
);
|
|
794
|
+
allFilters.push(`${slideFilter}[${outputLabel}]`);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
currentBase = outputLabel;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
finalVideoLabel = currentBase;
|
|
801
|
+
}
|
|
802
|
+
|
|
747
803
|
const clipAudioLayers = collectAudioLayers(clips);
|
|
748
804
|
const videoInputCount = allInputs.length;
|
|
749
805
|
const audioFilter = buildAudioFilter(
|
|
@@ -796,6 +852,7 @@ export async function editly(config: EditlyConfig): Promise<void> {
|
|
|
796
852
|
"-r",
|
|
797
853
|
String(fps),
|
|
798
854
|
...outputArgs,
|
|
855
|
+
...(config.shortest ? ["-shortest"] : []),
|
|
799
856
|
"-y",
|
|
800
857
|
outPath,
|
|
801
858
|
];
|
|
@@ -309,6 +309,8 @@ export interface EditlyConfig {
|
|
|
309
309
|
audioNorm?: AudioNormalizationOptions;
|
|
310
310
|
verbose?: boolean;
|
|
311
311
|
enableFfmpegLog?: boolean;
|
|
312
|
+
/** End output when shortest stream ends (video or audio) */
|
|
313
|
+
shortest?: boolean;
|
|
312
314
|
}
|
|
313
315
|
|
|
314
316
|
// Internal types used by our implementation
|
|
@@ -196,8 +196,16 @@ export function createElevenLabs(
|
|
|
196
196
|
};
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
|
|
200
|
-
export {
|
|
199
|
+
let _elevenlabs: ElevenLabsProvider | undefined;
|
|
200
|
+
export const elevenlabs = new Proxy({} as ElevenLabsProvider, {
|
|
201
|
+
get(_, prop) {
|
|
202
|
+
if (!_elevenlabs) {
|
|
203
|
+
_elevenlabs = createElevenLabs();
|
|
204
|
+
}
|
|
205
|
+
return _elevenlabs[prop as keyof ElevenLabsProvider];
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
export { VOICES };
|
|
201
209
|
|
|
202
210
|
export interface GenerateMusicOptions {
|
|
203
211
|
prompt: string;
|
|
@@ -58,7 +58,12 @@ const IMAGE_MODELS: Record<string, string> = {
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
// Models that use image_size instead of aspect_ratio
|
|
61
|
-
const IMAGE_SIZE_MODELS = new Set([
|
|
61
|
+
const IMAGE_SIZE_MODELS = new Set([
|
|
62
|
+
"flux-schnell",
|
|
63
|
+
"flux-dev",
|
|
64
|
+
"flux-pro",
|
|
65
|
+
"seedream-v4.5/edit",
|
|
66
|
+
]);
|
|
62
67
|
|
|
63
68
|
// Map aspect ratio strings to image_size enum values
|
|
64
69
|
const ASPECT_RATIO_TO_IMAGE_SIZE: Record<string, string> = {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
|
|
5
|
+
const HELLO_TEMPLATE = `/** @jsxImportSource vargai */
|
|
6
|
+
import { Render, Clip, Image, Video, assets } from "vargai/react";
|
|
7
|
+
import { fal } from "vargai/ai";
|
|
8
|
+
|
|
9
|
+
const girl = Image({
|
|
10
|
+
prompt: {
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
Framing: Head and shoulders, cropped at upper chest. Direct eye contact with camera.
|
|
14
|
+
|
|
15
|
+
Natural confident expression, relaxed shoulders.
|
|
16
|
+
Preserve the outfit neckline and visible clothing details from reference.
|
|
17
|
+
|
|
18
|
+
Background: Deep black with two contrasting orange gradient accents matching Reference 2. Soft gradient bleed, no hard edges.
|
|
19
|
+
|
|
20
|
+
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.\`,
|
|
21
|
+
images: [assets.characters.orangeGirl, assets.backgrounds.orangeGradient],
|
|
22
|
+
},
|
|
23
|
+
model: fal.imageModel("nano-banana-pro/edit"),
|
|
24
|
+
aspectRatio: "9:16",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default (
|
|
28
|
+
<Render width={1080} height={1920}>
|
|
29
|
+
<Clip duration={5}>
|
|
30
|
+
<Video
|
|
31
|
+
prompt={{
|
|
32
|
+
text: "She waves hello warmly, natural smile, friendly expression. Studio lighting, authentic confident slightly playful atmosphere. Camera static. Intense orange lighting.",
|
|
33
|
+
images: [girl],
|
|
34
|
+
}}
|
|
35
|
+
model={fal.videoModel("kling-v2.5")}
|
|
36
|
+
/>
|
|
37
|
+
</Clip>
|
|
38
|
+
</Render>
|
|
39
|
+
);
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
export const helloCmd = defineCommand({
|
|
43
|
+
meta: {
|
|
44
|
+
name: "hello",
|
|
45
|
+
description: "create hello.tsx starter video",
|
|
46
|
+
},
|
|
47
|
+
args: {
|
|
48
|
+
directory: {
|
|
49
|
+
type: "positional",
|
|
50
|
+
description: "directory (default: current)",
|
|
51
|
+
required: false,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
async run({ args }) {
|
|
55
|
+
const dir = (args.directory as string) || ".";
|
|
56
|
+
const cwd = dir === "." ? process.cwd() : join(process.cwd(), dir);
|
|
57
|
+
|
|
58
|
+
if (!existsSync(cwd) && dir !== ".") {
|
|
59
|
+
mkdirSync(cwd, { recursive: true });
|
|
60
|
+
console.log(`created ${dir}/`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const outputDir = join(cwd, "output");
|
|
64
|
+
if (!existsSync(outputDir)) {
|
|
65
|
+
mkdirSync(outputDir, { recursive: true });
|
|
66
|
+
console.log(`created output/`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const cacheDir = join(cwd, ".cache/ai");
|
|
70
|
+
if (!existsSync(cacheDir)) {
|
|
71
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
72
|
+
console.log(`created .cache/ai/`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const helloPath = join(cwd, "hello.tsx");
|
|
76
|
+
if (existsSync(helloPath)) {
|
|
77
|
+
console.log(`hello.tsx already exists, skipping`);
|
|
78
|
+
} else {
|
|
79
|
+
writeFileSync(helloPath, HELLO_TEMPLATE);
|
|
80
|
+
console.log(`created hello.tsx`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log(`\ndone! run: bunx vargai render hello.tsx`);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* varg help command
|
|
3
|
-
* Ink-based help display
|
|
4
|
-
*/
|
|
1
|
+
/** @jsxImportSource react */
|
|
5
2
|
|
|
6
3
|
import { defineCommand } from "citty";
|
|
7
4
|
import { Box, Text } from "ink";
|
|
@@ -24,44 +21,35 @@ function CommandRow({ name, description }: CommandRowProps) {
|
|
|
24
21
|
|
|
25
22
|
function HelpView() {
|
|
26
23
|
const examples = [
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
command: 'varg run video --prompt "person talking" --image photo.jpg',
|
|
33
|
-
description: "generate video from image",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
command: 'varg run voice --text "hello world" --voice sam',
|
|
37
|
-
description: "text to speech",
|
|
38
|
-
},
|
|
39
|
-
{ command: "varg list", description: "see all available" },
|
|
40
|
-
{
|
|
41
|
-
command: "varg which video",
|
|
42
|
-
description: "inspect an action or model",
|
|
43
|
-
},
|
|
24
|
+
{ command: "vargai hello", description: "create hello.tsx starter" },
|
|
25
|
+
{ command: "vargai render hello.tsx", description: "render jsx to video" },
|
|
26
|
+
{ command: "vargai init", description: "full setup with api keys" },
|
|
44
27
|
];
|
|
45
28
|
|
|
46
29
|
return (
|
|
47
|
-
<VargBox title="
|
|
30
|
+
<VargBox title="vargai">
|
|
48
31
|
<Box marginBottom={1}>
|
|
49
|
-
<Text>ai video
|
|
32
|
+
<Text>ai video generation sdk. jsx for videos.</Text>
|
|
50
33
|
</Box>
|
|
51
34
|
|
|
52
35
|
<Header>COMMANDS</Header>
|
|
53
36
|
<Box flexDirection="column" marginY={1}>
|
|
54
|
-
<CommandRow name="
|
|
55
|
-
<CommandRow name="
|
|
37
|
+
<CommandRow name="hello" description="create hello.tsx starter video" />
|
|
38
|
+
<CommandRow name="render" description="render jsx component to video" />
|
|
39
|
+
<CommandRow name="preview" description="fast preview (placeholders)" />
|
|
40
|
+
<CommandRow name="init" description="full setup with api keys" />
|
|
56
41
|
<CommandRow
|
|
57
|
-
name="
|
|
58
|
-
description="
|
|
42
|
+
name="studio"
|
|
43
|
+
description="visual editor at localhost:8282"
|
|
44
|
+
/>
|
|
45
|
+
<CommandRow name="run" description="run a single model or action" />
|
|
46
|
+
<CommandRow
|
|
47
|
+
name="list"
|
|
48
|
+
description="discover models, actions, skills"
|
|
59
49
|
/>
|
|
60
|
-
<CommandRow name="which" description="inspect a specific item" />
|
|
61
|
-
<CommandRow name="help" description="show this help" />
|
|
62
50
|
</Box>
|
|
63
51
|
|
|
64
|
-
<Header>
|
|
52
|
+
<Header>QUICKSTART</Header>
|
|
65
53
|
<Box marginTop={1}>
|
|
66
54
|
<HelpBlock examples={examples} />
|
|
67
55
|
</Box>
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export { findCmd, showFindHelp } from "./find.tsx";
|
|
2
|
+
export { helloCmd } from "./hello.ts";
|
|
2
3
|
export { helpCmd, showHelp } from "./help.tsx";
|
|
4
|
+
export { initCmd, showInitHelp } from "./init.tsx";
|
|
3
5
|
export { listCmd, showListHelp } from "./list.tsx";
|
|
4
|
-
export {
|
|
6
|
+
export {
|
|
7
|
+
previewCmd,
|
|
8
|
+
renderCmd,
|
|
9
|
+
showPreviewHelp,
|
|
10
|
+
showRenderHelp,
|
|
11
|
+
} from "./render.tsx";
|
|
5
12
|
export { runCmd, showRunHelp, showTargetHelp } from "./run.tsx";
|
|
13
|
+
export { studioCmd } from "./studio.ts";
|
|
6
14
|
export { showWhichHelp, whichCmd } from "./which.tsx";
|