@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.
package/dist/index.js CHANGED
@@ -7299,6 +7299,39 @@ var init_whisper = __esm({
7299
7299
  }
7300
7300
  });
7301
7301
 
7302
+ // ../ai-providers/dist/gemini/gemini-models.js
7303
+ function isGeminiTextModelAlias(model) {
7304
+ return Object.prototype.hasOwnProperty.call(GEMINI_TEXT_MODEL_ALIASES, model);
7305
+ }
7306
+ function resolveGeminiTextModel(model) {
7307
+ const trimmed = model?.trim();
7308
+ if (!trimmed)
7309
+ return GEMINI_DEFAULT_TEXT_MODEL;
7310
+ if (isGeminiTextModelAlias(trimmed))
7311
+ return GEMINI_TEXT_MODEL_ALIASES[trimmed];
7312
+ if (trimmed.startsWith("gemini-"))
7313
+ return trimmed;
7314
+ return GEMINI_DEFAULT_TEXT_MODEL;
7315
+ }
7316
+ var GEMINI_DEFAULT_TEXT_MODEL, GEMINI_AGENT_DEFAULT_TEXT_MODEL, GEMINI_TEXT_MODEL_ALIASES, GEMINI_TEXT_MODEL_HELP;
7317
+ var init_gemini_models = __esm({
7318
+ "../ai-providers/dist/gemini/gemini-models.js"() {
7319
+ "use strict";
7320
+ GEMINI_DEFAULT_TEXT_MODEL = "gemini-3.5-flash";
7321
+ GEMINI_AGENT_DEFAULT_TEXT_MODEL = "gemini-2.5-flash";
7322
+ GEMINI_TEXT_MODEL_ALIASES = {
7323
+ flash: GEMINI_DEFAULT_TEXT_MODEL,
7324
+ latest: GEMINI_DEFAULT_TEXT_MODEL,
7325
+ "flash-3.5": GEMINI_DEFAULT_TEXT_MODEL,
7326
+ "flash-3": "gemini-3-flash-preview",
7327
+ "flash-2.5": "gemini-2.5-flash",
7328
+ pro: "gemini-2.5-pro",
7329
+ "pro-3.1": "gemini-3.1-pro-preview"
7330
+ };
7331
+ 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";
7332
+ }
7333
+ });
7334
+
7302
7335
  // ../ai-providers/dist/gemini/gemini-motion.js
7303
7336
  function buildMotionSystemPrompt(width, height, fps, duration, durationInFrames, stylePreset) {
7304
7337
  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".
@@ -7452,7 +7485,7 @@ async function generateMotion(api, description, options = {}) {
7452
7485
  const duration = options.duration || 5;
7453
7486
  const durationInFrames = Math.round(duration * fps);
7454
7487
  const stylePreset = options.style || "modern and clean";
7455
- const modelId = options.model || "gemini-2.5-pro";
7488
+ const modelId = resolveGeminiTextModel(options.model);
7456
7489
  let systemPrompt = buildMotionSystemPrompt(width, height, fps, duration, durationInFrames, stylePreset);
7457
7490
  if (options.videoContext) {
7458
7491
  const sourceLabel = options.sourceType === "image" ? "IMAGE" : "VIDEO";
@@ -7533,7 +7566,7 @@ async function refineMotion(api, existingCode, instructions, options = {}) {
7533
7566
  const fps = options.fps || 30;
7534
7567
  const duration = options.duration || 5;
7535
7568
  const durationInFrames = Math.round(duration * fps);
7536
- const modelId = options.model || "gemini-2.5-pro";
7569
+ const modelId = resolveGeminiTextModel(options.model);
7537
7570
  const systemPrompt = `You are a world-class broadcast motion graphics designer. Modify the provided Remotion component based on instructions.
7538
7571
 
7539
7572
  CANVAS: ${width}\xD7${height}px | ${fps}fps | ${durationInFrames} frames (${duration}s)
@@ -7628,9 +7661,18 @@ var GEMINI_MOTION_MODELS, STYLE_USER_INSTRUCTIONS;
7628
7661
  var init_gemini_motion = __esm({
7629
7662
  "../ai-providers/dist/gemini/gemini-motion.js"() {
7630
7663
  "use strict";
7664
+ init_gemini_models();
7631
7665
  GEMINI_MOTION_MODELS = {
7666
+ gemini: GEMINI_DEFAULT_TEXT_MODEL,
7667
+ flash: GEMINI_DEFAULT_TEXT_MODEL,
7668
+ latest: GEMINI_DEFAULT_TEXT_MODEL,
7669
+ "flash-3.5": GEMINI_DEFAULT_TEXT_MODEL,
7670
+ "flash-3": "gemini-3-flash-preview",
7671
+ "flash-2.5": "gemini-2.5-flash",
7632
7672
  pro: "gemini-2.5-pro",
7633
- "3.1-pro": "gemini-3.1-pro-preview"
7673
+ "2.5-pro": "gemini-2.5-pro",
7674
+ "3.1-pro": "gemini-3.1-pro-preview",
7675
+ "pro-3.1": "gemini-3.1-pro-preview"
7634
7676
  };
7635
7677
  STYLE_USER_INSTRUCTIONS = {
7636
7678
  minimal: "CENTER-ALIGNED pure typography. Thin lines + large title + subtitle. NO glass panels, NO bokeh, NO dark bars. Breathing pulse during hold.",
@@ -7753,7 +7795,7 @@ async function analyzeContent(api, content, targetDuration, options) {
7753
7795
  maxOutputTokens: 4096
7754
7796
  }
7755
7797
  };
7756
- const response = await fetch(`${api.baseUrl}/models/gemini-2.5-flash:generateContent?key=${api.apiKey}`, {
7798
+ const response = await fetch(`${api.baseUrl}/models/${GEMINI_DEFAULT_TEXT_MODEL}:generateContent?key=${api.apiKey}`, {
7757
7799
  method: "POST",
7758
7800
  headers: { "Content-Type": "application/json" },
7759
7801
  body: JSON.stringify(payload)
@@ -7786,6 +7828,7 @@ var init_gemini_storyboard = __esm({
7786
7828
  "../ai-providers/dist/gemini/gemini-storyboard.js"() {
7787
7829
  "use strict";
7788
7830
  init_storyboard_prompt();
7831
+ init_gemini_models();
7789
7832
  }
7790
7833
  });
7791
7834
 
@@ -7824,6 +7867,7 @@ var init_GeminiProvider = __esm({
7824
7867
  "../ai-providers/dist/gemini/GeminiProvider.js"() {
7825
7868
  "use strict";
7826
7869
  init_gemini_motion();
7870
+ init_gemini_models();
7827
7871
  init_gemini_storyboard();
7828
7872
  init_http();
7829
7873
  MODEL_MAP = {
@@ -8332,7 +8376,7 @@ var init_GeminiProvider = __esm({
8332
8376
  error: "Google API key not configured"
8333
8377
  };
8334
8378
  }
8335
- const modelId = options.model || "gemini-3-flash-preview";
8379
+ const modelId = resolveGeminiTextModel(options.model);
8336
8380
  try {
8337
8381
  let videoPart;
8338
8382
  if (typeof videoData === "string") {
@@ -8439,7 +8483,7 @@ var init_GeminiProvider = __esm({
8439
8483
  error: "Google API key not configured"
8440
8484
  };
8441
8485
  }
8442
- const modelId = options.model || "gemini-3-flash-preview";
8486
+ const modelId = resolveGeminiTextModel(options.model);
8443
8487
  try {
8444
8488
  const buffers = Array.isArray(imageData) ? imageData : [imageData];
8445
8489
  const imageParts = buffers.map((buf) => ({
@@ -8541,7 +8585,7 @@ Example response:
8541
8585
  [{"type":"trim","description":"Trim intro to 3 seconds","clipIds":["clip-1"],"params":{"newDuration":3},"confidence":0.9}]
8542
8586
 
8543
8587
  Respond with ONLY the JSON array, no other text.`;
8544
- const response = await fetch(`${this.baseUrl}/models/gemini-2.5-flash:generateContent?key=${this.apiKey}`, {
8588
+ const response = await fetch(`${this.baseUrl}/models/${GEMINI_DEFAULT_TEXT_MODEL}:generateContent?key=${this.apiKey}`, {
8545
8589
  method: "POST",
8546
8590
  headers: {
8547
8591
  "Content-Type": "application/json"
@@ -8663,6 +8707,7 @@ var init_gemini = __esm({
8663
8707
  "use strict";
8664
8708
  init_GeminiProvider();
8665
8709
  init_gemini_motion();
8710
+ init_gemini_models();
8666
8711
  init_define_provider();
8667
8712
  defineProvider({
8668
8713
  id: "gemini",
@@ -18026,6 +18071,10 @@ __export(dist_exports2, {
18026
18071
  ClaudeProvider: () => ClaudeProvider,
18027
18072
  ElevenLabsProvider: () => ElevenLabsProvider,
18028
18073
  FalProvider: () => FalProvider,
18074
+ GEMINI_AGENT_DEFAULT_TEXT_MODEL: () => GEMINI_AGENT_DEFAULT_TEXT_MODEL,
18075
+ GEMINI_DEFAULT_TEXT_MODEL: () => GEMINI_DEFAULT_TEXT_MODEL,
18076
+ GEMINI_TEXT_MODEL_ALIASES: () => GEMINI_TEXT_MODEL_ALIASES,
18077
+ GEMINI_TEXT_MODEL_HELP: () => GEMINI_TEXT_MODEL_HELP,
18029
18078
  GeminiProvider: () => GeminiProvider,
18030
18079
  GrokProvider: () => GrokProvider,
18031
18080
  KNOWN_VOICES: () => KNOWN_VOICES,
@@ -18056,6 +18105,7 @@ __export(dist_exports2, {
18056
18105
  getProvidersFor: () => getProvidersFor,
18057
18106
  getSetupProviders: () => getSetupProviders,
18058
18107
  grokProvider: () => grokProvider,
18108
+ isGeminiTextModelAlias: () => isGeminiTextModelAlias,
18059
18109
  klingProvider: () => klingProvider,
18060
18110
  kokoroProvider: () => kokoroProvider,
18061
18111
  ollamaProvider: () => ollamaProvider,
@@ -18063,6 +18113,7 @@ __export(dist_exports2, {
18063
18113
  openaiProvider: () => openaiProvider,
18064
18114
  providerRegistry: () => providerRegistry,
18065
18115
  replicateProvider: () => replicateProvider,
18116
+ resolveGeminiTextModel: () => resolveGeminiTextModel,
18066
18117
  resolveVoiceId: () => resolveVoiceId,
18067
18118
  runwayProvider: () => runwayProvider,
18068
18119
  whisperProvider: () => whisperProvider
@@ -27471,6 +27522,52 @@ function buildPreset(input) {
27471
27522
  }
27472
27523
  }
27473
27524
  }
27525
+ function lottieOverlayStyle(input) {
27526
+ const pos = input.position ?? "full";
27527
+ const rawScale = input.scale ?? (pos === "full" ? 1 : 0.25);
27528
+ const scale = Math.max(0.01, Math.min(2, rawScale));
27529
+ const opacity = Math.max(0, Math.min(1, input.opacity ?? 1));
27530
+ const base = [
27531
+ "position:absolute",
27532
+ "pointer-events:none",
27533
+ `opacity:${opacity}`
27534
+ ];
27535
+ if (pos === "full") {
27536
+ base.push("inset:0", "width:100%", "height:100%");
27537
+ } else {
27538
+ const pct = scale * 100;
27539
+ base.push(`width:${pct}%`, `height:${pct}%`);
27540
+ switch (pos) {
27541
+ case "center":
27542
+ base.push("top:50%", "left:50%", "transform:translate(-50%,-50%)");
27543
+ break;
27544
+ case "top-left":
27545
+ base.push("top:4%", "left:4%");
27546
+ break;
27547
+ case "top-right":
27548
+ base.push("top:4%", "right:4%");
27549
+ break;
27550
+ case "bottom-left":
27551
+ base.push("bottom:4%", "left:4%");
27552
+ break;
27553
+ case "bottom-right":
27554
+ base.push("bottom:4%", "right:4%");
27555
+ break;
27556
+ }
27557
+ }
27558
+ return base.join(";");
27559
+ }
27560
+ function buildLottieOverlay(input, sceneId) {
27561
+ const loop = input.loop ?? true ? " loop" : "";
27562
+ const style = lottieOverlayStyle(input);
27563
+ const overlayId = `lottie-overlay-${sceneId}`;
27564
+ const markup = `<dotlottie-wc id="${overlayId}" src="${esc(input.src)}" autoplay${loop} style="${style}"></dotlottie-wc>`;
27565
+ const script = `<script type="module">
27566
+ import { setWasmUrl } from "${DOTLOTTIE_WC_CDN}";
27567
+ setWasmUrl("${DOTLOTTIE_WASM_CDN}");
27568
+ </script>`;
27569
+ return { markup, script };
27570
+ }
27474
27571
  function emitSceneHtml(input) {
27475
27572
  if (input.duration <= 0) {
27476
27573
  throw new Error(`Scene duration must be > 0, got ${input.duration}`);
@@ -27491,15 +27588,20 @@ function emitSceneHtml(input) {
27491
27588
  data-volume="1"
27492
27589
  ></audio>
27493
27590
  ` : "";
27591
+ const lottieLayer = input.lottie ? buildLottieOverlay(input.lottie, id) : null;
27592
+ const lottieMarkup = lottieLayer ? `
27593
+ ${lottieLayer.markup}` : "";
27594
+ const lottieScript = lottieLayer ? `
27595
+ ${lottieLayer.script}` : "";
27494
27596
  return `<template id="scene-${id}-template">
27495
27597
  <div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input.width}" data-height="${input.height}">
27496
27598
  <style>
27497
27599
  ${parts.css}
27498
27600
  </style>
27499
27601
 
27500
- ${parts.body}
27602
+ ${parts.body}${lottieMarkup}
27501
27603
  ${audioBlock}
27502
- <script src="${GSAP_CDN}"></script>
27604
+ <script src="${GSAP_CDN}"></script>${lottieScript}
27503
27605
  <script>
27504
27606
  window.__timelines = window.__timelines || {};
27505
27607
  const tl = gsap.timeline({ paused: true });
@@ -27561,7 +27663,7 @@ function readRootDims(rootHtml) {
27561
27663
  if (!widthMatch || !heightMatch) return null;
27562
27664
  return { width: parseInt(widthMatch[1], 10), height: parseInt(heightMatch[1], 10) };
27563
27665
  }
27564
- var SCENE_PRESETS, GSAP_CDN, SCENE_OVERLAP_SECONDS;
27666
+ var SCENE_PRESETS, LOTTIE_POSITIONS, GSAP_CDN, SCENE_OVERLAP_SECONDS, DOTLOTTIE_WC_VERSION, DOTLOTTIE_WEB_VERSION, DOTLOTTIE_WC_CDN, DOTLOTTIE_WASM_CDN;
27565
27667
  var init_scene_html_emit = __esm({
27566
27668
  "src/commands/_shared/scene-html-emit.ts"() {
27567
27669
  "use strict";
@@ -27572,8 +27674,20 @@ var init_scene_html_emit = __esm({
27572
27674
  "kinetic-type",
27573
27675
  "product-shot"
27574
27676
  ];
27677
+ LOTTIE_POSITIONS = [
27678
+ "full",
27679
+ "center",
27680
+ "top-left",
27681
+ "top-right",
27682
+ "bottom-left",
27683
+ "bottom-right"
27684
+ ];
27575
27685
  GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
27576
27686
  SCENE_OVERLAP_SECONDS = 0.4;
27687
+ DOTLOTTIE_WC_VERSION = "0.9.12";
27688
+ DOTLOTTIE_WEB_VERSION = "0.71.0";
27689
+ DOTLOTTIE_WC_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-wc@${DOTLOTTIE_WC_VERSION}/dist/index.js`;
27690
+ DOTLOTTIE_WASM_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web@${DOTLOTTIE_WEB_VERSION}/dist/dotlottie-player.wasm`;
27577
27691
  }
27578
27692
  });
27579
27693
 
@@ -29607,6 +29721,7 @@ var MODEL_SETTINGS_BY_PROVIDER, ComposeBeatError, defaultCallLLM, CONFIG_PROVIDE
29607
29721
  var init_compose_scenes_skills = __esm({
29608
29722
  "src/commands/_shared/compose-scenes-skills.ts"() {
29609
29723
  "use strict";
29724
+ init_dist2();
29610
29725
  init_config();
29611
29726
  init_bundle();
29612
29727
  init_scene_lint();
@@ -29628,12 +29743,12 @@ var init_compose_scenes_skills = __esm({
29628
29743
  medium: { model: "gpt-5", maxTokens: 6e3, costPerMTokIn: 1.25, costPerMTokOut: 10 },
29629
29744
  high: { model: "gpt-5", maxTokens: 8e3, costPerMTokIn: 1.25, costPerMTokOut: 10 }
29630
29745
  },
29631
- // Google Gemini 2.5 Pro — ~2.6× cheaper than Claude per spike, ~20 s/beat.
29632
- // Strong instruction-following on the Hyperframes skill bundle.
29746
+ // Google Gemini 3.5 Flash — current one-shot Gemini default for
29747
+ // analysis/composition. Keep agent loops on their explicit 2.5 Flash default.
29633
29748
  gemini: {
29634
- low: { model: "gemini-2.5-pro", maxTokens: 4e3, costPerMTokIn: 1.25, costPerMTokOut: 5 },
29635
- medium: { model: "gemini-2.5-pro", maxTokens: 6e3, costPerMTokIn: 1.25, costPerMTokOut: 5 },
29636
- high: { model: "gemini-2.5-pro", maxTokens: 8e3, costPerMTokIn: 1.25, costPerMTokOut: 5 }
29749
+ low: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 4e3, costPerMTokIn: 1.5, costPerMTokOut: 9 },
29750
+ medium: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 6e3, costPerMTokIn: 1.5, costPerMTokOut: 9 },
29751
+ high: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 8e3, costPerMTokIn: 1.5, costPerMTokOut: 9 }
29637
29752
  }
29638
29753
  };
29639
29754
  ComposeBeatError = class extends Error {
@@ -30579,6 +30694,30 @@ async function executeSceneAdd(opts) {
30579
30694
  await writeFile11(imageAbsPath, buffer);
30580
30695
  }
30581
30696
  }
30697
+ let lottieRelPath;
30698
+ let lottieAbsPath;
30699
+ let lottieInput;
30700
+ if (opts.lottie) {
30701
+ const sourceAbs = resolve15(opts.lottie.src);
30702
+ if (!await pathExists2(sourceAbs)) {
30703
+ return errResult(`Lottie file not found: ${sourceAbs}`);
30704
+ }
30705
+ const ext = (sourceAbs.match(/\.([a-z0-9]+)$/i)?.[1] ?? "json").toLowerCase();
30706
+ if (ext !== "json" && ext !== "lottie") {
30707
+ return errResult(`Unsupported Lottie file extension: .${ext}. Use .json or .lottie.`);
30708
+ }
30709
+ lottieRelPath = `assets/lottie-${id}.${ext}`;
30710
+ lottieAbsPath = resolve15(projectDir, lottieRelPath);
30711
+ await mkdir6(dirname3(lottieAbsPath), { recursive: true });
30712
+ await copyFile(sourceAbs, lottieAbsPath);
30713
+ lottieInput = {
30714
+ src: lottieRelPath,
30715
+ position: opts.lottie.position,
30716
+ scale: opts.lottie.scale,
30717
+ opacity: opts.lottie.opacity,
30718
+ loop: opts.lottie.loop
30719
+ };
30720
+ }
30582
30721
  const cfg = await loadVibeProjectConfig(projectDir);
30583
30722
  const fallback2 = cfg?.defaultSceneDuration ?? 5;
30584
30723
  const NARRATION_TAIL_BUFFER = 0.5;
@@ -30607,7 +30746,8 @@ async function executeSceneAdd(opts) {
30607
30746
  kicker: opts.kicker,
30608
30747
  imagePath: imageRelPath,
30609
30748
  audioPath: audioRelPath,
30610
- transcript: transcriptWords
30749
+ transcript: transcriptWords,
30750
+ lottie: lottieInput
30611
30751
  });
30612
30752
  await mkdir6(dirname3(scenePath), { recursive: true });
30613
30753
  await writeFile11(scenePath, sceneHtml, "utf-8");
@@ -30628,6 +30768,7 @@ async function executeSceneAdd(opts) {
30628
30768
  rootPath: relative6(process.cwd(), rootPath) || rootPath,
30629
30769
  audioPath: audioAbsPath ? relative6(process.cwd(), audioAbsPath) || audioAbsPath : void 0,
30630
30770
  imagePath: imageAbsPath ? relative6(process.cwd(), imageAbsPath) || imageAbsPath : void 0,
30771
+ lottiePath: lottieAbsPath ? relative6(process.cwd(), lottieAbsPath) || lottieAbsPath : void 0,
30631
30772
  transcriptPath: transcriptAbsPath ? relative6(process.cwd(), transcriptAbsPath) || transcriptAbsPath : void 0,
30632
30773
  transcriptWordCount
30633
30774
  };
@@ -30905,9 +31046,40 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
30905
31046
  ).option("--no-image", "Skip image generation even when --visuals is provided").option(
30906
31047
  "--no-transcribe",
30907
31048
  "Skip Whisper word-level transcribe step (no transcript-<id>.json emitted)"
30908
- ).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) => {
31049
+ ).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(
31050
+ "--lottie-position <position>",
31051
+ `Lottie position: ${LOTTIE_POSITIONS.join(", ")}`,
31052
+ "full"
31053
+ ).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) => {
30909
31054
  const startedAt = Date.now();
30910
31055
  if (options.style) options.style = validatePreset(options.style);
31056
+ if (options.lottiePosition) {
31057
+ if (!LOTTIE_POSITIONS.includes(options.lottiePosition)) {
31058
+ exitWithError(
31059
+ usageError(
31060
+ `Invalid --lottie-position "${options.lottiePosition}". Valid: ${LOTTIE_POSITIONS.join(", ")}`
31061
+ )
31062
+ );
31063
+ }
31064
+ }
31065
+ let lottieScale;
31066
+ if (options.lottieScale !== void 0) {
31067
+ lottieScale = Number(options.lottieScale);
31068
+ if (!Number.isFinite(lottieScale) || lottieScale < 0.01 || lottieScale > 2) {
31069
+ exitWithError(
31070
+ usageError(`Invalid --lottie-scale "${options.lottieScale}". Must be between 0.01 and 2.`)
31071
+ );
31072
+ }
31073
+ }
31074
+ let lottieOpacity = 1;
31075
+ if (options.lottieOpacity !== void 0) {
31076
+ lottieOpacity = Number(options.lottieOpacity);
31077
+ if (!Number.isFinite(lottieOpacity) || lottieOpacity < 0 || lottieOpacity > 1) {
31078
+ exitWithError(
31079
+ usageError(`Invalid --lottie-opacity "${options.lottieOpacity}". Must be between 0 and 1.`)
31080
+ );
31081
+ }
31082
+ }
30911
31083
  if (options.duration !== void 0) options.duration = validateDuration(options.duration);
30912
31084
  let tts;
30913
31085
  try {
@@ -30936,7 +31108,11 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
30936
31108
  imageProvider: options.imageProvider,
30937
31109
  tts,
30938
31110
  audio: options.audio,
30939
- // commander sets `audio: false` when --no-audio is passed
31111
+ lottie: options.lottie ?? null,
31112
+ lottiePosition: options.lottiePosition,
31113
+ lottieScale: lottieScale ?? null,
31114
+ lottieOpacity: options.lottieOpacity !== void 0 ? lottieOpacity : null,
31115
+ lottieLoop: !options.lottieNoLoop,
30940
31116
  image: options.image
30941
31117
  }
30942
31118
  }
@@ -30964,6 +31140,13 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
30964
31140
  skipTranscribe: options.transcribe === false,
30965
31141
  transcribeLanguage: options.transcribeLanguage,
30966
31142
  force: !!options.force,
31143
+ lottie: options.lottie ? {
31144
+ src: options.lottie,
31145
+ position: options.lottiePosition ?? "full",
31146
+ scale: lottieScale,
31147
+ opacity: options.lottieOpacity !== void 0 ? lottieOpacity : 1,
31148
+ loop: !options.lottieNoLoop
31149
+ } : void 0,
30967
31150
  onProgress: (msg) => {
30968
31151
  if (spinner2) spinner2.text = msg;
30969
31152
  }
@@ -32028,7 +32211,7 @@ Use this image analysis to inform the color palette, typography placement, and o
32028
32211
  6. Recommended animation timing and entrance/exit style
32029
32212
  7. Any moments where overlays should pause, fade, or stay minimal`;
32030
32213
  const analysisResult = await gemini.analyzeVideo(videoBuffer, analysisPrompt, {
32031
- model: "gemini-2.5-flash",
32214
+ model: GEMINI_DEFAULT_TEXT_MODEL,
32032
32215
  fps: 1,
32033
32216
  lowResolution: true
32034
32217
  });
@@ -32197,7 +32380,7 @@ function registerMotionCommand(aiCommand) {
32197
32380
  "auto"
32198
32381
  ).option("--understanding-prompt <text>", "Custom prompt for --video understanding").option("--from-tsx <path>", "Refine an existing TSX file instead of generating from scratch").option(
32199
32382
  "-m, --model <alias>",
32200
- "LLM model: sonnet (default), opus, gemini, gemini-3.1-pro",
32383
+ "LLM model: sonnet (default), opus, gemini, gemini-2.5-pro, gemini-3.1-pro",
32201
32384
  "sonnet"
32202
32385
  ).option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
32203
32386
  const startedAt = Date.now();
@@ -32341,7 +32524,8 @@ var init_ai_motion = __esm({
32341
32524
  sonnet: { provider: "claude", modelId: "claude-sonnet-4-6" },
32342
32525
  opus: { provider: "claude", modelId: "claude-opus-4-7" },
32343
32526
  "opus-4-6": { provider: "claude", modelId: "claude-opus-4-6" },
32344
- gemini: { provider: "gemini", modelId: "gemini-2.5-pro" },
32527
+ gemini: { provider: "gemini", modelId: GEMINI_DEFAULT_TEXT_MODEL },
32528
+ "gemini-2.5-pro": { provider: "gemini", modelId: "gemini-2.5-pro" },
32345
32529
  "gemini-3.1-pro": { provider: "gemini", modelId: "gemini-3.1-pro-preview" }
32346
32530
  };
32347
32531
  }
@@ -34263,12 +34447,37 @@ async function executeVideoStatus(options) {
34263
34447
  } = options;
34264
34448
  try {
34265
34449
  const envKeyMap = {
34450
+ grok: "XAI_API_KEY",
34266
34451
  runway: "RUNWAY_API_SECRET",
34267
- kling: "KLING_API_KEY"
34452
+ kling: "KLING_API_KEY",
34453
+ veo: "GOOGLE_API_KEY"
34268
34454
  };
34269
34455
  const key = apiKey || process.env[envKeyMap[provider] || ""];
34270
34456
  if (!key) return { success: false, error: `${envKeyMap[provider]} required` };
34271
- if (provider === "runway") {
34457
+ if (provider === "grok") {
34458
+ const grok = new GrokProvider();
34459
+ await grok.initialize({ apiKey: key });
34460
+ let result = await grok.getGenerationStatus(taskId);
34461
+ if (wait && result.status !== "completed" && result.status !== "failed") {
34462
+ result = await grok.waitForCompletion(taskId, () => {
34463
+ });
34464
+ }
34465
+ let outputPath;
34466
+ if (output && result.videoUrl) {
34467
+ const buffer = await downloadVideo(result.videoUrl);
34468
+ outputPath = resolve23(process.cwd(), output);
34469
+ await writeFile20(outputPath, buffer);
34470
+ }
34471
+ return {
34472
+ success: true,
34473
+ taskId,
34474
+ status: result.status,
34475
+ progress: result.progress,
34476
+ videoUrl: result.videoUrl,
34477
+ outputPath,
34478
+ error: result.error
34479
+ };
34480
+ } else if (provider === "runway") {
34272
34481
  const runway = new RunwayProvider();
34273
34482
  await runway.initialize({ apiKey: key });
34274
34483
  let result = await runway.getGenerationStatus(taskId);
@@ -34310,7 +34519,31 @@ async function executeVideoStatus(options) {
34310
34519
  status: result.status,
34311
34520
  videoUrl: result.videoUrl,
34312
34521
  duration: result.duration,
34313
- outputPath
34522
+ outputPath,
34523
+ error: result.error
34524
+ };
34525
+ } else if (provider === "veo") {
34526
+ const gemini = new GeminiProvider();
34527
+ await gemini.initialize({ apiKey: key });
34528
+ let result = await gemini.getGenerationStatus(taskId);
34529
+ if (wait && result.status !== "completed" && result.status !== "failed" && result.status !== "cancelled") {
34530
+ result = await gemini.waitForVideoCompletion(taskId, () => {
34531
+ });
34532
+ }
34533
+ let outputPath;
34534
+ if (output && result.videoUrl) {
34535
+ const buffer = await downloadVideo(result.videoUrl, key);
34536
+ outputPath = resolve23(process.cwd(), output);
34537
+ await writeFile20(outputPath, buffer);
34538
+ }
34539
+ return {
34540
+ success: true,
34541
+ taskId,
34542
+ status: result.status,
34543
+ progress: result.progress,
34544
+ videoUrl: result.videoUrl,
34545
+ outputPath,
34546
+ error: result.error
34314
34547
  };
34315
34548
  }
34316
34549
  return { success: false, error: `Unsupported provider: ${provider}` };
@@ -34840,7 +35073,7 @@ function resolveRefreshOutput(record, opts) {
34840
35073
  return record.outputPath ? resolve24(record.outputPath) : void 0;
34841
35074
  }
34842
35075
  function liveSupport(record) {
34843
- if (record.jobType === "generate-video" && (record.provider === "runway" || record.provider === "kling")) {
35076
+ if (record.jobType === "generate-video" && (record.provider === "grok" || record.provider === "runway" || record.provider === "kling" || record.provider === "veo")) {
34844
35077
  return { supported: true };
34845
35078
  }
34846
35079
  if (record.jobType === "generate-music" && record.provider === "replicate") {
@@ -35222,7 +35455,9 @@ function isActiveStatus(status) {
35222
35455
  }
35223
35456
  function providerStatusCommand(record) {
35224
35457
  if (record.jobType === "generate-video") {
35225
- if (record.provider !== "runway" && record.provider !== "kling") return void 0;
35458
+ if (record.provider !== "grok" && record.provider !== "runway" && record.provider !== "kling" && record.provider !== "veo") {
35459
+ return void 0;
35460
+ }
35226
35461
  const type = record.provider === "kling" && record.providerTaskType ? ` --type ${record.providerTaskType}` : "";
35227
35462
  return `vibe generate video-status ${record.providerTaskId} -p ${record.provider}${type} --json`;
35228
35463
  }
@@ -35768,13 +36003,7 @@ async function executeThumbnailBestFrame(options) {
35768
36003
  await gemini.initialize({ apiKey: googleKey });
35769
36004
  const videoData = await readFile17(videoPath);
35770
36005
  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>"}';
35771
- const modelMap = {
35772
- flash: "gemini-3-flash-preview",
35773
- latest: "gemini-2.5-flash",
35774
- "flash-2.5": "gemini-2.5-flash",
35775
- pro: "gemini-2.5-pro"
35776
- };
35777
- const modelId = modelMap[model] || "gemini-3-flash-preview";
36006
+ const modelId = resolveGeminiTextModel(model);
35778
36007
  const result = await gemini.analyzeVideo(videoData, analysisPrompt, {
35779
36008
  model: modelId,
35780
36009
  fps: 1
@@ -35822,7 +36051,7 @@ import { resolve as resolve27, dirname as dirname8, basename as basename5, extna
35822
36051
  import { existsSync as existsSync17 } from "node:fs";
35823
36052
  import { writeFile as writeFile25, mkdir as mkdir12 } from "node:fs/promises";
35824
36053
  function registerThumbnailCommand(parent) {
35825
- 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) => {
36054
+ 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) => {
35826
36055
  const startedAt = Date.now();
35827
36056
  try {
35828
36057
  if (description) rejectControlChars(description);
@@ -35988,7 +36217,10 @@ function getStatusColor(status) {
35988
36217
  }
35989
36218
  }
35990
36219
  function registerVideoStatusCommand(parent) {
35991
- 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) => {
36220
+ 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(
36221
+ "-k, --api-key <key>",
36222
+ "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)"
36223
+ ).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) => {
35992
36224
  const startedAt = Date.now();
35993
36225
  try {
35994
36226
  const provider = (options.provider || "grok").toLowerCase();
@@ -36048,18 +36280,12 @@ function registerVideoStatusCommand(parent) {
36048
36280
  downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
36049
36281
  } catch (err) {
36050
36282
  downloadSpinner.fail(
36051
- source_default.red(
36052
- `Failed to download video: ${err instanceof Error ? err.message : err}`
36053
- )
36283
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
36054
36284
  );
36055
36285
  }
36056
36286
  }
36057
36287
  } else if (provider === "runway") {
36058
- const apiKey = await requireApiKey(
36059
- "RUNWAY_API_SECRET",
36060
- "Runway",
36061
- options.apiKey
36062
- );
36288
+ const apiKey = await requireApiKey("RUNWAY_API_SECRET", "Runway", options.apiKey);
36063
36289
  const spinner2 = ora("Checking status...").start();
36064
36290
  const runway = new RunwayProvider();
36065
36291
  await runway.initialize({ apiKey });
@@ -36120,9 +36346,7 @@ function registerVideoStatusCommand(parent) {
36120
36346
  downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
36121
36347
  } catch (err) {
36122
36348
  downloadSpinner.fail(
36123
- source_default.red(
36124
- `Failed to download video: ${err instanceof Error ? err.message : err}`
36125
- )
36349
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
36126
36350
  );
36127
36351
  }
36128
36352
  }
@@ -36188,15 +36412,77 @@ function registerVideoStatusCommand(parent) {
36188
36412
  downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
36189
36413
  } catch (err) {
36190
36414
  downloadSpinner.fail(
36191
- source_default.red(
36192
- `Failed to download video: ${err instanceof Error ? err.message : err}`
36193
- )
36415
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
36416
+ );
36417
+ }
36418
+ }
36419
+ } else if (provider === "veo") {
36420
+ const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
36421
+ const spinner2 = ora("Checking status...").start();
36422
+ const gemini = new GeminiProvider();
36423
+ await gemini.initialize({ apiKey });
36424
+ let result = await gemini.getGenerationStatus(taskId);
36425
+ if (options.wait && result.status !== "completed" && result.status !== "failed" && result.status !== "cancelled") {
36426
+ spinner2.text = "Waiting for completion...";
36427
+ result = await gemini.waitForVideoCompletion(taskId, (status) => {
36428
+ spinner2.text = status.progress !== void 0 ? `Generating... ${status.progress}%` : `Generating... ${status.status}`;
36429
+ });
36430
+ }
36431
+ spinner2.stop();
36432
+ if (isJsonMode()) {
36433
+ let outputPath;
36434
+ if (options.output && result.videoUrl) {
36435
+ const buffer = await downloadVideo(result.videoUrl, apiKey);
36436
+ outputPath = resolve28(process.cwd(), options.output);
36437
+ await writeFile26(outputPath, buffer);
36438
+ }
36439
+ outputSuccess({
36440
+ command: "generate video-status",
36441
+ startedAt,
36442
+ data: {
36443
+ taskId,
36444
+ provider: "veo",
36445
+ status: result.status,
36446
+ videoUrl: result.videoUrl,
36447
+ progress: result.progress,
36448
+ error: result.error,
36449
+ outputPath
36450
+ }
36451
+ });
36452
+ return;
36453
+ }
36454
+ console.log();
36455
+ console.log(source_default.bold.cyan("Generation Status"));
36456
+ console.log(source_default.dim("\u2500".repeat(60)));
36457
+ console.log(`Task ID: ${taskId}`);
36458
+ console.log(`Provider: Veo`);
36459
+ console.log(`Status: ${getStatusColor(result.status)}`);
36460
+ if (result.progress !== void 0) {
36461
+ console.log(`Progress: ${result.progress}%`);
36462
+ }
36463
+ if (result.videoUrl) {
36464
+ console.log(`Video URL: ${result.videoUrl}`);
36465
+ }
36466
+ if (result.error) {
36467
+ console.log(`Error: ${source_default.red(result.error)}`);
36468
+ }
36469
+ console.log();
36470
+ if (options.output && result.videoUrl) {
36471
+ const downloadSpinner = ora("Downloading video...").start();
36472
+ try {
36473
+ const buffer = await downloadVideo(result.videoUrl, apiKey);
36474
+ const outputPath = resolve28(process.cwd(), options.output);
36475
+ await writeFile26(outputPath, buffer);
36476
+ downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
36477
+ } catch (err) {
36478
+ downloadSpinner.fail(
36479
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
36194
36480
  );
36195
36481
  }
36196
36482
  }
36197
36483
  } else {
36198
36484
  exitWithError(
36199
- usageError(`Invalid provider: ${provider}. Use grok, runway, or kling.`)
36485
+ usageError(`Invalid provider: ${provider}. Use grok, runway, kling, or veo.`)
36200
36486
  );
36201
36487
  }
36202
36488
  } catch (error) {
@@ -37977,7 +38263,7 @@ function registerVideoCommand(parent) {
37977
38263
  "--seedance-model <model>",
37978
38264
  "Seedance variant: quality or fast (fal.ai only)",
37979
38265
  "quality"
37980
- ).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(
38266
+ ).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(
37981
38267
  "--ref-images <paths...>",
37982
38268
  "Reference images for Seedance reference-to-video or Veo character consistency"
37983
38269
  ).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(
@@ -38438,6 +38724,30 @@ Examples:
38438
38724
  exitWithError(apiError(message, true));
38439
38725
  }
38440
38726
  }
38727
+ let seedanceEndImage;
38728
+ const seedanceEndImagePath = options.endImage ?? options.lastFrame;
38729
+ if (seedanceEndImagePath && seedanceReferences.length === 0) {
38730
+ try {
38731
+ const absEndImagePath = resolve31(process.cwd(), seedanceEndImagePath);
38732
+ const endImageBuffer = await readFile18(absEndImagePath);
38733
+ const ext = seedanceEndImagePath.toLowerCase().split(".").pop();
38734
+ const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
38735
+ const uploadHost = await resolveUploadHost();
38736
+ spinner2.text = `Uploading end image via ${uploadHost.provider} for Seedance...`;
38737
+ const upload = await uploadHost.uploadImage(endImageBuffer, {
38738
+ filename: seedanceEndImagePath,
38739
+ mimeType
38740
+ });
38741
+ seedanceEndImage = upload.url;
38742
+ } catch (err) {
38743
+ spinner2.fail("End image upload failed");
38744
+ const message = err instanceof Error ? err.message : String(err);
38745
+ if (message.includes("IMGBB_API_KEY")) {
38746
+ exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
38747
+ }
38748
+ exitWithError(apiError(message, true));
38749
+ }
38750
+ }
38441
38751
  spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
38442
38752
  const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
38443
38753
  const falModel = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
@@ -38450,7 +38760,8 @@ Examples:
38450
38760
  negativePrompt: options.negative,
38451
38761
  model: falModel,
38452
38762
  resolution: options.resolution,
38453
- generateAudio: options.generateAudio
38763
+ generateAudio: options.generateAudio,
38764
+ lastFrame: seedanceEndImage
38454
38765
  });
38455
38766
  finalResult = result;
38456
38767
  }
@@ -38760,12 +39071,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
38760
39071
  const gemini = new GeminiProvider();
38761
39072
  await gemini.initialize({ apiKey: geminiApiKey });
38762
39073
  const videoBuffer = await readFile19(videoPath);
38763
- const modelMap = {
38764
- flash: "gemini-3-flash-preview",
38765
- "flash-2.5": "gemini-2.5-flash",
38766
- pro: "gemini-2.5-pro"
38767
- };
38768
- const modelId = options.model ? modelMap[options.model] || modelMap.flash : void 0;
39074
+ const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
38769
39075
  const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
38770
39076
 
38771
39077
  Detect these as silent/dead segments:
@@ -41671,7 +41977,7 @@ async function executeMotionOverlay(options) {
41671
41977
  };
41672
41978
  }
41673
41979
  function registerMotionOverlayCommand(parent) {
41674
- 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) => {
41980
+ 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) => {
41675
41981
  const startedAt = Date.now();
41676
41982
  try {
41677
41983
  if (options.output) validateOutputPath(options.output);
@@ -42761,12 +43067,7 @@ async function executeGeminiVideo(options) {
42761
43067
  return { success: false, error: "Google API key required. Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
42762
43068
  }
42763
43069
  const isYouTube = options.source.includes("youtube.com") || options.source.includes("youtu.be");
42764
- const modelMap = {
42765
- flash: "gemini-3-flash-preview",
42766
- "flash-2.5": "gemini-2.5-flash",
42767
- pro: "gemini-2.5-pro"
42768
- };
42769
- const modelId = modelMap[options.model || "flash"] || modelMap.flash;
43070
+ const modelId = resolveGeminiTextModel(options.model);
42770
43071
  let videoData;
42771
43072
  if (isYouTube) {
42772
43073
  videoData = options.source;
@@ -42827,12 +43128,7 @@ async function executeAnalyze(options) {
42827
43128
  error: "Cannot detect source type. Supported: images (.png/.jpg/.webp/.gif), videos (.mp4/.mov/.webm), YouTube URLs, image URLs."
42828
43129
  };
42829
43130
  }
42830
- const modelMap = {
42831
- flash: "gemini-3-flash-preview",
42832
- "flash-2.5": "gemini-2.5-flash",
42833
- pro: "gemini-2.5-pro"
42834
- };
42835
- const modelId = modelMap[options.model || "flash"] || modelMap.flash;
43131
+ const modelId = resolveGeminiTextModel(options.model);
42836
43132
  const gemini = new GeminiProvider();
42837
43133
  await gemini.initialize({ apiKey });
42838
43134
  if (isImage) {
@@ -42912,7 +43208,7 @@ async function executeAnalyze(options) {
42912
43208
  }
42913
43209
  }
42914
43210
  function registerAnalyzeCommands(aiCommand) {
42915
- 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) => {
43211
+ 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) => {
42916
43212
  try {
42917
43213
  if (options.apiKey) {
42918
43214
  process.env.GOOGLE_API_KEY = options.apiKey;
@@ -42955,7 +43251,7 @@ function registerAnalyzeCommands(aiCommand) {
42955
43251
  exitWithError(apiError(`Video analysis failed: ${error instanceof Error ? error.message : String(error)}`, true));
42956
43252
  }
42957
43253
  });
42958
- 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) => {
43254
+ 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) => {
42959
43255
  try {
42960
43256
  if (options.apiKey) {
42961
43257
  process.env.GOOGLE_API_KEY = options.apiKey;
@@ -43076,12 +43372,7 @@ ${content}`;
43076
43372
 
43077
43373
  Project context for beat-aware review:
43078
43374
  ${projectContext}` : "";
43079
- const modelMap = {
43080
- flash: "gemini-3-flash-preview",
43081
- "flash-2.5": "gemini-2.5-flash",
43082
- pro: "gemini-2.5-pro"
43083
- };
43084
- const modelId = modelMap[model] || modelMap.flash;
43375
+ const modelId = resolveGeminiTextModel(model);
43085
43376
  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.
43086
43377
 
43087
43378
  {
@@ -43188,7 +43479,7 @@ Score each category 1-10. Prefer beatIssues when you can map a problem to a stor
43188
43479
  return result;
43189
43480
  }
43190
43481
  function registerReviewCommand(aiCommand) {
43191
- 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) => {
43482
+ 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) => {
43192
43483
  const startedAt = Date.now();
43193
43484
  try {
43194
43485
  if (options.output) {
@@ -67614,7 +67905,7 @@ var init_edit = __esm({
67614
67905
  duration: external_exports.number().optional().describe("Overlay/render duration in seconds"),
67615
67906
  start: external_exports.number().optional().describe("Overlay start time in seconds"),
67616
67907
  style: external_exports.string().optional().describe("Style preset for generated overlays"),
67617
- model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-3.1-pro"]).optional().describe("LLM model for generated overlays"),
67908
+ model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-2.5-pro", "gemini-3.1-pro"]).optional().describe("LLM model for generated overlays"),
67618
67909
  understand: external_exports.enum(["auto", "off", "required"]).optional().describe("Analyze video before generated overlay (default: auto)"),
67619
67910
  understandingPrompt: external_exports.string().optional().describe("Custom prompt for video understanding"),
67620
67911
  position: external_exports.enum(["full", "center", "top-left", "top-right", "bottom-left", "bottom-right"]).optional().describe("Lottie overlay position"),
@@ -67990,7 +68281,7 @@ var init_inspect = __esm({
67990
68281
  outputPath: external_exports.string().optional().describe("Optional review report path. Defaults to <project>/review-report.json."),
67991
68282
  report: external_exports.boolean().optional().describe("Write review-report.json. Default true."),
67992
68283
  ai: external_exports.boolean().optional().describe("Also run Gemini video review and merge findings into review-report.json. Default false."),
67993
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant for ai review. Default flash."),
68284
+ 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."),
67994
68285
  dryRun: external_exports.boolean().optional().describe("Preview resolved inputs without probing video or calling Gemini.")
67995
68286
  }),
67996
68287
  async execute(args, ctx) {
@@ -68042,7 +68333,7 @@ var init_inspect = __esm({
68042
68333
  schema: external_exports.object({
68043
68334
  source: external_exports.string().describe("Path to image/video or YouTube URL"),
68044
68335
  prompt: external_exports.string().describe("Analysis prompt (e.g., 'Describe the scene', 'Count people')"),
68045
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant (default: flash)"),
68336
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
68046
68337
  fps: external_exports.number().optional().describe("Frames per second for video sampling (default: 1)"),
68047
68338
  start: external_exports.number().optional().describe("Start time in seconds for video analysis"),
68048
68339
  end: external_exports.number().optional().describe("End time in seconds for video analysis"),
@@ -68070,7 +68361,7 @@ var init_inspect = __esm({
68070
68361
  schema: external_exports.object({
68071
68362
  source: external_exports.string().describe("Path to video file"),
68072
68363
  prompt: external_exports.string().describe("Analysis prompt"),
68073
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant (default: flash)"),
68364
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
68074
68365
  fps: external_exports.number().optional().describe("Frames per second for sampling"),
68075
68366
  start: external_exports.number().optional().describe("Start time in seconds"),
68076
68367
  end: external_exports.number().optional().describe("End time in seconds"),
@@ -68100,7 +68391,7 @@ var init_inspect = __esm({
68100
68391
  storyboardPath: external_exports.string().optional().describe("Path to storyboard.json for intent comparison"),
68101
68392
  autoApply: external_exports.boolean().optional().describe("Automatically apply suggested fixes (default: false)"),
68102
68393
  verify: external_exports.boolean().optional().describe("Re-review after applying fixes (default: false)"),
68103
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant (default: flash)"),
68394
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
68104
68395
  outputPath: external_exports.string().optional().describe("Output path for fixed video")
68105
68396
  }),
68106
68397
  async execute(args) {
@@ -68192,7 +68483,7 @@ var init_generate2 = __esm({
68192
68483
  "Analyze the base video with Gemini before generating motion graphics: auto, off, or required (default: auto)"
68193
68484
  ),
68194
68485
  understandingPrompt: external_exports.string().optional().describe("Custom prompt for video understanding when --video is provided"),
68195
- model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-3.1-pro"]).optional().describe("LLM model for code generation (default: sonnet)"),
68486
+ model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-2.5-pro", "gemini-3.1-pro"]).optional().describe("LLM model for code generation (default: sonnet)"),
68196
68487
  output: external_exports.string().optional().describe("Output path (TSX if code-only, MP4 if rendered)")
68197
68488
  }),
68198
68489
  async execute(args) {
@@ -68457,6 +68748,7 @@ var init_generate2 = __esm({
68457
68748
  "Video provider (default: seedance when FAL_API_KEY is configured, otherwise first configured provider)"
68458
68749
  ),
68459
68750
  image: external_exports.string().optional().describe("Reference image path for image-to-video"),
68751
+ endImage: external_exports.string().optional().describe("Ending frame image path for Seedance image-to-video"),
68460
68752
  refImages: external_exports.array(external_exports.string()).optional().describe("Reference images for Seedance reference-to-video"),
68461
68753
  refVideos: external_exports.array(external_exports.string()).optional().describe("Reference videos for Seedance reference-to-video"),
68462
68754
  refAudio: external_exports.array(external_exports.string()).optional().describe("Reference audio files for Seedance reference-to-video"),
@@ -68464,7 +68756,7 @@ var init_generate2 = __esm({
68464
68756
  ratio: external_exports.string().optional().describe("Aspect ratio: 16:9, 9:16, 1:1 (default: 16:9)"),
68465
68757
  mode: external_exports.string().optional().describe("Kling mode: std or pro"),
68466
68758
  negative: external_exports.string().optional().describe("Negative prompt (Seedance/Kling/Veo)"),
68467
- resolution: external_exports.string().optional().describe("Resolution: 720p, 1080p, 4k (Veo only)"),
68759
+ resolution: external_exports.string().optional().describe("Resolution: 480p, 720p, 1080p, or 4k depending on provider"),
68468
68760
  veoModel: external_exports.string().optional().describe("Veo model: 3.0, 3.1, 3.1-fast"),
68469
68761
  runwayModel: external_exports.string().optional().describe("Runway model: gen4.5, gen4_turbo"),
68470
68762
  seedanceModel: external_exports.string().optional().describe("Seedance variant: quality or fast (fal.ai only)"),
@@ -68510,10 +68802,10 @@ var init_generate2 = __esm({
68510
68802
  name: "generate_video_status",
68511
68803
  category: "generate",
68512
68804
  cost: "free",
68513
- description: "Check video generation status for Runway or Kling tasks.",
68805
+ description: "Check video generation status for Grok, Runway, Kling, or Veo tasks.",
68514
68806
  schema: external_exports.object({
68515
68807
  taskId: external_exports.string().describe("Task ID from video generation"),
68516
- provider: external_exports.enum(["runway", "kling"]).optional().describe("Provider (default: runway)"),
68808
+ provider: external_exports.enum(["grok", "runway", "kling", "veo"]).optional().describe("Provider (default: runway)"),
68517
68809
  taskType: external_exports.enum(["text2video", "image2video"]).optional().describe("Kling task type (default: text2video)"),
68518
68810
  wait: external_exports.boolean().optional().describe("Wait for completion"),
68519
68811
  output: external_exports.string().optional().describe("Download video when complete")
@@ -71702,7 +71994,7 @@ init_source();
71702
71994
  // package.json
71703
71995
  var package_default = {
71704
71996
  name: "@vibeframe/cli",
71705
- version: "0.105.2",
71997
+ version: "0.106.2",
71706
71998
  description: "VibeFrame CLI - natural language video editing from the terminal",
71707
71999
  private: false,
71708
72000
  license: "MIT",
@@ -72801,7 +73093,14 @@ init_scene_inspect();
72801
73093
  init_render_inspect();
72802
73094
  import { readFile as readFile29, writeFile as writeFile36 } from "node:fs/promises";
72803
73095
  import { resolve as resolve42 } from "node:path";
72804
- var VALID_RENDER_AI_MODELS = ["flash", "flash-2.5", "pro"];
73096
+ var VALID_RENDER_AI_MODELS = [
73097
+ "flash",
73098
+ "latest",
73099
+ "flash-3",
73100
+ "flash-2.5",
73101
+ "pro",
73102
+ "pro-3.1"
73103
+ ];
72805
73104
  var inspectCommand = new Command("inspect").description("Inspect projects, renders, and media").addHelpText(
72806
73105
  "after",
72807
73106
  `
@@ -72866,7 +73165,7 @@ inspectCommand.command("project").description(
72866
73165
  }
72867
73166
  });
72868
73167
  applyTier(inspectCommand.commands[inspectCommand.commands.length - 1], "free");
72869
- inspectCommand.command("render").description("Inspect a rendered project video with local checks and optional Gemini review").argument("[project-dir]", "VibeFrame project directory", ".").option("--cheap", "Run local checks only (default; no AI/API calls)").option("--ai", "Also run Gemini video review and merge findings into review-report.json").option("-m, --model <model>", "Gemini model for --ai: flash (default), flash-2.5, pro", "flash").option("--beat <id>", "Inspect a render for one storyboard beat").option(
73168
+ inspectCommand.command("render").description("Inspect a rendered project video with local checks and optional Gemini review").argument("[project-dir]", "VibeFrame project directory", ".").option("--cheap", "Run local checks only (default; no AI/API calls)").option("--ai", "Also run Gemini video review and merge findings into review-report.json").option("-m, --model <model>", `Gemini model for --ai: ${GEMINI_TEXT_MODEL_HELP}`, "flash").option("--beat <id>", "Inspect a render for one storyboard beat").option(
72870
73169
  "--video <path>",
72871
73170
  "Rendered video path. Defaults to build-report outputPath or latest renders/* video."
72872
73171
  ).option(
@@ -72931,7 +73230,7 @@ inspectCommand.command("render").description("Inspect a rendered project video w
72931
73230
  }
72932
73231
  });
72933
73232
  applyTier(inspectCommand.commands[inspectCommand.commands.length - 1], "low");
72934
- inspectCommand.command("media").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("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
73233
+ inspectCommand.command("media").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("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
72935
73234
  const startedAt = Date.now();
72936
73235
  try {
72937
73236
  rejectControlChars(prompt3);
@@ -73034,14 +73333,17 @@ function printInspectSummary(title, status, score, issues, reportPath) {
73034
73333
  }
73035
73334
  }
73036
73335
  function parseRenderAiModel(value) {
73037
- if (VALID_RENDER_AI_MODELS.includes(value)) {
73336
+ if (isGeminiTextModelAlias(value) || value.startsWith("gemini-")) {
73038
73337
  return value;
73039
73338
  }
73040
73339
  exitWithError(
73041
- usageError(`Invalid --model: ${value}`, `Must be one of: ${VALID_RENDER_AI_MODELS.join(", ")}`)
73340
+ usageError(
73341
+ `Invalid --model: ${value}`,
73342
+ `Must be one of: ${VALID_RENDER_AI_MODELS.join(", ")}, or a full gemini-* model ID`
73343
+ )
73042
73344
  );
73043
73345
  }
73044
- inspectCommand.command("video", { hidden: true }).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("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
73346
+ inspectCommand.command("video", { hidden: true }).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("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
73045
73347
  const startedAt = Date.now();
73046
73348
  try {
73047
73349
  rejectControlChars(prompt3);