@vibeframe/cli 0.105.2 → 0.106.2

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.
@@ -14543,6 +14543,39 @@ var init_whisper = __esm({
14543
14543
  }
14544
14544
  });
14545
14545
 
14546
+ // ../ai-providers/dist/gemini/gemini-models.js
14547
+ function isGeminiTextModelAlias(model) {
14548
+ return Object.prototype.hasOwnProperty.call(GEMINI_TEXT_MODEL_ALIASES, model);
14549
+ }
14550
+ function resolveGeminiTextModel(model) {
14551
+ const trimmed = model?.trim();
14552
+ if (!trimmed)
14553
+ return GEMINI_DEFAULT_TEXT_MODEL;
14554
+ if (isGeminiTextModelAlias(trimmed))
14555
+ return GEMINI_TEXT_MODEL_ALIASES[trimmed];
14556
+ if (trimmed.startsWith("gemini-"))
14557
+ return trimmed;
14558
+ return GEMINI_DEFAULT_TEXT_MODEL;
14559
+ }
14560
+ var GEMINI_DEFAULT_TEXT_MODEL, GEMINI_AGENT_DEFAULT_TEXT_MODEL, GEMINI_TEXT_MODEL_ALIASES, GEMINI_TEXT_MODEL_HELP;
14561
+ var init_gemini_models = __esm({
14562
+ "../ai-providers/dist/gemini/gemini-models.js"() {
14563
+ "use strict";
14564
+ GEMINI_DEFAULT_TEXT_MODEL = "gemini-3.5-flash";
14565
+ GEMINI_AGENT_DEFAULT_TEXT_MODEL = "gemini-2.5-flash";
14566
+ GEMINI_TEXT_MODEL_ALIASES = {
14567
+ flash: GEMINI_DEFAULT_TEXT_MODEL,
14568
+ latest: GEMINI_DEFAULT_TEXT_MODEL,
14569
+ "flash-3.5": GEMINI_DEFAULT_TEXT_MODEL,
14570
+ "flash-3": "gemini-3-flash-preview",
14571
+ "flash-2.5": "gemini-2.5-flash",
14572
+ pro: "gemini-2.5-pro",
14573
+ "pro-3.1": "gemini-3.1-pro-preview"
14574
+ };
14575
+ GEMINI_TEXT_MODEL_HELP = "flash/latest (Gemini 3.5 Flash), flash-3.5, flash-3, flash-2.5, pro, pro-3.1, or a full gemini-* model ID";
14576
+ }
14577
+ });
14578
+
14546
14579
  // ../ai-providers/dist/gemini/gemini-motion.js
14547
14580
  function buildMotionSystemPrompt(width, height, fps, duration, durationInFrames, stylePreset) {
14548
14581
  return `You are a world-class broadcast motion graphics designer (like Apple Keynote, Netflix intros, ESPN graphics). Create STUNNING, jaw-dropping overlays that make viewers go "wow".
@@ -14696,7 +14729,7 @@ async function generateMotion(api, description, options = {}) {
14696
14729
  const duration = options.duration || 5;
14697
14730
  const durationInFrames = Math.round(duration * fps);
14698
14731
  const stylePreset = options.style || "modern and clean";
14699
- const modelId = options.model || "gemini-2.5-pro";
14732
+ const modelId = resolveGeminiTextModel(options.model);
14700
14733
  let systemPrompt = buildMotionSystemPrompt(width, height, fps, duration, durationInFrames, stylePreset);
14701
14734
  if (options.videoContext) {
14702
14735
  const sourceLabel = options.sourceType === "image" ? "IMAGE" : "VIDEO";
@@ -14777,7 +14810,7 @@ async function refineMotion(api, existingCode, instructions, options = {}) {
14777
14810
  const fps = options.fps || 30;
14778
14811
  const duration = options.duration || 5;
14779
14812
  const durationInFrames = Math.round(duration * fps);
14780
- const modelId = options.model || "gemini-2.5-pro";
14813
+ const modelId = resolveGeminiTextModel(options.model);
14781
14814
  const systemPrompt = `You are a world-class broadcast motion graphics designer. Modify the provided Remotion component based on instructions.
14782
14815
 
14783
14816
  CANVAS: ${width}\xD7${height}px | ${fps}fps | ${durationInFrames} frames (${duration}s)
@@ -14872,9 +14905,18 @@ var GEMINI_MOTION_MODELS, STYLE_USER_INSTRUCTIONS;
14872
14905
  var init_gemini_motion = __esm({
14873
14906
  "../ai-providers/dist/gemini/gemini-motion.js"() {
14874
14907
  "use strict";
14908
+ init_gemini_models();
14875
14909
  GEMINI_MOTION_MODELS = {
14910
+ gemini: GEMINI_DEFAULT_TEXT_MODEL,
14911
+ flash: GEMINI_DEFAULT_TEXT_MODEL,
14912
+ latest: GEMINI_DEFAULT_TEXT_MODEL,
14913
+ "flash-3.5": GEMINI_DEFAULT_TEXT_MODEL,
14914
+ "flash-3": "gemini-3-flash-preview",
14915
+ "flash-2.5": "gemini-2.5-flash",
14876
14916
  pro: "gemini-2.5-pro",
14877
- "3.1-pro": "gemini-3.1-pro-preview"
14917
+ "2.5-pro": "gemini-2.5-pro",
14918
+ "3.1-pro": "gemini-3.1-pro-preview",
14919
+ "pro-3.1": "gemini-3.1-pro-preview"
14878
14920
  };
14879
14921
  STYLE_USER_INSTRUCTIONS = {
14880
14922
  minimal: "CENTER-ALIGNED pure typography. Thin lines + large title + subtitle. NO glass panels, NO bokeh, NO dark bars. Breathing pulse during hold.",
@@ -14997,7 +15039,7 @@ async function analyzeContent(api, content, targetDuration, options) {
14997
15039
  maxOutputTokens: 4096
14998
15040
  }
14999
15041
  };
15000
- const response = await fetch(`${api.baseUrl}/models/gemini-2.5-flash:generateContent?key=${api.apiKey}`, {
15042
+ const response = await fetch(`${api.baseUrl}/models/${GEMINI_DEFAULT_TEXT_MODEL}:generateContent?key=${api.apiKey}`, {
15001
15043
  method: "POST",
15002
15044
  headers: { "Content-Type": "application/json" },
15003
15045
  body: JSON.stringify(payload)
@@ -15030,6 +15072,7 @@ var init_gemini_storyboard = __esm({
15030
15072
  "../ai-providers/dist/gemini/gemini-storyboard.js"() {
15031
15073
  "use strict";
15032
15074
  init_storyboard_prompt();
15075
+ init_gemini_models();
15033
15076
  }
15034
15077
  });
15035
15078
 
@@ -15068,6 +15111,7 @@ var init_GeminiProvider = __esm({
15068
15111
  "../ai-providers/dist/gemini/GeminiProvider.js"() {
15069
15112
  "use strict";
15070
15113
  init_gemini_motion();
15114
+ init_gemini_models();
15071
15115
  init_gemini_storyboard();
15072
15116
  init_http();
15073
15117
  MODEL_MAP = {
@@ -15576,7 +15620,7 @@ var init_GeminiProvider = __esm({
15576
15620
  error: "Google API key not configured"
15577
15621
  };
15578
15622
  }
15579
- const modelId = options.model || "gemini-3-flash-preview";
15623
+ const modelId = resolveGeminiTextModel(options.model);
15580
15624
  try {
15581
15625
  let videoPart;
15582
15626
  if (typeof videoData === "string") {
@@ -15683,7 +15727,7 @@ var init_GeminiProvider = __esm({
15683
15727
  error: "Google API key not configured"
15684
15728
  };
15685
15729
  }
15686
- const modelId = options.model || "gemini-3-flash-preview";
15730
+ const modelId = resolveGeminiTextModel(options.model);
15687
15731
  try {
15688
15732
  const buffers = Array.isArray(imageData) ? imageData : [imageData];
15689
15733
  const imageParts = buffers.map((buf) => ({
@@ -15785,7 +15829,7 @@ Example response:
15785
15829
  [{"type":"trim","description":"Trim intro to 3 seconds","clipIds":["clip-1"],"params":{"newDuration":3},"confidence":0.9}]
15786
15830
 
15787
15831
  Respond with ONLY the JSON array, no other text.`;
15788
- const response = await fetch(`${this.baseUrl}/models/gemini-2.5-flash:generateContent?key=${this.apiKey}`, {
15832
+ const response = await fetch(`${this.baseUrl}/models/${GEMINI_DEFAULT_TEXT_MODEL}:generateContent?key=${this.apiKey}`, {
15789
15833
  method: "POST",
15790
15834
  headers: {
15791
15835
  "Content-Type": "application/json"
@@ -15907,6 +15951,7 @@ var init_gemini = __esm({
15907
15951
  "use strict";
15908
15952
  init_GeminiProvider();
15909
15953
  init_gemini_motion();
15954
+ init_gemini_models();
15910
15955
  init_define_provider();
15911
15956
  defineProvider({
15912
15957
  id: "gemini",
@@ -25270,6 +25315,10 @@ __export(dist_exports2, {
25270
25315
  ClaudeProvider: () => ClaudeProvider,
25271
25316
  ElevenLabsProvider: () => ElevenLabsProvider,
25272
25317
  FalProvider: () => FalProvider,
25318
+ GEMINI_AGENT_DEFAULT_TEXT_MODEL: () => GEMINI_AGENT_DEFAULT_TEXT_MODEL,
25319
+ GEMINI_DEFAULT_TEXT_MODEL: () => GEMINI_DEFAULT_TEXT_MODEL,
25320
+ GEMINI_TEXT_MODEL_ALIASES: () => GEMINI_TEXT_MODEL_ALIASES,
25321
+ GEMINI_TEXT_MODEL_HELP: () => GEMINI_TEXT_MODEL_HELP,
25273
25322
  GeminiProvider: () => GeminiProvider,
25274
25323
  GrokProvider: () => GrokProvider,
25275
25324
  KNOWN_VOICES: () => KNOWN_VOICES,
@@ -25300,6 +25349,7 @@ __export(dist_exports2, {
25300
25349
  getProvidersFor: () => getProvidersFor,
25301
25350
  getSetupProviders: () => getSetupProviders,
25302
25351
  grokProvider: () => grokProvider,
25352
+ isGeminiTextModelAlias: () => isGeminiTextModelAlias,
25303
25353
  klingProvider: () => klingProvider,
25304
25354
  kokoroProvider: () => kokoroProvider,
25305
25355
  ollamaProvider: () => ollamaProvider,
@@ -25307,6 +25357,7 @@ __export(dist_exports2, {
25307
25357
  openaiProvider: () => openaiProvider,
25308
25358
  providerRegistry: () => providerRegistry,
25309
25359
  replicateProvider: () => replicateProvider,
25360
+ resolveGeminiTextModel: () => resolveGeminiTextModel,
25310
25361
  resolveVoiceId: () => resolveVoiceId,
25311
25362
  runwayProvider: () => runwayProvider,
25312
25363
  whisperProvider: () => whisperProvider
@@ -28479,6 +28530,7 @@ var MODEL_SETTINGS_BY_PROVIDER, ComposeBeatError, defaultCallLLM, CONFIG_PROVIDE
28479
28530
  var init_compose_scenes_skills = __esm({
28480
28531
  "src/commands/_shared/compose-scenes-skills.ts"() {
28481
28532
  "use strict";
28533
+ init_dist2();
28482
28534
  init_config();
28483
28535
  init_bundle();
28484
28536
  init_scene_lint();
@@ -28500,12 +28552,12 @@ var init_compose_scenes_skills = __esm({
28500
28552
  medium: { model: "gpt-5", maxTokens: 6e3, costPerMTokIn: 1.25, costPerMTokOut: 10 },
28501
28553
  high: { model: "gpt-5", maxTokens: 8e3, costPerMTokIn: 1.25, costPerMTokOut: 10 }
28502
28554
  },
28503
- // Google Gemini 2.5 Pro — ~2.6× cheaper than Claude per spike, ~20 s/beat.
28504
- // Strong instruction-following on the Hyperframes skill bundle.
28555
+ // Google Gemini 3.5 Flash — current one-shot Gemini default for
28556
+ // analysis/composition. Keep agent loops on their explicit 2.5 Flash default.
28505
28557
  gemini: {
28506
- low: { model: "gemini-2.5-pro", maxTokens: 4e3, costPerMTokIn: 1.25, costPerMTokOut: 5 },
28507
- medium: { model: "gemini-2.5-pro", maxTokens: 6e3, costPerMTokIn: 1.25, costPerMTokOut: 5 },
28508
- high: { model: "gemini-2.5-pro", maxTokens: 8e3, costPerMTokIn: 1.25, costPerMTokOut: 5 }
28558
+ low: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 4e3, costPerMTokIn: 1.5, costPerMTokOut: 9 },
28559
+ medium: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 6e3, costPerMTokIn: 1.5, costPerMTokOut: 9 },
28560
+ high: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 8e3, costPerMTokIn: 1.5, costPerMTokOut: 9 }
28509
28561
  }
28510
28562
  };
28511
28563
  ComposeBeatError = class extends Error {
@@ -31670,12 +31722,37 @@ async function executeVideoStatus(options) {
31670
31722
  } = options;
31671
31723
  try {
31672
31724
  const envKeyMap = {
31725
+ grok: "XAI_API_KEY",
31673
31726
  runway: "RUNWAY_API_SECRET",
31674
- kling: "KLING_API_KEY"
31727
+ kling: "KLING_API_KEY",
31728
+ veo: "GOOGLE_API_KEY"
31675
31729
  };
31676
31730
  const key = apiKey || process.env[envKeyMap[provider] || ""];
31677
31731
  if (!key) return { success: false, error: `${envKeyMap[provider]} required` };
31678
- if (provider === "runway") {
31732
+ if (provider === "grok") {
31733
+ const grok = new GrokProvider();
31734
+ await grok.initialize({ apiKey: key });
31735
+ let result = await grok.getGenerationStatus(taskId);
31736
+ if (wait && result.status !== "completed" && result.status !== "failed") {
31737
+ result = await grok.waitForCompletion(taskId, () => {
31738
+ });
31739
+ }
31740
+ let outputPath;
31741
+ if (output && result.videoUrl) {
31742
+ const buffer = await downloadVideo(result.videoUrl);
31743
+ outputPath = resolve19(process.cwd(), output);
31744
+ await writeFile14(outputPath, buffer);
31745
+ }
31746
+ return {
31747
+ success: true,
31748
+ taskId,
31749
+ status: result.status,
31750
+ progress: result.progress,
31751
+ videoUrl: result.videoUrl,
31752
+ outputPath,
31753
+ error: result.error
31754
+ };
31755
+ } else if (provider === "runway") {
31679
31756
  const runway = new RunwayProvider();
31680
31757
  await runway.initialize({ apiKey: key });
31681
31758
  let result = await runway.getGenerationStatus(taskId);
@@ -31717,7 +31794,31 @@ async function executeVideoStatus(options) {
31717
31794
  status: result.status,
31718
31795
  videoUrl: result.videoUrl,
31719
31796
  duration: result.duration,
31720
- outputPath
31797
+ outputPath,
31798
+ error: result.error
31799
+ };
31800
+ } else if (provider === "veo") {
31801
+ const gemini = new GeminiProvider();
31802
+ await gemini.initialize({ apiKey: key });
31803
+ let result = await gemini.getGenerationStatus(taskId);
31804
+ if (wait && result.status !== "completed" && result.status !== "failed" && result.status !== "cancelled") {
31805
+ result = await gemini.waitForVideoCompletion(taskId, () => {
31806
+ });
31807
+ }
31808
+ let outputPath;
31809
+ if (output && result.videoUrl) {
31810
+ const buffer = await downloadVideo(result.videoUrl, key);
31811
+ outputPath = resolve19(process.cwd(), output);
31812
+ await writeFile14(outputPath, buffer);
31813
+ }
31814
+ return {
31815
+ success: true,
31816
+ taskId,
31817
+ status: result.status,
31818
+ progress: result.progress,
31819
+ videoUrl: result.videoUrl,
31820
+ outputPath,
31821
+ error: result.error
31721
31822
  };
31722
31823
  }
31723
31824
  return { success: false, error: `Unsupported provider: ${provider}` };
@@ -32295,7 +32396,7 @@ function resolveRefreshOutput(record, opts) {
32295
32396
  return record.outputPath ? resolve21(record.outputPath) : void 0;
32296
32397
  }
32297
32398
  function liveSupport(record) {
32298
- if (record.jobType === "generate-video" && (record.provider === "runway" || record.provider === "kling")) {
32399
+ if (record.jobType === "generate-video" && (record.provider === "grok" || record.provider === "runway" || record.provider === "kling" || record.provider === "veo")) {
32299
32400
  return { supported: true };
32300
32401
  }
32301
32402
  if (record.jobType === "generate-music" && record.provider === "replicate") {
@@ -32677,7 +32778,9 @@ function isActiveStatus(status) {
32677
32778
  }
32678
32779
  function providerStatusCommand(record) {
32679
32780
  if (record.jobType === "generate-video") {
32680
- if (record.provider !== "runway" && record.provider !== "kling") return void 0;
32781
+ if (record.provider !== "grok" && record.provider !== "runway" && record.provider !== "kling" && record.provider !== "veo") {
32782
+ return void 0;
32783
+ }
32681
32784
  const type = record.provider === "kling" && record.providerTaskType ? ` --type ${record.providerTaskType}` : "";
32682
32785
  return `vibe generate video-status ${record.providerTaskId} -p ${record.provider}${type} --json`;
32683
32786
  }
@@ -35178,12 +35281,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
35178
35281
  const gemini = new GeminiProvider();
35179
35282
  await gemini.initialize({ apiKey: geminiApiKey });
35180
35283
  const videoBuffer = await readFile18(videoPath);
35181
- const modelMap = {
35182
- flash: "gemini-3-flash-preview",
35183
- "flash-2.5": "gemini-2.5-flash",
35184
- pro: "gemini-2.5-pro"
35185
- };
35186
- const modelId = options.model ? modelMap[options.model] || modelMap.flash : void 0;
35284
+ const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
35187
35285
  const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
35188
35286
 
35189
35287
  Detect these as silent/dead segments:
@@ -38635,7 +38733,7 @@ Use this image analysis to inform the color palette, typography placement, and o
38635
38733
  6. Recommended animation timing and entrance/exit style
38636
38734
  7. Any moments where overlays should pause, fade, or stay minimal`;
38637
38735
  const analysisResult = await gemini.analyzeVideo(videoBuffer, analysisPrompt, {
38638
- model: "gemini-2.5-flash",
38736
+ model: GEMINI_DEFAULT_TEXT_MODEL,
38639
38737
  fps: 1,
38640
38738
  lowResolution: true
38641
38739
  });
@@ -38804,7 +38902,7 @@ function registerMotionCommand(aiCommand) {
38804
38902
  "auto"
38805
38903
  ).option("--understanding-prompt <text>", "Custom prompt for --video understanding").option("--from-tsx <path>", "Refine an existing TSX file instead of generating from scratch").option(
38806
38904
  "-m, --model <alias>",
38807
- "LLM model: sonnet (default), opus, gemini, gemini-3.1-pro",
38905
+ "LLM model: sonnet (default), opus, gemini, gemini-2.5-pro, gemini-3.1-pro",
38808
38906
  "sonnet"
38809
38907
  ).option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
38810
38908
  const startedAt = Date.now();
@@ -38948,7 +39046,8 @@ var init_ai_motion = __esm({
38948
39046
  sonnet: { provider: "claude", modelId: "claude-sonnet-4-6" },
38949
39047
  opus: { provider: "claude", modelId: "claude-opus-4-7" },
38950
39048
  "opus-4-6": { provider: "claude", modelId: "claude-opus-4-6" },
38951
- gemini: { provider: "gemini", modelId: "gemini-2.5-pro" },
39049
+ gemini: { provider: "gemini", modelId: GEMINI_DEFAULT_TEXT_MODEL },
39050
+ "gemini-2.5-pro": { provider: "gemini", modelId: "gemini-2.5-pro" },
38952
39051
  "gemini-3.1-pro": { provider: "gemini", modelId: "gemini-3.1-pro-preview" }
38953
39052
  };
38954
39053
  }
@@ -39529,7 +39628,7 @@ async function executeMotionOverlay(options) {
39529
39628
  };
39530
39629
  }
39531
39630
  function registerMotionOverlayCommand(parent) {
39532
- parent.command("motion-overlay").description("Apply designed motion graphics overlays to an existing video").argument("<video>", "Video file path").argument("[description]", "Motion overlay description (omit when using --asset)").option("--asset <path>", "User-provided .json/.lottie animation to overlay").option("-o, --output <path>", "Output video file path").option("-d, --duration <sec>", "Overlay/render duration in seconds").option("--start <sec>", "Overlay start time in seconds", "0").option("--style <style>", "Style preset for generated overlays: minimal, corporate, playful, cinematic").option("-m, --model <alias>", "LLM model for generated overlays: sonnet, opus, gemini, gemini-3.1-pro", "sonnet").option("--understand <mode>", "Analyze video before generated overlay: auto, off, required", "auto").option("--understanding-prompt <text>", "Custom prompt for video understanding").option("--position <position>", "Lottie position: full, center, top-left, top-right, bottom-left, bottom-right", "full").option("--scale <number>", "Lottie overlay scale (0.01-2)").option("--opacity <number>", "Lottie overlay opacity (0-1)", "1").option("--loop", "Loop Lottie overlay", true).option("--no-loop", "Do not loop Lottie overlay").option("--dry-run", "Preview parameters without executing").action(async (videoPath, description, options) => {
39631
+ parent.command("motion-overlay").description("Apply designed motion graphics overlays to an existing video").argument("<video>", "Video file path").argument("[description]", "Motion overlay description (omit when using --asset)").option("--asset <path>", "User-provided .json/.lottie animation to overlay").option("-o, --output <path>", "Output video file path").option("-d, --duration <sec>", "Overlay/render duration in seconds").option("--start <sec>", "Overlay start time in seconds", "0").option("--style <style>", "Style preset for generated overlays: minimal, corporate, playful, cinematic").option("-m, --model <alias>", "LLM model for generated overlays: sonnet, opus, gemini, gemini-2.5-pro, gemini-3.1-pro", "sonnet").option("--understand <mode>", "Analyze video before generated overlay: auto, off, required", "auto").option("--understanding-prompt <text>", "Custom prompt for video understanding").option("--position <position>", "Lottie position: full, center, top-left, top-right, bottom-left, bottom-right", "full").option("--scale <number>", "Lottie overlay scale (0.01-2)").option("--opacity <number>", "Lottie overlay opacity (0-1)", "1").option("--loop", "Loop Lottie overlay", true).option("--no-loop", "Do not loop Lottie overlay").option("--dry-run", "Preview parameters without executing").action(async (videoPath, description, options) => {
39533
39632
  const startedAt = Date.now();
39534
39633
  try {
39535
39634
  if (options.output) validateOutputPath(options.output);
@@ -40818,13 +40917,7 @@ async function executeThumbnailBestFrame(options) {
40818
40917
  await gemini.initialize({ apiKey: googleKey });
40819
40918
  const videoData = await readFile25(videoPath);
40820
40919
  const analysisPrompt = prompt3 || 'Analyze this video and find the single best frame for a thumbnail. Look for frames that are visually striking, well-composed, emotionally engaging, and representative of the video content. Avoid blurry frames, transitions, or dark scenes. Return ONLY a JSON object: {"timestamp": <seconds as number>, "reason": "<brief explanation>"}';
40821
- const modelMap = {
40822
- flash: "gemini-3-flash-preview",
40823
- latest: "gemini-2.5-flash",
40824
- "flash-2.5": "gemini-2.5-flash",
40825
- pro: "gemini-2.5-pro"
40826
- };
40827
- const modelId = modelMap[model] || "gemini-3-flash-preview";
40920
+ const modelId = resolveGeminiTextModel(model);
40828
40921
  const result = await gemini.analyzeVideo(videoData, analysisPrompt, {
40829
40922
  model: modelId,
40830
40923
  fps: 1
@@ -40884,12 +40977,7 @@ async function executeGeminiVideo(options) {
40884
40977
  return { success: false, error: "Google API key required. Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
40885
40978
  }
40886
40979
  const isYouTube = options.source.includes("youtube.com") || options.source.includes("youtu.be");
40887
- const modelMap = {
40888
- flash: "gemini-3-flash-preview",
40889
- "flash-2.5": "gemini-2.5-flash",
40890
- pro: "gemini-2.5-pro"
40891
- };
40892
- const modelId = modelMap[options.model || "flash"] || modelMap.flash;
40980
+ const modelId = resolveGeminiTextModel(options.model);
40893
40981
  let videoData;
40894
40982
  if (isYouTube) {
40895
40983
  videoData = options.source;
@@ -40950,12 +41038,7 @@ async function executeAnalyze(options) {
40950
41038
  error: "Cannot detect source type. Supported: images (.png/.jpg/.webp/.gif), videos (.mp4/.mov/.webm), YouTube URLs, image URLs."
40951
41039
  };
40952
41040
  }
40953
- const modelMap = {
40954
- flash: "gemini-3-flash-preview",
40955
- "flash-2.5": "gemini-2.5-flash",
40956
- pro: "gemini-2.5-pro"
40957
- };
40958
- const modelId = modelMap[options.model || "flash"] || modelMap.flash;
41041
+ const modelId = resolveGeminiTextModel(options.model);
40959
41042
  const gemini = new GeminiProvider();
40960
41043
  await gemini.initialize({ apiKey });
40961
41044
  if (isImage) {
@@ -41035,7 +41118,7 @@ async function executeAnalyze(options) {
41035
41118
  }
41036
41119
  }
41037
41120
  function registerAnalyzeCommands(aiCommand) {
41038
- aiCommand.command("gemini-video").description("Analyze video using Gemini (summarize, Q&A, extract info)").argument("<source>", "Video file path or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second (default: 1, higher for action)").option("--start <seconds>", "Start offset in seconds (for clipping)").option("--end <seconds>", "End offset in seconds (for clipping)").option("--low-res", "Use low resolution mode (fewer tokens, longer videos)").option("-v, --verbose", "Show token usage").action(async (source, prompt3, options) => {
41121
+ aiCommand.command("gemini-video").description("Analyze video using Gemini (summarize, Q&A, extract info)").argument("<source>", "Video file path or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", `Model: ${GEMINI_TEXT_MODEL_HELP}`, "flash").option("--fps <number>", "Frames per second (default: 1, higher for action)").option("--start <seconds>", "Start offset in seconds (for clipping)").option("--end <seconds>", "End offset in seconds (for clipping)").option("--low-res", "Use low resolution mode (fewer tokens, longer videos)").option("-v, --verbose", "Show token usage").action(async (source, prompt3, options) => {
41039
41122
  try {
41040
41123
  if (options.apiKey) {
41041
41124
  process.env.GOOGLE_API_KEY = options.apiKey;
@@ -41078,7 +41161,7 @@ function registerAnalyzeCommands(aiCommand) {
41078
41161
  exitWithError(apiError(`Video analysis failed: ${error instanceof Error ? error.message : String(error)}`, true));
41079
41162
  }
41080
41163
  });
41081
- aiCommand.command("analyze").description("Analyze any media: images, videos, or YouTube URLs using Gemini").argument("<source>", "Image/video file path, image URL, or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Describe this image', 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second for video (default: 1)").option("--start <seconds>", "Start offset in seconds (video only)").option("--end <seconds>", "End offset in seconds (video only)").option("--low-res", "Use low resolution mode (fewer tokens)").option("-v, --verbose", "Show token usage").action(async (source, prompt3, options) => {
41164
+ aiCommand.command("analyze").description("Analyze any media: images, videos, or YouTube URLs using Gemini").argument("<source>", "Image/video file path, image URL, or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Describe this image', 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", `Model: ${GEMINI_TEXT_MODEL_HELP}`, "flash").option("--fps <number>", "Frames per second for video (default: 1)").option("--start <seconds>", "Start offset in seconds (video only)").option("--end <seconds>", "End offset in seconds (video only)").option("--low-res", "Use low resolution mode (fewer tokens)").option("-v, --verbose", "Show token usage").action(async (source, prompt3, options) => {
41082
41165
  try {
41083
41166
  if (options.apiKey) {
41084
41167
  process.env.GOOGLE_API_KEY = options.apiKey;
@@ -41199,12 +41282,7 @@ ${content}`;
41199
41282
 
41200
41283
  Project context for beat-aware review:
41201
41284
  ${projectContext}` : "";
41202
- const modelMap = {
41203
- flash: "gemini-3-flash-preview",
41204
- "flash-2.5": "gemini-2.5-flash",
41205
- pro: "gemini-2.5-pro"
41206
- };
41207
- const modelId = modelMap[model] || modelMap.flash;
41285
+ const modelId = resolveGeminiTextModel(model);
41208
41286
  const reviewPrompt = `You are a professional video editor reviewing this video for quality. Analyze the video and return a JSON review with the following structure. Return ONLY valid JSON, no extra text.
41209
41287
 
41210
41288
  {
@@ -41311,7 +41389,7 @@ Score each category 1-10. Prefer beatIssues when you can map a problem to a stor
41311
41389
  return result;
41312
41390
  }
41313
41391
  function registerReviewCommand(aiCommand) {
41314
- aiCommand.command("review", { hidden: true }).description("Review video quality using Gemini AI and optionally auto-fix issues").argument("<source>", "Video file path").option("--storyboard <path>", "Storyboard JSON file for context").option("--auto-apply", "Automatically apply fixable corrections").option("--verify", "Run verification pass after applying fixes").option("-m, --model <model>", "Gemini model: flash (default), flash-2.5, pro", "flash").option("-o, --output <path>", "Output video file path (for auto-apply)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
41392
+ aiCommand.command("review", { hidden: true }).description("Review video quality using Gemini AI and optionally auto-fix issues").argument("<source>", "Video file path").option("--storyboard <path>", "Storyboard JSON file for context").option("--auto-apply", "Automatically apply fixable corrections").option("--verify", "Run verification pass after applying fixes").option("-m, --model <model>", `Gemini model: ${GEMINI_TEXT_MODEL_HELP}`, "flash").option("-o, --output <path>", "Output video file path (for auto-apply)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
41315
41393
  const startedAt = Date.now();
41316
41394
  try {
41317
41395
  if (options.output) {
@@ -42198,7 +42276,7 @@ import { resolve as resolve46, dirname as dirname19, basename as basename13, ext
42198
42276
  import { existsSync as existsSync38 } from "node:fs";
42199
42277
  import { writeFile as writeFile34, mkdir as mkdir20 } from "node:fs/promises";
42200
42278
  function registerThumbnailCommand(parent) {
42201
- parent.command("thumbnail").description("Generate video thumbnail (DALL-E) or extract best frame from video (Gemini)").argument("[description]", "Thumbnail description (for DALL-E generation)").option("-k, --api-key <key>", "API key (OpenAI for generation, Google for best-frame)").option("-o, --output <path>", "Output file path").option("--style <style>", "Platform style: youtube, instagram, tiktok, twitter").option("--best-frame <video>", "Extract best thumbnail frame from video using Gemini AI").option("--prompt <prompt>", "Custom prompt for best-frame analysis").option("--model <model>", "Gemini model: flash, latest, pro (default: flash)", "flash").action(async (description, options) => {
42279
+ parent.command("thumbnail").description("Generate video thumbnail (DALL-E) or extract best frame from video (Gemini)").argument("[description]", "Thumbnail description (for DALL-E generation)").option("-k, --api-key <key>", "API key (OpenAI for generation, Google for best-frame)").option("-o, --output <path>", "Output file path").option("--style <style>", "Platform style: youtube, instagram, tiktok, twitter").option("--best-frame <video>", "Extract best thumbnail frame from video using Gemini AI").option("--prompt <prompt>", "Custom prompt for best-frame analysis").option("--model <model>", `Gemini model: ${GEMINI_TEXT_MODEL_HELP}`, "flash").action(async (description, options) => {
42202
42280
  const startedAt = Date.now();
42203
42281
  try {
42204
42282
  if (description) rejectControlChars(description);
@@ -42364,7 +42442,10 @@ function getStatusColor(status) {
42364
42442
  }
42365
42443
  }
42366
42444
  function registerVideoStatusCommand(parent) {
42367
- parent.command("video-status", { hidden: true }).description("Check video generation status (Grok, Runway, or Kling)").argument("<task-id>", "Task ID from video generation").option("-p, --provider <provider>", "Provider: grok, runway, kling", "grok").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY env)").option("--type <type>", "Task type: text2video or image2video (Kling only)", "text2video").option("--wait", "Wait for completion").option("-o, --output <path>", "Download video when complete").action(async (taskId, options) => {
42445
+ parent.command("video-status", { hidden: true }).description("Check video generation status (Grok, Runway, Kling, or Veo)").argument("<task-id>", "Task ID from video generation").option("-p, --provider <provider>", "Provider: grok, runway, kling, veo", "grok").option(
42446
+ "-k, --api-key <key>",
42447
+ "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)"
42448
+ ).option("--type <type>", "Task type: text2video or image2video (Kling only)", "text2video").option("--wait", "Wait for completion").option("-o, --output <path>", "Download video when complete").action(async (taskId, options) => {
42368
42449
  const startedAt = Date.now();
42369
42450
  try {
42370
42451
  const provider = (options.provider || "grok").toLowerCase();
@@ -42424,18 +42505,12 @@ function registerVideoStatusCommand(parent) {
42424
42505
  downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
42425
42506
  } catch (err) {
42426
42507
  downloadSpinner.fail(
42427
- source_default.red(
42428
- `Failed to download video: ${err instanceof Error ? err.message : err}`
42429
- )
42508
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
42430
42509
  );
42431
42510
  }
42432
42511
  }
42433
42512
  } else if (provider === "runway") {
42434
- const apiKey = await requireApiKey(
42435
- "RUNWAY_API_SECRET",
42436
- "Runway",
42437
- options.apiKey
42438
- );
42513
+ const apiKey = await requireApiKey("RUNWAY_API_SECRET", "Runway", options.apiKey);
42439
42514
  const spinner2 = ora("Checking status...").start();
42440
42515
  const runway = new RunwayProvider();
42441
42516
  await runway.initialize({ apiKey });
@@ -42496,9 +42571,7 @@ function registerVideoStatusCommand(parent) {
42496
42571
  downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
42497
42572
  } catch (err) {
42498
42573
  downloadSpinner.fail(
42499
- source_default.red(
42500
- `Failed to download video: ${err instanceof Error ? err.message : err}`
42501
- )
42574
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
42502
42575
  );
42503
42576
  }
42504
42577
  }
@@ -42564,15 +42637,77 @@ function registerVideoStatusCommand(parent) {
42564
42637
  downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
42565
42638
  } catch (err) {
42566
42639
  downloadSpinner.fail(
42567
- source_default.red(
42568
- `Failed to download video: ${err instanceof Error ? err.message : err}`
42569
- )
42640
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
42641
+ );
42642
+ }
42643
+ }
42644
+ } else if (provider === "veo") {
42645
+ const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
42646
+ const spinner2 = ora("Checking status...").start();
42647
+ const gemini = new GeminiProvider();
42648
+ await gemini.initialize({ apiKey });
42649
+ let result = await gemini.getGenerationStatus(taskId);
42650
+ if (options.wait && result.status !== "completed" && result.status !== "failed" && result.status !== "cancelled") {
42651
+ spinner2.text = "Waiting for completion...";
42652
+ result = await gemini.waitForVideoCompletion(taskId, (status) => {
42653
+ spinner2.text = status.progress !== void 0 ? `Generating... ${status.progress}%` : `Generating... ${status.status}`;
42654
+ });
42655
+ }
42656
+ spinner2.stop();
42657
+ if (isJsonMode()) {
42658
+ let outputPath;
42659
+ if (options.output && result.videoUrl) {
42660
+ const buffer = await downloadVideo(result.videoUrl, apiKey);
42661
+ outputPath = resolve47(process.cwd(), options.output);
42662
+ await writeFile35(outputPath, buffer);
42663
+ }
42664
+ outputSuccess({
42665
+ command: "generate video-status",
42666
+ startedAt,
42667
+ data: {
42668
+ taskId,
42669
+ provider: "veo",
42670
+ status: result.status,
42671
+ videoUrl: result.videoUrl,
42672
+ progress: result.progress,
42673
+ error: result.error,
42674
+ outputPath
42675
+ }
42676
+ });
42677
+ return;
42678
+ }
42679
+ console.log();
42680
+ console.log(source_default.bold.cyan("Generation Status"));
42681
+ console.log(source_default.dim("\u2500".repeat(60)));
42682
+ console.log(`Task ID: ${taskId}`);
42683
+ console.log(`Provider: Veo`);
42684
+ console.log(`Status: ${getStatusColor(result.status)}`);
42685
+ if (result.progress !== void 0) {
42686
+ console.log(`Progress: ${result.progress}%`);
42687
+ }
42688
+ if (result.videoUrl) {
42689
+ console.log(`Video URL: ${result.videoUrl}`);
42690
+ }
42691
+ if (result.error) {
42692
+ console.log(`Error: ${source_default.red(result.error)}`);
42693
+ }
42694
+ console.log();
42695
+ if (options.output && result.videoUrl) {
42696
+ const downloadSpinner = ora("Downloading video...").start();
42697
+ try {
42698
+ const buffer = await downloadVideo(result.videoUrl, apiKey);
42699
+ const outputPath = resolve47(process.cwd(), options.output);
42700
+ await writeFile35(outputPath, buffer);
42701
+ downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
42702
+ } catch (err) {
42703
+ downloadSpinner.fail(
42704
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
42570
42705
  );
42571
42706
  }
42572
42707
  }
42573
42708
  } else {
42574
42709
  exitWithError(
42575
- usageError(`Invalid provider: ${provider}. Use grok, runway, or kling.`)
42710
+ usageError(`Invalid provider: ${provider}. Use grok, runway, kling, or veo.`)
42576
42711
  );
42577
42712
  }
42578
42713
  } catch (error) {
@@ -44353,7 +44488,7 @@ function registerVideoCommand(parent) {
44353
44488
  "--seedance-model <model>",
44354
44489
  "Seedance variant: quality or fast (fal.ai only)",
44355
44490
  "quality"
44356
- ).option("--negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)").option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)").option(
44491
+ ).option("--negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 480p, 720p, 1080p, or 4k depending on provider").option("--last-frame <path>", "Last frame image for frame interpolation (Veo) or Seedance end frame").option("--end-image <path>", "Ending frame image for Seedance image-to-video").option(
44357
44492
  "--ref-images <paths...>",
44358
44493
  "Reference images for Seedance reference-to-video or Veo character consistency"
44359
44494
  ).option("--ref-videos <paths...>", "Reference videos for Seedance reference-to-video").option("--ref-audio <paths...>", "Reference audio for Seedance reference-to-video").option("--no-generate-audio", "Disable native audio when the provider supports it").option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast").option(
@@ -44814,6 +44949,30 @@ Examples:
44814
44949
  exitWithError(apiError(message, true));
44815
44950
  }
44816
44951
  }
44952
+ let seedanceEndImage;
44953
+ const seedanceEndImagePath = options.endImage ?? options.lastFrame;
44954
+ if (seedanceEndImagePath && seedanceReferences.length === 0) {
44955
+ try {
44956
+ const absEndImagePath = resolve50(process.cwd(), seedanceEndImagePath);
44957
+ const endImageBuffer = await readFile32(absEndImagePath);
44958
+ const ext = seedanceEndImagePath.toLowerCase().split(".").pop();
44959
+ const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
44960
+ const uploadHost = await resolveUploadHost();
44961
+ spinner2.text = `Uploading end image via ${uploadHost.provider} for Seedance...`;
44962
+ const upload = await uploadHost.uploadImage(endImageBuffer, {
44963
+ filename: seedanceEndImagePath,
44964
+ mimeType
44965
+ });
44966
+ seedanceEndImage = upload.url;
44967
+ } catch (err) {
44968
+ spinner2.fail("End image upload failed");
44969
+ const message = err instanceof Error ? err.message : String(err);
44970
+ if (message.includes("IMGBB_API_KEY")) {
44971
+ exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
44972
+ }
44973
+ exitWithError(apiError(message, true));
44974
+ }
44975
+ }
44817
44976
  spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
44818
44977
  const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
44819
44978
  const falModel = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
@@ -44826,7 +44985,8 @@ Examples:
44826
44985
  negativePrompt: options.negative,
44827
44986
  model: falModel,
44828
44987
  resolution: options.resolution,
44829
- generateAudio: options.generateAudio
44988
+ generateAudio: options.generateAudio,
44989
+ lastFrame: seedanceEndImage
44830
44990
  });
44831
44991
  finalResult = result;
44832
44992
  }
@@ -50718,6 +50878,14 @@ var SCENE_PRESETS = [
50718
50878
  "kinetic-type",
50719
50879
  "product-shot"
50720
50880
  ];
50881
+ var LOTTIE_POSITIONS = [
50882
+ "full",
50883
+ "center",
50884
+ "top-left",
50885
+ "top-right",
50886
+ "bottom-left",
50887
+ "bottom-right"
50888
+ ];
50721
50889
  var GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
50722
50890
  function esc(text) {
50723
50891
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -50998,6 +51166,56 @@ function buildPreset(input) {
50998
51166
  }
50999
51167
  }
51000
51168
  }
51169
+ var DOTLOTTIE_WC_VERSION = "0.9.12";
51170
+ var DOTLOTTIE_WEB_VERSION = "0.71.0";
51171
+ var DOTLOTTIE_WC_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-wc@${DOTLOTTIE_WC_VERSION}/dist/index.js`;
51172
+ var DOTLOTTIE_WASM_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web@${DOTLOTTIE_WEB_VERSION}/dist/dotlottie-player.wasm`;
51173
+ function lottieOverlayStyle(input) {
51174
+ const pos = input.position ?? "full";
51175
+ const rawScale = input.scale ?? (pos === "full" ? 1 : 0.25);
51176
+ const scale = Math.max(0.01, Math.min(2, rawScale));
51177
+ const opacity = Math.max(0, Math.min(1, input.opacity ?? 1));
51178
+ const base = [
51179
+ "position:absolute",
51180
+ "pointer-events:none",
51181
+ `opacity:${opacity}`
51182
+ ];
51183
+ if (pos === "full") {
51184
+ base.push("inset:0", "width:100%", "height:100%");
51185
+ } else {
51186
+ const pct = scale * 100;
51187
+ base.push(`width:${pct}%`, `height:${pct}%`);
51188
+ switch (pos) {
51189
+ case "center":
51190
+ base.push("top:50%", "left:50%", "transform:translate(-50%,-50%)");
51191
+ break;
51192
+ case "top-left":
51193
+ base.push("top:4%", "left:4%");
51194
+ break;
51195
+ case "top-right":
51196
+ base.push("top:4%", "right:4%");
51197
+ break;
51198
+ case "bottom-left":
51199
+ base.push("bottom:4%", "left:4%");
51200
+ break;
51201
+ case "bottom-right":
51202
+ base.push("bottom:4%", "right:4%");
51203
+ break;
51204
+ }
51205
+ }
51206
+ return base.join(";");
51207
+ }
51208
+ function buildLottieOverlay(input, sceneId) {
51209
+ const loop = input.loop ?? true ? " loop" : "";
51210
+ const style = lottieOverlayStyle(input);
51211
+ const overlayId = `lottie-overlay-${sceneId}`;
51212
+ const markup = `<dotlottie-wc id="${overlayId}" src="${esc(input.src)}" autoplay${loop} style="${style}"></dotlottie-wc>`;
51213
+ const script = `<script type="module">
51214
+ import { setWasmUrl } from "${DOTLOTTIE_WC_CDN}";
51215
+ setWasmUrl("${DOTLOTTIE_WASM_CDN}");
51216
+ </script>`;
51217
+ return { markup, script };
51218
+ }
51001
51219
  function emitSceneHtml(input) {
51002
51220
  if (input.duration <= 0) {
51003
51221
  throw new Error(`Scene duration must be > 0, got ${input.duration}`);
@@ -51018,15 +51236,20 @@ function emitSceneHtml(input) {
51018
51236
  data-volume="1"
51019
51237
  ></audio>
51020
51238
  ` : "";
51239
+ const lottieLayer = input.lottie ? buildLottieOverlay(input.lottie, id) : null;
51240
+ const lottieMarkup = lottieLayer ? `
51241
+ ${lottieLayer.markup}` : "";
51242
+ const lottieScript = lottieLayer ? `
51243
+ ${lottieLayer.script}` : "";
51021
51244
  return `<template id="scene-${id}-template">
51022
51245
  <div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input.width}" data-height="${input.height}">
51023
51246
  <style>
51024
51247
  ${parts.css}
51025
51248
  </style>
51026
51249
 
51027
- ${parts.body}
51250
+ ${parts.body}${lottieMarkup}
51028
51251
  ${audioBlock}
51029
- <script src="${GSAP_CDN}"></script>
51252
+ <script src="${GSAP_CDN}"></script>${lottieScript}
51030
51253
  <script>
51031
51254
  window.__timelines = window.__timelines || {};
51032
51255
  const tl = gsap.timeline({ paused: true });
@@ -51450,9 +51673,40 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
51450
51673
  ).option("--no-image", "Skip image generation even when --visuals is provided").option(
51451
51674
  "--no-transcribe",
51452
51675
  "Skip Whisper word-level transcribe step (no transcript-<id>.json emitted)"
51453
- ).option("--transcribe-language <code>", "BCP-47 language code passed to Whisper (e.g. en, ko)").option("--force", "Overwrite an existing compositions/scene-<id>.html").option("--dry-run", "Preview parameters without writing files or calling APIs").action(async (name, options) => {
51676
+ ).option("--transcribe-language <code>", "BCP-47 language code passed to Whisper (e.g. en, ko)").option("--force", "Overwrite an existing compositions/scene-<id>.html").option("--lottie <path>", "Lottie animation file (.json/.lottie) to overlay on the scene").option(
51677
+ "--lottie-position <position>",
51678
+ `Lottie position: ${LOTTIE_POSITIONS.join(", ")}`,
51679
+ "full"
51680
+ ).option("--lottie-scale <number>", "Lottie overlay scale (0.01-2)").option("--lottie-opacity <number>", "Lottie overlay opacity (0-1)", "1").option("--lottie-no-loop", "Do not loop the Lottie animation").option("--dry-run", "Preview parameters without writing files or calling APIs").action(async (name, options) => {
51454
51681
  const startedAt = Date.now();
51455
51682
  if (options.style) options.style = validatePreset(options.style);
51683
+ if (options.lottiePosition) {
51684
+ if (!LOTTIE_POSITIONS.includes(options.lottiePosition)) {
51685
+ exitWithError(
51686
+ usageError(
51687
+ `Invalid --lottie-position "${options.lottiePosition}". Valid: ${LOTTIE_POSITIONS.join(", ")}`
51688
+ )
51689
+ );
51690
+ }
51691
+ }
51692
+ let lottieScale;
51693
+ if (options.lottieScale !== void 0) {
51694
+ lottieScale = Number(options.lottieScale);
51695
+ if (!Number.isFinite(lottieScale) || lottieScale < 0.01 || lottieScale > 2) {
51696
+ exitWithError(
51697
+ usageError(`Invalid --lottie-scale "${options.lottieScale}". Must be between 0.01 and 2.`)
51698
+ );
51699
+ }
51700
+ }
51701
+ let lottieOpacity = 1;
51702
+ if (options.lottieOpacity !== void 0) {
51703
+ lottieOpacity = Number(options.lottieOpacity);
51704
+ if (!Number.isFinite(lottieOpacity) || lottieOpacity < 0 || lottieOpacity > 1) {
51705
+ exitWithError(
51706
+ usageError(`Invalid --lottie-opacity "${options.lottieOpacity}". Must be between 0 and 1.`)
51707
+ );
51708
+ }
51709
+ }
51456
51710
  if (options.duration !== void 0) options.duration = validateDuration(options.duration);
51457
51711
  let tts;
51458
51712
  try {
@@ -51481,7 +51735,11 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
51481
51735
  imageProvider: options.imageProvider,
51482
51736
  tts,
51483
51737
  audio: options.audio,
51484
- // commander sets `audio: false` when --no-audio is passed
51738
+ lottie: options.lottie ?? null,
51739
+ lottiePosition: options.lottiePosition,
51740
+ lottieScale: lottieScale ?? null,
51741
+ lottieOpacity: options.lottieOpacity !== void 0 ? lottieOpacity : null,
51742
+ lottieLoop: !options.lottieNoLoop,
51485
51743
  image: options.image
51486
51744
  }
51487
51745
  }
@@ -51509,6 +51767,13 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
51509
51767
  skipTranscribe: options.transcribe === false,
51510
51768
  transcribeLanguage: options.transcribeLanguage,
51511
51769
  force: !!options.force,
51770
+ lottie: options.lottie ? {
51771
+ src: options.lottie,
51772
+ position: options.lottiePosition ?? "full",
51773
+ scale: lottieScale,
51774
+ opacity: options.lottieOpacity !== void 0 ? lottieOpacity : 1,
51775
+ loop: !options.lottieNoLoop
51776
+ } : void 0,
51512
51777
  onProgress: (msg) => {
51513
51778
  if (spinner2) spinner2.text = msg;
51514
51779
  }
@@ -51784,6 +52049,30 @@ async function executeSceneAdd(opts) {
51784
52049
  await writeFile10(imageAbsPath, buffer);
51785
52050
  }
51786
52051
  }
52052
+ let lottieRelPath;
52053
+ let lottieAbsPath;
52054
+ let lottieInput;
52055
+ if (opts.lottie) {
52056
+ const sourceAbs = resolve12(opts.lottie.src);
52057
+ if (!await pathExists2(sourceAbs)) {
52058
+ return errResult(`Lottie file not found: ${sourceAbs}`);
52059
+ }
52060
+ const ext = (sourceAbs.match(/\.([a-z0-9]+)$/i)?.[1] ?? "json").toLowerCase();
52061
+ if (ext !== "json" && ext !== "lottie") {
52062
+ return errResult(`Unsupported Lottie file extension: .${ext}. Use .json or .lottie.`);
52063
+ }
52064
+ lottieRelPath = `assets/lottie-${id}.${ext}`;
52065
+ lottieAbsPath = resolve12(projectDir, lottieRelPath);
52066
+ await mkdir5(dirname3(lottieAbsPath), { recursive: true });
52067
+ await copyFile(sourceAbs, lottieAbsPath);
52068
+ lottieInput = {
52069
+ src: lottieRelPath,
52070
+ position: opts.lottie.position,
52071
+ scale: opts.lottie.scale,
52072
+ opacity: opts.lottie.opacity,
52073
+ loop: opts.lottie.loop
52074
+ };
52075
+ }
51787
52076
  const cfg = await loadVibeProjectConfig(projectDir);
51788
52077
  const fallback2 = cfg?.defaultSceneDuration ?? 5;
51789
52078
  const NARRATION_TAIL_BUFFER = 0.5;
@@ -51812,7 +52101,8 @@ async function executeSceneAdd(opts) {
51812
52101
  kicker: opts.kicker,
51813
52102
  imagePath: imageRelPath,
51814
52103
  audioPath: audioRelPath,
51815
- transcript: transcriptWords
52104
+ transcript: transcriptWords,
52105
+ lottie: lottieInput
51816
52106
  });
51817
52107
  await mkdir5(dirname3(scenePath), { recursive: true });
51818
52108
  await writeFile10(scenePath, sceneHtml, "utf-8");
@@ -51833,6 +52123,7 @@ async function executeSceneAdd(opts) {
51833
52123
  rootPath: relative5(process.cwd(), rootPath) || rootPath,
51834
52124
  audioPath: audioAbsPath ? relative5(process.cwd(), audioAbsPath) || audioAbsPath : void 0,
51835
52125
  imagePath: imageAbsPath ? relative5(process.cwd(), imageAbsPath) || imageAbsPath : void 0,
52126
+ lottiePath: lottieAbsPath ? relative5(process.cwd(), lottieAbsPath) || lottieAbsPath : void 0,
51836
52127
  transcriptPath: transcriptAbsPath ? relative5(process.cwd(), transcriptAbsPath) || transcriptAbsPath : void 0,
51837
52128
  transcriptWordCount
51838
52129
  };
@@ -53099,7 +53390,7 @@ var editMotionOverlayTool = defineTool({
53099
53390
  duration: external_exports.number().optional().describe("Overlay/render duration in seconds"),
53100
53391
  start: external_exports.number().optional().describe("Overlay start time in seconds"),
53101
53392
  style: external_exports.string().optional().describe("Style preset for generated overlays"),
53102
- model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-3.1-pro"]).optional().describe("LLM model for generated overlays"),
53393
+ model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-2.5-pro", "gemini-3.1-pro"]).optional().describe("LLM model for generated overlays"),
53103
53394
  understand: external_exports.enum(["auto", "off", "required"]).optional().describe("Analyze video before generated overlay (default: auto)"),
53104
53395
  understandingPrompt: external_exports.string().optional().describe("Custom prompt for video understanding"),
53105
53396
  position: external_exports.enum(["full", "center", "top-left", "top-right", "bottom-left", "bottom-right"]).optional().describe("Lottie overlay position"),
@@ -54833,7 +55124,7 @@ var inspectRenderTool = defineTool({
54833
55124
  outputPath: external_exports.string().optional().describe("Optional review report path. Defaults to <project>/review-report.json."),
54834
55125
  report: external_exports.boolean().optional().describe("Write review-report.json. Default true."),
54835
55126
  ai: external_exports.boolean().optional().describe("Also run Gemini video review and merge findings into review-report.json. Default false."),
54836
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant for ai review. Default flash."),
55127
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant for ai review. Default flash."),
54837
55128
  dryRun: external_exports.boolean().optional().describe("Preview resolved inputs without probing video or calling Gemini.")
54838
55129
  }),
54839
55130
  async execute(args, ctx) {
@@ -54885,7 +55176,7 @@ var analyzeMediaTool = defineTool({
54885
55176
  schema: external_exports.object({
54886
55177
  source: external_exports.string().describe("Path to image/video or YouTube URL"),
54887
55178
  prompt: external_exports.string().describe("Analysis prompt (e.g., 'Describe the scene', 'Count people')"),
54888
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant (default: flash)"),
55179
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
54889
55180
  fps: external_exports.number().optional().describe("Frames per second for video sampling (default: 1)"),
54890
55181
  start: external_exports.number().optional().describe("Start time in seconds for video analysis"),
54891
55182
  end: external_exports.number().optional().describe("End time in seconds for video analysis"),
@@ -54913,7 +55204,7 @@ var analyzeVideoTool = defineTool({
54913
55204
  schema: external_exports.object({
54914
55205
  source: external_exports.string().describe("Path to video file"),
54915
55206
  prompt: external_exports.string().describe("Analysis prompt"),
54916
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant (default: flash)"),
55207
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
54917
55208
  fps: external_exports.number().optional().describe("Frames per second for sampling"),
54918
55209
  start: external_exports.number().optional().describe("Start time in seconds"),
54919
55210
  end: external_exports.number().optional().describe("End time in seconds"),
@@ -54943,7 +55234,7 @@ var analyzeReviewTool = defineTool({
54943
55234
  storyboardPath: external_exports.string().optional().describe("Path to storyboard.json for intent comparison"),
54944
55235
  autoApply: external_exports.boolean().optional().describe("Automatically apply suggested fixes (default: false)"),
54945
55236
  verify: external_exports.boolean().optional().describe("Re-review after applying fixes (default: false)"),
54946
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant (default: flash)"),
55237
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
54947
55238
  outputPath: external_exports.string().optional().describe("Output path for fixed video")
54948
55239
  }),
54949
55240
  async execute(args) {
@@ -55027,7 +55318,7 @@ var generateMotionTool = defineTool({
55027
55318
  "Analyze the base video with Gemini before generating motion graphics: auto, off, or required (default: auto)"
55028
55319
  ),
55029
55320
  understandingPrompt: external_exports.string().optional().describe("Custom prompt for video understanding when --video is provided"),
55030
- model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-3.1-pro"]).optional().describe("LLM model for code generation (default: sonnet)"),
55321
+ model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-2.5-pro", "gemini-3.1-pro"]).optional().describe("LLM model for code generation (default: sonnet)"),
55031
55322
  output: external_exports.string().optional().describe("Output path (TSX if code-only, MP4 if rendered)")
55032
55323
  }),
55033
55324
  async execute(args) {
@@ -55292,6 +55583,7 @@ var generateVideoTool = defineTool({
55292
55583
  "Video provider (default: seedance when FAL_API_KEY is configured, otherwise first configured provider)"
55293
55584
  ),
55294
55585
  image: external_exports.string().optional().describe("Reference image path for image-to-video"),
55586
+ endImage: external_exports.string().optional().describe("Ending frame image path for Seedance image-to-video"),
55295
55587
  refImages: external_exports.array(external_exports.string()).optional().describe("Reference images for Seedance reference-to-video"),
55296
55588
  refVideos: external_exports.array(external_exports.string()).optional().describe("Reference videos for Seedance reference-to-video"),
55297
55589
  refAudio: external_exports.array(external_exports.string()).optional().describe("Reference audio files for Seedance reference-to-video"),
@@ -55299,7 +55591,7 @@ var generateVideoTool = defineTool({
55299
55591
  ratio: external_exports.string().optional().describe("Aspect ratio: 16:9, 9:16, 1:1 (default: 16:9)"),
55300
55592
  mode: external_exports.string().optional().describe("Kling mode: std or pro"),
55301
55593
  negative: external_exports.string().optional().describe("Negative prompt (Seedance/Kling/Veo)"),
55302
- resolution: external_exports.string().optional().describe("Resolution: 720p, 1080p, 4k (Veo only)"),
55594
+ resolution: external_exports.string().optional().describe("Resolution: 480p, 720p, 1080p, or 4k depending on provider"),
55303
55595
  veoModel: external_exports.string().optional().describe("Veo model: 3.0, 3.1, 3.1-fast"),
55304
55596
  runwayModel: external_exports.string().optional().describe("Runway model: gen4.5, gen4_turbo"),
55305
55597
  seedanceModel: external_exports.string().optional().describe("Seedance variant: quality or fast (fal.ai only)"),
@@ -55345,10 +55637,10 @@ var generateVideoStatusTool = defineTool({
55345
55637
  name: "generate_video_status",
55346
55638
  category: "generate",
55347
55639
  cost: "free",
55348
- description: "Check video generation status for Runway or Kling tasks.",
55640
+ description: "Check video generation status for Grok, Runway, Kling, or Veo tasks.",
55349
55641
  schema: external_exports.object({
55350
55642
  taskId: external_exports.string().describe("Task ID from video generation"),
55351
- provider: external_exports.enum(["runway", "kling"]).optional().describe("Provider (default: runway)"),
55643
+ provider: external_exports.enum(["grok", "runway", "kling", "veo"]).optional().describe("Provider (default: runway)"),
55352
55644
  taskType: external_exports.enum(["text2video", "image2video"]).optional().describe("Kling task type (default: text2video)"),
55353
55645
  wait: external_exports.boolean().optional().describe("Wait for completion"),
55354
55646
  output: external_exports.string().optional().describe("Download video when complete")