@vibeframe/cli 0.27.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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-lint.log +21 -0
- package/.turbo/turbo-test.log +689 -0
- package/dist/agent/adapters/claude.d.ts +15 -0
- package/dist/agent/adapters/claude.d.ts.map +1 -0
- package/dist/agent/adapters/claude.js +119 -0
- package/dist/agent/adapters/claude.js.map +1 -0
- package/dist/agent/adapters/gemini.d.ts +15 -0
- package/dist/agent/adapters/gemini.d.ts.map +1 -0
- package/dist/agent/adapters/gemini.js +132 -0
- package/dist/agent/adapters/gemini.js.map +1 -0
- package/dist/agent/adapters/index.d.ts +27 -0
- package/dist/agent/adapters/index.d.ts.map +1 -0
- package/dist/agent/adapters/index.js +38 -0
- package/dist/agent/adapters/index.js.map +1 -0
- package/dist/agent/adapters/ollama.d.ts +20 -0
- package/dist/agent/adapters/ollama.d.ts.map +1 -0
- package/dist/agent/adapters/ollama.js +186 -0
- package/dist/agent/adapters/ollama.js.map +1 -0
- package/dist/agent/adapters/openai.d.ts +15 -0
- package/dist/agent/adapters/openai.d.ts.map +1 -0
- package/dist/agent/adapters/openai.js +92 -0
- package/dist/agent/adapters/openai.js.map +1 -0
- package/dist/agent/adapters/xai.d.ts +15 -0
- package/dist/agent/adapters/xai.d.ts.map +1 -0
- package/dist/agent/adapters/xai.js +95 -0
- package/dist/agent/adapters/xai.js.map +1 -0
- package/dist/agent/index.d.ts +69 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +180 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/memory/index.d.ts +70 -0
- package/dist/agent/memory/index.d.ts.map +1 -0
- package/dist/agent/memory/index.js +132 -0
- package/dist/agent/memory/index.js.map +1 -0
- package/dist/agent/prompts/system.d.ts +6 -0
- package/dist/agent/prompts/system.d.ts.map +1 -0
- package/dist/agent/prompts/system.js +103 -0
- package/dist/agent/prompts/system.js.map +1 -0
- package/dist/agent/tools/ai-editing.d.ts +15 -0
- package/dist/agent/tools/ai-editing.d.ts.map +1 -0
- package/dist/agent/tools/ai-editing.js +763 -0
- package/dist/agent/tools/ai-editing.js.map +1 -0
- package/dist/agent/tools/ai-generation.d.ts +13 -0
- package/dist/agent/tools/ai-generation.d.ts.map +1 -0
- package/dist/agent/tools/ai-generation.js +973 -0
- package/dist/agent/tools/ai-generation.js.map +1 -0
- package/dist/agent/tools/ai-pipeline.d.ts +14 -0
- package/dist/agent/tools/ai-pipeline.d.ts.map +1 -0
- package/dist/agent/tools/ai-pipeline.js +961 -0
- package/dist/agent/tools/ai-pipeline.js.map +1 -0
- package/dist/agent/tools/ai.d.ts +13 -0
- package/dist/agent/tools/ai.d.ts.map +1 -0
- package/dist/agent/tools/ai.js +19 -0
- package/dist/agent/tools/ai.js.map +1 -0
- package/dist/agent/tools/batch.d.ts +6 -0
- package/dist/agent/tools/batch.d.ts.map +1 -0
- package/dist/agent/tools/batch.js +383 -0
- package/dist/agent/tools/batch.js.map +1 -0
- package/dist/agent/tools/e2e.test.d.ts +26 -0
- package/dist/agent/tools/e2e.test.d.ts.map +1 -0
- package/dist/agent/tools/e2e.test.js +397 -0
- package/dist/agent/tools/e2e.test.js.map +1 -0
- package/dist/agent/tools/export.d.ts +6 -0
- package/dist/agent/tools/export.d.ts.map +1 -0
- package/dist/agent/tools/export.js +171 -0
- package/dist/agent/tools/export.js.map +1 -0
- package/dist/agent/tools/filesystem.d.ts +6 -0
- package/dist/agent/tools/filesystem.d.ts.map +1 -0
- package/dist/agent/tools/filesystem.js +212 -0
- package/dist/agent/tools/filesystem.js.map +1 -0
- package/dist/agent/tools/index.d.ts +65 -0
- package/dist/agent/tools/index.d.ts.map +1 -0
- package/dist/agent/tools/index.js +120 -0
- package/dist/agent/tools/index.js.map +1 -0
- package/dist/agent/tools/integration.test.d.ts +11 -0
- package/dist/agent/tools/integration.test.d.ts.map +1 -0
- package/dist/agent/tools/integration.test.js +659 -0
- package/dist/agent/tools/integration.test.js.map +1 -0
- package/dist/agent/tools/media.d.ts +6 -0
- package/dist/agent/tools/media.d.ts.map +1 -0
- package/dist/agent/tools/media.js +616 -0
- package/dist/agent/tools/media.js.map +1 -0
- package/dist/agent/tools/project.d.ts +6 -0
- package/dist/agent/tools/project.d.ts.map +1 -0
- package/dist/agent/tools/project.js +284 -0
- package/dist/agent/tools/project.js.map +1 -0
- package/dist/agent/tools/timeline.d.ts +6 -0
- package/dist/agent/tools/timeline.d.ts.map +1 -0
- package/dist/agent/tools/timeline.js +873 -0
- package/dist/agent/tools/timeline.js.map +1 -0
- package/dist/agent/types.d.ts +59 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +5 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/commands/agent.d.ts +21 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +290 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/ai-analyze.d.ts +106 -0
- package/dist/commands/ai-analyze.d.ts.map +1 -0
- package/dist/commands/ai-analyze.js +327 -0
- package/dist/commands/ai-analyze.js.map +1 -0
- package/dist/commands/ai-animated-caption.d.ts +64 -0
- package/dist/commands/ai-animated-caption.d.ts.map +1 -0
- package/dist/commands/ai-animated-caption.js +272 -0
- package/dist/commands/ai-animated-caption.js.map +1 -0
- package/dist/commands/ai-audio.d.ts +20 -0
- package/dist/commands/ai-audio.d.ts.map +1 -0
- package/dist/commands/ai-audio.js +808 -0
- package/dist/commands/ai-audio.js.map +1 -0
- package/dist/commands/ai-broll.d.ts +15 -0
- package/dist/commands/ai-broll.d.ts.map +1 -0
- package/dist/commands/ai-broll.js +406 -0
- package/dist/commands/ai-broll.js.map +1 -0
- package/dist/commands/ai-edit-cli.d.ts +14 -0
- package/dist/commands/ai-edit-cli.d.ts.map +1 -0
- package/dist/commands/ai-edit-cli.js +579 -0
- package/dist/commands/ai-edit-cli.js.map +1 -0
- package/dist/commands/ai-edit.d.ts +398 -0
- package/dist/commands/ai-edit.d.ts.map +1 -0
- package/dist/commands/ai-edit.js +1019 -0
- package/dist/commands/ai-edit.js.map +1 -0
- package/dist/commands/ai-fill-gaps.d.ts +14 -0
- package/dist/commands/ai-fill-gaps.d.ts.map +1 -0
- package/dist/commands/ai-fill-gaps.js +451 -0
- package/dist/commands/ai-fill-gaps.js.map +1 -0
- package/dist/commands/ai-helpers.d.ts +20 -0
- package/dist/commands/ai-helpers.d.ts.map +1 -0
- package/dist/commands/ai-helpers.js +59 -0
- package/dist/commands/ai-helpers.js.map +1 -0
- package/dist/commands/ai-highlights.d.ts +127 -0
- package/dist/commands/ai-highlights.d.ts.map +1 -0
- package/dist/commands/ai-highlights.js +1026 -0
- package/dist/commands/ai-highlights.js.map +1 -0
- package/dist/commands/ai-image.d.ts +34 -0
- package/dist/commands/ai-image.d.ts.map +1 -0
- package/dist/commands/ai-image.js +653 -0
- package/dist/commands/ai-image.js.map +1 -0
- package/dist/commands/ai-motion.d.ts +50 -0
- package/dist/commands/ai-motion.d.ts.map +1 -0
- package/dist/commands/ai-motion.js +271 -0
- package/dist/commands/ai-motion.js.map +1 -0
- package/dist/commands/ai-narrate.d.ts +66 -0
- package/dist/commands/ai-narrate.d.ts.map +1 -0
- package/dist/commands/ai-narrate.js +329 -0
- package/dist/commands/ai-narrate.js.map +1 -0
- package/dist/commands/ai-review.d.ts +57 -0
- package/dist/commands/ai-review.d.ts.map +1 -0
- package/dist/commands/ai-review.js +251 -0
- package/dist/commands/ai-review.js.map +1 -0
- package/dist/commands/ai-script-pipeline-cli.d.ts +9 -0
- package/dist/commands/ai-script-pipeline-cli.d.ts.map +1 -0
- package/dist/commands/ai-script-pipeline-cli.js +1494 -0
- package/dist/commands/ai-script-pipeline-cli.js.map +1 -0
- package/dist/commands/ai-script-pipeline.d.ts +259 -0
- package/dist/commands/ai-script-pipeline.d.ts.map +1 -0
- package/dist/commands/ai-script-pipeline.js +1027 -0
- package/dist/commands/ai-script-pipeline.js.map +1 -0
- package/dist/commands/ai-suggest-edit.d.ts +14 -0
- package/dist/commands/ai-suggest-edit.d.ts.map +1 -0
- package/dist/commands/ai-suggest-edit.js +220 -0
- package/dist/commands/ai-suggest-edit.js.map +1 -0
- package/dist/commands/ai-video-fx.d.ts +14 -0
- package/dist/commands/ai-video-fx.d.ts.map +1 -0
- package/dist/commands/ai-video-fx.js +395 -0
- package/dist/commands/ai-video-fx.js.map +1 -0
- package/dist/commands/ai-video.d.ts +15 -0
- package/dist/commands/ai-video.d.ts.map +1 -0
- package/dist/commands/ai-video.js +785 -0
- package/dist/commands/ai-video.js.map +1 -0
- package/dist/commands/ai-viral.d.ts +15 -0
- package/dist/commands/ai-viral.d.ts.map +1 -0
- package/dist/commands/ai-viral.js +519 -0
- package/dist/commands/ai-viral.js.map +1 -0
- package/dist/commands/ai-visual-fx.d.ts +14 -0
- package/dist/commands/ai-visual-fx.d.ts.map +1 -0
- package/dist/commands/ai-visual-fx.js +505 -0
- package/dist/commands/ai-visual-fx.js.map +1 -0
- package/dist/commands/ai.d.ts +38 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +225 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/ai.test.d.ts +2 -0
- package/dist/commands/ai.test.d.ts.map +1 -0
- package/dist/commands/ai.test.js +554 -0
- package/dist/commands/ai.test.js.map +1 -0
- package/dist/commands/analyze.d.ts +16 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +247 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/audio.d.ts +18 -0
- package/dist/commands/audio.d.ts.map +1 -0
- package/dist/commands/audio.js +539 -0
- package/dist/commands/audio.js.map +1 -0
- package/dist/commands/batch.d.ts +3 -0
- package/dist/commands/batch.d.ts.map +1 -0
- package/dist/commands/batch.js +366 -0
- package/dist/commands/batch.js.map +1 -0
- package/dist/commands/batch.test.d.ts +2 -0
- package/dist/commands/batch.test.d.ts.map +1 -0
- package/dist/commands/batch.test.js +203 -0
- package/dist/commands/batch.test.js.map +1 -0
- package/dist/commands/detect.d.ts +3 -0
- package/dist/commands/detect.d.ts.map +1 -0
- package/dist/commands/detect.js +273 -0
- package/dist/commands/detect.js.map +1 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +191 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/edit-cmd.d.ts +26 -0
- package/dist/commands/edit-cmd.d.ts.map +1 -0
- package/dist/commands/edit-cmd.js +870 -0
- package/dist/commands/edit-cmd.js.map +1 -0
- package/dist/commands/export.d.ts +39 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +730 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/generate.d.ts +25 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +1885 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/media.d.ts +3 -0
- package/dist/commands/media.d.ts.map +1 -0
- package/dist/commands/media.js +165 -0
- package/dist/commands/media.js.map +1 -0
- package/dist/commands/output.d.ts +45 -0
- package/dist/commands/output.d.ts.map +1 -0
- package/dist/commands/output.js +122 -0
- package/dist/commands/output.js.map +1 -0
- package/dist/commands/pipeline.d.ts +19 -0
- package/dist/commands/pipeline.d.ts.map +1 -0
- package/dist/commands/pipeline.js +345 -0
- package/dist/commands/pipeline.js.map +1 -0
- package/dist/commands/project.d.ts +3 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +139 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/project.test.d.ts +2 -0
- package/dist/commands/project.test.d.ts.map +1 -0
- package/dist/commands/project.test.js +105 -0
- package/dist/commands/project.test.js.map +1 -0
- package/dist/commands/sanitize.d.ts +21 -0
- package/dist/commands/sanitize.d.ts.map +1 -0
- package/dist/commands/sanitize.js +56 -0
- package/dist/commands/sanitize.js.map +1 -0
- package/dist/commands/schema.d.ts +11 -0
- package/dist/commands/schema.d.ts.map +1 -0
- package/dist/commands/schema.js +101 -0
- package/dist/commands/schema.js.map +1 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +440 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/timeline.d.ts +3 -0
- package/dist/commands/timeline.d.ts.map +1 -0
- package/dist/commands/timeline.js +469 -0
- package/dist/commands/timeline.js.map +1 -0
- package/dist/commands/timeline.test.d.ts +2 -0
- package/dist/commands/timeline.test.d.ts.map +1 -0
- package/dist/commands/timeline.test.js +320 -0
- package/dist/commands/timeline.test.js.map +1 -0
- package/dist/commands/validate.d.ts +32 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +63 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/config/config.test.d.ts +2 -0
- package/dist/config/config.test.d.ts.map +1 -0
- package/dist/config/config.test.js +164 -0
- package/dist/config/config.test.js.map +1 -0
- package/dist/config/index.d.ts +35 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +101 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +43 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +42 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/engine/index.d.ts +3 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +2 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/project.d.ts +84 -0
- package/dist/engine/project.d.ts.map +1 -0
- package/dist/engine/project.js +355 -0
- package/dist/engine/project.js.map +1 -0
- package/dist/engine/project.test.d.ts +2 -0
- package/dist/engine/project.test.d.ts.map +1 -0
- package/dist/engine/project.test.js +599 -0
- package/dist/engine/project.test.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/api-key.d.ts +36 -0
- package/dist/utils/api-key.d.ts.map +1 -0
- package/dist/utils/api-key.js +211 -0
- package/dist/utils/api-key.js.map +1 -0
- package/dist/utils/api-key.test.d.ts +2 -0
- package/dist/utils/api-key.test.d.ts.map +1 -0
- package/dist/utils/api-key.test.js +35 -0
- package/dist/utils/api-key.test.js.map +1 -0
- package/dist/utils/audio.d.ts +23 -0
- package/dist/utils/audio.d.ts.map +1 -0
- package/dist/utils/audio.js +79 -0
- package/dist/utils/audio.js.map +1 -0
- package/dist/utils/exec-safe.d.ts +22 -0
- package/dist/utils/exec-safe.d.ts.map +1 -0
- package/dist/utils/exec-safe.js +62 -0
- package/dist/utils/exec-safe.js.map +1 -0
- package/dist/utils/first-run.d.ts +13 -0
- package/dist/utils/first-run.d.ts.map +1 -0
- package/dist/utils/first-run.js +48 -0
- package/dist/utils/first-run.js.map +1 -0
- package/dist/utils/provider-resolver.d.ts +15 -0
- package/dist/utils/provider-resolver.d.ts.map +1 -0
- package/dist/utils/provider-resolver.js +42 -0
- package/dist/utils/provider-resolver.js.map +1 -0
- package/dist/utils/remotion.d.ts +210 -0
- package/dist/utils/remotion.d.ts.map +1 -0
- package/dist/utils/remotion.js +731 -0
- package/dist/utils/remotion.js.map +1 -0
- package/dist/utils/subtitle.d.ts +65 -0
- package/dist/utils/subtitle.d.ts.map +1 -0
- package/dist/utils/subtitle.js +135 -0
- package/dist/utils/subtitle.js.map +1 -0
- package/dist/utils/subtitle.test.d.ts +2 -0
- package/dist/utils/subtitle.test.d.ts.map +1 -0
- package/dist/utils/subtitle.test.js +175 -0
- package/dist/utils/subtitle.test.js.map +1 -0
- package/dist/utils/tty.d.ts +45 -0
- package/dist/utils/tty.d.ts.map +1 -0
- package/dist/utils/tty.js +172 -0
- package/dist/utils/tty.js.map +1 -0
- package/package.json +102 -0
- package/src/agent/adapters/claude.ts +143 -0
- package/src/agent/adapters/gemini.ts +159 -0
- package/src/agent/adapters/index.ts +61 -0
- package/src/agent/adapters/ollama.ts +231 -0
- package/src/agent/adapters/openai.ts +116 -0
- package/src/agent/adapters/xai.ts +119 -0
- package/src/agent/index.ts +251 -0
- package/src/agent/memory/index.ts +151 -0
- package/src/agent/prompts/system.ts +106 -0
- package/src/agent/tools/ai-editing.ts +845 -0
- package/src/agent/tools/ai-generation.ts +1073 -0
- package/src/agent/tools/ai-pipeline.ts +1055 -0
- package/src/agent/tools/ai.ts +21 -0
- package/src/agent/tools/batch.ts +429 -0
- package/src/agent/tools/e2e.test.ts +545 -0
- package/src/agent/tools/export.ts +184 -0
- package/src/agent/tools/filesystem.ts +237 -0
- package/src/agent/tools/index.ts +150 -0
- package/src/agent/tools/integration.test.ts +775 -0
- package/src/agent/tools/media.ts +697 -0
- package/src/agent/tools/project.ts +313 -0
- package/src/agent/tools/timeline.ts +951 -0
- package/src/agent/types.ts +68 -0
- package/src/commands/agent.ts +340 -0
- package/src/commands/ai-analyze.ts +429 -0
- package/src/commands/ai-animated-caption.ts +390 -0
- package/src/commands/ai-audio.ts +941 -0
- package/src/commands/ai-broll.ts +490 -0
- package/src/commands/ai-edit-cli.ts +658 -0
- package/src/commands/ai-edit.ts +1542 -0
- package/src/commands/ai-fill-gaps.ts +566 -0
- package/src/commands/ai-helpers.ts +65 -0
- package/src/commands/ai-highlights.ts +1303 -0
- package/src/commands/ai-image.ts +761 -0
- package/src/commands/ai-motion.ts +347 -0
- package/src/commands/ai-narrate.ts +451 -0
- package/src/commands/ai-review.ts +309 -0
- package/src/commands/ai-script-pipeline-cli.ts +1710 -0
- package/src/commands/ai-script-pipeline.ts +1365 -0
- package/src/commands/ai-suggest-edit.ts +264 -0
- package/src/commands/ai-video-fx.ts +445 -0
- package/src/commands/ai-video.ts +915 -0
- package/src/commands/ai-viral.ts +595 -0
- package/src/commands/ai-visual-fx.ts +601 -0
- package/src/commands/ai.test.ts +627 -0
- package/src/commands/ai.ts +307 -0
- package/src/commands/analyze.ts +282 -0
- package/src/commands/audio.ts +644 -0
- package/src/commands/batch.test.ts +279 -0
- package/src/commands/batch.ts +440 -0
- package/src/commands/detect.ts +329 -0
- package/src/commands/doctor.ts +237 -0
- package/src/commands/edit-cmd.ts +1014 -0
- package/src/commands/export.ts +918 -0
- package/src/commands/generate.ts +2146 -0
- package/src/commands/media.ts +177 -0
- package/src/commands/output.ts +142 -0
- package/src/commands/pipeline.ts +398 -0
- package/src/commands/project.test.ts +127 -0
- package/src/commands/project.ts +149 -0
- package/src/commands/sanitize.ts +60 -0
- package/src/commands/schema.ts +130 -0
- package/src/commands/setup.ts +509 -0
- package/src/commands/timeline.test.ts +499 -0
- package/src/commands/timeline.ts +529 -0
- package/src/commands/validate.ts +77 -0
- package/src/config/config.test.ts +197 -0
- package/src/config/index.ts +125 -0
- package/src/config/schema.ts +82 -0
- package/src/engine/index.ts +2 -0
- package/src/engine/project.test.ts +702 -0
- package/src/engine/project.ts +439 -0
- package/src/index.ts +146 -0
- package/src/utils/api-key.test.ts +41 -0
- package/src/utils/api-key.ts +247 -0
- package/src/utils/audio.ts +83 -0
- package/src/utils/exec-safe.ts +75 -0
- package/src/utils/first-run.ts +52 -0
- package/src/utils/provider-resolver.ts +56 -0
- package/src/utils/remotion.ts +951 -0
- package/src/utils/subtitle.test.ts +227 -0
- package/src/utils/subtitle.ts +169 -0
- package/src/utils/tty.ts +196 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,915 @@
|
|
|
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
|
+
}
|