@vibeframe/mcp-server 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.
Files changed (2) hide show
  1. package/dist/index.js +381 -88
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -435908,6 +435908,40 @@ var init_whisper = __esm3({
435908
435908
  init_WhisperProvider();
435909
435909
  }
435910
435910
  });
435911
+ function isGeminiTextModelAlias(model) {
435912
+ return Object.prototype.hasOwnProperty.call(GEMINI_TEXT_MODEL_ALIASES, model);
435913
+ }
435914
+ function resolveGeminiTextModel(model) {
435915
+ const trimmed = model?.trim();
435916
+ if (!trimmed)
435917
+ return GEMINI_DEFAULT_TEXT_MODEL;
435918
+ if (isGeminiTextModelAlias(trimmed))
435919
+ return GEMINI_TEXT_MODEL_ALIASES[trimmed];
435920
+ if (trimmed.startsWith("gemini-"))
435921
+ return trimmed;
435922
+ return GEMINI_DEFAULT_TEXT_MODEL;
435923
+ }
435924
+ var GEMINI_DEFAULT_TEXT_MODEL;
435925
+ var GEMINI_AGENT_DEFAULT_TEXT_MODEL;
435926
+ var GEMINI_TEXT_MODEL_ALIASES;
435927
+ var GEMINI_TEXT_MODEL_HELP;
435928
+ var init_gemini_models = __esm3({
435929
+ "../ai-providers/dist/gemini/gemini-models.js"() {
435930
+ "use strict";
435931
+ GEMINI_DEFAULT_TEXT_MODEL = "gemini-3.5-flash";
435932
+ GEMINI_AGENT_DEFAULT_TEXT_MODEL = "gemini-2.5-flash";
435933
+ GEMINI_TEXT_MODEL_ALIASES = {
435934
+ flash: GEMINI_DEFAULT_TEXT_MODEL,
435935
+ latest: GEMINI_DEFAULT_TEXT_MODEL,
435936
+ "flash-3.5": GEMINI_DEFAULT_TEXT_MODEL,
435937
+ "flash-3": "gemini-3-flash-preview",
435938
+ "flash-2.5": "gemini-2.5-flash",
435939
+ pro: "gemini-2.5-pro",
435940
+ "pro-3.1": "gemini-3.1-pro-preview"
435941
+ };
435942
+ 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";
435943
+ }
435944
+ });
435911
435945
  function buildMotionSystemPrompt(width, height, fps, duration, durationInFrames, stylePreset) {
435912
435946
  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".
435913
435947
 
@@ -436060,7 +436094,7 @@ async function generateMotion(api, description, options = {}) {
436060
436094
  const duration = options.duration || 5;
436061
436095
  const durationInFrames = Math.round(duration * fps);
436062
436096
  const stylePreset = options.style || "modern and clean";
436063
- const modelId = options.model || "gemini-2.5-pro";
436097
+ const modelId = resolveGeminiTextModel(options.model);
436064
436098
  let systemPrompt = buildMotionSystemPrompt(width, height, fps, duration, durationInFrames, stylePreset);
436065
436099
  if (options.videoContext) {
436066
436100
  const sourceLabel = options.sourceType === "image" ? "IMAGE" : "VIDEO";
@@ -436141,7 +436175,7 @@ async function refineMotion(api, existingCode, instructions, options = {}) {
436141
436175
  const fps = options.fps || 30;
436142
436176
  const duration = options.duration || 5;
436143
436177
  const durationInFrames = Math.round(duration * fps);
436144
- const modelId = options.model || "gemini-2.5-pro";
436178
+ const modelId = resolveGeminiTextModel(options.model);
436145
436179
  const systemPrompt = `You are a world-class broadcast motion graphics designer. Modify the provided Remotion component based on instructions.
436146
436180
 
436147
436181
  CANVAS: ${width}\xD7${height}px | ${fps}fps | ${durationInFrames} frames (${duration}s)
@@ -436237,9 +436271,18 @@ var STYLE_USER_INSTRUCTIONS;
436237
436271
  var init_gemini_motion = __esm3({
436238
436272
  "../ai-providers/dist/gemini/gemini-motion.js"() {
436239
436273
  "use strict";
436274
+ init_gemini_models();
436240
436275
  GEMINI_MOTION_MODELS = {
436276
+ gemini: GEMINI_DEFAULT_TEXT_MODEL,
436277
+ flash: GEMINI_DEFAULT_TEXT_MODEL,
436278
+ latest: GEMINI_DEFAULT_TEXT_MODEL,
436279
+ "flash-3.5": GEMINI_DEFAULT_TEXT_MODEL,
436280
+ "flash-3": "gemini-3-flash-preview",
436281
+ "flash-2.5": "gemini-2.5-flash",
436241
436282
  pro: "gemini-2.5-pro",
436242
- "3.1-pro": "gemini-3.1-pro-preview"
436283
+ "2.5-pro": "gemini-2.5-pro",
436284
+ "3.1-pro": "gemini-3.1-pro-preview",
436285
+ "pro-3.1": "gemini-3.1-pro-preview"
436243
436286
  };
436244
436287
  STYLE_USER_INSTRUCTIONS = {
436245
436288
  minimal: "CENTER-ALIGNED pure typography. Thin lines + large title + subtitle. NO glass panels, NO bokeh, NO dark bars. Breathing pulse during hold.",
@@ -436358,7 +436401,7 @@ async function analyzeContent(api, content, targetDuration, options) {
436358
436401
  maxOutputTokens: 4096
436359
436402
  }
436360
436403
  };
436361
- const response = await fetch(`${api.baseUrl}/models/gemini-2.5-flash:generateContent?key=${api.apiKey}`, {
436404
+ const response = await fetch(`${api.baseUrl}/models/${GEMINI_DEFAULT_TEXT_MODEL}:generateContent?key=${api.apiKey}`, {
436362
436405
  method: "POST",
436363
436406
  headers: { "Content-Type": "application/json" },
436364
436407
  body: JSON.stringify(payload)
@@ -436391,6 +436434,7 @@ var init_gemini_storyboard = __esm3({
436391
436434
  "../ai-providers/dist/gemini/gemini-storyboard.js"() {
436392
436435
  "use strict";
436393
436436
  init_storyboard_prompt();
436437
+ init_gemini_models();
436394
436438
  }
436395
436439
  });
436396
436440
  async function fetchJson(label, url, init) {
@@ -436427,6 +436471,7 @@ var init_GeminiProvider = __esm3({
436427
436471
  "../ai-providers/dist/gemini/GeminiProvider.js"() {
436428
436472
  "use strict";
436429
436473
  init_gemini_motion();
436474
+ init_gemini_models();
436430
436475
  init_gemini_storyboard();
436431
436476
  init_http();
436432
436477
  MODEL_MAP = {
@@ -436935,7 +436980,7 @@ var init_GeminiProvider = __esm3({
436935
436980
  error: "Google API key not configured"
436936
436981
  };
436937
436982
  }
436938
- const modelId = options.model || "gemini-3-flash-preview";
436983
+ const modelId = resolveGeminiTextModel(options.model);
436939
436984
  try {
436940
436985
  let videoPart;
436941
436986
  if (typeof videoData === "string") {
@@ -437042,7 +437087,7 @@ var init_GeminiProvider = __esm3({
437042
437087
  error: "Google API key not configured"
437043
437088
  };
437044
437089
  }
437045
- const modelId = options.model || "gemini-3-flash-preview";
437090
+ const modelId = resolveGeminiTextModel(options.model);
437046
437091
  try {
437047
437092
  const buffers = Array.isArray(imageData) ? imageData : [imageData];
437048
437093
  const imageParts = buffers.map((buf) => ({
@@ -437144,7 +437189,7 @@ Example response:
437144
437189
  [{"type":"trim","description":"Trim intro to 3 seconds","clipIds":["clip-1"],"params":{"newDuration":3},"confidence":0.9}]
437145
437190
 
437146
437191
  Respond with ONLY the JSON array, no other text.`;
437147
- const response = await fetch(`${this.baseUrl}/models/gemini-2.5-flash:generateContent?key=${this.apiKey}`, {
437192
+ const response = await fetch(`${this.baseUrl}/models/${GEMINI_DEFAULT_TEXT_MODEL}:generateContent?key=${this.apiKey}`, {
437148
437193
  method: "POST",
437149
437194
  headers: {
437150
437195
  "Content-Type": "application/json"
@@ -437264,6 +437309,7 @@ var init_gemini = __esm3({
437264
437309
  "use strict";
437265
437310
  init_GeminiProvider();
437266
437311
  init_gemini_motion();
437312
+ init_gemini_models();
437267
437313
  init_define_provider();
437268
437314
  defineProvider({
437269
437315
  id: "gemini",
@@ -446526,6 +446572,10 @@ __export3(dist_exports22, {
446526
446572
  ClaudeProvider: () => ClaudeProvider,
446527
446573
  ElevenLabsProvider: () => ElevenLabsProvider,
446528
446574
  FalProvider: () => FalProvider,
446575
+ GEMINI_AGENT_DEFAULT_TEXT_MODEL: () => GEMINI_AGENT_DEFAULT_TEXT_MODEL,
446576
+ GEMINI_DEFAULT_TEXT_MODEL: () => GEMINI_DEFAULT_TEXT_MODEL,
446577
+ GEMINI_TEXT_MODEL_ALIASES: () => GEMINI_TEXT_MODEL_ALIASES,
446578
+ GEMINI_TEXT_MODEL_HELP: () => GEMINI_TEXT_MODEL_HELP,
446529
446579
  GeminiProvider: () => GeminiProvider,
446530
446580
  GrokProvider: () => GrokProvider,
446531
446581
  KNOWN_VOICES: () => KNOWN_VOICES,
@@ -446556,6 +446606,7 @@ __export3(dist_exports22, {
446556
446606
  getProvidersFor: () => getProvidersFor,
446557
446607
  getSetupProviders: () => getSetupProviders,
446558
446608
  grokProvider: () => grokProvider,
446609
+ isGeminiTextModelAlias: () => isGeminiTextModelAlias,
446559
446610
  klingProvider: () => klingProvider,
446560
446611
  kokoroProvider: () => kokoroProvider,
446561
446612
  ollamaProvider: () => ollamaProvider,
@@ -446563,6 +446614,7 @@ __export3(dist_exports22, {
446563
446614
  openaiProvider: () => openaiProvider,
446564
446615
  providerRegistry: () => providerRegistry,
446565
446616
  replicateProvider: () => replicateProvider,
446617
+ resolveGeminiTextModel: () => resolveGeminiTextModel,
446566
446618
  resolveVoiceId: () => resolveVoiceId,
446567
446619
  runwayProvider: () => runwayProvider,
446568
446620
  whisperProvider: () => whisperProvider
@@ -449708,6 +449760,7 @@ var CONFIG_PROVIDER_BY_COMPOSER;
449708
449760
  var init_compose_scenes_skills = __esm3({
449709
449761
  "src/commands/_shared/compose-scenes-skills.ts"() {
449710
449762
  "use strict";
449763
+ init_dist22();
449711
449764
  init_config();
449712
449765
  init_bundle();
449713
449766
  init_scene_lint();
@@ -449729,12 +449782,12 @@ var init_compose_scenes_skills = __esm3({
449729
449782
  medium: { model: "gpt-5", maxTokens: 6e3, costPerMTokIn: 1.25, costPerMTokOut: 10 },
449730
449783
  high: { model: "gpt-5", maxTokens: 8e3, costPerMTokIn: 1.25, costPerMTokOut: 10 }
449731
449784
  },
449732
- // Google Gemini 2.5 Pro~2.6× cheaper than Claude per spike, ~20 s/beat.
449733
- // Strong instruction-following on the Hyperframes skill bundle.
449785
+ // Google Gemini 3.5 Flashcurrent one-shot Gemini default for
449786
+ // analysis/composition. Keep agent loops on their explicit 2.5 Flash default.
449734
449787
  gemini: {
449735
- low: { model: "gemini-2.5-pro", maxTokens: 4e3, costPerMTokIn: 1.25, costPerMTokOut: 5 },
449736
- medium: { model: "gemini-2.5-pro", maxTokens: 6e3, costPerMTokIn: 1.25, costPerMTokOut: 5 },
449737
- high: { model: "gemini-2.5-pro", maxTokens: 8e3, costPerMTokIn: 1.25, costPerMTokOut: 5 }
449788
+ low: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 4e3, costPerMTokIn: 1.5, costPerMTokOut: 9 },
449789
+ medium: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 6e3, costPerMTokIn: 1.5, costPerMTokOut: 9 },
449790
+ high: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 8e3, costPerMTokIn: 1.5, costPerMTokOut: 9 }
449738
449791
  }
449739
449792
  };
449740
449793
  ComposeBeatError = class extends Error {
@@ -452835,12 +452888,37 @@ async function executeVideoStatus(options) {
452835
452888
  } = options;
452836
452889
  try {
452837
452890
  const envKeyMap = {
452891
+ grok: "XAI_API_KEY",
452838
452892
  runway: "RUNWAY_API_SECRET",
452839
- kling: "KLING_API_KEY"
452893
+ kling: "KLING_API_KEY",
452894
+ veo: "GOOGLE_API_KEY"
452840
452895
  };
452841
452896
  const key2 = apiKey || process.env[envKeyMap[provider] || ""];
452842
452897
  if (!key2) return { success: false, error: `${envKeyMap[provider]} required` };
452843
- if (provider === "runway") {
452898
+ if (provider === "grok") {
452899
+ const grok = new GrokProvider();
452900
+ await grok.initialize({ apiKey: key2 });
452901
+ let result = await grok.getGenerationStatus(taskId);
452902
+ if (wait && result.status !== "completed" && result.status !== "failed") {
452903
+ result = await grok.waitForCompletion(taskId, () => {
452904
+ });
452905
+ }
452906
+ let outputPath;
452907
+ if (output3 && result.videoUrl) {
452908
+ const buffer = await downloadVideo(result.videoUrl);
452909
+ outputPath = resolve19(process.cwd(), output3);
452910
+ await writeFile14(outputPath, buffer);
452911
+ }
452912
+ return {
452913
+ success: true,
452914
+ taskId,
452915
+ status: result.status,
452916
+ progress: result.progress,
452917
+ videoUrl: result.videoUrl,
452918
+ outputPath,
452919
+ error: result.error
452920
+ };
452921
+ } else if (provider === "runway") {
452844
452922
  const runway = new RunwayProvider();
452845
452923
  await runway.initialize({ apiKey: key2 });
452846
452924
  let result = await runway.getGenerationStatus(taskId);
@@ -452882,7 +452960,31 @@ async function executeVideoStatus(options) {
452882
452960
  status: result.status,
452883
452961
  videoUrl: result.videoUrl,
452884
452962
  duration: result.duration,
452885
- outputPath
452963
+ outputPath,
452964
+ error: result.error
452965
+ };
452966
+ } else if (provider === "veo") {
452967
+ const gemini = new GeminiProvider();
452968
+ await gemini.initialize({ apiKey: key2 });
452969
+ let result = await gemini.getGenerationStatus(taskId);
452970
+ if (wait && result.status !== "completed" && result.status !== "failed" && result.status !== "cancelled") {
452971
+ result = await gemini.waitForVideoCompletion(taskId, () => {
452972
+ });
452973
+ }
452974
+ let outputPath;
452975
+ if (output3 && result.videoUrl) {
452976
+ const buffer = await downloadVideo(result.videoUrl, key2);
452977
+ outputPath = resolve19(process.cwd(), output3);
452978
+ await writeFile14(outputPath, buffer);
452979
+ }
452980
+ return {
452981
+ success: true,
452982
+ taskId,
452983
+ status: result.status,
452984
+ progress: result.progress,
452985
+ videoUrl: result.videoUrl,
452986
+ outputPath,
452987
+ error: result.error
452886
452988
  };
452887
452989
  }
452888
452990
  return { success: false, error: `Unsupported provider: ${provider}` };
@@ -453449,7 +453551,7 @@ function resolveRefreshOutput(record, opts) {
453449
453551
  return record.outputPath ? resolve21(record.outputPath) : void 0;
453450
453552
  }
453451
453553
  function liveSupport(record) {
453452
- if (record.jobType === "generate-video" && (record.provider === "runway" || record.provider === "kling")) {
453554
+ if (record.jobType === "generate-video" && (record.provider === "grok" || record.provider === "runway" || record.provider === "kling" || record.provider === "veo")) {
453453
453555
  return { supported: true };
453454
453556
  }
453455
453557
  if (record.jobType === "generate-music" && record.provider === "replicate") {
@@ -453831,7 +453933,9 @@ function isActiveStatus(status) {
453831
453933
  }
453832
453934
  function providerStatusCommand(record) {
453833
453935
  if (record.jobType === "generate-video") {
453834
- if (record.provider !== "runway" && record.provider !== "kling") return void 0;
453936
+ if (record.provider !== "grok" && record.provider !== "runway" && record.provider !== "kling" && record.provider !== "veo") {
453937
+ return void 0;
453938
+ }
453835
453939
  const type = record.provider === "kling" && record.providerTaskType ? ` --type ${record.providerTaskType}` : "";
453836
453940
  return `vibe generate video-status ${record.providerTaskId} -p ${record.provider}${type} --json`;
453837
453941
  }
@@ -456309,12 +456413,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
456309
456413
  const gemini = new GeminiProvider();
456310
456414
  await gemini.initialize({ apiKey: geminiApiKey });
456311
456415
  const videoBuffer = await readFile18(videoPath);
456312
- const modelMap = {
456313
- flash: "gemini-3-flash-preview",
456314
- "flash-2.5": "gemini-2.5-flash",
456315
- pro: "gemini-2.5-pro"
456316
- };
456317
- const modelId = options.model ? modelMap[options.model] || modelMap.flash : void 0;
456416
+ const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
456318
456417
  const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
456319
456418
 
456320
456419
  Detect these as silent/dead segments:
@@ -459710,7 +459809,7 @@ Use this image analysis to inform the color palette, typography placement, and o
459710
459809
  6. Recommended animation timing and entrance/exit style
459711
459810
  7. Any moments where overlays should pause, fade, or stay minimal`;
459712
459811
  const analysisResult = await gemini.analyzeVideo(videoBuffer, analysisPrompt, {
459713
- model: "gemini-2.5-flash",
459812
+ model: GEMINI_DEFAULT_TEXT_MODEL,
459714
459813
  fps: 1,
459715
459814
  lowResolution: true
459716
459815
  });
@@ -459879,7 +459978,7 @@ function registerMotionCommand(aiCommand) {
459879
459978
  "auto"
459880
459979
  ).option("--understanding-prompt <text>", "Custom prompt for --video understanding").option("--from-tsx <path>", "Refine an existing TSX file instead of generating from scratch").option(
459881
459980
  "-m, --model <alias>",
459882
- "LLM model: sonnet (default), opus, gemini, gemini-3.1-pro",
459981
+ "LLM model: sonnet (default), opus, gemini, gemini-2.5-pro, gemini-3.1-pro",
459883
459982
  "sonnet"
459884
459983
  ).option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
459885
459984
  const startedAt = Date.now();
@@ -460023,7 +460122,8 @@ var init_ai_motion = __esm3({
460023
460122
  sonnet: { provider: "claude", modelId: "claude-sonnet-4-6" },
460024
460123
  opus: { provider: "claude", modelId: "claude-opus-4-7" },
460025
460124
  "opus-4-6": { provider: "claude", modelId: "claude-opus-4-6" },
460026
- gemini: { provider: "gemini", modelId: "gemini-2.5-pro" },
460125
+ gemini: { provider: "gemini", modelId: GEMINI_DEFAULT_TEXT_MODEL },
460126
+ "gemini-2.5-pro": { provider: "gemini", modelId: "gemini-2.5-pro" },
460027
460127
  "gemini-3.1-pro": { provider: "gemini", modelId: "gemini-3.1-pro-preview" }
460028
460128
  };
460029
460129
  }
@@ -460584,7 +460684,7 @@ async function executeMotionOverlay(options) {
460584
460684
  };
460585
460685
  }
460586
460686
  function registerMotionOverlayCommand(parent) {
460587
- 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) => {
460687
+ 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) => {
460588
460688
  const startedAt = Date.now();
460589
460689
  try {
460590
460690
  if (options.output) validateOutputPath(options.output);
@@ -461864,13 +461964,7 @@ async function executeThumbnailBestFrame(options) {
461864
461964
  await gemini.initialize({ apiKey: googleKey });
461865
461965
  const videoData = await readFile25(videoPath);
461866
461966
  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>"}';
461867
- const modelMap = {
461868
- flash: "gemini-3-flash-preview",
461869
- latest: "gemini-2.5-flash",
461870
- "flash-2.5": "gemini-2.5-flash",
461871
- pro: "gemini-2.5-pro"
461872
- };
461873
- const modelId = modelMap[model] || "gemini-3-flash-preview";
461967
+ const modelId = resolveGeminiTextModel(model);
461874
461968
  const result = await gemini.analyzeVideo(videoData, analysisPrompt, {
461875
461969
  model: modelId,
461876
461970
  fps: 1
@@ -461925,12 +462019,7 @@ async function executeGeminiVideo(options) {
461925
462019
  return { success: false, error: "Google API key required. Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
461926
462020
  }
461927
462021
  const isYouTube = options.source.includes("youtube.com") || options.source.includes("youtu.be");
461928
- const modelMap = {
461929
- flash: "gemini-3-flash-preview",
461930
- "flash-2.5": "gemini-2.5-flash",
461931
- pro: "gemini-2.5-pro"
461932
- };
461933
- const modelId = modelMap[options.model || "flash"] || modelMap.flash;
462022
+ const modelId = resolveGeminiTextModel(options.model);
461934
462023
  let videoData;
461935
462024
  if (isYouTube) {
461936
462025
  videoData = options.source;
@@ -461991,12 +462080,7 @@ async function executeAnalyze(options) {
461991
462080
  error: "Cannot detect source type. Supported: images (.png/.jpg/.webp/.gif), videos (.mp4/.mov/.webm), YouTube URLs, image URLs."
461992
462081
  };
461993
462082
  }
461994
- const modelMap = {
461995
- flash: "gemini-3-flash-preview",
461996
- "flash-2.5": "gemini-2.5-flash",
461997
- pro: "gemini-2.5-pro"
461998
- };
461999
- const modelId = modelMap[options.model || "flash"] || modelMap.flash;
462083
+ const modelId = resolveGeminiTextModel(options.model);
462000
462084
  const gemini = new GeminiProvider();
462001
462085
  await gemini.initialize({ apiKey });
462002
462086
  if (isImage) {
@@ -462076,7 +462160,7 @@ async function executeAnalyze(options) {
462076
462160
  }
462077
462161
  }
462078
462162
  function registerAnalyzeCommands(aiCommand) {
462079
- 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 (source3, prompt3, options) => {
462163
+ 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 (source3, prompt3, options) => {
462080
462164
  try {
462081
462165
  if (options.apiKey) {
462082
462166
  process.env.GOOGLE_API_KEY = options.apiKey;
@@ -462119,7 +462203,7 @@ function registerAnalyzeCommands(aiCommand) {
462119
462203
  exitWithError(apiError(`Video analysis failed: ${error instanceof Error ? error.message : String(error)}`, true));
462120
462204
  }
462121
462205
  });
462122
- 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 (source3, prompt3, options) => {
462206
+ 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 (source3, prompt3, options) => {
462123
462207
  try {
462124
462208
  if (options.apiKey) {
462125
462209
  process.env.GOOGLE_API_KEY = options.apiKey;
@@ -462235,12 +462319,7 @@ ${content}`;
462235
462319
 
462236
462320
  Project context for beat-aware review:
462237
462321
  ${projectContext}` : "";
462238
- const modelMap = {
462239
- flash: "gemini-3-flash-preview",
462240
- "flash-2.5": "gemini-2.5-flash",
462241
- pro: "gemini-2.5-pro"
462242
- };
462243
- const modelId = modelMap[model] || modelMap.flash;
462322
+ const modelId = resolveGeminiTextModel(model);
462244
462323
  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.
462245
462324
 
462246
462325
  {
@@ -462347,7 +462426,7 @@ Score each category 1-10. Prefer beatIssues when you can map a problem to a stor
462347
462426
  return result;
462348
462427
  }
462349
462428
  function registerReviewCommand(aiCommand) {
462350
- 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) => {
462429
+ 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) => {
462351
462430
  const startedAt = Date.now();
462352
462431
  try {
462353
462432
  if (options.output) {
@@ -463204,7 +463283,7 @@ var init_speech = __esm3({
463204
463283
  }
463205
463284
  });
463206
463285
  function registerThumbnailCommand(parent) {
463207
- 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) => {
463286
+ 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) => {
463208
463287
  const startedAt = Date.now();
463209
463288
  try {
463210
463289
  if (description) rejectControlChars(description);
@@ -463366,7 +463445,10 @@ function getStatusColor(status) {
463366
463445
  }
463367
463446
  }
463368
463447
  function registerVideoStatusCommand(parent) {
463369
- 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) => {
463448
+ 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(
463449
+ "-k, --api-key <key>",
463450
+ "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)"
463451
+ ).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) => {
463370
463452
  const startedAt = Date.now();
463371
463453
  try {
463372
463454
  const provider = (options.provider || "grok").toLowerCase();
@@ -463426,18 +463508,12 @@ function registerVideoStatusCommand(parent) {
463426
463508
  downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
463427
463509
  } catch (err) {
463428
463510
  downloadSpinner.fail(
463429
- source_default.red(
463430
- `Failed to download video: ${err instanceof Error ? err.message : err}`
463431
- )
463511
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
463432
463512
  );
463433
463513
  }
463434
463514
  }
463435
463515
  } else if (provider === "runway") {
463436
- const apiKey = await requireApiKey(
463437
- "RUNWAY_API_SECRET",
463438
- "Runway",
463439
- options.apiKey
463440
- );
463516
+ const apiKey = await requireApiKey("RUNWAY_API_SECRET", "Runway", options.apiKey);
463441
463517
  const spinner2 = ora("Checking status...").start();
463442
463518
  const runway = new RunwayProvider();
463443
463519
  await runway.initialize({ apiKey });
@@ -463498,9 +463574,7 @@ function registerVideoStatusCommand(parent) {
463498
463574
  downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
463499
463575
  } catch (err) {
463500
463576
  downloadSpinner.fail(
463501
- source_default.red(
463502
- `Failed to download video: ${err instanceof Error ? err.message : err}`
463503
- )
463577
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
463504
463578
  );
463505
463579
  }
463506
463580
  }
@@ -463566,15 +463640,77 @@ function registerVideoStatusCommand(parent) {
463566
463640
  downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
463567
463641
  } catch (err) {
463568
463642
  downloadSpinner.fail(
463569
- source_default.red(
463570
- `Failed to download video: ${err instanceof Error ? err.message : err}`
463571
- )
463643
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
463644
+ );
463645
+ }
463646
+ }
463647
+ } else if (provider === "veo") {
463648
+ const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
463649
+ const spinner2 = ora("Checking status...").start();
463650
+ const gemini = new GeminiProvider();
463651
+ await gemini.initialize({ apiKey });
463652
+ let result = await gemini.getGenerationStatus(taskId);
463653
+ if (options.wait && result.status !== "completed" && result.status !== "failed" && result.status !== "cancelled") {
463654
+ spinner2.text = "Waiting for completion...";
463655
+ result = await gemini.waitForVideoCompletion(taskId, (status) => {
463656
+ spinner2.text = status.progress !== void 0 ? `Generating... ${status.progress}%` : `Generating... ${status.status}`;
463657
+ });
463658
+ }
463659
+ spinner2.stop();
463660
+ if (isJsonMode()) {
463661
+ let outputPath;
463662
+ if (options.output && result.videoUrl) {
463663
+ const buffer = await downloadVideo(result.videoUrl, apiKey);
463664
+ outputPath = resolve47(process.cwd(), options.output);
463665
+ await writeFile35(outputPath, buffer);
463666
+ }
463667
+ outputSuccess({
463668
+ command: "generate video-status",
463669
+ startedAt,
463670
+ data: {
463671
+ taskId,
463672
+ provider: "veo",
463673
+ status: result.status,
463674
+ videoUrl: result.videoUrl,
463675
+ progress: result.progress,
463676
+ error: result.error,
463677
+ outputPath
463678
+ }
463679
+ });
463680
+ return;
463681
+ }
463682
+ console.log();
463683
+ console.log(source_default.bold.cyan("Generation Status"));
463684
+ console.log(source_default.dim("\u2500".repeat(60)));
463685
+ console.log(`Task ID: ${taskId}`);
463686
+ console.log(`Provider: Veo`);
463687
+ console.log(`Status: ${getStatusColor(result.status)}`);
463688
+ if (result.progress !== void 0) {
463689
+ console.log(`Progress: ${result.progress}%`);
463690
+ }
463691
+ if (result.videoUrl) {
463692
+ console.log(`Video URL: ${result.videoUrl}`);
463693
+ }
463694
+ if (result.error) {
463695
+ console.log(`Error: ${source_default.red(result.error)}`);
463696
+ }
463697
+ console.log();
463698
+ if (options.output && result.videoUrl) {
463699
+ const downloadSpinner = ora("Downloading video...").start();
463700
+ try {
463701
+ const buffer = await downloadVideo(result.videoUrl, apiKey);
463702
+ const outputPath = resolve47(process.cwd(), options.output);
463703
+ await writeFile35(outputPath, buffer);
463704
+ downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
463705
+ } catch (err) {
463706
+ downloadSpinner.fail(
463707
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
463572
463708
  );
463573
463709
  }
463574
463710
  }
463575
463711
  } else {
463576
463712
  exitWithError(
463577
- usageError(`Invalid provider: ${provider}. Use grok, runway, or kling.`)
463713
+ usageError(`Invalid provider: ${provider}. Use grok, runway, kling, or veo.`)
463578
463714
  );
463579
463715
  }
463580
463716
  } catch (error) {
@@ -465404,7 +465540,7 @@ function registerVideoCommand(parent) {
465404
465540
  "--seedance-model <model>",
465405
465541
  "Seedance variant: quality or fast (fal.ai only)",
465406
465542
  "quality"
465407
- ).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(
465543
+ ).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(
465408
465544
  "--ref-images <paths...>",
465409
465545
  "Reference images for Seedance reference-to-video or Veo character consistency"
465410
465546
  ).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(
@@ -465865,6 +466001,30 @@ Examples:
465865
466001
  exitWithError(apiError(message, true));
465866
466002
  }
465867
466003
  }
466004
+ let seedanceEndImage;
466005
+ const seedanceEndImagePath = options.endImage ?? options.lastFrame;
466006
+ if (seedanceEndImagePath && seedanceReferences.length === 0) {
466007
+ try {
466008
+ const absEndImagePath = resolve50(process.cwd(), seedanceEndImagePath);
466009
+ const endImageBuffer = await readFile32(absEndImagePath);
466010
+ const ext = seedanceEndImagePath.toLowerCase().split(".").pop();
466011
+ const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
466012
+ const uploadHost = await resolveUploadHost();
466013
+ spinner2.text = `Uploading end image via ${uploadHost.provider} for Seedance...`;
466014
+ const upload = await uploadHost.uploadImage(endImageBuffer, {
466015
+ filename: seedanceEndImagePath,
466016
+ mimeType
466017
+ });
466018
+ seedanceEndImage = upload.url;
466019
+ } catch (err) {
466020
+ spinner2.fail("End image upload failed");
466021
+ const message = err instanceof Error ? err.message : String(err);
466022
+ if (message.includes("IMGBB_API_KEY")) {
466023
+ exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
466024
+ }
466025
+ exitWithError(apiError(message, true));
466026
+ }
466027
+ }
465868
466028
  spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
465869
466029
  const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
465870
466030
  const falModel = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
@@ -465877,7 +466037,8 @@ Examples:
465877
466037
  negativePrompt: options.negative,
465878
466038
  model: falModel,
465879
466039
  resolution: options.resolution,
465880
- generateAudio: options.generateAudio
466040
+ generateAudio: options.generateAudio,
466041
+ lastFrame: seedanceEndImage
465881
466042
  });
465882
466043
  finalResult = result;
465883
466044
  }
@@ -471712,6 +471873,14 @@ var SCENE_PRESETS = [
471712
471873
  "kinetic-type",
471713
471874
  "product-shot"
471714
471875
  ];
471876
+ var LOTTIE_POSITIONS = [
471877
+ "full",
471878
+ "center",
471879
+ "top-left",
471880
+ "top-right",
471881
+ "bottom-left",
471882
+ "bottom-right"
471883
+ ];
471715
471884
  var GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
471716
471885
  function esc(text) {
471717
471886
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -471992,6 +472161,56 @@ function buildPreset(input3) {
471992
472161
  }
471993
472162
  }
471994
472163
  }
472164
+ var DOTLOTTIE_WC_VERSION = "0.9.12";
472165
+ var DOTLOTTIE_WEB_VERSION = "0.71.0";
472166
+ var DOTLOTTIE_WC_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-wc@${DOTLOTTIE_WC_VERSION}/dist/index.js`;
472167
+ var DOTLOTTIE_WASM_CDN = `https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web@${DOTLOTTIE_WEB_VERSION}/dist/dotlottie-player.wasm`;
472168
+ function lottieOverlayStyle(input3) {
472169
+ const pos = input3.position ?? "full";
472170
+ const rawScale = input3.scale ?? (pos === "full" ? 1 : 0.25);
472171
+ const scale = Math.max(0.01, Math.min(2, rawScale));
472172
+ const opacity = Math.max(0, Math.min(1, input3.opacity ?? 1));
472173
+ const base = [
472174
+ "position:absolute",
472175
+ "pointer-events:none",
472176
+ `opacity:${opacity}`
472177
+ ];
472178
+ if (pos === "full") {
472179
+ base.push("inset:0", "width:100%", "height:100%");
472180
+ } else {
472181
+ const pct = scale * 100;
472182
+ base.push(`width:${pct}%`, `height:${pct}%`);
472183
+ switch (pos) {
472184
+ case "center":
472185
+ base.push("top:50%", "left:50%", "transform:translate(-50%,-50%)");
472186
+ break;
472187
+ case "top-left":
472188
+ base.push("top:4%", "left:4%");
472189
+ break;
472190
+ case "top-right":
472191
+ base.push("top:4%", "right:4%");
472192
+ break;
472193
+ case "bottom-left":
472194
+ base.push("bottom:4%", "left:4%");
472195
+ break;
472196
+ case "bottom-right":
472197
+ base.push("bottom:4%", "right:4%");
472198
+ break;
472199
+ }
472200
+ }
472201
+ return base.join(";");
472202
+ }
472203
+ function buildLottieOverlay(input3, sceneId) {
472204
+ const loop = input3.loop ?? true ? " loop" : "";
472205
+ const style = lottieOverlayStyle(input3);
472206
+ const overlayId = `lottie-overlay-${sceneId}`;
472207
+ const markup = `<dotlottie-wc id="${overlayId}" src="${esc(input3.src)}" autoplay${loop} style="${style}"></dotlottie-wc>`;
472208
+ const script = `<script type="module">
472209
+ import { setWasmUrl } from "${DOTLOTTIE_WC_CDN}";
472210
+ setWasmUrl("${DOTLOTTIE_WASM_CDN}");
472211
+ </script>`;
472212
+ return { markup, script };
472213
+ }
471995
472214
  function emitSceneHtml(input3) {
471996
472215
  if (input3.duration <= 0) {
471997
472216
  throw new Error(`Scene duration must be > 0, got ${input3.duration}`);
@@ -472012,15 +472231,20 @@ function emitSceneHtml(input3) {
472012
472231
  data-volume="1"
472013
472232
  ></audio>
472014
472233
  ` : "";
472234
+ const lottieLayer = input3.lottie ? buildLottieOverlay(input3.lottie, id) : null;
472235
+ const lottieMarkup = lottieLayer ? `
472236
+ ${lottieLayer.markup}` : "";
472237
+ const lottieScript = lottieLayer ? `
472238
+ ${lottieLayer.script}` : "";
472015
472239
  return `<template id="scene-${id}-template">
472016
472240
  <div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input3.width}" data-height="${input3.height}">
472017
472241
  <style>
472018
472242
  ${parts.css}
472019
472243
  </style>
472020
472244
 
472021
- ${parts.body}
472245
+ ${parts.body}${lottieMarkup}
472022
472246
  ${audioBlock}
472023
- <script src="${GSAP_CDN}"></script>
472247
+ <script src="${GSAP_CDN}"></script>${lottieScript}
472024
472248
  <script>
472025
472249
  window.__timelines = window.__timelines || {};
472026
472250
  const tl = gsap.timeline({ paused: true });
@@ -472435,9 +472659,40 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
472435
472659
  ).option("--no-image", "Skip image generation even when --visuals is provided").option(
472436
472660
  "--no-transcribe",
472437
472661
  "Skip Whisper word-level transcribe step (no transcript-<id>.json emitted)"
472438
- ).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) => {
472662
+ ).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(
472663
+ "--lottie-position <position>",
472664
+ `Lottie position: ${LOTTIE_POSITIONS.join(", ")}`,
472665
+ "full"
472666
+ ).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) => {
472439
472667
  const startedAt = Date.now();
472440
472668
  if (options.style) options.style = validatePreset(options.style);
472669
+ if (options.lottiePosition) {
472670
+ if (!LOTTIE_POSITIONS.includes(options.lottiePosition)) {
472671
+ exitWithError(
472672
+ usageError(
472673
+ `Invalid --lottie-position "${options.lottiePosition}". Valid: ${LOTTIE_POSITIONS.join(", ")}`
472674
+ )
472675
+ );
472676
+ }
472677
+ }
472678
+ let lottieScale;
472679
+ if (options.lottieScale !== void 0) {
472680
+ lottieScale = Number(options.lottieScale);
472681
+ if (!Number.isFinite(lottieScale) || lottieScale < 0.01 || lottieScale > 2) {
472682
+ exitWithError(
472683
+ usageError(`Invalid --lottie-scale "${options.lottieScale}". Must be between 0.01 and 2.`)
472684
+ );
472685
+ }
472686
+ }
472687
+ let lottieOpacity = 1;
472688
+ if (options.lottieOpacity !== void 0) {
472689
+ lottieOpacity = Number(options.lottieOpacity);
472690
+ if (!Number.isFinite(lottieOpacity) || lottieOpacity < 0 || lottieOpacity > 1) {
472691
+ exitWithError(
472692
+ usageError(`Invalid --lottie-opacity "${options.lottieOpacity}". Must be between 0 and 1.`)
472693
+ );
472694
+ }
472695
+ }
472441
472696
  if (options.duration !== void 0) options.duration = validateDuration(options.duration);
472442
472697
  let tts;
472443
472698
  try {
@@ -472466,7 +472721,11 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
472466
472721
  imageProvider: options.imageProvider,
472467
472722
  tts,
472468
472723
  audio: options.audio,
472469
- // commander sets `audio: false` when --no-audio is passed
472724
+ lottie: options.lottie ?? null,
472725
+ lottiePosition: options.lottiePosition,
472726
+ lottieScale: lottieScale ?? null,
472727
+ lottieOpacity: options.lottieOpacity !== void 0 ? lottieOpacity : null,
472728
+ lottieLoop: !options.lottieNoLoop,
472470
472729
  image: options.image
472471
472730
  }
472472
472731
  }
@@ -472494,6 +472753,13 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
472494
472753
  skipTranscribe: options.transcribe === false,
472495
472754
  transcribeLanguage: options.transcribeLanguage,
472496
472755
  force: !!options.force,
472756
+ lottie: options.lottie ? {
472757
+ src: options.lottie,
472758
+ position: options.lottiePosition ?? "full",
472759
+ scale: lottieScale,
472760
+ opacity: options.lottieOpacity !== void 0 ? lottieOpacity : 1,
472761
+ loop: !options.lottieNoLoop
472762
+ } : void 0,
472497
472763
  onProgress: (msg) => {
472498
472764
  if (spinner2) spinner2.text = msg;
472499
472765
  }
@@ -472769,6 +473035,30 @@ async function executeSceneAdd(opts) {
472769
473035
  await writeFile10(imageAbsPath, buffer);
472770
473036
  }
472771
473037
  }
473038
+ let lottieRelPath;
473039
+ let lottieAbsPath;
473040
+ let lottieInput;
473041
+ if (opts.lottie) {
473042
+ const sourceAbs = resolve122(opts.lottie.src);
473043
+ if (!await pathExists2(sourceAbs)) {
473044
+ return errResult(`Lottie file not found: ${sourceAbs}`);
473045
+ }
473046
+ const ext = (sourceAbs.match(/\.([a-z0-9]+)$/i)?.[1] ?? "json").toLowerCase();
473047
+ if (ext !== "json" && ext !== "lottie") {
473048
+ return errResult(`Unsupported Lottie file extension: .${ext}. Use .json or .lottie.`);
473049
+ }
473050
+ lottieRelPath = `assets/lottie-${id}.${ext}`;
473051
+ lottieAbsPath = resolve122(projectDir, lottieRelPath);
473052
+ await mkdir5(dirname33(lottieAbsPath), { recursive: true });
473053
+ await copyFile6(sourceAbs, lottieAbsPath);
473054
+ lottieInput = {
473055
+ src: lottieRelPath,
473056
+ position: opts.lottie.position,
473057
+ scale: opts.lottie.scale,
473058
+ opacity: opts.lottie.opacity,
473059
+ loop: opts.lottie.loop
473060
+ };
473061
+ }
472772
473062
  const cfg = await loadVibeProjectConfig(projectDir);
472773
473063
  const fallback2 = cfg?.defaultSceneDuration ?? 5;
472774
473064
  const NARRATION_TAIL_BUFFER = 0.5;
@@ -472797,7 +473087,8 @@ async function executeSceneAdd(opts) {
472797
473087
  kicker: opts.kicker,
472798
473088
  imagePath: imageRelPath,
472799
473089
  audioPath: audioRelPath,
472800
- transcript: transcriptWords
473090
+ transcript: transcriptWords,
473091
+ lottie: lottieInput
472801
473092
  });
472802
473093
  await mkdir5(dirname33(scenePath), { recursive: true });
472803
473094
  await writeFile10(scenePath, sceneHtml, "utf-8");
@@ -472818,6 +473109,7 @@ async function executeSceneAdd(opts) {
472818
473109
  rootPath: relative5(process.cwd(), rootPath) || rootPath,
472819
473110
  audioPath: audioAbsPath ? relative5(process.cwd(), audioAbsPath) || audioAbsPath : void 0,
472820
473111
  imagePath: imageAbsPath ? relative5(process.cwd(), imageAbsPath) || imageAbsPath : void 0,
473112
+ lottiePath: lottieAbsPath ? relative5(process.cwd(), lottieAbsPath) || lottieAbsPath : void 0,
472821
473113
  transcriptPath: transcriptAbsPath ? relative5(process.cwd(), transcriptAbsPath) || transcriptAbsPath : void 0,
472822
473114
  transcriptWordCount
472823
473115
  };
@@ -474070,7 +474362,7 @@ var editMotionOverlayTool = defineTool({
474070
474362
  duration: external_exports.number().optional().describe("Overlay/render duration in seconds"),
474071
474363
  start: external_exports.number().optional().describe("Overlay start time in seconds"),
474072
474364
  style: external_exports.string().optional().describe("Style preset for generated overlays"),
474073
- model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-3.1-pro"]).optional().describe("LLM model for generated overlays"),
474365
+ model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-2.5-pro", "gemini-3.1-pro"]).optional().describe("LLM model for generated overlays"),
474074
474366
  understand: external_exports.enum(["auto", "off", "required"]).optional().describe("Analyze video before generated overlay (default: auto)"),
474075
474367
  understandingPrompt: external_exports.string().optional().describe("Custom prompt for video understanding"),
474076
474368
  position: external_exports.enum(["full", "center", "top-left", "top-right", "bottom-left", "bottom-right"]).optional().describe("Lottie overlay position"),
@@ -475785,7 +476077,7 @@ var inspectRenderTool = defineTool({
475785
476077
  outputPath: external_exports.string().optional().describe("Optional review report path. Defaults to <project>/review-report.json."),
475786
476078
  report: external_exports.boolean().optional().describe("Write review-report.json. Default true."),
475787
476079
  ai: external_exports.boolean().optional().describe("Also run Gemini video review and merge findings into review-report.json. Default false."),
475788
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant for ai review. Default flash."),
476080
+ 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."),
475789
476081
  dryRun: external_exports.boolean().optional().describe("Preview resolved inputs without probing video or calling Gemini.")
475790
476082
  }),
475791
476083
  async execute(args, ctx) {
@@ -475837,7 +476129,7 @@ var analyzeMediaTool = defineTool({
475837
476129
  schema: external_exports.object({
475838
476130
  source: external_exports.string().describe("Path to image/video or YouTube URL"),
475839
476131
  prompt: external_exports.string().describe("Analysis prompt (e.g., 'Describe the scene', 'Count people')"),
475840
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant (default: flash)"),
476132
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
475841
476133
  fps: external_exports.number().optional().describe("Frames per second for video sampling (default: 1)"),
475842
476134
  start: external_exports.number().optional().describe("Start time in seconds for video analysis"),
475843
476135
  end: external_exports.number().optional().describe("End time in seconds for video analysis"),
@@ -475865,7 +476157,7 @@ var analyzeVideoTool = defineTool({
475865
476157
  schema: external_exports.object({
475866
476158
  source: external_exports.string().describe("Path to video file"),
475867
476159
  prompt: external_exports.string().describe("Analysis prompt"),
475868
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant (default: flash)"),
476160
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
475869
476161
  fps: external_exports.number().optional().describe("Frames per second for sampling"),
475870
476162
  start: external_exports.number().optional().describe("Start time in seconds"),
475871
476163
  end: external_exports.number().optional().describe("End time in seconds"),
@@ -475895,7 +476187,7 @@ var analyzeReviewTool = defineTool({
475895
476187
  storyboardPath: external_exports.string().optional().describe("Path to storyboard.json for intent comparison"),
475896
476188
  autoApply: external_exports.boolean().optional().describe("Automatically apply suggested fixes (default: false)"),
475897
476189
  verify: external_exports.boolean().optional().describe("Re-review after applying fixes (default: false)"),
475898
- model: external_exports.enum(["flash", "flash-2.5", "pro"]).optional().describe("Gemini model variant (default: flash)"),
476190
+ model: external_exports.enum(["flash", "latest", "flash-3", "flash-2.5", "pro", "pro-3.1"]).optional().describe("Gemini model variant (default: flash)"),
475899
476191
  outputPath: external_exports.string().optional().describe("Output path for fixed video")
475900
476192
  }),
475901
476193
  async execute(args) {
@@ -475977,7 +476269,7 @@ var generateMotionTool = defineTool({
475977
476269
  "Analyze the base video with Gemini before generating motion graphics: auto, off, or required (default: auto)"
475978
476270
  ),
475979
476271
  understandingPrompt: external_exports.string().optional().describe("Custom prompt for video understanding when --video is provided"),
475980
- model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-3.1-pro"]).optional().describe("LLM model for code generation (default: sonnet)"),
476272
+ model: external_exports.enum(["sonnet", "opus", "gemini", "gemini-2.5-pro", "gemini-3.1-pro"]).optional().describe("LLM model for code generation (default: sonnet)"),
475981
476273
  output: external_exports.string().optional().describe("Output path (TSX if code-only, MP4 if rendered)")
475982
476274
  }),
475983
476275
  async execute(args) {
@@ -476242,6 +476534,7 @@ var generateVideoTool = defineTool({
476242
476534
  "Video provider (default: seedance when FAL_API_KEY is configured, otherwise first configured provider)"
476243
476535
  ),
476244
476536
  image: external_exports.string().optional().describe("Reference image path for image-to-video"),
476537
+ endImage: external_exports.string().optional().describe("Ending frame image path for Seedance image-to-video"),
476245
476538
  refImages: external_exports.array(external_exports.string()).optional().describe("Reference images for Seedance reference-to-video"),
476246
476539
  refVideos: external_exports.array(external_exports.string()).optional().describe("Reference videos for Seedance reference-to-video"),
476247
476540
  refAudio: external_exports.array(external_exports.string()).optional().describe("Reference audio files for Seedance reference-to-video"),
@@ -476249,7 +476542,7 @@ var generateVideoTool = defineTool({
476249
476542
  ratio: external_exports.string().optional().describe("Aspect ratio: 16:9, 9:16, 1:1 (default: 16:9)"),
476250
476543
  mode: external_exports.string().optional().describe("Kling mode: std or pro"),
476251
476544
  negative: external_exports.string().optional().describe("Negative prompt (Seedance/Kling/Veo)"),
476252
- resolution: external_exports.string().optional().describe("Resolution: 720p, 1080p, 4k (Veo only)"),
476545
+ resolution: external_exports.string().optional().describe("Resolution: 480p, 720p, 1080p, or 4k depending on provider"),
476253
476546
  veoModel: external_exports.string().optional().describe("Veo model: 3.0, 3.1, 3.1-fast"),
476254
476547
  runwayModel: external_exports.string().optional().describe("Runway model: gen4.5, gen4_turbo"),
476255
476548
  seedanceModel: external_exports.string().optional().describe("Seedance variant: quality or fast (fal.ai only)"),
@@ -476295,10 +476588,10 @@ var generateVideoStatusTool = defineTool({
476295
476588
  name: "generate_video_status",
476296
476589
  category: "generate",
476297
476590
  cost: "free",
476298
- description: "Check video generation status for Runway or Kling tasks.",
476591
+ description: "Check video generation status for Grok, Runway, Kling, or Veo tasks.",
476299
476592
  schema: external_exports.object({
476300
476593
  taskId: external_exports.string().describe("Task ID from video generation"),
476301
- provider: external_exports.enum(["runway", "kling"]).optional().describe("Provider (default: runway)"),
476594
+ provider: external_exports.enum(["grok", "runway", "kling", "veo"]).optional().describe("Provider (default: runway)"),
476302
476595
  taskType: external_exports.enum(["text2video", "image2video"]).optional().describe("Kling task type (default: text2video)"),
476303
476596
  wait: external_exports.boolean().optional().describe("Wait for completion"),
476304
476597
  output: external_exports.string().optional().describe("Download video when complete")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/mcp-server",
3
- "version": "0.105.2",
3
+ "version": "0.106.2",
4
4
  "description": "VibeFrame MCP Server - AI-native video editing via Model Context Protocol",
5
5
  "type": "module",
6
6
  "bin": {
@@ -57,8 +57,8 @@
57
57
  "tsx": "^4.21.0",
58
58
  "typescript": "^5.3.3",
59
59
  "vitest": "^1.2.2",
60
- "@vibeframe/cli": "0.105.2",
61
- "@vibeframe/core": "0.105.2"
60
+ "@vibeframe/core": "0.106.2",
61
+ "@vibeframe/cli": "0.106.2"
62
62
  },
63
63
  "engines": {
64
64
  "node": ">=20"