@vibeframe/cli 0.104.2 → 0.105.1

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 +314 -51
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -17106,6 +17106,53 @@ function pickReferenceImageUrl(input) {
17106
17106
  return input;
17107
17107
  return void 0;
17108
17108
  }
17109
+ function isFalFileInput(value) {
17110
+ return value.startsWith("http://") || value.startsWith("https://") || value.startsWith("data:");
17111
+ }
17112
+ async function normaliseReferences(input, client) {
17113
+ if (!input)
17114
+ return [];
17115
+ const references = [];
17116
+ for (const ref of input) {
17117
+ if (!isFalFileInput(ref.url))
17118
+ continue;
17119
+ references.push({
17120
+ ...ref,
17121
+ url: ref.url.startsWith("data:") ? await uploadDataUri(client, ref.url) : ref.url
17122
+ });
17123
+ }
17124
+ return references;
17125
+ }
17126
+ async function uploadDataUri(client, dataUri) {
17127
+ const blob = dataUriToBlob(dataUri);
17128
+ return client.storage.upload(blob, {
17129
+ lifecycle: { expiresIn: "1h" }
17130
+ });
17131
+ }
17132
+ function dataUriToBlob(dataUri) {
17133
+ const match = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(dataUri);
17134
+ if (!match)
17135
+ throw new Error("Invalid data URI reference.");
17136
+ const mimeType = match[1] || "application/octet-stream";
17137
+ const isBase64 = Boolean(match[2]);
17138
+ const payload = match[3] ?? "";
17139
+ const buffer = isBase64 ? Buffer.from(payload, "base64") : Buffer.from(decodeURIComponent(payload), "utf-8");
17140
+ return new Blob([new Uint8Array(buffer)], { type: mimeType });
17141
+ }
17142
+ function groupReferences(references) {
17143
+ const image_urls = [];
17144
+ const video_urls = [];
17145
+ const audio_urls = [];
17146
+ for (const ref of references) {
17147
+ if (ref.kind === "image")
17148
+ image_urls.push(ref.url);
17149
+ if (ref.kind === "video")
17150
+ video_urls.push(ref.url);
17151
+ if (ref.kind === "audio")
17152
+ audio_urls.push(ref.url);
17153
+ }
17154
+ return { image_urls, video_urls, audio_urls };
17155
+ }
17109
17156
  function normaliseAspect(value) {
17110
17157
  if (!value)
17111
17158
  return "auto";
@@ -17128,7 +17175,7 @@ function normaliseDuration(value) {
17128
17175
  return "auto";
17129
17176
  return Math.max(4, Math.min(15, Math.round(value)));
17130
17177
  }
17131
- var import_client, ENDPOINT_TEXT_TO_VIDEO, ENDPOINT_IMAGE_TO_VIDEO, DEFAULT_VARIANT, VALID_RESOLUTIONS, VALID_ASPECTS, FalProvider, falProvider;
17178
+ var import_client, ENDPOINT_TEXT_TO_VIDEO, ENDPOINT_IMAGE_TO_VIDEO, ENDPOINT_REFERENCE_TO_VIDEO, DEFAULT_VARIANT, VALID_RESOLUTIONS, VALID_ASPECTS, FalProvider, falProvider;
17132
17179
  var init_FalProvider = __esm({
17133
17180
  "../ai-providers/dist/fal/FalProvider.js"() {
17134
17181
  "use strict";
@@ -17141,6 +17188,10 @@ var init_FalProvider = __esm({
17141
17188
  "seedance-2.0": "bytedance/seedance-2.0/image-to-video",
17142
17189
  "seedance-2.0-fast": "bytedance/seedance-2.0/fast/image-to-video"
17143
17190
  };
17191
+ ENDPOINT_REFERENCE_TO_VIDEO = {
17192
+ "seedance-2.0": "bytedance/seedance-2.0/reference-to-video",
17193
+ "seedance-2.0-fast": "bytedance/seedance-2.0/fast/reference-to-video"
17194
+ };
17144
17195
  DEFAULT_VARIANT = "seedance-2.0";
17145
17196
  VALID_RESOLUTIONS = ["480p", "720p", "1080p"];
17146
17197
  VALID_ASPECTS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "auto"];
@@ -17149,7 +17200,7 @@ var init_FalProvider = __esm({
17149
17200
  this.id = "seedance";
17150
17201
  this.name = "fal.ai (Seedance 2.0)";
17151
17202
  this.description = "fal.ai hosting ByteDance Seedance 2.0 \u2014 Artificial Analysis #2 on both text-to-video and image-to-video leaderboards";
17152
- this.capabilities = ["text-to-video", "image-to-video"];
17203
+ this.capabilities = ["text-to-video", "image-to-video", "reference-to-video"];
17153
17204
  this.iconUrl = "/icons/fal.svg";
17154
17205
  this.isAvailable = true;
17155
17206
  }
@@ -17188,9 +17239,11 @@ var init_FalProvider = __esm({
17188
17239
  error: `Unknown Seedance variant: ${variant}. Valid: ${Object.keys(ENDPOINT_TEXT_TO_VIDEO).join(", ")}.`
17189
17240
  };
17190
17241
  }
17191
- const referenceImage = pickReferenceImageUrl(options?.referenceImage);
17242
+ const references = await normaliseReferences(options?.references, this.client);
17243
+ const hasReferences = references.length > 0;
17244
+ const referenceImage = hasReferences ? void 0 : pickReferenceImageUrl(options?.referenceImage);
17192
17245
  const isImageToVideo = !!referenceImage;
17193
- const endpointId = isImageToVideo ? ENDPOINT_IMAGE_TO_VIDEO[variant] : ENDPOINT_TEXT_TO_VIDEO[variant];
17246
+ const endpointId = hasReferences ? ENDPOINT_REFERENCE_TO_VIDEO[variant] : isImageToVideo ? ENDPOINT_IMAGE_TO_VIDEO[variant] : ENDPOINT_TEXT_TO_VIDEO[variant];
17194
17247
  const aspect = normaliseAspect(options?.aspectRatio);
17195
17248
  const resolution = normaliseResolution(options?.resolution);
17196
17249
  const duration = normaliseDuration(options?.duration);
@@ -17200,13 +17253,26 @@ var init_FalProvider = __esm({
17200
17253
  resolution,
17201
17254
  duration
17202
17255
  };
17203
- if (referenceImage)
17256
+ if (hasReferences) {
17257
+ const grouped = groupReferences(references);
17258
+ if (grouped.image_urls.length > 0)
17259
+ input.image_urls = grouped.image_urls;
17260
+ if (grouped.video_urls.length > 0)
17261
+ input.video_urls = grouped.video_urls;
17262
+ if (grouped.audio_urls.length > 0)
17263
+ input.audio_urls = grouped.audio_urls;
17264
+ } else if (referenceImage) {
17204
17265
  input.image_url = referenceImage;
17266
+ }
17205
17267
  if (options?.negativePrompt)
17206
17268
  input.negative_prompt = options.negativePrompt;
17207
17269
  if (typeof options?.seed === "number")
17208
17270
  input.seed = options.seed;
17209
- if (options?.lastFrame)
17271
+ if (typeof options?.generateAudio === "boolean")
17272
+ input.generate_audio = options.generateAudio;
17273
+ if (options?.endUserId)
17274
+ input.end_user_id = options.endUserId;
17275
+ if (!hasReferences && options?.lastFrame)
17210
17276
  input.end_image_url = options.lastFrame;
17211
17277
  try {
17212
17278
  const out = await this.client.subscribe(endpointId, { input, logs: false });
@@ -17253,14 +17319,15 @@ var init_fal = __esm({
17253
17319
  // Review this alias at the 1.0 cut.
17254
17320
  aliases: ["fal"],
17255
17321
  models: ["seedance-2.0", "seedance-2.0-fast"],
17256
- capabilities: ["text-to-video", "image-to-video", "native-audio"],
17322
+ capabilities: ["text-to-video", "image-to-video", "reference-to-video", "native-audio"],
17257
17323
  apiKey: "fal",
17258
17324
  kinds: ["video"],
17259
17325
  resolverPriority: { video: 1 },
17260
17326
  commandsUnlocked: [
17261
17327
  "generate video -p seedance (Seedance 2.0 via fal.ai \u2014 default since v0.57)",
17262
17328
  "generate video -p seedance --seedance-model fast (lower-latency variant)",
17263
- "generate video -p seedance -i <image> (image-to-video)"
17329
+ "generate video -p seedance -i <image> (image-to-video)",
17330
+ "generate video -p seedance --ref-images <images...> (reference-to-video)"
17264
17331
  ]
17265
17332
  });
17266
17333
  }
@@ -26478,7 +26545,7 @@ YAML cues that drive narration, backdrop generation, and timing.
26478
26545
 
26479
26546
  \`\`\`yaml
26480
26547
  narration: "Introduce the promise in one crisp sentence."
26481
- backdrop: "Cinematic abstract technology backdrop, precise light, premium editorial feel"
26548
+ backdrop: "Topic-aligned editorial background plate, abstract visual system, no readable text, no logos, no consumer products, clean negative space for HTML overlays"
26482
26549
  duration: 4
26483
26550
  \`\`\`
26484
26551
 
@@ -26489,7 +26556,7 @@ screen and one spoken breath.
26489
26556
 
26490
26557
  \`\`\`yaml
26491
26558
  narration: "Show the mechanism or proof point that makes the promise believable."
26492
- backdrop: "Layered interface details, subtle motion trails, high-contrast product storytelling"
26559
+ backdrop: "Topic-aligned analytical background plate, abstract dashboard structure, no readable text, no product photos, no shoes, no unrelated objects"
26493
26560
  duration: 4
26494
26561
  \`\`\`
26495
26562
 
@@ -26500,7 +26567,7 @@ before/after.
26500
26567
 
26501
26568
  \`\`\`yaml
26502
26569
  narration: "Close with the action the viewer should remember."
26503
- backdrop: "Resolved hero frame, confident final composition, clean negative space"
26570
+ backdrop: "Resolved editorial background plate, confident final composition, clean negative space, no readable text, no logos, no unrelated products"
26504
26571
  duration: 4
26505
26572
  \`\`\`
26506
26573
 
@@ -26553,6 +26620,33 @@ consult this file \u2014 run the generate command directly.
26553
26620
  Browse named styles: \`vibe scene list-styles\`. Re-seed from one with
26554
26621
  \`vibe scene init . --visual-style "Swiss Pulse"\` (idempotent).
26555
26622
 
26623
+ ## Brief and local media
26624
+
26625
+ \`brief.md\` is raw intent, not a strict schema. It may contain messy notes,
26626
+ pasted research, links, product requirements, or a one-line idea. \`vibe init
26627
+ --from brief.md\` uses it only to seed \`STORYBOARD.md\` and \`DESIGN.md\`;
26628
+ after init, those two files are the working source of truth.
26629
+
26630
+ Use \`media/\` for user-provided source files: product photos, screenshots,
26631
+ logos, B-roll, recorded narration, or reference clips. Keep those inputs
26632
+ inside this project so build references stay project-relative. Do not put user
26633
+ media in \`references/\`; that directory is reserved for local composition
26634
+ rules installed by VibeFrame.
26635
+
26636
+ When a beat should reuse a local file, reference it from \`STORYBOARD.md\`
26637
+ with a project-relative path:
26638
+
26639
+ \`\`\`yaml
26640
+ backdrop: "media/product-shot.png" # existing still image
26641
+ video: "media/broll.mp4" # existing video/B-roll
26642
+ narration: "media/voice.wav" # existing recorded narration
26643
+ asset: "media/logo.png" # generic local asset reference
26644
+ \`\`\`
26645
+
26646
+ Use text cues when you want VibeFrame to generate an asset. Use path cues
26647
+ when you want VibeFrame to reuse a local file. Avoid absolute paths or parent
26648
+ directory references; copy files into \`media/\` first.
26649
+
26556
26650
  ## Provider keys and project scope
26557
26651
 
26558
26652
  Use VibeFrame CLI generation for project assets:
@@ -26600,9 +26694,11 @@ the framework-level minimum, not the cinematic craft layer.
26600
26694
 
26601
26695
  - \`DESIGN.md\` \u2014 visual identity contract (palette, type, motion, transitions)
26602
26696
  - \`STORYBOARD.md\` \u2014 per-beat narration/backdrop/duration cues for \`vibe build\`
26697
+ - \`media/\` \u2014 user-provided source files (photos, logos, clips, voice recordings)
26603
26698
  - \`index.html\` \u2014 root composition (timeline)
26604
26699
  - \`compositions/scene-*.html\` \u2014 per-scene HTML authored by you or the agent
26605
- - \`assets/\` \u2014 shared media (narration audio, images, video)
26700
+ - \`assets/\` \u2014 generated/canonical build media (narration audio, images, video)
26701
+ - \`references/\` \u2014 composition rule docs installed by VibeFrame, not user media
26606
26702
  - \`transcript.json\` \u2014 Whisper word-level transcript (if narration exists)
26607
26703
  - \`hyperframes.json\` \u2014 HF registry config (speak to both toolchains)
26608
26704
  - \`vibe.config.json\` \u2014 canonical VibeFrame config (providers, budget)
@@ -26633,7 +26729,11 @@ npx hyperframes render
26633
26729
  \`\`\`
26634
26730
  4. Videos use \`muted\` with a separate \`<audio>\` element for the audio track.
26635
26731
  5. Sub-compositions use \`data-composition-src="compositions/file.html"\`.
26636
- 6. Only deterministic logic \u2014 no \`Date.now()\`, \`Math.random()\`, or network fetches.
26732
+ 6. For render-stable text, do not apply continuous \`scale\`, \`x\`, \`y\`, or
26733
+ \`filter\` tweens to \`.scene-content\` or any ancestor containing live text.
26734
+ Animate background/media layers instead; text/cards should enter briefly and
26735
+ then hold still at their final CSS positions.
26736
+ 7. Only deterministic logic \u2014 no \`Date.now()\`, \`Math.random()\`, or network fetches.
26637
26737
 
26638
26738
  ## Linting \u2014 run after changes
26639
26739
 
@@ -26746,7 +26846,11 @@ async function scaffoldSceneProject(opts) {
26746
26846
  if (await pathExists(metaPath)) {
26747
26847
  skipped2.push(metaPath);
26748
26848
  } else {
26749
- await writeFile4(metaPath, JSON.stringify(buildHyperframesMeta(name, now), null, 2) + "\n", "utf-8");
26849
+ await writeFile4(
26850
+ metaPath,
26851
+ JSON.stringify(buildHyperframesMeta(name, now), null, 2) + "\n",
26852
+ "utf-8"
26853
+ );
26750
26854
  created.push(metaPath);
26751
26855
  }
26752
26856
  const rootPath = resolve7(dir, "index.html");
@@ -26761,11 +26865,7 @@ async function scaffoldSceneProject(opts) {
26761
26865
  if (await pathExists(vibeConfigJsonPath)) {
26762
26866
  skipped2.push(vibeConfigJsonPath);
26763
26867
  } else {
26764
- await writeFile4(
26765
- vibeConfigJsonPath,
26766
- projectConfigJson({ name, aspect }),
26767
- "utf-8"
26768
- );
26868
+ await writeFile4(vibeConfigJsonPath, projectConfigJson({ name, aspect }), "utf-8");
26769
26869
  created.push(vibeConfigJsonPath);
26770
26870
  }
26771
26871
  const vibePath = resolve7(dir, "vibe.project.yaml");
@@ -26804,11 +26904,7 @@ ${existing}`, "utf-8");
26804
26904
  if (await pathExists(designPath)) {
26805
26905
  skipped2.push(designPath);
26806
26906
  } else {
26807
- await writeFile4(
26808
- designPath,
26809
- buildDesignMd({ name, style: opts.visualStyle }),
26810
- "utf-8"
26811
- );
26907
+ await writeFile4(designPath, buildDesignMd({ name, style: opts.visualStyle }), "utf-8");
26812
26908
  created.push(designPath);
26813
26909
  }
26814
26910
  const storyboardPath2 = resolve7(dir, "STORYBOARD.md");
@@ -29061,13 +29157,18 @@ Requirements (non-negotiable):
29061
29157
  producer's seek lands past the timeline's natural end and visibility state
29062
29158
  goes stale \u2014 the hold phase renders BLACK. Anchor the timeline to the full
29063
29159
  beat duration via either:
29064
- 1. A subtle idle motion spanning 0\u2192duration on a parent element, e.g.
29065
- \`tl.fromTo(".scene-content", { scale: 1.0 }, { scale: 1.015, duration: <beat>, ease: "none" }, 0);\`
29160
+ 1. A subtle idle motion spanning 0\u2192duration on a background/media layer,
29161
+ e.g. \`tl.fromTo(".backdrop", { scale: 1.0 }, { scale: 1.015, duration: <beat>, ease: "none" }, 0);\`
29066
29162
  (Ken-Burns, breathing opacity, gradient drift \u2014 should be barely
29067
29163
  perceptible so it doesn't compete with entry/exit beats).
29068
29164
  2. OR an explicit \`tl.set(target, { ...natural state... }, <beat - 0.001>)\`
29069
29165
  anchor at the end.
29070
29166
  This is the #2 source of "text disappears mid-beat" bugs after \`.clip\` sizing.
29167
+ - Do not apply continuous \`scale\`, \`x\`, \`y\`, \`filter\`, or other transform
29168
+ tweens to \`.scene-content\` or any ancestor that contains live text/cards.
29169
+ Animate the backdrop/media plane instead; let text enter briefly, then hold
29170
+ still at its final CSS position. Continuous transforms on text ancestors can
29171
+ create subpixel shimmer in screenshot-captured renders.
29071
29172
  - Timed children inside the composition have \`class="clip"\` plus
29072
29173
  \`data-start\`, \`data-duration\`, \`data-track-index\`.
29073
29174
  - If \`assets/backdrop-${ctx.beat.id}.png\` exists, use that local file as the
@@ -29137,7 +29238,9 @@ Reference shape (verbatim \u2014 match this skeleton exactly, no DOCTYPE / html
29137
29238
  const tl = gsap.timeline({ paused: true });
29138
29239
  // Idle motion spanning full beat duration \u2014 required to keep timeline
29139
29240
  // length aligned with data-duration (otherwise hold phase goes black).
29140
- tl.fromTo(".scene-content", { scale: 1.0 }, { scale: 1.015, duration: <sec>, ease: "none" }, 0);
29241
+ // Keep continuous motion on the background/media layer so live text does
29242
+ // not shimmer from subpixel resampling.
29243
+ tl.fromTo(".backdrop", { scale: 1.0 }, { scale: 1.015, duration: <sec>, ease: "none" }, 0);
29141
29244
  // entry tweens
29142
29245
  window.__timelines["${compositionId}"] = tl;
29143
29246
  </script>
@@ -33809,6 +33912,9 @@ async function executeVideoGenerate(options) {
33809
33912
  prompt: prompt3,
33810
33913
  provider = "kling",
33811
33914
  image,
33915
+ refImages,
33916
+ refVideos,
33917
+ refAudio,
33812
33918
  duration = 5,
33813
33919
  ratio = "16:9",
33814
33920
  seed,
@@ -33817,6 +33923,7 @@ async function executeVideoGenerate(options) {
33817
33923
  resolution,
33818
33924
  veoModel = "3.1-fast",
33819
33925
  seedanceModel = "quality",
33926
+ generateAudio,
33820
33927
  output,
33821
33928
  wait = true,
33822
33929
  apiKey
@@ -33855,8 +33962,13 @@ async function executeVideoGenerate(options) {
33855
33962
  if (provider === "seedance" || provider === "fal") {
33856
33963
  const fal = new FalProvider();
33857
33964
  await fal.initialize({ apiKey: key });
33965
+ const references = await prepareSeedanceReferences({
33966
+ refImages,
33967
+ refVideos,
33968
+ refAudio
33969
+ });
33858
33970
  let falImage = referenceImage;
33859
- if (falImage && falImage.startsWith("data:")) {
33971
+ if (falImage && falImage.startsWith("data:") && references.length === 0) {
33860
33972
  const uploadHost = await resolveUploadHost();
33861
33973
  const upload = await uploadHost.uploadImage(referenceImageBuffer, {
33862
33974
  filename: image,
@@ -33867,11 +33979,14 @@ async function executeVideoGenerate(options) {
33867
33979
  const model = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
33868
33980
  const result = await fal.generateVideo(prompt3, {
33869
33981
  prompt: prompt3,
33870
- referenceImage: falImage,
33982
+ referenceImage: references.length > 0 ? void 0 : falImage,
33983
+ references: references.length > 0 ? references : void 0,
33871
33984
  duration,
33872
33985
  aspectRatio: ratio,
33873
33986
  negativePrompt: negative,
33874
- model
33987
+ model,
33988
+ resolution,
33989
+ generateAudio
33875
33990
  });
33876
33991
  if (result.status === "failed")
33877
33992
  return { success: false, error: result.error || "Seedance generation failed" };
@@ -34048,6 +34163,54 @@ async function executeVideoGenerate(options) {
34048
34163
  };
34049
34164
  }
34050
34165
  }
34166
+ async function prepareSeedanceReferences(opts) {
34167
+ const references = [];
34168
+ for (const sourcePath of opts.refImages ?? []) {
34169
+ references.push({
34170
+ kind: "image",
34171
+ url: await fileInputToUrlOrDataUri(sourcePath, "image/png"),
34172
+ sourcePath
34173
+ });
34174
+ }
34175
+ for (const sourcePath of opts.refVideos ?? []) {
34176
+ references.push({
34177
+ kind: "video",
34178
+ url: await fileInputToUrlOrDataUri(sourcePath, "video/mp4"),
34179
+ sourcePath
34180
+ });
34181
+ }
34182
+ for (const sourcePath of opts.refAudio ?? []) {
34183
+ references.push({
34184
+ kind: "audio",
34185
+ url: await fileInputToUrlOrDataUri(sourcePath, "audio/mpeg"),
34186
+ sourcePath
34187
+ });
34188
+ }
34189
+ return references;
34190
+ }
34191
+ async function fileInputToUrlOrDataUri(input, fallbackMimeType) {
34192
+ if (input.startsWith("http://") || input.startsWith("https://") || input.startsWith("data:")) {
34193
+ return input;
34194
+ }
34195
+ const absPath = resolve23(process.cwd(), input);
34196
+ const buffer = await readFile15(absPath);
34197
+ return `data:${mimeTypeForPath(input, fallbackMimeType)};base64,${buffer.toString("base64")}`;
34198
+ }
34199
+ function mimeTypeForPath(path2, fallback2) {
34200
+ const ext = path2.toLowerCase().split(".").pop();
34201
+ const mimeTypes2 = {
34202
+ jpg: "image/jpeg",
34203
+ jpeg: "image/jpeg",
34204
+ png: "image/png",
34205
+ gif: "image/gif",
34206
+ webp: "image/webp",
34207
+ mp4: "video/mp4",
34208
+ mov: "video/quicktime",
34209
+ mp3: "audio/mpeg",
34210
+ wav: "audio/wav"
34211
+ };
34212
+ return mimeTypes2[ext || ""] || fallback2;
34213
+ }
34051
34214
  async function executeVideoStatus(options) {
34052
34215
  const {
34053
34216
  taskId,
@@ -36301,10 +36464,10 @@ var init_provider_resolver = __esm({
36301
36464
 
36302
36465
  // src/commands/_shared/openai-image.ts
36303
36466
  function resolveOpenAIImageModel(modelAlias) {
36304
- const isGptImage2 = modelAlias === "2" || modelAlias === "gpt-image-2";
36467
+ const isGptImage15 = modelAlias === "1.5" || modelAlias === "gpt-image-1.5";
36305
36468
  return {
36306
- openaiModel: isGptImage2 ? "gpt-image-2" : void 0,
36307
- modelLabel: isGptImage2 ? "GPT Image 2" : "GPT Image 1.5"
36469
+ openaiModel: isGptImage15 ? "gpt-image-1.5" : "gpt-image-2",
36470
+ modelLabel: isGptImage15 ? "GPT Image 1.5" : "GPT Image 2"
36308
36471
  };
36309
36472
  }
36310
36473
  async function executeOpenAIImageGenerate(prompt3, options, ctx) {
@@ -36341,7 +36504,7 @@ function registerImageCommand(parent) {
36341
36504
  "1:1"
36342
36505
  ).option("--quality <quality>", "Quality: standard, hd (openai only)", "standard").option("--style <style>", "Style: vivid, natural (openai only)", "vivid").option("--count <n>", "Number of images to generate", "1").option(
36343
36506
  "-m, --model <model>",
36344
- "Model. Gemini: flash, 3.1-flash, latest, pro. OpenAI: 1.5 (default), 2 (gpt-image-2)"
36507
+ "Model. Gemini: flash, 3.1-flash, latest, pro. OpenAI: 2 (default), 1.5"
36345
36508
  ).option("--dry-run", "Preview parameters without executing").addHelpText(
36346
36509
  "after",
36347
36510
  `
@@ -37775,8 +37938,8 @@ function registerVideoCommand(parent) {
37775
37938
  "quality"
37776
37939
  ).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(
37777
37940
  "--ref-images <paths...>",
37778
- "Reference images for character consistency (Veo 3.1 only, max 3)"
37779
- ).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(
37941
+ "Reference images for Seedance reference-to-video or Veo character consistency"
37942
+ ).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(
37780
37943
  "--runway-model <model>",
37781
37944
  "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)",
37782
37945
  "gen4.5"
@@ -37919,7 +38082,11 @@ Examples:
37919
38082
  negative: options.negative,
37920
38083
  resolution: options.resolution,
37921
38084
  veoModel: options.veoModel,
37922
- seedanceModel: options.seedanceModel
38085
+ seedanceModel: options.seedanceModel,
38086
+ refImages: options.refImages,
38087
+ refVideos: options.refVideos,
38088
+ refAudio: options.refAudio,
38089
+ generateAudio: options.generateAudio
37923
38090
  }
37924
38091
  }
37925
38092
  });
@@ -38206,8 +38373,13 @@ Examples:
38206
38373
  } else if (provider === "seedance") {
38207
38374
  const fal = new FalProvider();
38208
38375
  await fal.initialize({ apiKey });
38376
+ const seedanceReferences = await prepareSeedanceReferences2({
38377
+ refImages: options.refImages,
38378
+ refVideos: options.refVideos,
38379
+ refAudio: options.refAudio
38380
+ });
38209
38381
  let falImage = referenceImage;
38210
- if (falImage && falImage.startsWith("data:")) {
38382
+ if (falImage && falImage.startsWith("data:") && seedanceReferences.length === 0) {
38211
38383
  try {
38212
38384
  const uploadHost = await resolveUploadHost();
38213
38385
  spinner2.text = `Uploading image via ${uploadHost.provider} for Seedance...`;
@@ -38230,11 +38402,14 @@ Examples:
38230
38402
  const falModel = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
38231
38403
  result = await fal.generateVideo(prompt3, {
38232
38404
  prompt: prompt3,
38233
- referenceImage: falImage,
38405
+ referenceImage: seedanceReferences.length > 0 ? void 0 : falImage,
38406
+ references: seedanceReferences.length > 0 ? seedanceReferences : void 0,
38234
38407
  duration: options.duration ? parseInt(options.duration) : void 0,
38235
38408
  aspectRatio: options.ratio,
38236
38409
  negativePrompt: options.negative,
38237
- model: falModel
38410
+ model: falModel,
38411
+ resolution: options.resolution,
38412
+ generateAudio: options.generateAudio
38238
38413
  });
38239
38414
  finalResult = result;
38240
38415
  }
@@ -38289,6 +38464,54 @@ Examples:
38289
38464
  }
38290
38465
  });
38291
38466
  }
38467
+ async function prepareSeedanceReferences2(opts) {
38468
+ const references = [];
38469
+ for (const sourcePath of opts.refImages ?? []) {
38470
+ references.push({
38471
+ kind: "image",
38472
+ url: await fileInputToUrlOrDataUri2(sourcePath, "image/png"),
38473
+ sourcePath
38474
+ });
38475
+ }
38476
+ for (const sourcePath of opts.refVideos ?? []) {
38477
+ references.push({
38478
+ kind: "video",
38479
+ url: await fileInputToUrlOrDataUri2(sourcePath, "video/mp4"),
38480
+ sourcePath
38481
+ });
38482
+ }
38483
+ for (const sourcePath of opts.refAudio ?? []) {
38484
+ references.push({
38485
+ kind: "audio",
38486
+ url: await fileInputToUrlOrDataUri2(sourcePath, "audio/mpeg"),
38487
+ sourcePath
38488
+ });
38489
+ }
38490
+ return references;
38491
+ }
38492
+ async function fileInputToUrlOrDataUri2(input, fallbackMimeType) {
38493
+ if (input.startsWith("http://") || input.startsWith("https://") || input.startsWith("data:")) {
38494
+ return input;
38495
+ }
38496
+ const absPath = resolve31(process.cwd(), input);
38497
+ const buffer = await readFile18(absPath);
38498
+ return `data:${mimeTypeForPath2(input, fallbackMimeType)};base64,${buffer.toString("base64")}`;
38499
+ }
38500
+ function mimeTypeForPath2(path2, fallback2) {
38501
+ const ext = path2.toLowerCase().split(".").pop();
38502
+ const mimeTypes2 = {
38503
+ jpg: "image/jpeg",
38504
+ jpeg: "image/jpeg",
38505
+ png: "image/png",
38506
+ gif: "image/gif",
38507
+ webp: "image/webp",
38508
+ mp4: "video/mp4",
38509
+ mov: "video/quicktime",
38510
+ mp3: "audio/mpeg",
38511
+ wav: "audio/wav"
38512
+ };
38513
+ return mimeTypes2[ext || ""] || fallback2;
38514
+ }
38292
38515
  async function recordVideoNoWaitJob(opts) {
38293
38516
  return createAndWriteJobRecord({
38294
38517
  jobType: "generate-video",
@@ -58370,6 +58593,32 @@ var init_build_cache = __esm({
58370
58593
  }
58371
58594
  });
58372
58595
 
58596
+ // src/commands/_shared/build-backdrop-prompt.ts
58597
+ function augmentBackdropPrompt(cue) {
58598
+ const trimmed = cue.trim();
58599
+ const lower = trimmed.toLowerCase();
58600
+ const requestsTextOrMarks = /\b(text|typography|title|headline|label|caption|logo|logos|wordmark|brand mark|brand marks)\b/.test(lower);
58601
+ const forbidsTextOrMarks = /\b(no|without|avoid)\s+(readable\s+)?(text|typography|titles?|headlines?|labels?|captions?|brand\s+logos?|logos?|wordmarks?|brand\s+marks?)\b/.test(
58602
+ lower
58603
+ );
58604
+ const allowsTextOrMarks = requestsTextOrMarks && !forbidsTextOrMarks;
58605
+ const overlayContract = allowsTextOrMarks ? "The image is a video background or end-card plate; do not add any text, logos, charts, or UI beyond what the scene cue explicitly requests." : "The image is a background only; HTML overlays will provide all final text, charts, logos, and UI labels.";
58606
+ const textRule = allowsTextOrMarks ? "If text, logos, or brand marks are explicitly requested, keep them minimal, legible, and do not invent extras." : "No readable text, labels, UI copy, logos, brand marks, watermarks, or invented typography.";
58607
+ return [
58608
+ "Create a 16:9 video background plate for a HyperFrames scene.",
58609
+ overlayContract,
58610
+ `Scene cue: ${trimmed}`,
58611
+ textRule,
58612
+ "Avoid unrelated consumer product photography, shoes, packaging, food, people, celebrity faces, advertisements, and random objects unless explicitly requested by the scene cue.",
58613
+ "Leave generous negative space for overlay text and cards. Keep the result topic-aligned, editorial, cinematic, and non-distracting."
58614
+ ].join(" ");
58615
+ }
58616
+ var init_build_backdrop_prompt = __esm({
58617
+ "src/commands/_shared/build-backdrop-prompt.ts"() {
58618
+ "use strict";
58619
+ }
58620
+ });
58621
+
58373
58622
  // src/commands/_shared/build-plan.ts
58374
58623
  import { existsSync as existsSync44 } from "node:fs";
58375
58624
  import { readFile as readFile40 } from "node:fs/promises";
@@ -58463,6 +58712,7 @@ async function createBuildPlan(opts) {
58463
58712
  const voice = stringOrUndefined3(cue.voice) ?? resolved.voice;
58464
58713
  const narrationText = stringOrUndefined3(cue.narration);
58465
58714
  const backdropPrompt = stringOrUndefined3(cue.backdrop);
58715
+ const augmentedBackdropPrompt = backdropPrompt ? augmentBackdropPrompt(backdropPrompt) : null;
58466
58716
  const videoPrompt = stringOrUndefined3(cue.video);
58467
58717
  const musicPrompt = stringOrUndefined3(cue.music);
58468
58718
  const genericReference = resolveGenericAssetReference(projectDir, cue.asset);
@@ -58482,9 +58732,9 @@ async function createBuildPlan(opts) {
58482
58732
  voice,
58483
58733
  ext: resolved.narration.resolved === "elevenlabs" ? "mp3" : "wav"
58484
58734
  }) : null;
58485
- const backdropCache = backdropPrompt && !backdropReference ? backdropCacheDescriptor({
58735
+ const backdropCache = augmentedBackdropPrompt && !backdropReference ? backdropCacheDescriptor({
58486
58736
  beatId: beat.id,
58487
- cue: backdropPrompt,
58737
+ cue: augmentedBackdropPrompt,
58488
58738
  provider: resolved.image.resolved,
58489
58739
  quality: imageQuality,
58490
58740
  size: imageSize2,
@@ -59014,6 +59264,7 @@ var init_build_plan = __esm({
59014
59264
  init_build_asset_reference();
59015
59265
  init_build_cache();
59016
59266
  init_build_asset_metadata();
59267
+ init_build_backdrop_prompt();
59017
59268
  init_composer_resolve();
59018
59269
  init_storyboard_parse();
59019
59270
  init_project_config();
@@ -60428,8 +60679,9 @@ async function dispatchNarration(beat, ctx) {
60428
60679
  async function dispatchBackdrop(beat, ctx) {
60429
60680
  const reference = assetReferenceForBeat(ctx.projectDir, "backdrop", beat);
60430
60681
  if (reference) return referencePrimitiveOutcome("backdrop", beat, ctx, reference);
60431
- const prompt3 = stringOrUndefined4(beat.cues?.backdrop);
60432
- if (!prompt3) return { status: "no-cue" };
60682
+ const cue = stringOrUndefined4(beat.cues?.backdrop);
60683
+ if (!cue) return { status: "no-cue" };
60684
+ const prompt3 = augmentBackdropPrompt(cue);
60433
60685
  const rel = `assets/backdrop-${beat.id}.png`;
60434
60686
  const abs = join22(ctx.projectDir, rel);
60435
60687
  const size = ctx.imageSize ?? "1536x1024";
@@ -61407,6 +61659,7 @@ var init_scene_build = __esm({
61407
61659
  init_root_sync();
61408
61660
  init_build_cache();
61409
61661
  init_build_asset_metadata();
61662
+ init_build_backdrop_prompt();
61410
61663
  init_ai_video();
61411
61664
  init_music();
61412
61665
  init_status_jobs();
@@ -68156,6 +68409,9 @@ var init_generate2 = __esm({
68156
68409
  "Video provider (default: seedance when FAL_API_KEY is configured, otherwise first configured provider)"
68157
68410
  ),
68158
68411
  image: external_exports.string().optional().describe("Reference image path for image-to-video"),
68412
+ refImages: external_exports.array(external_exports.string()).optional().describe("Reference images for Seedance reference-to-video"),
68413
+ refVideos: external_exports.array(external_exports.string()).optional().describe("Reference videos for Seedance reference-to-video"),
68414
+ refAudio: external_exports.array(external_exports.string()).optional().describe("Reference audio files for Seedance reference-to-video"),
68159
68415
  duration: external_exports.number().optional().describe("Duration in seconds (default: 5; Seedance accepts 4-15)"),
68160
68416
  ratio: external_exports.string().optional().describe("Aspect ratio: 16:9, 9:16, 1:1 (default: 16:9)"),
68161
68417
  mode: external_exports.string().optional().describe("Kling mode: std or pro"),
@@ -68164,6 +68420,7 @@ var init_generate2 = __esm({
68164
68420
  veoModel: external_exports.string().optional().describe("Veo model: 3.0, 3.1, 3.1-fast"),
68165
68421
  runwayModel: external_exports.string().optional().describe("Runway model: gen4.5, gen4_turbo"),
68166
68422
  seedanceModel: external_exports.string().optional().describe("Seedance variant: quality or fast (fal.ai only)"),
68423
+ generateAudio: external_exports.boolean().optional().describe("Generate native synchronized audio when supported"),
68167
68424
  output: external_exports.string().optional().describe("Output file path (downloads video)"),
68168
68425
  wait: external_exports.boolean().optional().describe("Wait for completion (default: true)")
68169
68426
  }),
@@ -71397,7 +71654,7 @@ init_source();
71397
71654
  // package.json
71398
71655
  var package_default = {
71399
71656
  name: "@vibeframe/cli",
71400
- version: "0.104.2",
71657
+ version: "0.105.1",
71401
71658
  description: "VibeFrame CLI - natural language video editing from the terminal",
71402
71659
  private: false,
71403
71660
  license: "MIT",
@@ -79966,7 +80223,7 @@ function fourBeatDraft(opts) {
79966
80223
  title: scenic ? "First light" : "Hook",
79967
80224
  duration: opts.durations[0],
79968
80225
  narration: scenic ? `The first light reaches ${opts.product} before the day begins.` : `${opts.product} starts with one clear promise.`,
79969
- backdrop: scenic ? `misty mountain ridge at sunrise, golden light, fog layers, cinematic ${opts.theme}` : `polished opening frame for ${opts.product}, ${opts.theme}, clear hero subject`,
80226
+ backdrop: scenic ? `misty mountain ridge at sunrise, golden light, fog layers, cinematic ${opts.theme}` : `topic-aligned editorial background plate for ${opts.product}, ${opts.theme}, no readable text, no logos, no unrelated consumer products, clean negative space for HTML overlays`,
79970
80227
  motion: scenic ? "quiet title reveal, soft parallax, slow camera drift" : "large readable headline, restrained camera push, immediate value reveal",
79971
80228
  body: scenic ? "Show the mountain identity immediately. Keep text minimal." : "Make the value obvious in the first beat. Avoid setup that delays the payoff."
79972
80229
  },
@@ -79975,7 +80232,7 @@ function fourBeatDraft(opts) {
79975
80232
  title: scenic ? "The path appears" : "Proof",
79976
80233
  duration: opts.durations[1],
79977
80234
  narration: scenic ? "A clear path appears when every step is visible." : "The proof appears when each step is visible.",
79978
- backdrop: scenic ? "ridge trail emerging through fog, warm sunrise edge light, high contrast" : `concrete proof frame for ${opts.product}, visible workflow, before and after contrast`,
80235
+ backdrop: scenic ? "ridge trail emerging through fog, warm sunrise edge light, high contrast" : `topic-aligned proof background plate for ${opts.product}, abstract workflow structure, no readable text, no product photos, no shoes, no unrelated objects`,
79979
80236
  motion: scenic ? "thin line traces the path, small labels fade in" : "interface panels or proof points slide into place with precise transitions",
79980
80237
  body: "Connect the visual proof to the promise in one focused moment."
79981
80238
  },
@@ -79993,7 +80250,7 @@ function fourBeatDraft(opts) {
79993
80250
  title: scenic ? "Day one" : "Close",
79994
80251
  duration: opts.durations[3],
79995
80252
  narration: scenic ? `${opts.product}. Day one begins here.` : `${opts.product} is ready to show.`,
79996
- backdrop: scenic ? "wide mountain ridge at peak golden hour, valley filled with warm light" : `resolved final frame for ${opts.product}, clean negative space, confident lockup`,
80253
+ backdrop: scenic ? "wide mountain ridge at peak golden hour, valley filled with warm light" : `resolved editorial background plate for ${opts.product}, clean negative space, confident lockup, no readable text, no logos, no unrelated products`,
79997
80254
  motion: "clean final title, slow settling motion, gentle fade to the final frame",
79998
80255
  body: "End with a quiet branded title and no new idea."
79999
80256
  }
@@ -80006,7 +80263,7 @@ function threeBeatDraft(opts) {
80006
80263
  title: "Hook",
80007
80264
  duration: opts.durations[0],
80008
80265
  narration: `${opts.product} starts with one clear promise.`,
80009
- backdrop: `polished opening frame for ${opts.product}, ${opts.theme}, clear hero subject`,
80266
+ backdrop: `topic-aligned editorial background plate for ${opts.product}, ${opts.theme}, no readable text, no logos, no unrelated consumer products, clean negative space for HTML overlays`,
80010
80267
  motion: "large readable headline, restrained camera push, immediate value reveal",
80011
80268
  body: "Make the value obvious in one beat."
80012
80269
  },
@@ -80015,7 +80272,7 @@ function threeBeatDraft(opts) {
80015
80272
  title: "Proof",
80016
80273
  duration: opts.durations[1],
80017
80274
  narration: "The proof appears when each step is visible.",
80018
- backdrop: `concrete proof frame for ${opts.product}, visible workflow, before and after contrast`,
80275
+ backdrop: `topic-aligned proof background plate for ${opts.product}, abstract workflow structure, no readable text, no product photos, no shoes, no unrelated objects`,
80019
80276
  motion: "layered panels, highlighted cause and effect, precise transitions",
80020
80277
  body: "Show the mechanism, metric, or before/after that makes the promise believable."
80021
80278
  },
@@ -80024,7 +80281,7 @@ function threeBeatDraft(opts) {
80024
80281
  title: "Close",
80025
80282
  duration: opts.durations[2],
80026
80283
  narration: `${opts.product} is ready to show.`,
80027
- backdrop: `resolved final frame for ${opts.product}, clean negative space, confident lockup`,
80284
+ backdrop: `resolved editorial background plate for ${opts.product}, clean negative space, confident lockup, no readable text, no logos, no unrelated products`,
80028
80285
  motion: "clean final title, subtle parallax, gentle fade to the final frame",
80029
80286
  body: "End on the product name, offer, command, or memorable final line."
80030
80287
  }
@@ -80257,6 +80514,12 @@ principles, type system, and visual-identity hard-gate. The same skill
80257
80514
  governs \`vibe scene lint\` so your authored HTML and the linter stay in
80258
80515
  agreement.
80259
80516
 
80517
+ For render-stable text, do not apply continuous \`scale\`, \`x\`, \`y\`,
80518
+ \`filter\`, or other transform tweens to \`.scene-content\` or any ancestor
80519
+ containing live text/cards. Put ambient zoom/parallax on background or media
80520
+ layers only; text should enter briefly and then hold still at its final CSS
80521
+ position.
80522
+
80260
80523
  To retro-install on a project scaffolded before this command existed, run
80261
80524
  \`vibe scene install-skill [--host all]\`.
80262
80525
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.104.2",
3
+ "version": "0.105.1",
4
4
  "description": "VibeFrame CLI - natural language video editing from the terminal",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -183,8 +183,8 @@
183
183
  "tsx": "^4.21.0",
184
184
  "typescript": "^5.3.3",
185
185
  "vitest": "^1.2.2",
186
- "@vibeframe/ai-providers": "0.104.2",
187
- "@vibeframe/core": "0.104.2"
186
+ "@vibeframe/ai-providers": "0.105.1",
187
+ "@vibeframe/core": "0.105.1"
188
188
  },
189
189
  "engines": {
190
190
  "node": ">=20"