@vibeframe/cli 0.27.0 → 0.30.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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent/adapters/index.d.ts +1 -0
  3. package/dist/agent/adapters/index.d.ts.map +1 -1
  4. package/dist/agent/adapters/index.js +5 -0
  5. package/dist/agent/adapters/index.js.map +1 -1
  6. package/dist/agent/adapters/openrouter.d.ts +16 -0
  7. package/dist/agent/adapters/openrouter.d.ts.map +1 -0
  8. package/dist/agent/adapters/openrouter.js +100 -0
  9. package/dist/agent/adapters/openrouter.js.map +1 -0
  10. package/dist/agent/types.d.ts +1 -1
  11. package/dist/agent/types.d.ts.map +1 -1
  12. package/dist/commands/agent.d.ts.map +1 -1
  13. package/dist/commands/agent.js +3 -1
  14. package/dist/commands/agent.js.map +1 -1
  15. package/dist/commands/ai-edit-cli.d.ts.map +1 -1
  16. package/dist/commands/ai-edit-cli.js +18 -0
  17. package/dist/commands/ai-edit-cli.js.map +1 -1
  18. package/dist/commands/generate.js +14 -0
  19. package/dist/commands/generate.js.map +1 -1
  20. package/dist/commands/schema.d.ts +1 -0
  21. package/dist/commands/schema.d.ts.map +1 -1
  22. package/dist/commands/schema.js +122 -21
  23. package/dist/commands/schema.js.map +1 -1
  24. package/dist/commands/setup.js +5 -2
  25. package/dist/commands/setup.js.map +1 -1
  26. package/dist/config/schema.d.ts +2 -1
  27. package/dist/config/schema.d.ts.map +1 -1
  28. package/dist/config/schema.js +2 -0
  29. package/dist/config/schema.js.map +1 -1
  30. package/dist/index.js +0 -0
  31. package/package.json +16 -12
  32. package/.turbo/turbo-build.log +0 -4
  33. package/.turbo/turbo-lint.log +0 -21
  34. package/.turbo/turbo-test.log +0 -689
  35. package/src/agent/adapters/claude.ts +0 -143
  36. package/src/agent/adapters/gemini.ts +0 -159
  37. package/src/agent/adapters/index.ts +0 -61
  38. package/src/agent/adapters/ollama.ts +0 -231
  39. package/src/agent/adapters/openai.ts +0 -116
  40. package/src/agent/adapters/xai.ts +0 -119
  41. package/src/agent/index.ts +0 -251
  42. package/src/agent/memory/index.ts +0 -151
  43. package/src/agent/prompts/system.ts +0 -106
  44. package/src/agent/tools/ai-editing.ts +0 -845
  45. package/src/agent/tools/ai-generation.ts +0 -1073
  46. package/src/agent/tools/ai-pipeline.ts +0 -1055
  47. package/src/agent/tools/ai.ts +0 -21
  48. package/src/agent/tools/batch.ts +0 -429
  49. package/src/agent/tools/e2e.test.ts +0 -545
  50. package/src/agent/tools/export.ts +0 -184
  51. package/src/agent/tools/filesystem.ts +0 -237
  52. package/src/agent/tools/index.ts +0 -150
  53. package/src/agent/tools/integration.test.ts +0 -775
  54. package/src/agent/tools/media.ts +0 -697
  55. package/src/agent/tools/project.ts +0 -313
  56. package/src/agent/tools/timeline.ts +0 -951
  57. package/src/agent/types.ts +0 -68
  58. package/src/commands/agent.ts +0 -340
  59. package/src/commands/ai-analyze.ts +0 -429
  60. package/src/commands/ai-animated-caption.ts +0 -390
  61. package/src/commands/ai-audio.ts +0 -941
  62. package/src/commands/ai-broll.ts +0 -490
  63. package/src/commands/ai-edit-cli.ts +0 -658
  64. package/src/commands/ai-edit.ts +0 -1542
  65. package/src/commands/ai-fill-gaps.ts +0 -566
  66. package/src/commands/ai-helpers.ts +0 -65
  67. package/src/commands/ai-highlights.ts +0 -1303
  68. package/src/commands/ai-image.ts +0 -761
  69. package/src/commands/ai-motion.ts +0 -347
  70. package/src/commands/ai-narrate.ts +0 -451
  71. package/src/commands/ai-review.ts +0 -309
  72. package/src/commands/ai-script-pipeline-cli.ts +0 -1710
  73. package/src/commands/ai-script-pipeline.ts +0 -1365
  74. package/src/commands/ai-suggest-edit.ts +0 -264
  75. package/src/commands/ai-video-fx.ts +0 -445
  76. package/src/commands/ai-video.ts +0 -915
  77. package/src/commands/ai-viral.ts +0 -595
  78. package/src/commands/ai-visual-fx.ts +0 -601
  79. package/src/commands/ai.test.ts +0 -627
  80. package/src/commands/ai.ts +0 -307
  81. package/src/commands/analyze.ts +0 -282
  82. package/src/commands/audio.ts +0 -644
  83. package/src/commands/batch.test.ts +0 -279
  84. package/src/commands/batch.ts +0 -440
  85. package/src/commands/detect.ts +0 -329
  86. package/src/commands/doctor.ts +0 -237
  87. package/src/commands/edit-cmd.ts +0 -1014
  88. package/src/commands/export.ts +0 -918
  89. package/src/commands/generate.ts +0 -2146
  90. package/src/commands/media.ts +0 -177
  91. package/src/commands/output.ts +0 -142
  92. package/src/commands/pipeline.ts +0 -398
  93. package/src/commands/project.test.ts +0 -127
  94. package/src/commands/project.ts +0 -149
  95. package/src/commands/sanitize.ts +0 -60
  96. package/src/commands/schema.ts +0 -130
  97. package/src/commands/setup.ts +0 -509
  98. package/src/commands/timeline.test.ts +0 -499
  99. package/src/commands/timeline.ts +0 -529
  100. package/src/commands/validate.ts +0 -77
  101. package/src/config/config.test.ts +0 -197
  102. package/src/config/index.ts +0 -125
  103. package/src/config/schema.ts +0 -82
  104. package/src/engine/index.ts +0 -2
  105. package/src/engine/project.test.ts +0 -702
  106. package/src/engine/project.ts +0 -439
  107. package/src/index.ts +0 -146
  108. package/src/utils/api-key.test.ts +0 -41
  109. package/src/utils/api-key.ts +0 -247
  110. package/src/utils/audio.ts +0 -83
  111. package/src/utils/exec-safe.ts +0 -75
  112. package/src/utils/first-run.ts +0 -52
  113. package/src/utils/provider-resolver.ts +0 -56
  114. package/src/utils/remotion.ts +0 -951
  115. package/src/utils/subtitle.test.ts +0 -227
  116. package/src/utils/subtitle.ts +0 -169
  117. package/src/utils/tty.ts +0 -196
  118. package/tsconfig.json +0 -20
@@ -1,915 +0,0 @@
1
- /**
2
- * @module ai-video
3
- * @description Video generation and management commands for the VibeFrame CLI.
4
- *
5
- * ## Commands: vibe ai video, vibe ai video-status, vibe ai video-cancel,
6
- * vibe ai kling, vibe ai kling-status, vibe ai video-extend
7
- * ## Dependencies: Runway, Kling, Veo (Gemini)
8
- *
9
- * Extracted from ai.ts as part of modularisation.
10
- * ai.ts calls registerVideoCommands(aiCommand).
11
- * @see MODELS.md for AI model configuration
12
- */
13
-
14
- import { Command } from "commander";
15
- import { readFile, writeFile } from "node:fs/promises";
16
- import { resolve } from "node:path";
17
- import chalk from "chalk";
18
- import ora from "ora";
19
- import { GeminiProvider, GrokProvider, KlingProvider, RunwayProvider } from "@vibeframe/ai-providers";
20
- import { getApiKey } from "../utils/api-key.js";
21
- import { getApiKeyFromConfig } from "../config/index.js";
22
- import { uploadToImgbb } from "./ai-script-pipeline.js";
23
- import { downloadVideo } from "./ai-helpers.js";
24
-
25
- function getStatusColor(status: string): string {
26
- switch (status) {
27
- case "completed":
28
- return chalk.green(status);
29
- case "processing":
30
- case "running":
31
- case "in_progress":
32
- return chalk.yellow(status);
33
- case "failed":
34
- case "error":
35
- return chalk.red(status);
36
- default:
37
- return chalk.gray(status);
38
- }
39
- }
40
-
41
- export function registerVideoCommands(aiCommand: Command): void {
42
- aiCommand
43
- .command("video")
44
- .description("Generate video using AI (Grok, Runway, Kling, or Veo)")
45
- .argument("<prompt>", "Text prompt describing the video")
46
- .option("-p, --provider <provider>", "Provider: grok, kling, runway, veo", "kling")
47
- .option("-k, --api-key <key>", "API key (or set RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)")
48
- .option("-o, --output <path>", "Output file path (downloads video)")
49
- .option("-i, --image <path>", "Reference image for image-to-video")
50
- .option("-d, --duration <sec>", "Duration: 5 or 10 seconds", "5")
51
- .option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1", "16:9")
52
- .option("-s, --seed <number>", "Random seed for reproducibility (Runway only)")
53
- .option("-m, --mode <mode>", "Generation mode: std or pro (Kling only)", "std")
54
- .option("-n, --negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)")
55
- .option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)")
56
- .option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)")
57
- .option("--ref-images <paths...>", "Reference images for character consistency (Veo 3.1 only, max 3)")
58
- .option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)")
59
- .option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast")
60
- .option("--runway-model <model>", "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)", "gen4.5")
61
- .option("--no-wait", "Start generation and return task ID without waiting")
62
- .action(async (prompt: string, options) => {
63
- try {
64
- const provider = options.provider.toLowerCase();
65
- const validProviders = ["grok", "runway", "kling", "veo"];
66
- if (!validProviders.includes(provider)) {
67
- console.error(chalk.red(`Invalid provider: ${provider}`));
68
- console.error(chalk.dim(`Available providers: ${validProviders.join(", ")}`));
69
- process.exit(1);
70
- }
71
-
72
- const envKeyMap: Record<string, string> = {
73
- grok: "XAI_API_KEY",
74
- runway: "RUNWAY_API_SECRET",
75
- kling: "KLING_API_KEY",
76
- veo: "GOOGLE_API_KEY",
77
- };
78
- const providerNameMap: Record<string, string> = {
79
- grok: "Grok",
80
- runway: "Runway",
81
- kling: "Kling",
82
- veo: "Veo",
83
- };
84
- const envKey = envKeyMap[provider];
85
- const providerName = providerNameMap[provider];
86
- const apiKey = await getApiKey(envKey, providerName, options.apiKey);
87
- if (!apiKey) {
88
- console.error(chalk.red(`${providerName} API key required. Set ${envKey} in .env or run: vibe setup`));
89
- if (provider === "kling") {
90
- console.error(chalk.dim("Format: ACCESS_KEY:SECRET_KEY"));
91
- }
92
- console.error(chalk.dim(`Use --api-key or set ${envKey} environment variable`));
93
- process.exit(1);
94
- }
95
-
96
- // Runway gen4_turbo requires an input image (gen4.5 supports text-to-video)
97
- const runwayModel = options.runwayModel || "gen4.5";
98
- if (provider === "runway" && !options.image && runwayModel === "gen4_turbo") {
99
- console.error(chalk.red("Runway gen4_turbo requires an input image. Use -i <image> to specify."));
100
- console.error(chalk.dim("Tip: Use gen4.5 (default) for text-to-video, or provide -i <image>"));
101
- process.exit(1);
102
- }
103
-
104
- const spinner = ora(`Initializing ${providerName}...`).start();
105
-
106
- let referenceImage: string | undefined;
107
- let isImageToVideo = false;
108
- if (options.image) {
109
- spinner.text = "Reading reference image...";
110
- const imagePath = resolve(process.cwd(), options.image);
111
- const imageBuffer = await readFile(imagePath);
112
- const ext = options.image.toLowerCase().split(".").pop();
113
- const mimeTypes: Record<string, string> = {
114
- jpg: "image/jpeg",
115
- jpeg: "image/jpeg",
116
- png: "image/png",
117
- gif: "image/gif",
118
- webp: "image/webp",
119
- };
120
- const mimeType = mimeTypes[ext || "png"] || "image/png";
121
- referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
122
- isImageToVideo = true;
123
- }
124
-
125
- spinner.text = "Starting video generation...";
126
-
127
- let result;
128
- let finalResult;
129
-
130
- if (provider === "runway") {
131
- const runway = new RunwayProvider();
132
- await runway.initialize({ apiKey });
133
-
134
- result = await runway.generateVideo(prompt, {
135
- prompt,
136
- referenceImage,
137
- duration: parseInt(options.duration) as 5 | 10,
138
- aspectRatio: options.ratio as "16:9" | "9:16",
139
- seed: options.seed ? parseInt(options.seed) : undefined,
140
- });
141
-
142
- if (result.status === "failed") {
143
- spinner.fail(chalk.red(result.error || "Failed to start generation"));
144
- process.exit(1);
145
- }
146
-
147
- console.log();
148
- console.log(chalk.bold.cyan("Video Generation Started"));
149
- console.log(chalk.dim("─".repeat(60)));
150
- console.log(`Provider: ${chalk.bold(`Runway ${runwayModel}`)}`);
151
- console.log(`Task ID: ${chalk.bold(result.id)}`);
152
-
153
- if (!options.wait) {
154
- spinner.succeed(chalk.green("Generation started"));
155
- console.log();
156
- console.log(chalk.dim("Check status with:"));
157
- console.log(chalk.dim(` vibe ai video-status ${result.id}`));
158
- console.log();
159
- return;
160
- }
161
-
162
- spinner.text = "Generating video (this may take 1-2 minutes)...";
163
-
164
- finalResult = await runway.waitForCompletion(
165
- result.id,
166
- (status) => {
167
- if (status.progress !== undefined) {
168
- spinner.text = `Generating video... ${status.progress}%`;
169
- }
170
- },
171
- 300000
172
- );
173
- } else if (provider === "kling") {
174
- const kling = new KlingProvider();
175
- await kling.initialize({ apiKey });
176
-
177
- if (!kling.isConfigured()) {
178
- spinner.fail(chalk.red("Invalid API key format. Use ACCESS_KEY:SECRET_KEY"));
179
- process.exit(1);
180
- }
181
-
182
- // Kling v2.x requires image URL, not base64 — auto-upload to ImgBB
183
- let klingImage = referenceImage;
184
- if (klingImage && klingImage.startsWith("data:")) {
185
- spinner.text = "Uploading image to ImgBB for Kling...";
186
- const imgbbKey = (await getApiKeyFromConfig("imgbb")) || process.env.IMGBB_API_KEY;
187
- if (!imgbbKey) {
188
- spinner.fail(chalk.red("Kling requires image URL. Set IMGBB_API_KEY for auto-upload."));
189
- console.error(chalk.dim("Run: vibe setup --full to configure ImgBB"));
190
- process.exit(1);
191
- }
192
- // Extract raw base64 from data URI
193
- const base64Data = klingImage.split(",")[1];
194
- const imageBuffer = Buffer.from(base64Data, "base64");
195
- const uploadResult = await uploadToImgbb(imageBuffer, imgbbKey);
196
- if (!uploadResult.success || !uploadResult.url) {
197
- spinner.fail(chalk.red(`ImgBB upload failed: ${uploadResult.error}`));
198
- process.exit(1);
199
- }
200
- klingImage = uploadResult.url;
201
- spinner.text = "Starting video generation...";
202
- }
203
-
204
- result = await kling.generateVideo(prompt, {
205
- prompt,
206
- referenceImage: klingImage,
207
- duration: parseInt(options.duration) as 5 | 10,
208
- aspectRatio: options.ratio as "16:9" | "9:16" | "1:1",
209
- negativePrompt: options.negative,
210
- mode: options.mode as "std" | "pro",
211
- });
212
-
213
- if (result.status === "failed") {
214
- spinner.fail(chalk.red(result.error || "Failed to start generation"));
215
- process.exit(1);
216
- }
217
-
218
- console.log();
219
- console.log(chalk.bold.cyan("Video Generation Started"));
220
- console.log(chalk.dim("─".repeat(60)));
221
- console.log(`Provider: ${chalk.bold("Kling AI")}`);
222
- console.log(`Task ID: ${chalk.bold(result.id)}`);
223
- console.log(`Type: ${isImageToVideo ? "image2video" : "text2video"}`);
224
-
225
- if (!options.wait) {
226
- spinner.succeed(chalk.green("Generation started"));
227
- console.log();
228
- console.log(chalk.dim("Check status with:"));
229
- console.log(chalk.dim(` vibe ai kling-status ${result.id}${isImageToVideo ? " --type image2video" : ""}`));
230
- console.log();
231
- return;
232
- }
233
-
234
- spinner.text = "Generating video (this may take 2-5 minutes)...";
235
-
236
- const taskType = isImageToVideo ? "image2video" : "text2video";
237
- finalResult = await kling.waitForCompletion(
238
- result.id,
239
- taskType,
240
- (status) => {
241
- spinner.text = `Generating video... ${status.status}`;
242
- },
243
- 600000
244
- );
245
- } else if (provider === "veo") {
246
- const gemini = new GeminiProvider();
247
- await gemini.initialize({ apiKey });
248
-
249
- // Map Veo model alias to full model ID
250
- const veoModelMap: Record<string, string> = {
251
- "3.0": "veo-3.0-generate-preview",
252
- "3.1": "veo-3.1-generate-preview",
253
- "3.1-fast": "veo-3.1-fast-generate-preview",
254
- };
255
- const veoModel = veoModelMap[options.veoModel] || "veo-3.1-fast-generate-preview";
256
-
257
- const veoDuration = parseInt(options.duration) <= 6 ? 6 : 8;
258
-
259
- // Prepare last frame if provided
260
- let lastFrame: string | undefined;
261
- if (options.lastFrame) {
262
- const lastFramePath = resolve(process.cwd(), options.lastFrame);
263
- const lastFrameBuffer = await readFile(lastFramePath);
264
- const ext = options.lastFrame.toLowerCase().split(".").pop();
265
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
266
- lastFrame = `data:${mimeType};base64,${lastFrameBuffer.toString("base64")}`;
267
- }
268
-
269
- // Prepare reference images if provided
270
- let refImages: Array<{ base64: string; mimeType: string }> | undefined;
271
- if (options.refImages && options.refImages.length > 0) {
272
- refImages = [];
273
- for (const refPath of options.refImages.slice(0, 3)) {
274
- const absRefPath = resolve(process.cwd(), refPath);
275
- const refBuffer = await readFile(absRefPath);
276
- const ext = refPath.toLowerCase().split(".").pop();
277
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
278
- refImages.push({ base64: refBuffer.toString("base64"), mimeType });
279
- }
280
- }
281
-
282
- result = await gemini.generateVideo(prompt, {
283
- prompt,
284
- referenceImage,
285
- duration: veoDuration,
286
- aspectRatio: options.ratio as "16:9" | "9:16" | "1:1",
287
- model: veoModel as "veo-3.0-generate-preview" | "veo-3.1-generate-preview" | "veo-3.1-fast-generate-preview",
288
- negativePrompt: options.negative,
289
- resolution: options.resolution as "720p" | "1080p" | "4k" | undefined,
290
- lastFrame,
291
- referenceImages: refImages,
292
- personGeneration: options.person as "allow_all" | "allow_adult" | undefined,
293
- });
294
-
295
- if (result.status === "failed") {
296
- spinner.fail(chalk.red(result.error || "Failed to start generation"));
297
- process.exit(1);
298
- }
299
-
300
- console.log();
301
- console.log(chalk.bold.cyan("Video Generation Started"));
302
- console.log(chalk.dim("─".repeat(60)));
303
- console.log(`Provider: ${chalk.bold("Google Veo 3.1")}`);
304
- console.log(`Task ID: ${chalk.bold(result.id)}`);
305
-
306
- if (!options.wait) {
307
- spinner.succeed(chalk.green("Generation started"));
308
- console.log();
309
- console.log(chalk.dim("Veo generation is synchronous - video URL available above"));
310
- console.log();
311
- return;
312
- }
313
-
314
- spinner.text = "Generating video (this may take 1-3 minutes)...";
315
- finalResult = await gemini.waitForVideoCompletion(
316
- result.id,
317
- (status) => {
318
- spinner.text = `Generating video... ${status.status}`;
319
- },
320
- 300000
321
- );
322
- } else if (provider === "grok") {
323
- const grok = new GrokProvider();
324
- await grok.initialize({ apiKey });
325
-
326
- result = await grok.generateVideo(prompt, {
327
- prompt,
328
- referenceImage,
329
- duration: parseInt(options.duration),
330
- aspectRatio: options.ratio as "16:9" | "9:16" | "1:1",
331
- });
332
-
333
- if (result.status === "failed") {
334
- spinner.fail(chalk.red(result.error || "Failed to start generation"));
335
- process.exit(1);
336
- }
337
-
338
- console.log();
339
- console.log(chalk.bold.cyan("Video Generation Started"));
340
- console.log(chalk.dim("─".repeat(60)));
341
- console.log(`Provider: ${chalk.bold("Grok Imagine")}`);
342
- console.log(`Task ID: ${chalk.bold(result.id)}`);
343
-
344
- if (!options.wait) {
345
- spinner.succeed(chalk.green("Generation started"));
346
- console.log();
347
- return;
348
- }
349
-
350
- spinner.text = "Generating video (this may take 1-3 minutes)...";
351
-
352
- finalResult = await grok.waitForCompletion(
353
- result.id,
354
- (status) => {
355
- spinner.text = `Generating video... ${status.status}`;
356
- },
357
- 300000
358
- );
359
- }
360
-
361
- if (!finalResult || finalResult.status !== "completed") {
362
- spinner.fail(chalk.red(finalResult?.error || "Generation failed"));
363
- process.exit(1);
364
- }
365
-
366
- spinner.succeed(chalk.green("Video generated"));
367
-
368
- console.log();
369
- if (finalResult.videoUrl) {
370
- console.log(`Video URL: ${finalResult.videoUrl}`);
371
- }
372
- if (finalResult.duration) {
373
- console.log(`Duration: ${finalResult.duration}s`);
374
- }
375
- console.log();
376
-
377
- if (options.output && finalResult.videoUrl) {
378
- const downloadSpinner = ora("Downloading video...").start();
379
- try {
380
- const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
381
- const outputPath = resolve(process.cwd(), options.output);
382
- await writeFile(outputPath, buffer);
383
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
384
- } catch (err) {
385
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
386
- }
387
- }
388
- } catch (error) {
389
- console.error(chalk.red("Video generation failed"));
390
- console.error(error);
391
- process.exit(1);
392
- }
393
- });
394
-
395
- aiCommand
396
- .command("video-status")
397
- .description("Check Runway video generation status")
398
- .argument("<task-id>", "Task ID from video generation")
399
- .option("-k, --api-key <key>", "Runway API key (or set RUNWAY_API_SECRET env)")
400
- .option("-w, --wait", "Wait for completion")
401
- .option("-o, --output <path>", "Download video when complete")
402
- .action(async (taskId: string, options) => {
403
- try {
404
- const apiKey = await getApiKey("RUNWAY_API_SECRET", "Runway", options.apiKey);
405
- if (!apiKey) {
406
- console.error(chalk.red("Runway API key required. Set RUNWAY_API_SECRET in .env or run: vibe setup"));
407
- process.exit(1);
408
- }
409
-
410
- const spinner = ora("Checking status...").start();
411
-
412
- const runway = new RunwayProvider();
413
- await runway.initialize({ apiKey });
414
-
415
- let result = await runway.getGenerationStatus(taskId);
416
-
417
- if (options.wait && result.status !== "completed" && result.status !== "failed" && result.status !== "cancelled") {
418
- spinner.text = "Waiting for completion...";
419
- result = await runway.waitForCompletion(
420
- taskId,
421
- (status) => {
422
- if (status.progress !== undefined) {
423
- spinner.text = `Generating... ${status.progress}%`;
424
- }
425
- }
426
- );
427
- }
428
-
429
- spinner.stop();
430
-
431
- console.log();
432
- console.log(chalk.bold.cyan("Generation Status"));
433
- console.log(chalk.dim("─".repeat(60)));
434
- console.log(`Task ID: ${taskId}`);
435
- console.log(`Status: ${getStatusColor(result.status)}`);
436
- if (result.progress !== undefined) {
437
- console.log(`Progress: ${result.progress}%`);
438
- }
439
- if (result.videoUrl) {
440
- console.log(`Video URL: ${result.videoUrl}`);
441
- }
442
- if (result.error) {
443
- console.log(`Error: ${chalk.red(result.error)}`);
444
- }
445
- console.log();
446
-
447
- if (options.output && result.videoUrl) {
448
- const downloadSpinner = ora("Downloading video...").start();
449
- try {
450
- const buffer = await downloadVideo(result.videoUrl, apiKey);
451
- const outputPath = resolve(process.cwd(), options.output);
452
- await writeFile(outputPath, buffer);
453
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
454
- } catch (err) {
455
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
456
- }
457
- }
458
- } catch (error) {
459
- console.error(chalk.red("Failed to get status"));
460
- console.error(error);
461
- process.exit(1);
462
- }
463
- });
464
-
465
- aiCommand
466
- .command("video-cancel")
467
- .description("Cancel Runway video generation")
468
- .argument("<task-id>", "Task ID to cancel")
469
- .option("-k, --api-key <key>", "Runway API key (or set RUNWAY_API_SECRET env)")
470
- .action(async (taskId: string, options) => {
471
- try {
472
- const apiKey = await getApiKey("RUNWAY_API_SECRET", "Runway", options.apiKey);
473
- if (!apiKey) {
474
- console.error(chalk.red("Runway API key required. Set RUNWAY_API_SECRET in .env or run: vibe setup"));
475
- process.exit(1);
476
- }
477
-
478
- const spinner = ora("Cancelling generation...").start();
479
-
480
- const runway = new RunwayProvider();
481
- await runway.initialize({ apiKey });
482
-
483
- const success = await runway.cancelGeneration(taskId);
484
-
485
- if (success) {
486
- spinner.succeed(chalk.green("Generation cancelled"));
487
- } else {
488
- spinner.fail(chalk.red("Failed to cancel generation"));
489
- process.exit(1);
490
- }
491
- } catch (error) {
492
- console.error(chalk.red("Failed to cancel"));
493
- console.error(error);
494
- process.exit(1);
495
- }
496
- });
497
-
498
- aiCommand
499
- .command("kling")
500
- .description("Generate video using Kling AI")
501
- .argument("<prompt>", "Text prompt describing the video")
502
- .option("-k, --api-key <key>", "Kling API key (ACCESS_KEY:SECRET_KEY) or set KLING_API_KEY env")
503
- .option("-o, --output <path>", "Output file path (downloads video)")
504
- .option("-i, --image <path>", "Reference image for image-to-video")
505
- .option("-d, --duration <sec>", "Duration: 5 or 10 seconds", "5")
506
- .option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1", "16:9")
507
- .option("-m, --mode <mode>", "Generation mode: std (standard) or pro", "pro")
508
- .option("-n, --negative <prompt>", "Negative prompt (what to avoid)")
509
- .option("--no-wait", "Start generation and return task ID without waiting")
510
- .action(async (prompt: string, options) => {
511
- try {
512
- const apiKey = await getApiKey("KLING_API_KEY", "Kling", options.apiKey);
513
- if (!apiKey) {
514
- console.error(chalk.red("Kling API key required. Set KLING_API_KEY in .env or run: vibe setup"));
515
- console.error(chalk.dim("Format: ACCESS_KEY:SECRET_KEY"));
516
- console.error(chalk.dim("Use --api-key or set KLING_API_KEY environment variable"));
517
- process.exit(1);
518
- }
519
-
520
- const spinner = ora("Initializing Kling AI...").start();
521
-
522
- const kling = new KlingProvider();
523
- await kling.initialize({ apiKey });
524
-
525
- if (!kling.isConfigured()) {
526
- spinner.fail(chalk.red("Invalid API key format. Use ACCESS_KEY:SECRET_KEY"));
527
- process.exit(1);
528
- }
529
-
530
- let referenceImage: string | undefined;
531
- let isImageToVideo = false;
532
- if (options.image) {
533
- spinner.text = "Reading reference image...";
534
- const imagePath = resolve(process.cwd(), options.image);
535
- const imageBuffer = await readFile(imagePath);
536
- const ext = options.image.toLowerCase().split(".").pop();
537
- const mimeTypes: Record<string, string> = {
538
- jpg: "image/jpeg",
539
- jpeg: "image/jpeg",
540
- png: "image/png",
541
- gif: "image/gif",
542
- webp: "image/webp",
543
- };
544
- const mimeType = mimeTypes[ext || "png"] || "image/png";
545
- referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
546
- isImageToVideo = true;
547
- }
548
-
549
- // Kling v2.x requires image URL, not base64 — auto-upload to ImgBB
550
- let klingImage = referenceImage;
551
- if (klingImage && klingImage.startsWith("data:")) {
552
- spinner.text = "Uploading image to ImgBB for Kling...";
553
- const imgbbKey = (await getApiKeyFromConfig("imgbb")) || process.env.IMGBB_API_KEY;
554
- if (!imgbbKey) {
555
- spinner.fail(chalk.red("Kling requires image URL. Set IMGBB_API_KEY for auto-upload."));
556
- console.error(chalk.dim("Run: vibe setup --full to configure ImgBB"));
557
- process.exit(1);
558
- }
559
- const base64Data = klingImage.split(",")[1];
560
- const imageBuffer = Buffer.from(base64Data, "base64");
561
- const uploadResult = await uploadToImgbb(imageBuffer, imgbbKey);
562
- if (!uploadResult.success || !uploadResult.url) {
563
- spinner.fail(chalk.red(`ImgBB upload failed: ${uploadResult.error}`));
564
- process.exit(1);
565
- }
566
- klingImage = uploadResult.url;
567
- }
568
-
569
- spinner.text = "Starting video generation...";
570
-
571
- const result = await kling.generateVideo(prompt, {
572
- prompt,
573
- referenceImage: klingImage,
574
- duration: parseInt(options.duration) as 5 | 10,
575
- aspectRatio: options.ratio as "16:9" | "9:16" | "1:1",
576
- negativePrompt: options.negative,
577
- mode: options.mode as "std" | "pro",
578
- });
579
-
580
- if (result.status === "failed") {
581
- spinner.fail(chalk.red(result.error || "Failed to start generation"));
582
- process.exit(1);
583
- }
584
-
585
- console.log();
586
- console.log(chalk.bold.cyan("Kling Video Generation Started"));
587
- console.log(chalk.dim("─".repeat(60)));
588
- console.log(`Task ID: ${chalk.bold(result.id)}`);
589
- console.log(`Type: ${isImageToVideo ? "image2video" : "text2video"}`);
590
-
591
- if (!options.wait) {
592
- spinner.succeed(chalk.green("Generation started"));
593
- console.log();
594
- console.log(chalk.dim("Check status with:"));
595
- console.log(chalk.dim(` vibe ai kling-status ${result.id}${isImageToVideo ? " --type image2video" : ""}`));
596
- console.log();
597
- return;
598
- }
599
-
600
- spinner.text = "Generating video (this may take 2-5 minutes)...";
601
-
602
- const taskType = isImageToVideo ? "image2video" : "text2video";
603
- const finalResult = await kling.waitForCompletion(
604
- result.id,
605
- taskType,
606
- (status) => {
607
- spinner.text = `Generating video... ${status.status}`;
608
- },
609
- 600000
610
- );
611
-
612
- if (finalResult.status !== "completed") {
613
- spinner.fail(chalk.red(finalResult.error || "Generation failed"));
614
- process.exit(1);
615
- }
616
-
617
- spinner.succeed(chalk.green("Video generated"));
618
-
619
- console.log();
620
- if (finalResult.videoUrl) {
621
- console.log(`Video URL: ${finalResult.videoUrl}`);
622
- }
623
- if (finalResult.duration) {
624
- console.log(`Duration: ${finalResult.duration}s`);
625
- }
626
- console.log();
627
-
628
- if (options.output && finalResult.videoUrl) {
629
- const downloadSpinner = ora("Downloading video...").start();
630
- try {
631
- const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
632
- const outputPath = resolve(process.cwd(), options.output);
633
- await writeFile(outputPath, buffer);
634
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
635
- } catch (err) {
636
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
637
- }
638
- }
639
- } catch (error) {
640
- console.error(chalk.red("Video generation failed"));
641
- console.error(error);
642
- process.exit(1);
643
- }
644
- });
645
-
646
- aiCommand
647
- .command("kling-status")
648
- .description("Check Kling video generation status")
649
- .argument("<task-id>", "Task ID from video generation")
650
- .option("-k, --api-key <key>", "Kling API key (or set KLING_API_KEY env)")
651
- .option("-t, --type <type>", "Task type: text2video or image2video", "text2video")
652
- .option("-w, --wait", "Wait for completion")
653
- .option("-o, --output <path>", "Download video when complete")
654
- .action(async (taskId: string, options) => {
655
- try {
656
- const apiKey = await getApiKey("KLING_API_KEY", "Kling", options.apiKey);
657
- if (!apiKey) {
658
- console.error(chalk.red("Kling API key required. Set KLING_API_KEY in .env or run: vibe setup"));
659
- process.exit(1);
660
- }
661
-
662
- const spinner = ora("Checking status...").start();
663
-
664
- const kling = new KlingProvider();
665
- await kling.initialize({ apiKey });
666
-
667
- const taskType = options.type as "text2video" | "image2video";
668
- let result = await kling.getGenerationStatus(taskId, taskType);
669
-
670
- if (options.wait && result.status !== "completed" && result.status !== "failed" && result.status !== "cancelled") {
671
- spinner.text = "Waiting for completion...";
672
- result = await kling.waitForCompletion(
673
- taskId,
674
- taskType,
675
- (status) => {
676
- spinner.text = `Generating... ${status.status}`;
677
- }
678
- );
679
- }
680
-
681
- spinner.stop();
682
-
683
- console.log();
684
- console.log(chalk.bold.cyan("Kling Generation Status"));
685
- console.log(chalk.dim("─".repeat(60)));
686
- console.log(`Task ID: ${taskId}`);
687
- console.log(`Type: ${taskType}`);
688
- console.log(`Status: ${getStatusColor(result.status)}`);
689
- if (result.videoUrl) {
690
- console.log(`Video URL: ${result.videoUrl}`);
691
- }
692
- if (result.duration) {
693
- console.log(`Duration: ${result.duration}s`);
694
- }
695
- if (result.error) {
696
- console.log(`Error: ${chalk.red(result.error)}`);
697
- }
698
- console.log();
699
-
700
- if (options.output && result.videoUrl) {
701
- const downloadSpinner = ora("Downloading video...").start();
702
- try {
703
- const buffer = await downloadVideo(result.videoUrl, apiKey);
704
- const outputPath = resolve(process.cwd(), options.output);
705
- await writeFile(outputPath, buffer);
706
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
707
- } catch (err) {
708
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
709
- }
710
- }
711
- } catch (error) {
712
- console.error(chalk.red("Failed to get status"));
713
- console.error(error);
714
- process.exit(1);
715
- }
716
- });
717
-
718
- aiCommand
719
- .command("video-extend")
720
- .description("Extend video duration using Kling AI (requires Kling video ID)")
721
- .argument("<video-id>", "Kling video ID (from generation result)")
722
- .option("-k, --api-key <key>", "Kling API key (ACCESS_KEY:SECRET_KEY) or set KLING_API_KEY env")
723
- .option("-o, --output <path>", "Output file path")
724
- .option("--prompt <text>", "Continuation prompt")
725
- .option("-d, --duration <sec>", "Duration: 5 or 10 seconds", "5")
726
- .option("-n, --negative <prompt>", "Negative prompt (what to avoid)")
727
- .option("--no-wait", "Start generation and return task ID without waiting")
728
- .action(async (videoId: string, options) => {
729
- try {
730
- const apiKey = await getApiKey("KLING_API_KEY", "Kling", options.apiKey);
731
- if (!apiKey) {
732
- console.error(chalk.red("Kling API key required. Set KLING_API_KEY in .env or run: vibe setup"));
733
- console.error(chalk.dim("Format: ACCESS_KEY:SECRET_KEY"));
734
- console.error(chalk.dim("Use --api-key or set KLING_API_KEY environment variable"));
735
- process.exit(1);
736
- }
737
-
738
- const spinner = ora("Initializing Kling AI...").start();
739
-
740
- const kling = new KlingProvider();
741
- await kling.initialize({ apiKey });
742
-
743
- if (!kling.isConfigured()) {
744
- spinner.fail(chalk.red("Invalid API key format. Use ACCESS_KEY:SECRET_KEY"));
745
- process.exit(1);
746
- }
747
-
748
- spinner.text = "Starting video extension...";
749
-
750
- const result = await kling.extendVideo(videoId, {
751
- prompt: options.prompt,
752
- negativePrompt: options.negative,
753
- duration: options.duration as "5" | "10",
754
- });
755
-
756
- if (result.status === "failed") {
757
- spinner.fail(chalk.red(result.error || "Failed to start extension"));
758
- process.exit(1);
759
- }
760
-
761
- console.log();
762
- console.log(chalk.bold.cyan("Video Extension Started"));
763
- console.log(chalk.dim("─".repeat(60)));
764
- console.log(`Task ID: ${chalk.bold(result.id)}`);
765
-
766
- if (!options.wait) {
767
- spinner.succeed(chalk.green("Extension started"));
768
- console.log();
769
- console.log(chalk.dim("Check status with:"));
770
- console.log(chalk.dim(` pnpm vibe ai video-extend-status ${result.id}`));
771
- console.log();
772
- return;
773
- }
774
-
775
- spinner.text = "Extending video (this may take 2-5 minutes)...";
776
-
777
- const finalResult = await kling.waitForExtendCompletion(
778
- result.id,
779
- (status) => {
780
- spinner.text = `Extending video... ${status.status}`;
781
- },
782
- 600000
783
- );
784
-
785
- if (finalResult.status !== "completed") {
786
- spinner.fail(chalk.red(finalResult.error || "Extension failed"));
787
- process.exit(1);
788
- }
789
-
790
- spinner.succeed(chalk.green("Video extended"));
791
-
792
- console.log();
793
- if (finalResult.videoUrl) {
794
- console.log(`Video URL: ${finalResult.videoUrl}`);
795
- }
796
- if (finalResult.duration) {
797
- console.log(`Duration: ${finalResult.duration}s`);
798
- }
799
- console.log();
800
-
801
- if (options.output && finalResult.videoUrl) {
802
- const downloadSpinner = ora("Downloading video...").start();
803
- try {
804
- const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
805
- const outputPath = resolve(process.cwd(), options.output);
806
- await writeFile(outputPath, buffer);
807
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
808
- } catch (err) {
809
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
810
- }
811
- }
812
- } catch (error) {
813
- console.error(chalk.red("Video extension failed"));
814
- console.error(error);
815
- process.exit(1);
816
- }
817
- });
818
-
819
- aiCommand
820
- .command("veo-extend")
821
- .description("Extend a Veo video using the operation name from a previous generation")
822
- .argument("<operation-name>", "Veo operation name (from generation result)")
823
- .option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)")
824
- .option("-o, --output <path>", "Output file path")
825
- .option("--prompt <text>", "Continuation prompt")
826
- .option("-d, --duration <sec>", "Duration: 4, 6, or 8 seconds", "6")
827
- .option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast", "3.1")
828
- .option("--no-wait", "Start extension and return operation name without waiting")
829
- .action(async (operationName: string, options) => {
830
- try {
831
- const apiKey = await getApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
832
- if (!apiKey) {
833
- console.error(chalk.red("Google API key required. Set GOOGLE_API_KEY in .env or run: vibe setup"));
834
- console.error(chalk.dim("Use --api-key or set GOOGLE_API_KEY environment variable"));
835
- process.exit(1);
836
- }
837
-
838
- const spinner = ora("Initializing Veo...").start();
839
-
840
- const gemini = new GeminiProvider();
841
- await gemini.initialize({ apiKey });
842
-
843
- const veoModelMap: Record<string, string> = {
844
- "3.0": "veo-3.0-generate-preview",
845
- "3.1": "veo-3.1-generate-preview",
846
- "3.1-fast": "veo-3.1-fast-generate-preview",
847
- };
848
- const veoModel = veoModelMap[options.veoModel] || "veo-3.1-generate-preview";
849
-
850
- spinner.text = "Starting video extension...";
851
-
852
- const result = await gemini.extendVideo(operationName, options.prompt, {
853
- duration: parseInt(options.duration) as 4 | 6 | 8,
854
- model: veoModel as "veo-3.0-generate-preview" | "veo-3.1-generate-preview" | "veo-3.1-fast-generate-preview",
855
- });
856
-
857
- if (result.status === "failed") {
858
- spinner.fail(chalk.red(result.error || "Failed to start extension"));
859
- process.exit(1);
860
- }
861
-
862
- console.log();
863
- console.log(chalk.bold.cyan("Veo Video Extension Started"));
864
- console.log(chalk.dim("─".repeat(60)));
865
- console.log(`Operation: ${chalk.bold(result.id)}`);
866
-
867
- if (!options.wait) {
868
- spinner.succeed(chalk.green("Extension started"));
869
- console.log();
870
- console.log(chalk.dim("Check status or wait with:"));
871
- console.log(chalk.dim(` vibe ai veo-extend ${result.id} --wait`));
872
- console.log();
873
- return;
874
- }
875
-
876
- spinner.text = "Extending video (this may take 1-3 minutes)...";
877
- const finalResult = await gemini.waitForVideoCompletion(
878
- result.id,
879
- (status) => {
880
- spinner.text = `Extending video... ${status.status}`;
881
- },
882
- 300000
883
- );
884
-
885
- if (finalResult.status !== "completed") {
886
- spinner.fail(chalk.red(finalResult.error || "Extension failed"));
887
- process.exit(1);
888
- }
889
-
890
- spinner.succeed(chalk.green("Video extended"));
891
-
892
- console.log();
893
- if (finalResult.videoUrl) {
894
- console.log(`Video URL: ${finalResult.videoUrl}`);
895
- }
896
- console.log();
897
-
898
- if (options.output && finalResult.videoUrl) {
899
- const downloadSpinner = ora("Downloading video...").start();
900
- try {
901
- const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
902
- const outputPath = resolve(process.cwd(), options.output);
903
- await writeFile(outputPath, buffer);
904
- downloadSpinner.succeed(chalk.green(`Saved to: ${outputPath}`));
905
- } catch (err) {
906
- downloadSpinner.fail(chalk.red(`Failed to download video: ${err instanceof Error ? err.message : err}`));
907
- }
908
- }
909
- } catch (error) {
910
- console.error(chalk.red("Video extension failed"));
911
- console.error(error);
912
- process.exit(1);
913
- }
914
- });
915
- }