@vibeframe/mcp-server 0.113.2 → 0.113.3

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 +169 -12
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -471698,6 +471698,7 @@ var init_storyboard_edit = __esm({
471698
471698
  "narration",
471699
471699
  "backdrop",
471700
471700
  "video",
471701
+ "keyframe",
471701
471702
  "motion",
471702
471703
  "voice",
471703
471704
  "music",
@@ -471707,7 +471708,7 @@ var init_storyboard_edit = __esm({
471707
471708
  HEADING_RE2 = /^##\s+(.+?)\s*$/gm;
471708
471709
  LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
471709
471710
  ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
471710
- STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
471711
+ STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "keyframe", "motion", "voice", "music", "asset"]);
471711
471712
  MAX_RECOMMENDED_BEAT_SEC = 15;
471712
471713
  }
471713
471714
  });
@@ -472626,6 +472627,7 @@ var init_build_asset_reference = __esm({
472626
472627
  narration: [".mp3", ".wav", ".m4a"],
472627
472628
  backdrop: [".png", ".jpg", ".jpeg", ".webp"],
472628
472629
  character: [".png", ".jpg", ".jpeg", ".webp"],
472630
+ keyframe: [".png", ".jpg", ".jpeg", ".webp"],
472629
472631
  video: [".mp4", ".mov", ".webm"],
472630
472632
  music: [".mp3", ".wav", ".m4a"]
472631
472633
  };
@@ -472673,6 +472675,18 @@ function characterCacheDescriptor(opts) {
472673
472675
  ext: "png"
472674
472676
  });
472675
472677
  }
472678
+ function keyframeCacheDescriptor(opts) {
472679
+ return cacheAssetDescriptor("keyframe", {
472680
+ beatId: opts.beatId,
472681
+ cue: opts.cue,
472682
+ provider: opts.provider,
472683
+ quality: opts.quality,
472684
+ size: opts.size,
472685
+ ratio: opts.ratio,
472686
+ characters: opts.characters && opts.characters.length > 0 ? opts.characters : void 0,
472687
+ ext: "png"
472688
+ });
472689
+ }
472676
472690
  function imageRatioForSize(size) {
472677
472691
  switch (size) {
472678
472692
  case "1024x1024":
@@ -472694,6 +472708,8 @@ function videoCacheDescriptor(opts) {
472694
472708
  ratio: "16:9",
472695
472709
  // Omit when empty so existing (character-less) clips keep their cache key.
472696
472710
  characters: opts.characters && opts.characters.length > 0 ? opts.characters : void 0,
472711
+ // Omit when absent so existing (non-keyframe) clips keep their cache key.
472712
+ keyframe: opts.keyframe || void 0,
472697
472713
  ext: "mp4"
472698
472714
  });
472699
472715
  }
@@ -472844,6 +472860,7 @@ async function createBuildPlan(opts) {
472844
472860
  const backdropPrompt = stringOrUndefined3(cue.backdrop);
472845
472861
  const augmentedBackdropPrompt = backdropPrompt ? augmentBackdropPrompt(backdropPrompt) : null;
472846
472862
  const videoPrompt = stringOrUndefined3(cue.video);
472863
+ const keyframePrompt = stringOrUndefined3(cue.keyframe);
472847
472864
  const musicPrompt = stringOrUndefined3(cue.music);
472848
472865
  const genericReference = resolveGenericAssetReference(projectDir, cue.asset);
472849
472866
  const narrationReference = resolveTypedAssetReference(projectDir, "narration", cue.narration);
@@ -472852,7 +472869,7 @@ async function createBuildPlan(opts) {
472852
472869
  const musicReference = resolveTypedAssetReference(projectDir, "music", cue.music) ?? (!musicPrompt && genericReference?.kind === "music" ? genericReference : null);
472853
472870
  const narrationCue = narrationText ?? narrationReference?.raw;
472854
472871
  const backdropCue = backdropPrompt ?? backdropReference?.raw;
472855
- const videoCue = videoPrompt ?? videoReference?.raw;
472872
+ const videoCue = videoPrompt ?? videoReference?.raw ?? keyframePrompt;
472856
472873
  const musicCue = musicPrompt ?? musicReference?.raw;
472857
472874
  const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : resolved.narration.resolved === "openai" ? OPENAI_NARRATION_COST_USD : 0;
472858
472875
  const narrationCache = narrationText && !narrationReference ? narrationCacheDescriptor({
@@ -472870,11 +472887,12 @@ async function createBuildPlan(opts) {
472870
472887
  size: imageSize2,
472871
472888
  ratio: imageRatio
472872
472889
  }) : null;
472873
- const videoCache = videoPrompt && !videoReference ? videoCacheDescriptor({
472890
+ const videoCache = (videoPrompt || keyframePrompt) && !videoReference ? videoCacheDescriptor({
472874
472891
  beatId: beat.id,
472875
- cue: videoPrompt,
472892
+ cue: videoPrompt ?? keyframePrompt,
472876
472893
  provider: resolved.video.resolved,
472877
- duration: beat.duration
472894
+ duration: beat.duration,
472895
+ keyframe: keyframePrompt
472878
472896
  }) : null;
472879
472897
  const musicCache = musicPrompt && !musicReference ? musicCacheDescriptor({
472880
472898
  beatId: beat.id,
@@ -472969,6 +472987,12 @@ async function createBuildPlan(opts) {
472969
472987
  if (backdrop?.willGenerate && isSupportedBuildImageProvider(resolved.image.resolved))
472970
472988
  noteProviderNeed(resolved.image, "Backdrop generation");
472971
472989
  if (video?.willGenerate) noteProviderNeed(resolved.video, "Video generation");
472990
+ if (keyframePrompt && video?.willGenerate) {
472991
+ estimatedCostUsd += backdropCostUsd(imageQuality);
472992
+ providers.add(resolved.image.resolved);
472993
+ if (isSupportedBuildImageProvider(resolved.image.resolved))
472994
+ noteProviderNeed(resolved.image, "Keyframe generation");
472995
+ }
472972
472996
  if (music?.willGenerate) noteProviderNeed(resolved.music, "Music generation");
472973
472997
  if (!compositionExists) missing.add("compositions");
472974
472998
  if (includeCompose && !compositionExists && mode !== "agent") {
@@ -474379,6 +474403,8 @@ async function buildBeatPrimitives(beat, ctx) {
474379
474403
  videoMetadataPath: video.metadataPath,
474380
474404
  videoFreshness: video.freshness,
474381
474405
  videoSourcePath: video.sourcePath,
474406
+ keyframeStatus: video.keyframeStatus,
474407
+ keyframePath: video.keyframePath,
474382
474408
  musicStatus: music.status,
474383
474409
  musicPath: music.path,
474384
474410
  musicJobId: music.job?.id,
@@ -474823,6 +474849,112 @@ async function generateBackdropImage(prompt3, ctx, ratio) {
474823
474849
  });
474824
474850
  return imageBufferFromResult(result);
474825
474851
  }
474852
+ async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
474853
+ loadSceneBuildEnv(ctx.projectDir);
474854
+ const keyInfo = imageProviderKeyInfo(ctx.imageProvider);
474855
+ const apiKey = await getApiKeyFromConfig(keyInfo.configKey, { cwd: ctx.projectDir }) ?? process.env[keyInfo.envVar] ?? "";
474856
+ if (!apiKey) {
474857
+ return {
474858
+ success: false,
474859
+ error: `${keyInfo.envVar} not set \u2014 cannot generate keyframe with ${ctx.imageProvider}`
474860
+ };
474861
+ }
474862
+ if (ctx.imageProvider === "openai") {
474863
+ const provider2 = new OpenAIImageProvider();
474864
+ await provider2.initialize({ apiKey });
474865
+ const result2 = await provider2.editImage(sheetBuffers, prompt3);
474866
+ return imageBufferFromResult(result2);
474867
+ }
474868
+ if (ctx.imageProvider === "gemini") {
474869
+ const provider2 = new GeminiProvider();
474870
+ await provider2.initialize({ apiKey });
474871
+ const result2 = await provider2.editImage(sheetBuffers, prompt3, {
474872
+ model: "flash",
474873
+ aspectRatio: ratio
474874
+ });
474875
+ return imageBufferFromResult(result2);
474876
+ }
474877
+ const provider = new GrokProvider();
474878
+ await provider.initialize({ apiKey });
474879
+ const result = await provider.editImage(sheetBuffers[0], prompt3, { aspectRatio: ratio });
474880
+ return imageBufferFromResult(result);
474881
+ }
474882
+ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
474883
+ const rel = `assets/keyframe-${beat.id}.png`;
474884
+ const abs = join31(ctx.projectDir, rel);
474885
+ const size = ctx.imageSize ?? "1536x1024";
474886
+ const ratio = imageRatioForSize(size);
474887
+ const metadataOptions = { quality: ctx.imageQuality, size, ratio };
474888
+ const cache = keyframeCacheDescriptor({
474889
+ beatId: beat.id,
474890
+ cue: keyframeCue,
474891
+ provider: ctx.imageProvider,
474892
+ quality: ctx.imageQuality,
474893
+ size,
474894
+ ratio,
474895
+ characters: characterRefsRel
474896
+ });
474897
+ if (existsSync35(abs) && !ctx.force) {
474898
+ const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
474899
+ if (!metadata || isFreshCanonicalAsset({
474900
+ projectDir: ctx.projectDir,
474901
+ kind: "keyframe",
474902
+ beatId: beat.id,
474903
+ cue: keyframeCue,
474904
+ provider: ctx.imageProvider,
474905
+ options: metadataOptions,
474906
+ cacheKey: cache.key
474907
+ })) {
474908
+ return { status: "cached", rel };
474909
+ }
474910
+ }
474911
+ const cacheAbs = join31(ctx.projectDir, cache.path);
474912
+ if (existsSync35(cacheAbs) && !ctx.force) {
474913
+ await mkdir13(dirname18(abs), { recursive: true });
474914
+ await copyFile4(cacheAbs, abs);
474915
+ await writeAssetMetadata({
474916
+ projectDir: ctx.projectDir,
474917
+ kind: "keyframe",
474918
+ beatId: beat.id,
474919
+ cue: keyframeCue,
474920
+ provider: ctx.imageProvider,
474921
+ options: metadataOptions,
474922
+ cacheKey: cache.key,
474923
+ canonicalPath: rel,
474924
+ cachePath: cache.path
474925
+ });
474926
+ return { status: "cached", rel };
474927
+ }
474928
+ const sheetBuffers = [];
474929
+ for (const relPath of characterRefsRel) {
474930
+ const sheetAbs = join31(ctx.projectDir, relPath);
474931
+ if (existsSync35(sheetAbs)) sheetBuffers.push(await readFile18(sheetAbs));
474932
+ }
474933
+ const imgCtx = {
474934
+ projectDir: ctx.projectDir,
474935
+ imageProvider: ctx.imageProvider,
474936
+ imageSize: size,
474937
+ imageQuality: ctx.imageQuality
474938
+ };
474939
+ const generated = sheetBuffers.length > 0 ? await editKeyframeImage(keyframeCue, sheetBuffers, imgCtx, ratio) : await generateBackdropImage(keyframeCue, imgCtx, ratio);
474940
+ if (!generated.success) return { status: "failed", error: generated.error };
474941
+ await mkdir13(dirname18(abs), { recursive: true });
474942
+ await writeFile20(abs, generated.buffer);
474943
+ await mkdir13(dirname18(cacheAbs), { recursive: true });
474944
+ await writeFile20(cacheAbs, generated.buffer);
474945
+ await writeAssetMetadata({
474946
+ projectDir: ctx.projectDir,
474947
+ kind: "keyframe",
474948
+ beatId: beat.id,
474949
+ cue: keyframeCue,
474950
+ provider: ctx.imageProvider,
474951
+ options: metadataOptions,
474952
+ cacheKey: cache.key,
474953
+ canonicalPath: rel,
474954
+ cachePath: cache.path
474955
+ });
474956
+ return { status: "generated", rel };
474957
+ }
474826
474958
  async function imageBufferFromResult(result) {
474827
474959
  const image = result.images?.[0];
474828
474960
  if (!result.success || !image) {
@@ -474846,7 +474978,8 @@ function imageProviderKeyInfo(provider) {
474846
474978
  async function dispatchVideo(beat, ctx) {
474847
474979
  const reference = assetReferenceForBeat(ctx.projectDir, "video", beat);
474848
474980
  if (reference) return referencePrimitiveOutcome("video", beat, ctx, reference);
474849
- const prompt3 = stringOrUndefined4(beat.cues?.video);
474981
+ const keyframeCue = stringOrUndefined4(beat.cues?.keyframe);
474982
+ const prompt3 = stringOrUndefined4(beat.cues?.video) ?? keyframeCue;
474850
474983
  if (!prompt3) return { status: "no-cue" };
474851
474984
  const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p2) => typeof p2 === "string");
474852
474985
  const characterRefsAbs = characterRefsRel.map((p2) => join31(ctx.projectDir, p2));
@@ -474857,7 +474990,10 @@ async function dispatchVideo(beat, ctx) {
474857
474990
  cue: prompt3,
474858
474991
  provider: ctx.videoProvider,
474859
474992
  duration: normalizeVideoDuration(beat.duration),
474860
- characters: characterRefsRel
474993
+ // Keyframe mode keys the clip to the keyframe prompt; refs feed the keyframe,
474994
+ // not the clip directly.
474995
+ characters: keyframeCue ? void 0 : characterRefsRel,
474996
+ keyframe: keyframeCue
474861
474997
  });
474862
474998
  const metadataPath = assetMetadataPath("video", beat.id);
474863
474999
  if (existsSync35(abs) && !ctx.force) {
@@ -474910,6 +475046,19 @@ async function dispatchVideo(beat, ctx) {
474910
475046
  };
474911
475047
  }
474912
475048
  loadSceneBuildEnv(ctx.projectDir);
475049
+ let keyframeStatus;
475050
+ let keyframeRel;
475051
+ let keyframeImageAbs;
475052
+ if (keyframeCue) {
475053
+ const kf = await ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel);
475054
+ if (kf.status === "failed") {
475055
+ ctx.onProgress({ type: "video-failed", beatId: beat.id, error: kf.error });
475056
+ return { status: "failed", error: kf.error };
475057
+ }
475058
+ keyframeStatus = kf.status;
475059
+ keyframeRel = kf.rel;
475060
+ keyframeImageAbs = join31(ctx.projectDir, kf.rel);
475061
+ }
474913
475062
  const result = await executeVideoGenerate({
474914
475063
  prompt: prompt3,
474915
475064
  provider: ctx.videoProvider,
@@ -474917,7 +475066,10 @@ async function dispatchVideo(beat, ctx) {
474917
475066
  ratio: "16:9",
474918
475067
  output: abs,
474919
475068
  wait: false,
474920
- refImages: characterRefsAbs.length > 0 ? characterRefsAbs : void 0,
475069
+ // Keyframe mode → single init frame (image-to-video); otherwise character
475070
+ // reference-to-video.
475071
+ image: keyframeImageAbs,
475072
+ refImages: keyframeImageAbs ? void 0 : characterRefsAbs.length > 0 ? characterRefsAbs : void 0,
474921
475073
  apiKey: await apiKeyForVideoProvider(ctx.videoProvider, ctx.projectDir)
474922
475074
  });
474923
475075
  if (!result.success || !result.taskId) {
@@ -474952,14 +475104,16 @@ async function dispatchVideo(beat, ctx) {
474952
475104
  cachePath: cache.path,
474953
475105
  cacheKey: cache.key,
474954
475106
  metadataPath,
474955
- freshness: "fresh"
475107
+ freshness: "fresh",
475108
+ keyframeStatus,
475109
+ keyframePath: keyframeRel
474956
475110
  };
474957
475111
  }
474958
475112
  const job = await createAndWriteJobRecord({
474959
475113
  jobType: "generate-video",
474960
475114
  provider: result.provider ?? ctx.videoProvider,
474961
475115
  providerTaskId: result.taskId,
474962
- providerTaskType: "text2video",
475116
+ providerTaskType: keyframeImageAbs ? "image2video" : "text2video",
474963
475117
  status: "running",
474964
475118
  projectDir: ctx.projectDir,
474965
475119
  workingDirectory: ctx.projectDir,
@@ -474984,7 +475138,9 @@ async function dispatchVideo(beat, ctx) {
474984
475138
  provider: job.provider,
474985
475139
  cachePath: cache.path,
474986
475140
  cacheKey: cache.key,
474987
- metadataPath
475141
+ metadataPath,
475142
+ keyframeStatus,
475143
+ keyframePath: keyframeRel
474988
475144
  };
474989
475145
  }
474990
475146
  async function dispatchMusic(beat, ctx) {
@@ -475286,6 +475442,7 @@ function estimateActualAssetCost(outcomes, imageQuality) {
475286
475442
  for (const outcome of outcomes) {
475287
475443
  if (outcome.narrationStatus === "generated") cost += 0.05;
475288
475444
  if (outcome.backdropStatus === "generated") cost += backdropCostUsd(imageQuality);
475445
+ if (outcome.keyframeStatus === "generated") cost += backdropCostUsd(imageQuality);
475289
475446
  if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") {
475290
475447
  cost += outcome.videoProvider === "seedance" || outcome.videoProvider === "fal" ? estimateSeedanceVideoCostUsd({
475291
475448
  durationSec: normalizeVideoDuration(outcome.sceneDurationSec),
@@ -499542,7 +499699,7 @@ var server2 = new Server(
499542
499699
  {
499543
499700
  name: "vibeframe",
499544
499701
  // Stamped with the package version by build.js at bundle time.
499545
- version: "0.113.2"
499702
+ version: "0.113.3"
499546
499703
  },
499547
499704
  {
499548
499705
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/mcp-server",
3
- "version": "0.113.2",
3
+ "version": "0.113.3",
4
4
  "description": "VibeFrame MCP Server - AI-native video editing via Model Context Protocol",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,8 +60,8 @@
60
60
  "tsx": "^4.21.0",
61
61
  "typescript": "^5.3.3",
62
62
  "vitest": "^1.2.2",
63
- "@vibeframe/cli": "0.113.2",
64
- "@vibeframe/core": "0.113.2"
63
+ "@vibeframe/cli": "0.113.3",
64
+ "@vibeframe/core": "0.113.3"
65
65
  },
66
66
  "engines": {
67
67
  "node": ">=20"