@vibeframe/cli 0.105.2 → 0.106.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
  }
@@ -35768,13 +35952,7 @@ async function executeThumbnailBestFrame(options) {
35768
35952
  await gemini.initialize({ apiKey: googleKey });
35769
35953
  const videoData = await readFile17(videoPath);
35770
35954
  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";
35955
+ const modelId = resolveGeminiTextModel(model);
35778
35956
  const result = await gemini.analyzeVideo(videoData, analysisPrompt, {
35779
35957
  model: modelId,
35780
35958
  fps: 1
@@ -35822,7 +36000,7 @@ import { resolve as resolve27, dirname as dirname8, basename as basename5, extna
35822
36000
  import { existsSync as existsSync17 } from "node:fs";
35823
36001
  import { writeFile as writeFile25, mkdir as mkdir12 } from "node:fs/promises";
35824
36002
  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) => {
36003
+ 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
36004
  const startedAt = Date.now();
35827
36005
  try {
35828
36006
  if (description) rejectControlChars(description);
@@ -37977,7 +38155,7 @@ function registerVideoCommand(parent) {
37977
38155
  "--seedance-model <model>",
37978
38156
  "Seedance variant: quality or fast (fal.ai only)",
37979
38157
  "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(
38158
+ ).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
38159
  "--ref-images <paths...>",
37982
38160
  "Reference images for Seedance reference-to-video or Veo character consistency"
37983
38161
  ).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 +38616,30 @@ Examples:
38438
38616
  exitWithError(apiError(message, true));
38439
38617
  }
38440
38618
  }
38619
+ let seedanceEndImage;
38620
+ const seedanceEndImagePath = options.endImage ?? options.lastFrame;
38621
+ if (seedanceEndImagePath && seedanceReferences.length === 0) {
38622
+ try {
38623
+ const absEndImagePath = resolve31(process.cwd(), seedanceEndImagePath);
38624
+ const endImageBuffer = await readFile18(absEndImagePath);
38625
+ const ext = seedanceEndImagePath.toLowerCase().split(".").pop();
38626
+ const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
38627
+ const uploadHost = await resolveUploadHost();
38628
+ spinner2.text = `Uploading end image via ${uploadHost.provider} for Seedance...`;
38629
+ const upload = await uploadHost.uploadImage(endImageBuffer, {
38630
+ filename: seedanceEndImagePath,
38631
+ mimeType
38632
+ });
38633
+ seedanceEndImage = upload.url;
38634
+ } catch (err) {
38635
+ spinner2.fail("End image upload failed");
38636
+ const message = err instanceof Error ? err.message : String(err);
38637
+ if (message.includes("IMGBB_API_KEY")) {
38638
+ exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
38639
+ }
38640
+ exitWithError(apiError(message, true));
38641
+ }
38642
+ }
38441
38643
  spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
38442
38644
  const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
38443
38645
  const falModel = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
@@ -38450,7 +38652,8 @@ Examples:
38450
38652
  negativePrompt: options.negative,
38451
38653
  model: falModel,
38452
38654
  resolution: options.resolution,
38453
- generateAudio: options.generateAudio
38655
+ generateAudio: options.generateAudio,
38656
+ lastFrame: seedanceEndImage
38454
38657
  });
38455
38658
  finalResult = result;
38456
38659
  }
@@ -38760,12 +38963,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
38760
38963
  const gemini = new GeminiProvider();
38761
38964
  await gemini.initialize({ apiKey: geminiApiKey });
38762
38965
  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;
38966
+ const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
38769
38967
  const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
38770
38968
 
38771
38969
  Detect these as silent/dead segments:
@@ -41671,7 +41869,7 @@ async function executeMotionOverlay(options) {
41671
41869
  };
41672
41870
  }
41673
41871
  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) => {
41872
+ 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
41873
  const startedAt = Date.now();
41676
41874
  try {
41677
41875
  if (options.output) validateOutputPath(options.output);
@@ -42761,12 +42959,7 @@ async function executeGeminiVideo(options) {
42761
42959
  return { success: false, error: "Google API key required. Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
42762
42960
  }
42763
42961
  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;
42962
+ const modelId = resolveGeminiTextModel(options.model);
42770
42963
  let videoData;
42771
42964
  if (isYouTube) {
42772
42965
  videoData = options.source;
@@ -42827,12 +43020,7 @@ async function executeAnalyze(options) {
42827
43020
  error: "Cannot detect source type. Supported: images (.png/.jpg/.webp/.gif), videos (.mp4/.mov/.webm), YouTube URLs, image URLs."
42828
43021
  };
42829
43022
  }
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;
43023
+ const modelId = resolveGeminiTextModel(options.model);
42836
43024
  const gemini = new GeminiProvider();
42837
43025
  await gemini.initialize({ apiKey });
42838
43026
  if (isImage) {
@@ -42912,7 +43100,7 @@ async function executeAnalyze(options) {
42912
43100
  }
42913
43101
  }
42914
43102
  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) => {
43103
+ 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
43104
  try {
42917
43105
  if (options.apiKey) {
42918
43106
  process.env.GOOGLE_API_KEY = options.apiKey;
@@ -42955,7 +43143,7 @@ function registerAnalyzeCommands(aiCommand) {
42955
43143
  exitWithError(apiError(`Video analysis failed: ${error instanceof Error ? error.message : String(error)}`, true));
42956
43144
  }
42957
43145
  });
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) => {
43146
+ 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
43147
  try {
42960
43148
  if (options.apiKey) {
42961
43149
  process.env.GOOGLE_API_KEY = options.apiKey;
@@ -43076,12 +43264,7 @@ ${content}`;
43076
43264
 
43077
43265
  Project context for beat-aware review:
43078
43266
  ${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;
43267
+ const modelId = resolveGeminiTextModel(model);
43085
43268
  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
43269
 
43087
43270
  {
@@ -43188,7 +43371,7 @@ Score each category 1-10. Prefer beatIssues when you can map a problem to a stor
43188
43371
  return result;
43189
43372
  }
43190
43373
  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) => {
43374
+ 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
43375
  const startedAt = Date.now();
43193
43376
  try {
43194
43377
  if (options.output) {
@@ -67614,7 +67797,7 @@ var init_edit = __esm({
67614
67797
  duration: external_exports.number().optional().describe("Overlay/render duration in seconds"),
67615
67798
  start: external_exports.number().optional().describe("Overlay start time in seconds"),
67616
67799
  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"),
67800
+ model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-2.5-pro", "gemini-3.1-pro"]).optional().describe("LLM model for generated overlays"),
67618
67801
  understand: external_exports.enum(["auto", "off", "required"]).optional().describe("Analyze video before generated overlay (default: auto)"),
67619
67802
  understandingPrompt: external_exports.string().optional().describe("Custom prompt for video understanding"),
67620
67803
  position: external_exports.enum(["full", "center", "top-left", "top-right", "bottom-left", "bottom-right"]).optional().describe("Lottie overlay position"),
@@ -67990,7 +68173,7 @@ var init_inspect = __esm({
67990
68173
  outputPath: external_exports.string().optional().describe("Optional review report path. Defaults to <project>/review-report.json."),
67991
68174
  report: external_exports.boolean().optional().describe("Write review-report.json. Default true."),
67992
68175
  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."),
68176
+ 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
68177
  dryRun: external_exports.boolean().optional().describe("Preview resolved inputs without probing video or calling Gemini.")
67995
68178
  }),
67996
68179
  async execute(args, ctx) {
@@ -68042,7 +68225,7 @@ var init_inspect = __esm({
68042
68225
  schema: external_exports.object({
68043
68226
  source: external_exports.string().describe("Path to image/video or YouTube URL"),
68044
68227
  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)"),
68228
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
68046
68229
  fps: external_exports.number().optional().describe("Frames per second for video sampling (default: 1)"),
68047
68230
  start: external_exports.number().optional().describe("Start time in seconds for video analysis"),
68048
68231
  end: external_exports.number().optional().describe("End time in seconds for video analysis"),
@@ -68070,7 +68253,7 @@ var init_inspect = __esm({
68070
68253
  schema: external_exports.object({
68071
68254
  source: external_exports.string().describe("Path to video file"),
68072
68255
  prompt: external_exports.string().describe("Analysis prompt"),
68073
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant (default: flash)"),
68256
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
68074
68257
  fps: external_exports.number().optional().describe("Frames per second for sampling"),
68075
68258
  start: external_exports.number().optional().describe("Start time in seconds"),
68076
68259
  end: external_exports.number().optional().describe("End time in seconds"),
@@ -68100,7 +68283,7 @@ var init_inspect = __esm({
68100
68283
  storyboardPath: external_exports.string().optional().describe("Path to storyboard.json for intent comparison"),
68101
68284
  autoApply: external_exports.boolean().optional().describe("Automatically apply suggested fixes (default: false)"),
68102
68285
  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)"),
68286
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
68104
68287
  outputPath: external_exports.string().optional().describe("Output path for fixed video")
68105
68288
  }),
68106
68289
  async execute(args) {
@@ -68192,7 +68375,7 @@ var init_generate2 = __esm({
68192
68375
  "Analyze the base video with Gemini before generating motion graphics: auto, off, or required (default: auto)"
68193
68376
  ),
68194
68377
  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)"),
68378
+ 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
68379
  output: external_exports.string().optional().describe("Output path (TSX if code-only, MP4 if rendered)")
68197
68380
  }),
68198
68381
  async execute(args) {
@@ -68457,6 +68640,7 @@ var init_generate2 = __esm({
68457
68640
  "Video provider (default: seedance when FAL_API_KEY is configured, otherwise first configured provider)"
68458
68641
  ),
68459
68642
  image: external_exports.string().optional().describe("Reference image path for image-to-video"),
68643
+ endImage: external_exports.string().optional().describe("Ending frame image path for Seedance image-to-video"),
68460
68644
  refImages: external_exports.array(external_exports.string()).optional().describe("Reference images for Seedance reference-to-video"),
68461
68645
  refVideos: external_exports.array(external_exports.string()).optional().describe("Reference videos for Seedance reference-to-video"),
68462
68646
  refAudio: external_exports.array(external_exports.string()).optional().describe("Reference audio files for Seedance reference-to-video"),
@@ -68464,7 +68648,7 @@ var init_generate2 = __esm({
68464
68648
  ratio: external_exports.string().optional().describe("Aspect ratio: 16:9, 9:16, 1:1 (default: 16:9)"),
68465
68649
  mode: external_exports.string().optional().describe("Kling mode: std or pro"),
68466
68650
  negative: external_exports.string().optional().describe("Negative prompt (Seedance/Kling/Veo)"),
68467
- resolution: external_exports.string().optional().describe("Resolution: 720p, 1080p, 4k (Veo only)"),
68651
+ resolution: external_exports.string().optional().describe("Resolution: 480p, 720p, 1080p, or 4k depending on provider"),
68468
68652
  veoModel: external_exports.string().optional().describe("Veo model: 3.0, 3.1, 3.1-fast"),
68469
68653
  runwayModel: external_exports.string().optional().describe("Runway model: gen4.5, gen4_turbo"),
68470
68654
  seedanceModel: external_exports.string().optional().describe("Seedance variant: quality or fast (fal.ai only)"),
@@ -71702,7 +71886,7 @@ init_source();
71702
71886
  // package.json
71703
71887
  var package_default = {
71704
71888
  name: "@vibeframe/cli",
71705
- version: "0.105.2",
71889
+ version: "0.106.0",
71706
71890
  description: "VibeFrame CLI - natural language video editing from the terminal",
71707
71891
  private: false,
71708
71892
  license: "MIT",
@@ -72801,7 +72985,14 @@ init_scene_inspect();
72801
72985
  init_render_inspect();
72802
72986
  import { readFile as readFile29, writeFile as writeFile36 } from "node:fs/promises";
72803
72987
  import { resolve as resolve42 } from "node:path";
72804
- var VALID_RENDER_AI_MODELS = ["flash", "flash-2.5", "pro"];
72988
+ var VALID_RENDER_AI_MODELS = [
72989
+ "flash",
72990
+ "latest",
72991
+ "flash-3",
72992
+ "flash-2.5",
72993
+ "pro",
72994
+ "pro-3.1"
72995
+ ];
72805
72996
  var inspectCommand = new Command("inspect").description("Inspect projects, renders, and media").addHelpText(
72806
72997
  "after",
72807
72998
  `
@@ -72866,7 +73057,7 @@ inspectCommand.command("project").description(
72866
73057
  }
72867
73058
  });
72868
73059
  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(
73060
+ 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
73061
  "--video <path>",
72871
73062
  "Rendered video path. Defaults to build-report outputPath or latest renders/* video."
72872
73063
  ).option(
@@ -72931,7 +73122,7 @@ inspectCommand.command("render").description("Inspect a rendered project video w
72931
73122
  }
72932
73123
  });
72933
73124
  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) => {
73125
+ 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
73126
  const startedAt = Date.now();
72936
73127
  try {
72937
73128
  rejectControlChars(prompt3);
@@ -73034,14 +73225,17 @@ function printInspectSummary(title, status, score, issues, reportPath) {
73034
73225
  }
73035
73226
  }
73036
73227
  function parseRenderAiModel(value) {
73037
- if (VALID_RENDER_AI_MODELS.includes(value)) {
73228
+ if (isGeminiTextModelAlias(value) || value.startsWith("gemini-")) {
73038
73229
  return value;
73039
73230
  }
73040
73231
  exitWithError(
73041
- usageError(`Invalid --model: ${value}`, `Must be one of: ${VALID_RENDER_AI_MODELS.join(", ")}`)
73232
+ usageError(
73233
+ `Invalid --model: ${value}`,
73234
+ `Must be one of: ${VALID_RENDER_AI_MODELS.join(", ")}, or a full gemini-* model ID`
73235
+ )
73042
73236
  );
73043
73237
  }
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) => {
73238
+ 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
73239
  const startedAt = Date.now();
73046
73240
  try {
73047
73241
  rejectControlChars(prompt3);