@vibeframe/cli 0.27.0 → 0.29.0
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/LICENSE +21 -0
- package/dist/agent/adapters/index.d.ts +1 -0
- package/dist/agent/adapters/index.d.ts.map +1 -1
- package/dist/agent/adapters/index.js +5 -0
- package/dist/agent/adapters/index.js.map +1 -1
- package/dist/agent/adapters/openrouter.d.ts +16 -0
- package/dist/agent/adapters/openrouter.d.ts.map +1 -0
- package/dist/agent/adapters/openrouter.js +100 -0
- package/dist/agent/adapters/openrouter.js.map +1 -0
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +3 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/setup.js +5 -2
- package/dist/commands/setup.js.map +1 -1
- package/dist/config/schema.d.ts +2 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +2 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/index.js +0 -0
- package/package.json +16 -12
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-lint.log +0 -21
- package/.turbo/turbo-test.log +0 -689
- package/src/agent/adapters/claude.ts +0 -143
- package/src/agent/adapters/gemini.ts +0 -159
- package/src/agent/adapters/index.ts +0 -61
- package/src/agent/adapters/ollama.ts +0 -231
- package/src/agent/adapters/openai.ts +0 -116
- package/src/agent/adapters/xai.ts +0 -119
- package/src/agent/index.ts +0 -251
- package/src/agent/memory/index.ts +0 -151
- package/src/agent/prompts/system.ts +0 -106
- package/src/agent/tools/ai-editing.ts +0 -845
- package/src/agent/tools/ai-generation.ts +0 -1073
- package/src/agent/tools/ai-pipeline.ts +0 -1055
- package/src/agent/tools/ai.ts +0 -21
- package/src/agent/tools/batch.ts +0 -429
- package/src/agent/tools/e2e.test.ts +0 -545
- package/src/agent/tools/export.ts +0 -184
- package/src/agent/tools/filesystem.ts +0 -237
- package/src/agent/tools/index.ts +0 -150
- package/src/agent/tools/integration.test.ts +0 -775
- package/src/agent/tools/media.ts +0 -697
- package/src/agent/tools/project.ts +0 -313
- package/src/agent/tools/timeline.ts +0 -951
- package/src/agent/types.ts +0 -68
- package/src/commands/agent.ts +0 -340
- package/src/commands/ai-analyze.ts +0 -429
- package/src/commands/ai-animated-caption.ts +0 -390
- package/src/commands/ai-audio.ts +0 -941
- package/src/commands/ai-broll.ts +0 -490
- package/src/commands/ai-edit-cli.ts +0 -658
- package/src/commands/ai-edit.ts +0 -1542
- package/src/commands/ai-fill-gaps.ts +0 -566
- package/src/commands/ai-helpers.ts +0 -65
- package/src/commands/ai-highlights.ts +0 -1303
- package/src/commands/ai-image.ts +0 -761
- package/src/commands/ai-motion.ts +0 -347
- package/src/commands/ai-narrate.ts +0 -451
- package/src/commands/ai-review.ts +0 -309
- package/src/commands/ai-script-pipeline-cli.ts +0 -1710
- package/src/commands/ai-script-pipeline.ts +0 -1365
- package/src/commands/ai-suggest-edit.ts +0 -264
- package/src/commands/ai-video-fx.ts +0 -445
- package/src/commands/ai-video.ts +0 -915
- package/src/commands/ai-viral.ts +0 -595
- package/src/commands/ai-visual-fx.ts +0 -601
- package/src/commands/ai.test.ts +0 -627
- package/src/commands/ai.ts +0 -307
- package/src/commands/analyze.ts +0 -282
- package/src/commands/audio.ts +0 -644
- package/src/commands/batch.test.ts +0 -279
- package/src/commands/batch.ts +0 -440
- package/src/commands/detect.ts +0 -329
- package/src/commands/doctor.ts +0 -237
- package/src/commands/edit-cmd.ts +0 -1014
- package/src/commands/export.ts +0 -918
- package/src/commands/generate.ts +0 -2146
- package/src/commands/media.ts +0 -177
- package/src/commands/output.ts +0 -142
- package/src/commands/pipeline.ts +0 -398
- package/src/commands/project.test.ts +0 -127
- package/src/commands/project.ts +0 -149
- package/src/commands/sanitize.ts +0 -60
- package/src/commands/schema.ts +0 -130
- package/src/commands/setup.ts +0 -509
- package/src/commands/timeline.test.ts +0 -499
- package/src/commands/timeline.ts +0 -529
- package/src/commands/validate.ts +0 -77
- package/src/config/config.test.ts +0 -197
- package/src/config/index.ts +0 -125
- package/src/config/schema.ts +0 -82
- package/src/engine/index.ts +0 -2
- package/src/engine/project.test.ts +0 -702
- package/src/engine/project.ts +0 -439
- package/src/index.ts +0 -146
- package/src/utils/api-key.test.ts +0 -41
- package/src/utils/api-key.ts +0 -247
- package/src/utils/audio.ts +0 -83
- package/src/utils/exec-safe.ts +0 -75
- package/src/utils/first-run.ts +0 -52
- package/src/utils/provider-resolver.ts +0 -56
- package/src/utils/remotion.ts +0 -951
- package/src/utils/subtitle.test.ts +0 -227
- package/src/utils/subtitle.ts +0 -169
- package/src/utils/tty.ts +0 -196
- package/tsconfig.json +0 -20
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module ai-motion
|
|
3
|
-
* @description Motion graphics render and composite command.
|
|
4
|
-
*
|
|
5
|
-
* ## Commands: vibe ai motion
|
|
6
|
-
* ## Dependencies: Claude, Gemini, Remotion
|
|
7
|
-
*
|
|
8
|
-
* Extracted from ai.ts as part of modularisation.
|
|
9
|
-
* ai.ts re-exports all public types and functions from this module.
|
|
10
|
-
* @see MODELS.md for AI model configuration
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { type Command } from 'commander';
|
|
14
|
-
import { resolve } from 'node:path';
|
|
15
|
-
import { existsSync } from 'node:fs';
|
|
16
|
-
import { readFile, writeFile } from 'node:fs/promises';
|
|
17
|
-
import chalk from 'chalk';
|
|
18
|
-
import ora from 'ora';
|
|
19
|
-
import { ClaudeProvider, GeminiProvider } from '@vibeframe/ai-providers';
|
|
20
|
-
import { getApiKey } from '../utils/api-key.js';
|
|
21
|
-
|
|
22
|
-
// ── Motion: exported function for Agent tool ────────────────────────────────
|
|
23
|
-
|
|
24
|
-
export interface MotionCommandOptions {
|
|
25
|
-
description: string;
|
|
26
|
-
duration?: number;
|
|
27
|
-
width?: number;
|
|
28
|
-
height?: number;
|
|
29
|
-
fps?: number;
|
|
30
|
-
style?: string;
|
|
31
|
-
/** If set, render the generated code with Remotion */
|
|
32
|
-
render?: boolean;
|
|
33
|
-
/** Base video to composite the motion graphic onto */
|
|
34
|
-
video?: string;
|
|
35
|
-
/** Image to analyze with Gemini — color/mood/composition fed into Claude prompt */
|
|
36
|
-
image?: string;
|
|
37
|
-
/** Path to existing TSX file to refine instead of generating from scratch */
|
|
38
|
-
fromTsx?: string;
|
|
39
|
-
/**
|
|
40
|
-
* LLM model alias for code generation.
|
|
41
|
-
* sonnet (default) | opus | gemini | gemini-3.1-pro
|
|
42
|
-
*/
|
|
43
|
-
model?: string;
|
|
44
|
-
/** Output path (TSX if code-only, WebM/MP4 if rendered) */
|
|
45
|
-
output?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface MotionCommandResult {
|
|
49
|
-
success: boolean;
|
|
50
|
-
codePath?: string;
|
|
51
|
-
renderedPath?: string;
|
|
52
|
-
compositedPath?: string;
|
|
53
|
-
componentName?: string;
|
|
54
|
-
error?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Map model alias → { provider, modelId }
|
|
58
|
-
const MODEL_MAP: Record<string, { provider: "claude" | "gemini"; modelId: string }> = {
|
|
59
|
-
sonnet: { provider: "claude", modelId: "claude-sonnet-4-6" },
|
|
60
|
-
opus: { provider: "claude", modelId: "claude-opus-4-6" },
|
|
61
|
-
gemini: { provider: "gemini", modelId: "gemini-2.5-pro" },
|
|
62
|
-
"gemini-3.1-pro": { provider: "gemini", modelId: "gemini-3.1-pro-preview" },
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export async function executeMotion(options: MotionCommandOptions): Promise<MotionCommandResult> {
|
|
66
|
-
const modelAlias = options.model || "sonnet";
|
|
67
|
-
const modelConfig = MODEL_MAP[modelAlias] ?? MODEL_MAP["sonnet"];
|
|
68
|
-
const useGemini = modelConfig.provider === "gemini";
|
|
69
|
-
|
|
70
|
-
const width = options.width || 1920;
|
|
71
|
-
const height = options.height || 1080;
|
|
72
|
-
const fps = options.fps || 30;
|
|
73
|
-
const duration = options.duration || 5;
|
|
74
|
-
|
|
75
|
-
// Resolve API key based on provider
|
|
76
|
-
let apiKey: string | null;
|
|
77
|
-
if (useGemini) {
|
|
78
|
-
apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
|
|
79
|
-
if (!apiKey) return { success: false, error: "GOOGLE_API_KEY required for Gemini motion generation. Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
|
|
80
|
-
} else {
|
|
81
|
-
apiKey = await getApiKey("ANTHROPIC_API_KEY", "Anthropic");
|
|
82
|
-
if (!apiKey) return { success: false, error: "ANTHROPIC_API_KEY required for Claude motion generation. Run 'vibe setup' or set ANTHROPIC_API_KEY in .env" };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Step 0 (optional): Analyze image with Gemini, inject into description
|
|
86
|
-
let enrichedDescription = options.description;
|
|
87
|
-
if (options.image) {
|
|
88
|
-
const geminiApiKey = await getApiKey("GOOGLE_API_KEY", "Google");
|
|
89
|
-
if (!geminiApiKey) {
|
|
90
|
-
return { success: false, error: "GOOGLE_API_KEY required for image analysis (--image). Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const imagePath = resolve(process.cwd(), options.image);
|
|
94
|
-
const imageBuffer = await readFile(imagePath);
|
|
95
|
-
|
|
96
|
-
const gemini = new GeminiProvider();
|
|
97
|
-
await gemini.initialize({ apiKey: geminiApiKey });
|
|
98
|
-
|
|
99
|
-
const analysisResult = await gemini.analyzeImage(imageBuffer, `Analyze this image for motion graphics design purposes. Describe:
|
|
100
|
-
1. Dominant color palette (exact hex values if possible)
|
|
101
|
-
2. Subject placement and safe zones (where NOT to put text/graphics)
|
|
102
|
-
3. Overall mood and atmosphere
|
|
103
|
-
4. Lighting style (warm/cool, bright/dark, dramatic/soft)
|
|
104
|
-
5. Key visual elements and their positions
|
|
105
|
-
|
|
106
|
-
Be specific and concise — this analysis will guide a Remotion animation generator.`);
|
|
107
|
-
|
|
108
|
-
if (analysisResult.success && analysisResult.response) {
|
|
109
|
-
enrichedDescription = `${options.description}
|
|
110
|
-
|
|
111
|
-
[Image Analysis Context]
|
|
112
|
-
${analysisResult.response}
|
|
113
|
-
|
|
114
|
-
Use this image analysis to inform the color palette, typography placement, and overall aesthetic of the motion graphic.`;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
type MotionResult = Awaited<ReturnType<InstanceType<typeof ClaudeProvider>["generateMotion"]>>;
|
|
119
|
-
let result: MotionResult;
|
|
120
|
-
|
|
121
|
-
if (options.fromTsx) {
|
|
122
|
-
// Refine mode: modify existing TSX instead of generating from scratch
|
|
123
|
-
const tsxPath = resolve(process.cwd(), options.fromTsx);
|
|
124
|
-
if (!existsSync(tsxPath)) {
|
|
125
|
-
return { success: false, error: `TSX file not found: ${tsxPath}` };
|
|
126
|
-
}
|
|
127
|
-
const existingCode = await readFile(tsxPath, "utf-8");
|
|
128
|
-
|
|
129
|
-
if (useGemini) {
|
|
130
|
-
const gemini = new GeminiProvider();
|
|
131
|
-
await gemini.initialize({ apiKey });
|
|
132
|
-
result = await gemini.refineMotion(existingCode, options.description, {
|
|
133
|
-
duration, width, height, fps, model: modelConfig.modelId,
|
|
134
|
-
});
|
|
135
|
-
} else {
|
|
136
|
-
const claude = new ClaudeProvider();
|
|
137
|
-
await claude.initialize({ apiKey });
|
|
138
|
-
result = await claude.refineMotion(existingCode, options.description, {
|
|
139
|
-
duration, width, height, fps,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
if (useGemini) {
|
|
144
|
-
const gemini = new GeminiProvider();
|
|
145
|
-
await gemini.initialize({ apiKey });
|
|
146
|
-
result = await gemini.generateMotion(enrichedDescription, {
|
|
147
|
-
duration, width, height, fps,
|
|
148
|
-
style: options.style,
|
|
149
|
-
model: modelConfig.modelId,
|
|
150
|
-
});
|
|
151
|
-
} else {
|
|
152
|
-
const claude = new ClaudeProvider();
|
|
153
|
-
await claude.initialize({ apiKey });
|
|
154
|
-
result = await claude.generateMotion(enrichedDescription, {
|
|
155
|
-
duration, width, height, fps,
|
|
156
|
-
style: options.style as "minimal" | "corporate" | "playful" | "cinematic" | undefined,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (!result.success || !result.component) {
|
|
162
|
-
return { success: false, error: result.error || "Motion generation failed" };
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const { component } = result;
|
|
166
|
-
const defaultOutput = (options.video || options.image) ? "motion-output.mp4" : options.render ? "motion.webm" : "motion.tsx";
|
|
167
|
-
const outputPath = resolve(process.cwd(), options.output || defaultOutput);
|
|
168
|
-
|
|
169
|
-
// Save TSX code
|
|
170
|
-
const codePath = outputPath.replace(/\.\w+$/, ".tsx");
|
|
171
|
-
await writeFile(codePath, component.code, "utf-8");
|
|
172
|
-
|
|
173
|
-
const shouldRender = options.render || !!options.video || !!options.image;
|
|
174
|
-
if (!shouldRender) {
|
|
175
|
-
return { success: true, codePath, componentName: component.name };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Render (and optionally composite onto video)
|
|
179
|
-
const {
|
|
180
|
-
ensureRemotionInstalled,
|
|
181
|
-
renderMotion,
|
|
182
|
-
wrapComponentWithVideo,
|
|
183
|
-
renderWithEmbeddedVideo,
|
|
184
|
-
wrapComponentWithImage,
|
|
185
|
-
renderWithEmbeddedImage,
|
|
186
|
-
} = await import("../utils/remotion.js");
|
|
187
|
-
|
|
188
|
-
const notInstalled = await ensureRemotionInstalled();
|
|
189
|
-
if (notInstalled) {
|
|
190
|
-
return { success: false, codePath, componentName: component.name, error: notInstalled };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const baseVideo = options.video ? resolve(process.cwd(), options.video) : undefined;
|
|
194
|
-
const baseImage = options.image ? resolve(process.cwd(), options.image) : undefined;
|
|
195
|
-
|
|
196
|
-
if (baseVideo) {
|
|
197
|
-
// Embed video inside the component (no transparency needed)
|
|
198
|
-
const videoFileName = "source_video.mp4";
|
|
199
|
-
const wrapped = wrapComponentWithVideo(component.code, component.name, videoFileName);
|
|
200
|
-
|
|
201
|
-
const renderResult = await renderWithEmbeddedVideo({
|
|
202
|
-
componentCode: wrapped.code,
|
|
203
|
-
componentName: wrapped.name,
|
|
204
|
-
width,
|
|
205
|
-
height,
|
|
206
|
-
fps,
|
|
207
|
-
durationInFrames: component.durationInFrames,
|
|
208
|
-
videoPath: baseVideo,
|
|
209
|
-
videoFileName,
|
|
210
|
-
outputPath,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
if (!renderResult.success) {
|
|
214
|
-
return { success: false, codePath, componentName: component.name, error: renderResult.error };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return { success: true, codePath, componentName: component.name, compositedPath: renderResult.outputPath };
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (baseImage) {
|
|
221
|
-
// Embed image as background — motion graphic overlaid on top
|
|
222
|
-
const ext = baseImage.split(".").pop() || "png";
|
|
223
|
-
const imageFileName = `source_image.${ext}`;
|
|
224
|
-
const wrapped = wrapComponentWithImage(component.code, component.name, imageFileName);
|
|
225
|
-
|
|
226
|
-
const renderResult = await renderWithEmbeddedImage({
|
|
227
|
-
componentCode: wrapped.code,
|
|
228
|
-
componentName: wrapped.name,
|
|
229
|
-
width,
|
|
230
|
-
height,
|
|
231
|
-
fps,
|
|
232
|
-
durationInFrames: component.durationInFrames,
|
|
233
|
-
imagePath: baseImage,
|
|
234
|
-
imageFileName,
|
|
235
|
-
outputPath,
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
if (!renderResult.success) {
|
|
239
|
-
return { success: false, codePath, componentName: component.name, error: renderResult.error };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return { success: true, codePath, componentName: component.name, compositedPath: renderResult.outputPath };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// No base media — render standalone
|
|
246
|
-
const renderResult = await renderMotion({
|
|
247
|
-
componentCode: component.code,
|
|
248
|
-
componentName: component.name,
|
|
249
|
-
width,
|
|
250
|
-
height,
|
|
251
|
-
fps,
|
|
252
|
-
durationInFrames: component.durationInFrames,
|
|
253
|
-
outputPath,
|
|
254
|
-
transparent: false,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
if (!renderResult.success) {
|
|
258
|
-
return { success: false, codePath, componentName: component.name, error: renderResult.error };
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return { success: true, codePath, componentName: component.name, renderedPath: renderResult.outputPath };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Register the 'motion' sub-command on the given parent command.
|
|
266
|
-
* Called from ai.ts: registerMotionCommand(aiCommand)
|
|
267
|
-
*/
|
|
268
|
-
export function registerMotionCommand(aiCommand: Command): void {
|
|
269
|
-
aiCommand
|
|
270
|
-
.command("motion")
|
|
271
|
-
.description("Generate motion graphics using Claude + Remotion (render & composite)")
|
|
272
|
-
.argument("<description>", "Natural language description of the motion graphic")
|
|
273
|
-
.option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)")
|
|
274
|
-
.option("-o, --output <path>", "Output file path", "motion.tsx")
|
|
275
|
-
.option("-d, --duration <sec>", "Duration in seconds", "5")
|
|
276
|
-
.option("-w, --width <px>", "Width in pixels", "1920")
|
|
277
|
-
.option("-h, --height <px>", "Height in pixels", "1080")
|
|
278
|
-
.option("--fps <fps>", "Frame rate", "30")
|
|
279
|
-
.option("-s, --style <style>", "Style preset: minimal, corporate, playful, cinematic")
|
|
280
|
-
.option("--render", "Render the generated code with Remotion (output .webm)")
|
|
281
|
-
.option("--video <path>", "Base video to composite the motion graphic onto")
|
|
282
|
-
.option("--image <path>", "Image to analyze with Gemini — color/mood fed into Claude prompt")
|
|
283
|
-
.option("--from-tsx <path>", "Refine an existing TSX file instead of generating from scratch")
|
|
284
|
-
.option("-m, --model <alias>", "LLM model: sonnet (default), opus, gemini, gemini-3.1-pro", "sonnet")
|
|
285
|
-
.action(async (description: string, options) => {
|
|
286
|
-
try {
|
|
287
|
-
const shouldRender = options.render || !!options.video || !!options.image;
|
|
288
|
-
|
|
289
|
-
const spinner = ora("Generating motion graphic...").start();
|
|
290
|
-
if (options.image) {
|
|
291
|
-
spinner.text = "Analyzing image with Gemini...";
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const result = await executeMotion({
|
|
295
|
-
description,
|
|
296
|
-
duration: parseFloat(options.duration),
|
|
297
|
-
width: parseInt(options.width),
|
|
298
|
-
height: parseInt(options.height),
|
|
299
|
-
fps: parseInt(options.fps),
|
|
300
|
-
style: options.style,
|
|
301
|
-
render: options.render,
|
|
302
|
-
video: options.video,
|
|
303
|
-
image: options.image,
|
|
304
|
-
fromTsx: options.fromTsx,
|
|
305
|
-
model: options.model,
|
|
306
|
-
output: options.output !== "motion.tsx" ? options.output : undefined,
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
if (!result.success) {
|
|
310
|
-
spinner.fail(chalk.red(result.error || "Motion generation failed"));
|
|
311
|
-
if (result.codePath) {
|
|
312
|
-
console.log(chalk.dim(`TSX code saved to: ${result.codePath}`));
|
|
313
|
-
}
|
|
314
|
-
process.exit(1);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
spinner.succeed(chalk.green("Motion graphic generated"));
|
|
318
|
-
|
|
319
|
-
console.log();
|
|
320
|
-
console.log(chalk.bold.cyan("Motion Graphics Pipeline"));
|
|
321
|
-
console.log(chalk.dim("─".repeat(60)));
|
|
322
|
-
|
|
323
|
-
if (result.codePath) {
|
|
324
|
-
console.log(chalk.green(` Code: ${result.codePath}`));
|
|
325
|
-
}
|
|
326
|
-
if (result.renderedPath) {
|
|
327
|
-
console.log(chalk.green(` Rendered: ${result.renderedPath}`));
|
|
328
|
-
}
|
|
329
|
-
if (result.compositedPath) {
|
|
330
|
-
console.log(chalk.green(` Composited: ${result.compositedPath}`));
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (!shouldRender) {
|
|
334
|
-
console.log();
|
|
335
|
-
console.log(chalk.dim("To render, add --render flag or --video <path>:"));
|
|
336
|
-
console.log(chalk.dim(` vibe ai motion "${description}" --render -o motion.webm`));
|
|
337
|
-
console.log(chalk.dim(` vibe ai motion "${description}" --video input.mp4 -o output.mp4`));
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
console.log();
|
|
341
|
-
} catch (error) {
|
|
342
|
-
console.error(chalk.red("Motion generation failed"));
|
|
343
|
-
console.error(error);
|
|
344
|
-
process.exit(1);
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
}
|