@vibeframe/mcp-server 0.72.0 → 0.74.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +11 -11
  2. package/dist/index.js +795 -490
  3. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -7330,6 +7330,441 @@ var require_dist = __commonJS({
7330
7330
  }
7331
7331
  });
7332
7332
 
7333
+ // ../cli/src/commands/_shared/scene-project.ts
7334
+ import { mkdir, readFile, writeFile, access } from "node:fs/promises";
7335
+ import { resolve, basename } from "node:path";
7336
+ function aspectToDims(aspect) {
7337
+ return ASPECT_DIMS[aspect];
7338
+ }
7339
+ function defaultVibeProjectConfig(name) {
7340
+ return {
7341
+ name,
7342
+ aspect: "16:9",
7343
+ defaultSceneDuration: 5,
7344
+ providers: { image: null, tts: null, transcribe: null },
7345
+ budget: { maxUsd: 0 }
7346
+ };
7347
+ }
7348
+ function buildHyperframesConfig() {
7349
+ return {
7350
+ $schema: "https://hyperframes.heygen.com/schema/hyperframes.json",
7351
+ registry: "https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry",
7352
+ paths: {
7353
+ blocks: "compositions",
7354
+ components: "compositions/components",
7355
+ assets: "assets"
7356
+ }
7357
+ };
7358
+ }
7359
+ function buildHyperframesMeta(name, now = /* @__PURE__ */ new Date()) {
7360
+ return { id: name, name, createdAt: now.toISOString() };
7361
+ }
7362
+ function mergeHyperframesConfig(existing, defaults) {
7363
+ const out = { ...defaults, ...existing };
7364
+ if (existing.paths || defaults.paths) {
7365
+ out.paths = { ...defaults.paths ?? {}, ...existing.paths ?? {} };
7366
+ }
7367
+ return out;
7368
+ }
7369
+ function buildEmptyRootHtml(opts) {
7370
+ const { width, height } = ASPECT_DIMS[opts.aspect];
7371
+ return `<!doctype html>
7372
+ <html lang="en">
7373
+ <head>
7374
+ <meta charset="UTF-8" />
7375
+ <meta name="viewport" content="width=${width}, height=${height}" />
7376
+ <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
7377
+ <style>
7378
+ * { margin: 0; padding: 0; box-sizing: border-box; }
7379
+ html, body {
7380
+ margin: 0;
7381
+ width: ${width}px;
7382
+ height: ${height}px;
7383
+ overflow: hidden;
7384
+ background: #000;
7385
+ }
7386
+ </style>
7387
+ </head>
7388
+ <body>
7389
+ <div
7390
+ id="root"
7391
+ data-composition-id="main"
7392
+ data-start="0"
7393
+ data-duration="${opts.duration}"
7394
+ data-width="${width}"
7395
+ data-height="${height}"
7396
+ >
7397
+ <!-- Scenes added via \`vibe scene add\` are inserted here. -->
7398
+ <!-- Each scene reference: data-composition-id, data-composition-src, data-start, data-duration, data-track-index. -->
7399
+ <!-- See compositions/*.html for sub-composition contents. -->
7400
+
7401
+ </div>
7402
+
7403
+ <script>
7404
+ window.__timelines = window.__timelines || {};
7405
+ window.__timelines["main"] = gsap.timeline({ paused: true });
7406
+ </script>
7407
+ </body>
7408
+ </html>
7409
+ `;
7410
+ }
7411
+ function buildDesignMd(opts) {
7412
+ const { name, style } = opts;
7413
+ const intro = style ? `Visual identity for **${name}**, scaffolded from the **${style.name}** style (after ${style.designer}). Customise freely \u2014 this file is the single source of truth for every scene's palette, typography, and motion.` : `Visual identity for **${name}**. Fill the sections below before authoring any scene HTML or generating any backdrop. Pick a named style with \`vibe scene styles\` if you want a credible starting point.`;
7414
+ const moodLine = style ? `**Mood:** ${style.mood} \xB7 **Best for:** ${style.bestFor}` : `**Mood:** _(one line \u2014 what should the viewer FEEL?)_`;
7415
+ const palette = style ? `${style.palette.map((c) => `- \`${c}\``).join("\n")}
7416
+
7417
+ ${style.paletteNotes}` : `- _hex_ \u2014 primary
7418
+ - _hex_ \u2014 accent
7419
+
7420
+ _2\u20133 colours max. Declare explicit hex values; never name colours abstractly._`;
7421
+ const typography = style ? style.typography : `_One family, two weights. State the role of each (headline / label / body)._`;
7422
+ const composition = style ? style.composition : `_Grid? Centered? Layered? How does negative space behave?_`;
7423
+ const motion = style ? `${style.motion}
7424
+
7425
+ **GSAP signature:** ${style.gsapSignature}` : `_How fast? Snappy or fluid? Overshoot or precision?_
7426
+
7427
+ **GSAP signature:** _e.g. \`expo.out\`, \`sine.inOut\`, \`back.out(1.8)\`_`;
7428
+ const transition = style ? style.transition : `_Which Hyperframes shader matches the energy? (Cinematic Zoom, Cross-Warp Morph, Glitch, Domain Warp, \u2026)_`;
7429
+ const avoid = style ? style.avoid.map((a) => `- ${a}`).join("\n") : `- _anti-pattern 1_
7430
+ - _anti-pattern 2_
7431
+ - _anti-pattern 3_`;
7432
+ return `# ${name} \u2014 Design
7433
+
7434
+ > **Hard-gate.** This file defines the visual identity of every scene.
7435
+ > Author it before generating any HTML, backdrop image, or motion.
7436
+ > The Hyperframes \`hyperframes\` skill enforces this: scenes that
7437
+ > contradict DESIGN.md are rejected.
7438
+
7439
+ ${intro}
7440
+
7441
+ ## Style
7442
+
7443
+ ${moodLine}
7444
+
7445
+ ## Palette
7446
+
7447
+ ${palette}
7448
+
7449
+ ## Typography
7450
+
7451
+ ${typography}
7452
+
7453
+ ## Composition
7454
+
7455
+ ${composition}
7456
+
7457
+ ## Motion
7458
+
7459
+ ${motion}
7460
+
7461
+ ## Transition
7462
+
7463
+ ${transition}
7464
+
7465
+ ## What NOT to do
7466
+
7467
+ ${avoid}
7468
+
7469
+ ---
7470
+
7471
+ _Browse other named styles: \`vibe scene styles\`_
7472
+ ${style ? `_This file was seeded by \`vibe scene init --visual-style "${style.name}"\`._` : `_Seed this file from a named style: \`vibe scene init <dir> --visual-style "<name>"\`._`}
7473
+ `;
7474
+ }
7475
+ function buildStoryboardMd(name, duration = 12) {
7476
+ return `---
7477
+ title: ${name}
7478
+ duration: ${duration}
7479
+ aspect: 16:9
7480
+ tts: auto
7481
+ imageProvider: openai
7482
+ ---
7483
+
7484
+ # ${name} \u2014 Storyboard
7485
+
7486
+ Edit these beats before running \`vibe build\`. Each beat starts with
7487
+ YAML cues that drive narration, backdrop generation, and timing.
7488
+
7489
+ ## Beat hook \u2014 Hook
7490
+
7491
+ \`\`\`yaml
7492
+ narration: "Introduce the promise in one crisp sentence."
7493
+ backdrop: "Cinematic abstract technology backdrop, precise light, premium editorial feel"
7494
+ duration: 4
7495
+ \`\`\`
7496
+
7497
+ Show the core visual identity immediately. Keep copy short enough for one
7498
+ screen and one spoken breath.
7499
+
7500
+ ## Beat proof \u2014 Proof
7501
+
7502
+ \`\`\`yaml
7503
+ narration: "Show the mechanism or proof point that makes the promise believable."
7504
+ backdrop: "Layered interface details, subtle motion trails, high-contrast product storytelling"
7505
+ duration: 4
7506
+ \`\`\`
7507
+
7508
+ Use this beat for the concrete differentiator: command, workflow, metric, or
7509
+ before/after.
7510
+
7511
+ ## Beat close \u2014 Close
7512
+
7513
+ \`\`\`yaml
7514
+ narration: "Close with the action the viewer should remember."
7515
+ backdrop: "Resolved hero frame, confident final composition, clean negative space"
7516
+ duration: 4
7517
+ \`\`\`
7518
+
7519
+ End on the product name, offer, or command. Avoid adding a new idea in the
7520
+ final beat.
7521
+ `;
7522
+ }
7523
+ function buildProjectClaudeMd(name) {
7524
+ return `# ${name} \u2014 Scene Authoring Project
7525
+
7526
+ This project is **bilingual**: it works with both VibeFrame (\`vibe\`) and
7527
+ HeyGen Hyperframes (\`hyperframes\`). You can run either CLI inside this
7528
+ directory.
7529
+
7530
+ ## Visual identity hard-gate
7531
+
7532
+ **Author \`DESIGN.md\` before any scene HTML.** It defines palette,
7533
+ typography, motion, and transition rules. Both the agent-driven path and
7534
+ the fallback emit reference it; scenes that contradict DESIGN.md are
7535
+ rejected by the Hyperframes \`hyperframes\` skill.
7536
+
7537
+ Browse named styles: \`vibe scene styles\`. Re-seed from one with
7538
+ \`vibe scene init . --visual-style "Swiss Pulse"\` (idempotent).
7539
+
7540
+ ## Skills \u2014 USE THESE FIRST
7541
+
7542
+ **Always invoke the relevant skill before authoring scenes.** Skills encode
7543
+ framework-specific patterns (GSAP timeline registration, data-attribute
7544
+ semantics, VibeFrame pipeline conventions) that are NOT in generic web docs.
7545
+
7546
+ | Skill | Command | When to use |
7547
+ | ----------------- | ---------------- | ------------------------------------------------------------------------------------- |
7548
+ | **hyperframes** | \`/hyperframes\` | Cinematic-quality composition \u2014 DESIGN.md hard-gate, named styles, motion principles |
7549
+ | **vibe-scene** | \`/vibe-scene\` | VibeFrame's authoring loop, AI assets, lint feedback, pipeline integration |
7550
+ | **gsap** | \`/gsap\` | GSAP tweens, timelines, easing |
7551
+
7552
+ Optional: install the upstream Hyperframes skills once per machine when your agent supports skill commands:
7553
+
7554
+ \`\`\`bash
7555
+ npx skills add heygen-com/hyperframes
7556
+ \`\`\`
7557
+
7558
+ Restart your agent session (or reload the skill list) after installing.
7559
+ If skills aren't available, follow the **Key Rules** below \u2014 they cover
7560
+ the framework-level minimum, not the cinematic craft layer.
7561
+
7562
+ ## Project structure
7563
+
7564
+ - \`DESIGN.md\` \u2014 visual identity contract (palette, type, motion, transitions)
7565
+ - \`STORYBOARD.md\` \u2014 per-beat narration/backdrop/duration cues for \`vibe build\`
7566
+ - \`index.html\` \u2014 root composition (timeline)
7567
+ - \`compositions/scene-*.html\` \u2014 per-scene HTML authored by you or the agent
7568
+ - \`assets/\` \u2014 shared media (narration audio, images, video)
7569
+ - \`transcript.json\` \u2014 Whisper word-level transcript (if narration exists)
7570
+ - \`hyperframes.json\` \u2014 HF registry config (speak to both toolchains)
7571
+ - \`vibe.project.yaml\` \u2014 VibeFrame config (providers, budget)
7572
+ - \`renders/\` \u2014 output MP4s
7573
+
7574
+ ## Commands
7575
+
7576
+ \`\`\`bash
7577
+ vibe scene add <name> --narration "..." --visuals "..." # Author a new scene via AI
7578
+ vibe build # STORYBOARD.md \u2192 narrated MP4
7579
+ vibe scene lint # Validate scenes (in-process HF linter)
7580
+ vibe scene render # Render to MP4
7581
+
7582
+ # Hyperframes CLI (if installed \u2014 works in this project too)
7583
+ npx hyperframes preview
7584
+ npx hyperframes render
7585
+ \`\`\`
7586
+
7587
+ ## Key Rules (for hand-authored scene HTML)
7588
+
7589
+ 1. Every timed element needs \`data-start\`, \`data-duration\`, and \`data-track-index\`.
7590
+ 2. Elements with timing **MUST** have \`class="clip"\` \u2014 the framework uses this for visibility control.
7591
+ 3. Timelines must be paused and registered on \`window.__timelines\`:
7592
+ \`\`\`js
7593
+ window.__timelines = window.__timelines || {};
7594
+ window.__timelines["composition-id"] = gsap.timeline({ paused: true });
7595
+ \`\`\`
7596
+ 4. Videos use \`muted\` with a separate \`<audio>\` element for the audio track.
7597
+ 5. Sub-compositions use \`data-composition-src="compositions/file.html"\`.
7598
+ 6. Only deterministic logic \u2014 no \`Date.now()\`, \`Math.random()\`, or network fetches.
7599
+
7600
+ ## Linting \u2014 run after changes
7601
+
7602
+ \`\`\`bash
7603
+ vibe scene lint # preferred \u2014 in-process, no network
7604
+ vibe scene lint --fix # auto-fix mechanical issues
7605
+ vibe scene lint --json # structured output for agent loops
7606
+ \`\`\`
7607
+ `;
7608
+ }
7609
+ function buildSceneGitignore() {
7610
+ return `# VibeFrame caches
7611
+ .vibeframe/cache/
7612
+ .vibeframe/checkpoints/
7613
+
7614
+ # Render outputs
7615
+ renders/*.mp4
7616
+ tmp/
7617
+
7618
+ # OS / editor
7619
+ .DS_Store
7620
+ *.log
7621
+ `;
7622
+ }
7623
+ function isSceneScaffoldProfile(value) {
7624
+ return value === "minimal" || value === "agent" || value === "full";
7625
+ }
7626
+ function describeSceneScaffold(opts) {
7627
+ const dir = resolve(opts.dir);
7628
+ const profile = opts.profile ?? "full";
7629
+ const groups = {
7630
+ authoring: [
7631
+ resolve(dir, "STORYBOARD.md"),
7632
+ resolve(dir, "DESIGN.md"),
7633
+ resolve(dir, "vibe.project.yaml"),
7634
+ resolve(dir, ".gitignore")
7635
+ ],
7636
+ render: [],
7637
+ agent: []
7638
+ };
7639
+ if (profile === "full") {
7640
+ groups.render = [
7641
+ resolve(dir, "index.html"),
7642
+ resolve(dir, "compositions"),
7643
+ resolve(dir, "assets"),
7644
+ resolve(dir, "renders"),
7645
+ resolve(dir, "hyperframes.json"),
7646
+ resolve(dir, "meta.json")
7647
+ ];
7648
+ }
7649
+ if (profile === "agent" || profile === "full") {
7650
+ groups.agent = [
7651
+ resolve(dir, "SKILL.md"),
7652
+ resolve(dir, "references"),
7653
+ resolve(dir, "CLAUDE.md")
7654
+ ];
7655
+ }
7656
+ return groups;
7657
+ }
7658
+ async function pathExists(p) {
7659
+ try {
7660
+ await access(p);
7661
+ return true;
7662
+ } catch {
7663
+ return false;
7664
+ }
7665
+ }
7666
+ async function scaffoldSceneProject(opts) {
7667
+ const dir = resolve(opts.dir);
7668
+ const name = opts.name ?? basename(dir);
7669
+ const aspect = opts.aspect ?? "16:9";
7670
+ const duration = opts.duration ?? 10;
7671
+ const now = opts.now ?? /* @__PURE__ */ new Date();
7672
+ const profile = opts.profile ?? "full";
7673
+ await mkdir(dir, { recursive: true });
7674
+ if (profile === "full") {
7675
+ await mkdir(resolve(dir, "compositions"), { recursive: true });
7676
+ await mkdir(resolve(dir, "assets"), { recursive: true });
7677
+ await mkdir(resolve(dir, "renders"), { recursive: true });
7678
+ }
7679
+ const created = [];
7680
+ const skipped2 = [];
7681
+ const merged = [];
7682
+ if (profile === "full") {
7683
+ const hfPath = resolve(dir, "hyperframes.json");
7684
+ const hfDefaults = buildHyperframesConfig();
7685
+ if (await pathExists(hfPath)) {
7686
+ const existingRaw = await readFile(hfPath, "utf-8");
7687
+ const existing = JSON.parse(existingRaw);
7688
+ const mergedConfig = mergeHyperframesConfig(existing, hfDefaults);
7689
+ await writeFile(hfPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf-8");
7690
+ merged.push(hfPath);
7691
+ } else {
7692
+ await writeFile(hfPath, JSON.stringify(hfDefaults, null, 2) + "\n", "utf-8");
7693
+ created.push(hfPath);
7694
+ }
7695
+ const metaPath = resolve(dir, "meta.json");
7696
+ if (await pathExists(metaPath)) {
7697
+ skipped2.push(metaPath);
7698
+ } else {
7699
+ await writeFile(metaPath, JSON.stringify(buildHyperframesMeta(name, now), null, 2) + "\n", "utf-8");
7700
+ created.push(metaPath);
7701
+ }
7702
+ const rootPath = resolve(dir, "index.html");
7703
+ if (await pathExists(rootPath)) {
7704
+ skipped2.push(rootPath);
7705
+ } else {
7706
+ await writeFile(rootPath, buildEmptyRootHtml({ aspect, duration }), "utf-8");
7707
+ created.push(rootPath);
7708
+ }
7709
+ }
7710
+ const vibePath = resolve(dir, "vibe.project.yaml");
7711
+ if (await pathExists(vibePath)) {
7712
+ skipped2.push(vibePath);
7713
+ } else {
7714
+ const cfg = { ...defaultVibeProjectConfig(name), aspect };
7715
+ await writeFile(vibePath, (0, import_yaml.stringify)(cfg), "utf-8");
7716
+ created.push(vibePath);
7717
+ }
7718
+ if (profile === "agent" || profile === "full") {
7719
+ const claudePath = resolve(dir, "CLAUDE.md");
7720
+ if (await pathExists(claudePath)) {
7721
+ skipped2.push(claudePath);
7722
+ } else {
7723
+ await writeFile(claudePath, buildProjectClaudeMd(name), "utf-8");
7724
+ created.push(claudePath);
7725
+ }
7726
+ }
7727
+ const designPath = resolve(dir, "DESIGN.md");
7728
+ if (await pathExists(designPath)) {
7729
+ skipped2.push(designPath);
7730
+ } else {
7731
+ await writeFile(
7732
+ designPath,
7733
+ buildDesignMd({ name, style: opts.visualStyle }),
7734
+ "utf-8"
7735
+ );
7736
+ created.push(designPath);
7737
+ }
7738
+ const storyboardPath = resolve(dir, "STORYBOARD.md");
7739
+ if (await pathExists(storyboardPath)) {
7740
+ skipped2.push(storyboardPath);
7741
+ } else {
7742
+ await writeFile(storyboardPath, buildStoryboardMd(name, duration), "utf-8");
7743
+ created.push(storyboardPath);
7744
+ }
7745
+ const gitignorePath = resolve(dir, ".gitignore");
7746
+ if (await pathExists(gitignorePath)) {
7747
+ skipped2.push(gitignorePath);
7748
+ } else {
7749
+ await writeFile(gitignorePath, buildSceneGitignore(), "utf-8");
7750
+ created.push(gitignorePath);
7751
+ }
7752
+ return { created, skipped: skipped2, merged, groups: describeSceneScaffold({ dir, profile }) };
7753
+ }
7754
+ var import_yaml, ASPECT_DIMS;
7755
+ var init_scene_project = __esm({
7756
+ "../cli/src/commands/_shared/scene-project.ts"() {
7757
+ "use strict";
7758
+ import_yaml = __toESM(require_dist(), 1);
7759
+ ASPECT_DIMS = {
7760
+ "16:9": { width: 1920, height: 1080 },
7761
+ "9:16": { width: 1080, height: 1920 },
7762
+ "1:1": { width: 1080, height: 1080 },
7763
+ "4:5": { width: 1080, height: 1350 }
7764
+ };
7765
+ }
7766
+ });
7767
+
7333
7768
  // ../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/error.js
7334
7769
  var require_error = __commonJS({
7335
7770
  "../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/error.js"(exports) {
@@ -13713,12 +14148,12 @@ var init_api_keys = __esm({
13713
14148
  label: "ImgBB",
13714
14149
  showInSetup: false,
13715
14150
  // not prompted in setup wizard — internal upload host
13716
- envExampleComment: "ImgBB API Key (image hosting \u2014 used by Kling and fal.ai for image-to-video uploads)",
14151
+ envExampleComment: "ImgBB API Key (image hosting \u2014 used by Kling and Seedance for image-to-video uploads)",
13717
14152
  envExampleUrl: "https://api.imgbb.com/",
13718
14153
  // ImgBB has no provider class (envvar-only); doctor still shows what it
13719
14154
  // unlocks at the apiKey level.
13720
14155
  commandsUnlocked: [
13721
- "generate video -p kling/fal (image-to-video upload host)"
14156
+ "generate video -p kling/seedance (image-to-video upload host)"
13722
14157
  ]
13723
14158
  });
13724
14159
  defineProvider({
@@ -23787,13 +24222,18 @@ var init_fal = __esm({
23787
24222
  defineProvider({
23788
24223
  id: "fal",
23789
24224
  label: "fal.ai (Seedance 2.0)",
24225
+ displayName: "Seedance 2.0",
24226
+ gateway: "fal.ai",
24227
+ aliases: ["seedance"],
24228
+ models: ["seedance-2.0", "seedance-2.0-fast"],
24229
+ capabilities: ["text-to-video", "image-to-video", "native-audio"],
23790
24230
  apiKey: "fal",
23791
24231
  kinds: ["video"],
23792
24232
  resolverPriority: { video: 1 },
23793
24233
  commandsUnlocked: [
23794
- "generate video -p fal (Seedance 2.0 \u2014 default since v0.57)",
23795
- "generate video -p fal -m fast (lower-latency variant)",
23796
- "generate video -p fal -i <image> (image-to-video)"
24234
+ "generate video -p seedance (Seedance 2.0 via fal.ai \u2014 default since v0.57)",
24235
+ "generate video -p seedance --seedance-model fast (lower-latency variant)",
24236
+ "generate video -p seedance -i <image> (image-to-video)"
23797
24237
  ]
23798
24238
  });
23799
24239
  }
@@ -446848,7 +447288,9 @@ function buildAudioMuxFilter(audios) {
446848
447288
  const inputIdx = i + 1;
446849
447289
  const delayMs = Math.max(0, Math.round(a.absoluteStart * 1e3));
446850
447290
  const volume = Number.isFinite(a.volume) ? a.volume : 1;
446851
- const trimSec = Math.max(0, a.clipDurationCap);
447291
+ const durationHint = typeof a.durationHint === "number" && Number.isFinite(a.durationHint) ? Math.max(0, a.durationHint) : null;
447292
+ const clipCap = Math.max(0, a.clipDurationCap);
447293
+ const trimSec = durationHint === null ? clipCap : Math.min(durationHint, clipCap);
446852
447294
  const label = `a${i}`;
446853
447295
  const stage = [
446854
447296
  `[${inputIdx}:a]`,
@@ -447107,6 +447549,57 @@ var init_scene_render = __esm({
447107
447549
  }
447108
447550
  });
447109
447551
 
447552
+ // ../cli/src/utils/audio.ts
447553
+ async function getAudioDuration(filePath) {
447554
+ try {
447555
+ return await ffprobeDuration(filePath);
447556
+ } catch (error) {
447557
+ const message = error instanceof Error ? error.message : String(error);
447558
+ throw new Error(`Failed to get audio duration: ${message}`);
447559
+ }
447560
+ }
447561
+ async function getVideoDuration(filePath) {
447562
+ try {
447563
+ return await ffprobeDuration(filePath);
447564
+ } catch (error) {
447565
+ const message = error instanceof Error ? error.message : String(error);
447566
+ throw new Error(`Failed to get video duration: ${message}`);
447567
+ }
447568
+ }
447569
+ async function extendVideoNaturally(videoPath, targetDuration, outputPath) {
447570
+ const videoDuration = await getVideoDuration(videoPath);
447571
+ const ratio = targetDuration / videoDuration;
447572
+ if (ratio <= 1) {
447573
+ const { copyFile: copyFile5 } = await import("node:fs/promises");
447574
+ await copyFile5(videoPath, outputPath);
447575
+ return;
447576
+ }
447577
+ if (ratio <= 1.15) {
447578
+ const slowFactor = (1 / ratio).toFixed(4);
447579
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS`, "-an", outputPath]);
447580
+ } else if (ratio <= 1.4) {
447581
+ const slowFactor = (1 / ratio).toFixed(4);
447582
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
447583
+ } else {
447584
+ const slowRatio = 0.7;
447585
+ const slowedDuration = videoDuration / slowRatio;
447586
+ const freezeDuration = targetDuration - slowedDuration;
447587
+ if (freezeDuration <= 0) {
447588
+ const slowFactor = (1 / ratio).toFixed(4);
447589
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
447590
+ } else {
447591
+ const slowFactor = (1 / slowRatio).toFixed(4);
447592
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS,tpad=stop_mode=clone:stop_duration=${freezeDuration.toFixed(2)}`, "-an", outputPath]);
447593
+ }
447594
+ }
447595
+ }
447596
+ var init_audio = __esm({
447597
+ "../cli/src/utils/audio.ts"() {
447598
+ "use strict";
447599
+ init_exec_safe();
447600
+ }
447601
+ });
447602
+
447110
447603
  // ../cli/src/commands/_shared/hf-skill-bundle/bundle-content.ts
447111
447604
  var SKILL_MD, HOUSE_STYLE_MD, MOTION_PRINCIPLES_MD, TYPOGRAPHY_MD, TRANSITIONS_MD;
447112
447605
  var init_bundle_content = __esm({
@@ -448703,7 +449196,7 @@ async function getComposePrompts(opts) {
448703
449196
  return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe scene init <dir>\` first.`);
448704
449197
  }
448705
449198
  if (!existsSync24(storyboardPath)) {
448706
- return baseError(`STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` first.`);
449199
+ return baseError(`STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
448707
449200
  }
448708
449201
  if (!existsSync24(skillPath)) {
448709
449202
  warnings.push(
@@ -448929,7 +449422,10 @@ async function executeSceneBuild(opts) {
448929
449422
  const mode = resolveSceneBuildMode(opts);
448930
449423
  const storyboardPath = join25(projectDir, "STORYBOARD.md");
448931
449424
  if (!existsSync26(storyboardPath)) {
448932
- return failBeforePrimitives(`STORYBOARD.md not found at ${storyboardPath}`, startedAt);
449425
+ return failBeforePrimitives(
449426
+ `STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`,
449427
+ startedAt
449428
+ );
448933
449429
  }
448934
449430
  const storyboardMd = await readFile9(storyboardPath, "utf-8");
448935
449431
  const parsed = parseStoryboard(storyboardMd);
@@ -449009,6 +449505,13 @@ async function executeSceneBuild(opts) {
449009
449505
  }
449010
449506
  composeData = composeResult.data;
449011
449507
  }
449508
+ if (!existsSync26(join25(projectDir, "index.html"))) {
449509
+ await scaffoldSceneProject({
449510
+ dir: projectDir,
449511
+ name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
449512
+ profile: "full"
449513
+ });
449514
+ }
449012
449515
  await syncRootClipReferences(parsed.beats, projectDir, beatOutcomes);
449013
449516
  let outputPath;
449014
449517
  let renderResult;
@@ -449107,6 +449610,7 @@ async function dispatchBackdrop(beat, ctx) {
449107
449610
  ctx.onProgress({ type: "backdrop-cached", beatId: beat.id, path: rel });
449108
449611
  return { status: "cached", path: rel };
449109
449612
  }
449613
+ loadSceneBuildEnv(ctx.projectDir);
449110
449614
  const apiKey = process.env.OPENAI_API_KEY ?? "";
449111
449615
  if (!apiKey) {
449112
449616
  const error = "OPENAI_API_KEY not set \u2014 cannot dispatch backdrop";
@@ -449135,6 +449639,18 @@ async function dispatchBackdrop(beat, ctx) {
449135
449639
  });
449136
449640
  return { status: "generated", path: rel };
449137
449641
  }
449642
+ function loadSceneBuildEnv(projectDir) {
449643
+ (0, import_dotenv2.config)({ path: join25(projectDir, ".env"), quiet: true });
449644
+ (0, import_dotenv2.config)({ path: resolve19(process.cwd(), ".env"), quiet: true });
449645
+ let dir = process.cwd();
449646
+ while (dir !== dirname15(dir)) {
449647
+ if (existsSync26(join25(dir, "pnpm-workspace.yaml"))) {
449648
+ (0, import_dotenv2.config)({ path: join25(dir, ".env"), quiet: true });
449649
+ return;
449650
+ }
449651
+ dir = dirname15(dir);
449652
+ }
449653
+ }
449138
449654
  async function skipped(kind, beatId, reason, ctx) {
449139
449655
  ctx.onProgress({ type: `${kind}-skipped`, beatId, reason });
449140
449656
  return { status: "skipped" };
@@ -449165,20 +449681,24 @@ async function syncRootClipReferences(beats, projectDir, outcomes) {
449165
449681
  const clipLines = [];
449166
449682
  const audioLines = [];
449167
449683
  for (const beat of beats) {
449168
- const duration = beat.duration ?? 3;
449684
+ const outcome = outcomes.find((o) => o.beatId === beat.id);
449685
+ const duration = await resolveBeatDuration({
449686
+ beatDuration: beat.duration,
449687
+ narrationPath: outcome?.narrationPath,
449688
+ projectDir
449689
+ });
449169
449690
  const compositionId = `scene-${beat.id}`;
449170
449691
  clipLines.push(
449171
449692
  ` <div class="clip" data-composition-id="${compositionId}" data-composition-src="compositions/${compositionId}.html" data-start="${cursor}" data-duration="${duration}" data-track-index="0"></div>`
449172
449693
  );
449173
- const outcome = outcomes.find((o) => o.beatId === beat.id);
449174
449694
  if (outcome?.narrationPath) {
449175
449695
  audioLines.push(
449176
- ` <audio src="${outcome.narrationPath}" data-start="${cursor}" data-duration="${duration}" data-track-index="2"></audio>`
449696
+ ` <audio id="narration-${beat.id}" src="${outcome.narrationPath}" data-start="${cursor}" data-duration="${duration}" data-track-index="2"></audio>`
449177
449697
  );
449178
449698
  }
449179
449699
  cursor += duration;
449180
449700
  }
449181
- const totalDuration = cursor;
449701
+ const totalDuration = Number(cursor.toFixed(2));
449182
449702
  const block = " <!-- vibe-scene-build: clip refs (auto-generated; safe to re-run) -->\n" + clipLines.join("\n") + (audioLines.length > 0 ? "\n" + audioLines.join("\n") : "") + "\n <!-- /vibe-scene-build -->";
449183
449703
  let next;
449184
449704
  const markerRe = /\n? *<!-- vibe-scene-build: clip refs.*?<!-- \/vibe-scene-build -->/s;
@@ -449196,22 +449716,36 @@ ${block}
449196
449716
  }
449197
449717
  }
449198
449718
  next = next.replace(
449199
- /(id="root"[\s\S]*?data-duration=")(\d+(?:\.\d+)?)(")/,
449719
+ /(id="root"[\s\S]*?data-duration=")([^"]*)(")/,
449200
449720
  `$1${totalDuration}$3`
449201
449721
  );
449202
449722
  if (next !== html) {
449203
449723
  await writeFile8(rootPath, next, "utf-8");
449204
449724
  }
449205
449725
  }
449726
+ async function resolveBeatDuration(opts) {
449727
+ const storyboardMin = opts.beatDuration ?? 3;
449728
+ if (!opts.narrationPath) return Number(storyboardMin.toFixed(2));
449729
+ try {
449730
+ const audioDuration = await getAudioDuration(join25(opts.projectDir, opts.narrationPath));
449731
+ return Number(Math.max(storyboardMin, audioDuration + 0.5).toFixed(2));
449732
+ } catch {
449733
+ return Number(storyboardMin.toFixed(2));
449734
+ }
449735
+ }
449736
+ var import_dotenv2;
449206
449737
  var init_scene_build = __esm({
449207
449738
  "../cli/src/commands/_shared/scene-build.ts"() {
449208
449739
  "use strict";
449740
+ import_dotenv2 = __toESM(require_main(), 1);
449209
449741
  init_dist();
449742
+ init_audio();
449210
449743
  init_compose_scenes_skills();
449211
449744
  init_compose_prompts();
449212
449745
  init_agent_host_detect();
449213
449746
  init_scene_render();
449214
449747
  init_storyboard_parse();
449748
+ init_scene_project();
449215
449749
  init_tts_resolve();
449216
449750
  }
449217
449751
  });
@@ -449221,8 +449755,10 @@ var output_exports = {};
449221
449755
  __export(output_exports, {
449222
449756
  COST_ESTIMATES: () => COST_ESTIMATES,
449223
449757
  ExitCode: () => ExitCode,
449758
+ _resetDeprecationMemoryForTesting: () => _resetDeprecationMemoryForTesting,
449224
449759
  apiError: () => apiError,
449225
449760
  authError: () => authError,
449761
+ emitDeprecationWarning: () => emitDeprecationWarning,
449226
449762
  exitWithError: () => exitWithError,
449227
449763
  generalError: () => generalError,
449228
449764
  isJsonMode: () => isJsonMode,
@@ -449367,6 +449903,19 @@ function suggestNext(tip) {
449367
449903
  Tip: ${tip}`));
449368
449904
  }
449369
449905
  }
449906
+ function emitDeprecationWarning(oldName, newName, removeIn) {
449907
+ if (isJsonMode() || isQuietMode()) return;
449908
+ if (!process.stderr.isTTY) return;
449909
+ const key2 = `${oldName}\u2192${newName}`;
449910
+ if (_seenDeprecations.has(key2)) return;
449911
+ _seenDeprecations.add(key2);
449912
+ process.stderr.write(
449913
+ source_default.yellow(`[deprecated] '${oldName}' is deprecated; use '${newName}' instead. Alias will be removed in ${removeIn}.`) + "\n"
449914
+ );
449915
+ }
449916
+ function _resetDeprecationMemoryForTesting() {
449917
+ _seenDeprecations.clear();
449918
+ }
449370
449919
  function outputError(error, details) {
449371
449920
  if (isJsonMode()) {
449372
449921
  console.error(JSON.stringify({ success: false, error, ...details }, null, 2));
@@ -449374,7 +449923,7 @@ function outputError(error, details) {
449374
449923
  console.error(error);
449375
449924
  }
449376
449925
  }
449377
- var ExitCode, PROVIDER_ERROR_HINTS, COST_ESTIMATES;
449926
+ var ExitCode, PROVIDER_ERROR_HINTS, COST_ESTIMATES, _seenDeprecations;
449378
449927
  var init_output = __esm({
449379
449928
  "../cli/src/commands/output.ts"() {
449380
449929
  "use strict";
@@ -449409,7 +449958,7 @@ var init_output = __esm({
449409
449958
  { pattern: /context_length_exceeded|maximum.*context.*length|token.*limit.*exceeded|prompt.*too.*long/i, suggestion: "Input exceeds the model's context window. Shorten the prompt, or use a model with larger context (run 'vibe schema <command>' for options).", retryable: false },
449410
449959
  { pattern: /model.*not.*found|invalid.*model|unknown.*model|model_not_found/i, suggestion: "The specified model is unavailable. Check 'vibe schema <command>' for valid model options.", retryable: false },
449411
449960
  // Provider-specific
449412
- { pattern: /voice.*not.*found|voice_not_found|invalid.*voice.?id/i, suggestion: "Voice ID not found. Run 'vibe audio voices' to list available voices, then pass --voice <id>.", retryable: false },
449961
+ { pattern: /voice.*not.*found|voice_not_found|invalid.*voice.?id/i, suggestion: "Voice ID not found. Run 'vibe audio list-voices' to list available voices, then pass --voice <id>.", retryable: false },
449413
449962
  { pattern: /character.*(count|limit).*exceeded|invalid_character_count/i, suggestion: "Text exceeds the TTS provider's character limit. Shorten the text or split into chunks.", retryable: false },
449414
449963
  { pattern: /invalid.*aspect.*ratio|unsupported.*aspect.*ratio|unsupported.*resolution/i, suggestion: "This aspect ratio or resolution isn't supported by the chosen model. Check 'vibe schema <command>' for supported values.", retryable: false },
449415
449964
  { pattern: /invalid.*file.*format|unsupported.*(format|codec)|unsupported.*media.?type/i, suggestion: "Input file format not supported. Convert to MP4/MP3/PNG first with 'vibe export' or 'ffmpeg'.", retryable: false },
@@ -449460,57 +450009,7 @@ var init_output = __esm({
449460
450009
  // pre-flight). Range covers a 1-beat preview to a 10-beat long-form.
449461
450010
  "compose scenes with skills": { min: 0.05, max: 1.5, unit: "per pipeline" }
449462
450011
  };
449463
- }
449464
- });
449465
-
449466
- // ../cli/src/utils/audio.ts
449467
- async function getAudioDuration(filePath) {
449468
- try {
449469
- return await ffprobeDuration(filePath);
449470
- } catch (error) {
449471
- const message = error instanceof Error ? error.message : String(error);
449472
- throw new Error(`Failed to get audio duration: ${message}`);
449473
- }
449474
- }
449475
- async function getVideoDuration(filePath) {
449476
- try {
449477
- return await ffprobeDuration(filePath);
449478
- } catch (error) {
449479
- const message = error instanceof Error ? error.message : String(error);
449480
- throw new Error(`Failed to get video duration: ${message}`);
449481
- }
449482
- }
449483
- async function extendVideoNaturally(videoPath, targetDuration, outputPath) {
449484
- const videoDuration = await getVideoDuration(videoPath);
449485
- const ratio = targetDuration / videoDuration;
449486
- if (ratio <= 1) {
449487
- const { copyFile: copyFile5 } = await import("node:fs/promises");
449488
- await copyFile5(videoPath, outputPath);
449489
- return;
449490
- }
449491
- if (ratio <= 1.15) {
449492
- const slowFactor = (1 / ratio).toFixed(4);
449493
- await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS`, "-an", outputPath]);
449494
- } else if (ratio <= 1.4) {
449495
- const slowFactor = (1 / ratio).toFixed(4);
449496
- await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
449497
- } else {
449498
- const slowRatio = 0.7;
449499
- const slowedDuration = videoDuration / slowRatio;
449500
- const freezeDuration = targetDuration - slowedDuration;
449501
- if (freezeDuration <= 0) {
449502
- const slowFactor = (1 / ratio).toFixed(4);
449503
- await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
449504
- } else {
449505
- const slowFactor = (1 / slowRatio).toFixed(4);
449506
- await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS,tpad=stop_mode=clone:stop_duration=${freezeDuration.toFixed(2)}`, "-an", outputPath]);
449507
- }
449508
- }
449509
- }
449510
- var init_audio = __esm({
449511
- "../cli/src/utils/audio.ts"() {
449512
- "use strict";
449513
- init_exec_safe();
450012
+ _seenDeprecations = /* @__PURE__ */ new Set();
449514
450013
  }
449515
450014
  });
449516
450015
 
@@ -459164,9 +459663,14 @@ var init_ai_script_pipeline = __esm({
459164
459663
  import { resolve as resolve49 } from "node:path";
459165
459664
  import { readFile as readFile25, writeFile as writeFile33 } from "node:fs/promises";
459166
459665
  function registerVideoCommand(parent) {
459167
- parent.command("video").alias("vid").description("Generate video using AI (Kling, Runway, Veo, or Grok)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option("-p, --provider <provider>", "Provider: fal (Seedance 2.0, default when FAL_KEY set), grok, kling, runway, veo").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)").option("-o, --output <path>", "Output file path (downloads video)").option("-i, --image <path>", "Reference image for image-to-video").option("-d, --duration <sec>", "Duration: 5 or 10 seconds", "5").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)").option("-s, --seed <number>", "Random seed for reproducibility (Runway only)").option("-m, --mode <mode>", "Generation mode: std or pro (Kling only)", "std").option("-n, --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("--ref-images <paths...>", "Reference images for character consistency (Veo 3.1 only, max 3)").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("--runway-model <model>", "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)", "gen4.5").option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
459666
+ parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, or Veo)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option("-p, --provider <provider>", "Provider: seedance (ByteDance Seedance 2.0 via fal.ai), grok, kling, runway, veo. `fal` is a backwards-compatible alias for seedance.").option("-k, --api-key <key>", "API key (or set FAL_KEY / XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)").option("-o, --output <path>", "Output file path (downloads video)").option("-i, --image <path>", "Reference image for image-to-video").option(
459667
+ "-d, --duration <sec>",
459668
+ "Duration in seconds. Seedance accepts 4-15 (`fal` alias supported); Kling accepts 5 or 10; Veo maps to 6 or 8.",
459669
+ "5"
459670
+ ).option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)").option("-s, --seed <number>", "Random seed for reproducibility (Runway only)").option("-m, --mode <mode>", "Generation mode: std or pro (Kling only)", "std").option("--seedance-model <model>", "Seedance variant: quality or fast (fal.ai only)", "quality").option("-n, --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("--ref-images <paths...>", "Reference images for character consistency (Veo 3.1 only, max 3)").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("--runway-model <model>", "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)", "gen4.5").option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
459168
459671
  Examples:
459169
- $ vibe generate video "dancing cat" -o cat.mp4 # Grok (default)
459672
+ $ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
459673
+ $ vibe gen vid "cinematic city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
459170
459674
  $ vibe gen vid "city timelapse" -o city.mp4 -p kling # Kling
459171
459675
  $ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
459172
459676
  $ vibe gen vid "ocean waves" -o waves.mp4 -p veo --resolution 1080p # Veo
@@ -459192,12 +459696,13 @@ Examples:
459192
459696
  if (options.output) {
459193
459697
  validateOutputPath(options.output);
459194
459698
  }
459195
- const validProviders = ["runway", "kling", "veo", "grok", "fal"];
459699
+ const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
459196
459700
  const videoEnvMap = {
459197
459701
  grok: "XAI_API_KEY",
459198
459702
  veo: "GOOGLE_API_KEY",
459199
459703
  kling: "KLING_API_KEY",
459200
459704
  runway: "RUNWAY_API_SECRET",
459705
+ seedance: "FAL_KEY",
459201
459706
  fal: "FAL_KEY"
459202
459707
  };
459203
459708
  let provider;
@@ -459207,7 +459712,7 @@ Examples:
459207
459712
  exitWithError(
459208
459713
  usageError(
459209
459714
  `Invalid provider: ${provider}`,
459210
- `Available providers: ${validProviders.join(", ")}`
459715
+ "Available providers: seedance, grok, kling, runway, veo. `fal` is a backwards-compatible alias for seedance."
459211
459716
  )
459212
459717
  );
459213
459718
  }
@@ -459270,14 +459775,15 @@ Examples:
459270
459775
  data: {
459271
459776
  params: {
459272
459777
  prompt: prompt3,
459273
- provider,
459778
+ provider: provider === "fal" ? "seedance" : provider,
459274
459779
  duration: options.duration,
459275
459780
  ratio: options.ratio,
459276
459781
  image: options.image,
459277
459782
  mode: options.mode,
459278
459783
  negative: options.negative,
459279
459784
  resolution: options.resolution,
459280
- veoModel: options.veoModel
459785
+ veoModel: options.veoModel,
459786
+ seedanceModel: options.seedanceModel
459281
459787
  }
459282
459788
  }
459283
459789
  });
@@ -459288,6 +459794,7 @@ Examples:
459288
459794
  kling: "KLING_API_KEY",
459289
459795
  veo: "GOOGLE_API_KEY",
459290
459796
  grok: "XAI_API_KEY",
459797
+ seedance: "FAL_KEY",
459291
459798
  fal: "FAL_KEY"
459292
459799
  };
459293
459800
  const providerNameMap = {
@@ -459295,7 +459802,8 @@ Examples:
459295
459802
  kling: "Kling",
459296
459803
  veo: "Veo",
459297
459804
  grok: "Grok",
459298
- fal: "fal.ai (Seedance 2.0)"
459805
+ seedance: "Seedance 2.0 via fal.ai",
459806
+ fal: "Seedance 2.0 via fal.ai"
459299
459807
  };
459300
459808
  const envKey = envKeyMap[provider];
459301
459809
  const providerName = providerNameMap[provider];
@@ -459514,15 +460022,15 @@ Examples:
459514
460022
  },
459515
460023
  3e5
459516
460024
  );
459517
- } else if (provider === "fal") {
460025
+ } else if (provider === "fal" || provider === "seedance") {
459518
460026
  const fal = new FalProvider();
459519
460027
  await fal.initialize({ apiKey });
459520
460028
  let falImage = referenceImage;
459521
460029
  if (falImage && falImage.startsWith("data:")) {
459522
- spinner2.text = "Uploading image to ImgBB for fal...";
460030
+ spinner2.text = "Uploading image to ImgBB for Seedance...";
459523
460031
  const imgbbKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
459524
460032
  if (!imgbbKey) {
459525
- spinner2.fail("ImgBB API key required for fal image-to-video");
460033
+ spinner2.fail("ImgBB API key required for Seedance image-to-video");
459526
460034
  exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
459527
460035
  }
459528
460036
  const base64Data = falImage.split(",")[1];
@@ -459534,8 +460042,9 @@ Examples:
459534
460042
  }
459535
460043
  falImage = uploadResult.url;
459536
460044
  }
459537
- spinner2.text = "Generating video with Seedance 2.0 (this may take 1-3 minutes)...";
459538
- const falModel = options.model === "fast" ? "seedance-2.0-fast" : "seedance-2.0";
460045
+ spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
460046
+ const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
460047
+ const falModel = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
459539
460048
  result = await fal.generateVideo(prompt3, {
459540
460049
  prompt: prompt3,
459541
460050
  referenceImage: falImage,
@@ -459660,7 +460169,8 @@ var init_generate = __esm({
459660
460169
  Examples:
459661
460170
  $ vibe generate image "a sunset over the ocean" -o sunset.png
459662
460171
  $ vibe generate image "logo design" -o logo.png -p openai
459663
- $ vibe generate video "dancing cat" -o cat.mp4 # Grok (default, native audio)
460172
+ $ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
460173
+ $ vibe generate video "city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
459664
460174
  $ vibe generate video "city timelapse" -o city.mp4 -p kling # Kling
459665
460175
  $ vibe generate video "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
459666
460176
  $ vibe generate speech "Hello world" -o hello.mp3
@@ -459670,7 +460180,8 @@ Examples:
459670
460180
  API Keys (per provider):
459671
460181
  GOOGLE_API_KEY Image (default), Veo video
459672
460182
  OPENAI_API_KEY Image (-p openai)
459673
- XAI_API_KEY Grok image/video (default video)
460183
+ FAL_KEY Seedance video (-p seedance, default video)
460184
+ XAI_API_KEY Grok image/video
459674
460185
  KLING_API_KEY Kling video (-p kling)
459675
460186
  RUNWAY_API_SECRET Runway video (-p runway)
459676
460187
  ELEVENLABS_API_KEY Speech, sound effects, music
@@ -459718,12 +460229,20 @@ async function executeVideoGenerate(options) {
459718
460229
  negative,
459719
460230
  resolution,
459720
460231
  veoModel = "3.1-fast",
460232
+ seedanceModel = "quality",
459721
460233
  output: output3,
459722
460234
  wait = true,
459723
460235
  apiKey
459724
460236
  } = options;
459725
460237
  try {
459726
- const envKeyMap = { grok: "XAI_API_KEY", runway: "RUNWAY_API_SECRET", kling: "KLING_API_KEY", veo: "GOOGLE_API_KEY" };
460238
+ const envKeyMap = {
460239
+ grok: "XAI_API_KEY",
460240
+ runway: "RUNWAY_API_SECRET",
460241
+ kling: "KLING_API_KEY",
460242
+ veo: "GOOGLE_API_KEY",
460243
+ seedance: "FAL_KEY",
460244
+ fal: "FAL_KEY"
460245
+ };
459727
460246
  const key2 = apiKey || process.env[envKeyMap[provider] || ""];
459728
460247
  if (!key2) return { success: false, error: `${envKeyMap[provider]} required for ${provider}` };
459729
460248
  let referenceImage;
@@ -459735,7 +460254,36 @@ async function executeVideoGenerate(options) {
459735
460254
  const mimeType = mimeTypes[ext || "png"] || "image/png";
459736
460255
  referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
459737
460256
  }
459738
- if (provider === "runway") {
460257
+ if (provider === "seedance" || provider === "fal") {
460258
+ const fal = new FalProvider();
460259
+ await fal.initialize({ apiKey: key2 });
460260
+ let falImage = referenceImage;
460261
+ if (falImage && falImage.startsWith("data:")) {
460262
+ const imgbbKey = process.env.IMGBB_API_KEY;
460263
+ if (!imgbbKey) return { success: false, error: "IMGBB_API_KEY required for Seedance image-to-video" };
460264
+ const base64Data = falImage.split(",")[1];
460265
+ const uploadResult = await uploadToImgbb(Buffer.from(base64Data, "base64"), imgbbKey);
460266
+ if (!uploadResult.success || !uploadResult.url) return { success: false, error: `ImgBB upload failed: ${uploadResult.error}` };
460267
+ falImage = uploadResult.url;
460268
+ }
460269
+ const model = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
460270
+ const result = await fal.generateVideo(prompt3, {
460271
+ prompt: prompt3,
460272
+ referenceImage: falImage,
460273
+ duration,
460274
+ aspectRatio: ratio,
460275
+ negativePrompt: negative,
460276
+ model
460277
+ });
460278
+ if (result.status === "failed") return { success: false, error: result.error || "Seedance generation failed" };
460279
+ let outputPath;
460280
+ if (output3 && result.videoUrl) {
460281
+ const buffer = await downloadVideo(result.videoUrl, key2);
460282
+ outputPath = resolve50(process.cwd(), output3);
460283
+ await writeFile34(outputPath, buffer);
460284
+ }
460285
+ return { success: true, taskId: result.id, status: "completed", videoUrl: result.videoUrl, outputPath, provider: "seedance" };
460286
+ } else if (provider === "runway") {
459739
460287
  const runway = new RunwayProvider();
459740
460288
  await runway.initialize({ apiKey: key2 });
459741
460289
  const result = await runway.generateVideo(prompt3, {
@@ -460950,334 +461498,8 @@ function visualStyleNames() {
460950
461498
  return STYLES.map((s) => `"${s.name}"`).join(", ");
460951
461499
  }
460952
461500
 
460953
- // ../cli/src/commands/_shared/scene-project.ts
460954
- var import_yaml = __toESM(require_dist(), 1);
460955
- import { mkdir, readFile, writeFile, access } from "node:fs/promises";
460956
- import { resolve, basename } from "node:path";
460957
- var ASPECT_DIMS = {
460958
- "16:9": { width: 1920, height: 1080 },
460959
- "9:16": { width: 1080, height: 1920 },
460960
- "1:1": { width: 1080, height: 1080 },
460961
- "4:5": { width: 1080, height: 1350 }
460962
- };
460963
- function aspectToDims(aspect) {
460964
- return ASPECT_DIMS[aspect];
460965
- }
460966
- function defaultVibeProjectConfig(name) {
460967
- return {
460968
- name,
460969
- aspect: "16:9",
460970
- defaultSceneDuration: 5,
460971
- providers: { image: null, tts: null, transcribe: null },
460972
- budget: { maxUsd: 0 }
460973
- };
460974
- }
460975
- function buildHyperframesConfig() {
460976
- return {
460977
- $schema: "https://hyperframes.heygen.com/schema/hyperframes.json",
460978
- registry: "https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry",
460979
- paths: {
460980
- blocks: "compositions",
460981
- components: "compositions/components",
460982
- assets: "assets"
460983
- }
460984
- };
460985
- }
460986
- function buildHyperframesMeta(name, now = /* @__PURE__ */ new Date()) {
460987
- return { id: name, name, createdAt: now.toISOString() };
460988
- }
460989
- function mergeHyperframesConfig(existing, defaults) {
460990
- const out = { ...defaults, ...existing };
460991
- if (existing.paths || defaults.paths) {
460992
- out.paths = { ...defaults.paths ?? {}, ...existing.paths ?? {} };
460993
- }
460994
- return out;
460995
- }
460996
- function buildEmptyRootHtml(opts) {
460997
- const { width, height } = ASPECT_DIMS[opts.aspect];
460998
- return `<!doctype html>
460999
- <html lang="en">
461000
- <head>
461001
- <meta charset="UTF-8" />
461002
- <meta name="viewport" content="width=${width}, height=${height}" />
461003
- <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
461004
- <style>
461005
- * { margin: 0; padding: 0; box-sizing: border-box; }
461006
- html, body {
461007
- margin: 0;
461008
- width: ${width}px;
461009
- height: ${height}px;
461010
- overflow: hidden;
461011
- background: #000;
461012
- }
461013
- </style>
461014
- </head>
461015
- <body>
461016
- <div
461017
- id="root"
461018
- data-composition-id="main"
461019
- data-start="0"
461020
- data-duration="${opts.duration}"
461021
- data-width="${width}"
461022
- data-height="${height}"
461023
- >
461024
- <!-- Scenes added via \`vibe scene add\` are inserted here. -->
461025
- <!-- Each scene reference: data-composition-id, data-composition-src, data-start, data-duration, data-track-index. -->
461026
- <!-- See compositions/*.html for sub-composition contents. -->
461027
-
461028
- </div>
461029
-
461030
- <script>
461031
- window.__timelines = window.__timelines || {};
461032
- window.__timelines["main"] = gsap.timeline({ paused: true });
461033
- </script>
461034
- </body>
461035
- </html>
461036
- `;
461037
- }
461038
- function buildDesignMd(opts) {
461039
- const { name, style } = opts;
461040
- const intro = style ? `Visual identity for **${name}**, scaffolded from the **${style.name}** style (after ${style.designer}). Customise freely \u2014 this file is the single source of truth for every scene's palette, typography, and motion.` : `Visual identity for **${name}**. Fill the sections below before authoring any scene HTML or generating any backdrop. Pick a named style with \`vibe scene styles\` if you want a credible starting point.`;
461041
- const moodLine = style ? `**Mood:** ${style.mood} \xB7 **Best for:** ${style.bestFor}` : `**Mood:** _(one line \u2014 what should the viewer FEEL?)_`;
461042
- const palette = style ? `${style.palette.map((c) => `- \`${c}\``).join("\n")}
461043
-
461044
- ${style.paletteNotes}` : `- _hex_ \u2014 primary
461045
- - _hex_ \u2014 accent
461046
-
461047
- _2\u20133 colours max. Declare explicit hex values; never name colours abstractly._`;
461048
- const typography = style ? style.typography : `_One family, two weights. State the role of each (headline / label / body)._`;
461049
- const composition = style ? style.composition : `_Grid? Centered? Layered? How does negative space behave?_`;
461050
- const motion = style ? `${style.motion}
461051
-
461052
- **GSAP signature:** ${style.gsapSignature}` : `_How fast? Snappy or fluid? Overshoot or precision?_
461053
-
461054
- **GSAP signature:** _e.g. \`expo.out\`, \`sine.inOut\`, \`back.out(1.8)\`_`;
461055
- const transition = style ? style.transition : `_Which Hyperframes shader matches the energy? (Cinematic Zoom, Cross-Warp Morph, Glitch, Domain Warp, \u2026)_`;
461056
- const avoid = style ? style.avoid.map((a) => `- ${a}`).join("\n") : `- _anti-pattern 1_
461057
- - _anti-pattern 2_
461058
- - _anti-pattern 3_`;
461059
- return `# ${name} \u2014 Design
461060
-
461061
- > **Hard-gate.** This file defines the visual identity of every scene.
461062
- > Author it before generating any HTML, backdrop image, or motion.
461063
- > The Hyperframes \`hyperframes\` skill enforces this: scenes that
461064
- > contradict DESIGN.md are rejected.
461065
-
461066
- ${intro}
461067
-
461068
- ## Style
461069
-
461070
- ${moodLine}
461071
-
461072
- ## Palette
461073
-
461074
- ${palette}
461075
-
461076
- ## Typography
461077
-
461078
- ${typography}
461079
-
461080
- ## Composition
461081
-
461082
- ${composition}
461083
-
461084
- ## Motion
461085
-
461086
- ${motion}
461087
-
461088
- ## Transition
461089
-
461090
- ${transition}
461091
-
461092
- ## What NOT to do
461093
-
461094
- ${avoid}
461095
-
461096
- ---
461097
-
461098
- _Browse other named styles: \`vibe scene styles\`_
461099
- ${style ? `_This file was seeded by \`vibe scene init --visual-style "${style.name}"\`._` : `_Seed this file from a named style: \`vibe scene init <dir> --visual-style "<name>"\`._`}
461100
- `;
461101
- }
461102
- function buildProjectClaudeMd(name) {
461103
- return `# ${name} \u2014 Scene Authoring Project
461104
-
461105
- This project is **bilingual**: it works with both VibeFrame (\`vibe\`) and
461106
- HeyGen Hyperframes (\`hyperframes\`). You can run either CLI inside this
461107
- directory.
461108
-
461109
- ## Visual identity hard-gate
461110
-
461111
- **Author \`DESIGN.md\` before any scene HTML.** It defines palette,
461112
- typography, motion, and transition rules. Both the agent-driven path and
461113
- the fallback emit reference it; scenes that contradict DESIGN.md are
461114
- rejected by the Hyperframes \`hyperframes\` skill.
461115
-
461116
- Browse named styles: \`vibe scene styles\`. Re-seed from one with
461117
- \`vibe scene init . --visual-style "Swiss Pulse"\` (idempotent).
461118
-
461119
- ## Skills \u2014 USE THESE FIRST
461120
-
461121
- **Always invoke the relevant skill before authoring scenes.** Skills encode
461122
- framework-specific patterns (GSAP timeline registration, data-attribute
461123
- semantics, VibeFrame pipeline conventions) that are NOT in generic web docs.
461124
-
461125
- | Skill | Command | When to use |
461126
- | ----------------- | ---------------- | ------------------------------------------------------------------------------------- |
461127
- | **hyperframes** | \`/hyperframes\` | Cinematic-quality composition \u2014 DESIGN.md hard-gate, named styles, motion principles |
461128
- | **vibe-scene** | \`/vibe-scene\` | VibeFrame's authoring loop, AI assets, lint feedback, pipeline integration |
461129
- | **gsap** | \`/gsap\` | GSAP tweens, timelines, easing |
461130
-
461131
- Install the Hyperframes skills once per machine:
461132
-
461133
- \`\`\`bash
461134
- npx skills add heygen-com/hyperframes
461135
- \`\`\`
461136
-
461137
- Restart your agent session (or reload the skill list) after installing.
461138
- If skills aren't available, follow the **Key Rules** below \u2014 they cover
461139
- the framework-level minimum, not the cinematic craft layer.
461140
-
461141
- ## Project structure
461142
-
461143
- - \`DESIGN.md\` \u2014 visual identity contract (palette, type, motion, transitions)
461144
- - \`index.html\` \u2014 root composition (timeline)
461145
- - \`compositions/scene-*.html\` \u2014 per-scene HTML authored by you or the agent
461146
- - \`assets/\` \u2014 shared media (narration audio, images, video)
461147
- - \`transcript.json\` \u2014 Whisper word-level transcript (if narration exists)
461148
- - \`hyperframes.json\` \u2014 HF registry config (speak to both toolchains)
461149
- - \`vibe.project.yaml\` \u2014 VibeFrame config (providers, budget)
461150
- - \`renders/\` \u2014 output MP4s
461151
-
461152
- ## Commands
461153
-
461154
- \`\`\`bash
461155
- vibe scene add <name> --narration "..." --visuals "..." # Author a new scene via AI
461156
- vibe scene lint # Validate scenes (in-process HF linter)
461157
- vibe scene render # Render to MP4
461158
-
461159
- # Hyperframes CLI (if installed \u2014 works in this project too)
461160
- npx hyperframes preview
461161
- npx hyperframes render
461162
- \`\`\`
461163
-
461164
- ## Key Rules (for hand-authored scene HTML)
461165
-
461166
- 1. Every timed element needs \`data-start\`, \`data-duration\`, and \`data-track-index\`.
461167
- 2. Elements with timing **MUST** have \`class="clip"\` \u2014 the framework uses this for visibility control.
461168
- 3. Timelines must be paused and registered on \`window.__timelines\`:
461169
- \`\`\`js
461170
- window.__timelines = window.__timelines || {};
461171
- window.__timelines["composition-id"] = gsap.timeline({ paused: true });
461172
- \`\`\`
461173
- 4. Videos use \`muted\` with a separate \`<audio>\` element for the audio track.
461174
- 5. Sub-compositions use \`data-composition-src="compositions/file.html"\`.
461175
- 6. Only deterministic logic \u2014 no \`Date.now()\`, \`Math.random()\`, or network fetches.
461176
-
461177
- ## Linting \u2014 run after changes
461178
-
461179
- \`\`\`bash
461180
- vibe scene lint # preferred \u2014 in-process, no network
461181
- vibe scene lint --fix # auto-fix mechanical issues
461182
- vibe scene lint --json # structured output for agent loops
461183
- \`\`\`
461184
- `;
461185
- }
461186
- function buildSceneGitignore() {
461187
- return `# VibeFrame caches
461188
- .vibeframe/cache/
461189
- .vibeframe/checkpoints/
461190
-
461191
- # Render outputs
461192
- renders/*.mp4
461193
- tmp/
461194
-
461195
- # OS / editor
461196
- .DS_Store
461197
- *.log
461198
- `;
461199
- }
461200
- async function pathExists(p) {
461201
- try {
461202
- await access(p);
461203
- return true;
461204
- } catch {
461205
- return false;
461206
- }
461207
- }
461208
- async function scaffoldSceneProject(opts) {
461209
- const dir = resolve(opts.dir);
461210
- const name = opts.name ?? basename(dir);
461211
- const aspect = opts.aspect ?? "16:9";
461212
- const duration = opts.duration ?? 10;
461213
- const now = opts.now ?? /* @__PURE__ */ new Date();
461214
- await mkdir(dir, { recursive: true });
461215
- await mkdir(resolve(dir, "compositions"), { recursive: true });
461216
- await mkdir(resolve(dir, "assets"), { recursive: true });
461217
- const created = [];
461218
- const skipped2 = [];
461219
- const merged = [];
461220
- const hfPath = resolve(dir, "hyperframes.json");
461221
- const hfDefaults = buildHyperframesConfig();
461222
- if (await pathExists(hfPath)) {
461223
- const existingRaw = await readFile(hfPath, "utf-8");
461224
- const existing = JSON.parse(existingRaw);
461225
- const mergedConfig = mergeHyperframesConfig(existing, hfDefaults);
461226
- await writeFile(hfPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf-8");
461227
- merged.push(hfPath);
461228
- } else {
461229
- await writeFile(hfPath, JSON.stringify(hfDefaults, null, 2) + "\n", "utf-8");
461230
- created.push(hfPath);
461231
- }
461232
- const metaPath = resolve(dir, "meta.json");
461233
- if (await pathExists(metaPath)) {
461234
- skipped2.push(metaPath);
461235
- } else {
461236
- await writeFile(metaPath, JSON.stringify(buildHyperframesMeta(name, now), null, 2) + "\n", "utf-8");
461237
- created.push(metaPath);
461238
- }
461239
- const rootPath = resolve(dir, "index.html");
461240
- if (await pathExists(rootPath)) {
461241
- skipped2.push(rootPath);
461242
- } else {
461243
- await writeFile(rootPath, buildEmptyRootHtml({ aspect, duration }), "utf-8");
461244
- created.push(rootPath);
461245
- }
461246
- const vibePath = resolve(dir, "vibe.project.yaml");
461247
- if (await pathExists(vibePath)) {
461248
- skipped2.push(vibePath);
461249
- } else {
461250
- const cfg = { ...defaultVibeProjectConfig(name), aspect };
461251
- await writeFile(vibePath, (0, import_yaml.stringify)(cfg), "utf-8");
461252
- created.push(vibePath);
461253
- }
461254
- const claudePath = resolve(dir, "CLAUDE.md");
461255
- if (await pathExists(claudePath)) {
461256
- skipped2.push(claudePath);
461257
- } else {
461258
- await writeFile(claudePath, buildProjectClaudeMd(name), "utf-8");
461259
- created.push(claudePath);
461260
- }
461261
- const designPath = resolve(dir, "DESIGN.md");
461262
- if (await pathExists(designPath)) {
461263
- skipped2.push(designPath);
461264
- } else {
461265
- await writeFile(
461266
- designPath,
461267
- buildDesignMd({ name, style: opts.visualStyle }),
461268
- "utf-8"
461269
- );
461270
- created.push(designPath);
461271
- }
461272
- const gitignorePath = resolve(dir, ".gitignore");
461273
- if (await pathExists(gitignorePath)) {
461274
- skipped2.push(gitignorePath);
461275
- } else {
461276
- await writeFile(gitignorePath, buildSceneGitignore(), "utf-8");
461277
- created.push(gitignorePath);
461278
- }
461279
- return { created, skipped: skipped2, merged };
461280
- }
461501
+ // ../cli/src/tools/manifest/scene.ts
461502
+ init_scene_project();
461281
461503
 
461282
461504
  // ../cli/src/commands/scene.ts
461283
461505
  init_esm();
@@ -461286,6 +461508,7 @@ init_ora();
461286
461508
  var import_yaml5 = __toESM(require_dist(), 1);
461287
461509
  init_dist();
461288
461510
  init_tts_resolve();
461511
+ init_scene_project();
461289
461512
  import { basename as basename6, resolve as resolve21, relative as relative7, dirname as dirname17 } from "node:path";
461290
461513
  import { mkdir as mkdir10, readFile as readFile10, writeFile as writeFile10, access as access4, copyFile as copyFile2 } from "node:fs/promises";
461291
461514
  import { existsSync as existsSync28 } from "node:fs";
@@ -461777,6 +462000,7 @@ function deriveInstallHosts(detected) {
461777
462000
  // ../cli/src/commands/scene.ts
461778
462001
  init_compose_prompts();
461779
462002
  var VALID_ASPECTS2 = ["16:9", "9:16", "1:1", "4:5"];
462003
+ var VALID_SCENE_INIT_PROFILES = ["minimal", "agent", "full"];
461780
462004
  function validateAspect(value) {
461781
462005
  if (!VALID_ASPECTS2.includes(value)) {
461782
462006
  exitWithError(usageError(`Invalid aspect ratio: ${value}`, `Valid: ${VALID_ASPECTS2.join(", ")}`));
@@ -461808,30 +462032,42 @@ function validateVisualStyle(value) {
461808
462032
  }
461809
462033
  return found;
461810
462034
  }
461811
- var sceneCommand = new Command("scene").description("Author and render per-scene HTML compositions (Hyperframes backend)").addHelpText("after", `
462035
+ function formatSceneInitProfile(profile) {
462036
+ if (profile === "minimal") return "authoring files only; build will add render scaffold when needed";
462037
+ if (profile === "agent") return "authoring files plus local composition rules for host agents";
462038
+ return "complete authoring, agent, and render scaffold";
462039
+ }
462040
+ var sceneCommand = new Command("scene").description("Lower-level scene authoring (add, lint, styles). For project flow use `vibe init` / `vibe build` / `vibe render`.").addHelpText("after", `
461812
462041
  Examples:
461813
- $ vibe scene init my-video # Scaffold a new project
461814
- $ vibe scene init my-video -r 9:16 -d 30 # Vertical 30s project
461815
462042
  $ vibe scene add intro --style announcement \\
461816
462043
  --headline "Welcome to VibeFrame" # Headline-only scene
461817
462044
  $ vibe scene add overview --narration "VibeFrame turns scripts into video." \\
461818
462045
  --visuals "studio desk, soft lighting" # AI narration + image
461819
- $ vibe scene lint # Validate every scene against Hyperframes rules
462046
+ $ vibe scene lint # Validate every scene against composition rules
461820
462047
  $ vibe scene lint --fix # Auto-fix mechanical issues (e.g. missing class="clip")
461821
462048
  $ vibe scene lint --json # Structured output for agent loops
461822
- $ vibe scene render # Render to renders/<name>-<timestamp>.mp4
461823
- $ vibe scene render -o demo.mp4 --quality high # Custom output path + quality
461824
- $ vibe scene render --fps 60 --format webm # 60fps WebM render
462049
+ $ vibe scene styles # Browse seed visual styles for DESIGN.md
461825
462050
 
461826
- A scene project is bilingual: it works with both \`vibe\` and \`npx hyperframes\`.
462051
+ For the project flow (init / build / render), use the top-level commands.
462052
+ The \`scene init\`, \`scene build\`, and \`scene render\` legacy aliases
462053
+ are still callable but hidden from this help \u2014 they will be removed in v1.0.
461827
462054
  Run 'vibe schema scene.<command>' for structured parameter info.`);
461828
- sceneCommand.command("init").description("Scaffold a new scene project (or safely augment an existing Hyperframes project)").argument("<dir>", "Project directory (created if it doesn't exist)").option("-n, --name <name>", "Project name (defaults to directory basename)").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, 1:1, 4:5", "16:9").option("-d, --duration <sec>", "Default root composition duration (seconds)", "10").option("--visual-style <name>", `Seed DESIGN.md from a named style (browse via \`vibe scene styles\`). E.g. "Swiss Pulse"`).option("--dry-run", "Preview parameters without writing files").action(async (dir, options) => {
462055
+ sceneCommand.command("init", { hidden: true }).description("Scaffold a new scene project (or safely augment an existing project) [legacy \u2014 prefer `vibe init`]").argument("<dir>", "Project directory (created if it doesn't exist)").option("-n, --name <name>", "Project name (defaults to directory basename)").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, 1:1, 4:5", "16:9").option("-d, --duration <sec>", "Default root composition duration (seconds)", "10").option("--visual-style <name>", `Seed DESIGN.md from a named style (browse via \`vibe scene styles\`). E.g. "Swiss Pulse"`).option("--profile <profile>", "Scene profile: minimal (storyboard/design only), agent (recommended), full (render scaffold upfront)", "agent").option("--dry-run", "Preview parameters without writing files").action(async (dir, options) => {
461829
462056
  const startedAt = Date.now();
461830
462057
  const aspect = validateAspect(options.ratio);
461831
462058
  const duration = validateDuration(options.duration);
461832
462059
  const name = options.name ?? basename6(dir.replace(/\/+$/, ""));
461833
462060
  const visualStyle = options.visualStyle ? validateVisualStyle(options.visualStyle) : void 0;
462061
+ const profile = String(options.profile ?? "agent");
462062
+ if (!isSceneScaffoldProfile(profile)) {
462063
+ exitWithError(usageError(`Invalid --profile: ${profile}`, `Must be one of: ${VALID_SCENE_INIT_PROFILES.join(", ")}`));
462064
+ }
462065
+ const groups = describeSceneScaffold({ dir, profile });
461834
462066
  if (options.dryRun) {
462067
+ if (!isJsonMode() && !isQuietMode()) {
462068
+ printSceneInitDryRun({ dir, name, aspect, duration, visualStyleName: visualStyle?.name ?? null, profile, groups });
462069
+ return;
462070
+ }
461835
462071
  outputSuccess({
461836
462072
  command: "scene init",
461837
462073
  startedAt,
@@ -461842,23 +462078,25 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
461842
462078
  name,
461843
462079
  aspect,
461844
462080
  duration,
461845
- visualStyle: visualStyle?.name ?? null
461846
- }
462081
+ visualStyle: visualStyle?.name ?? null,
462082
+ profile
462083
+ },
462084
+ groups
461847
462085
  }
461848
462086
  });
461849
462087
  return;
461850
462088
  }
461851
- const spinner2 = isJsonMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
462089
+ const spinner2 = isJsonMode() || isQuietMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
461852
462090
  try {
461853
- const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle });
462091
+ const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle, profile });
461854
462092
  const detectedIds = detectedAgentHosts().map((h) => h.id);
461855
462093
  const skillHosts = deriveInstallHosts(detectedIds);
461856
462094
  const projectAbs = resolve21(dir);
461857
- const skillResult = await installHyperframesSkill({
462095
+ const skillResult = profile === "agent" || profile === "full" ? await installHyperframesSkill({
461858
462096
  projectDir: projectAbs,
461859
462097
  hosts: skillHosts
461860
- });
461861
- if (isJsonMode()) {
462098
+ }) : { success: true, files: [], bundleVersion: "not-installed" };
462099
+ if (isJsonMode() || isQuietMode()) {
461862
462100
  outputSuccess({
461863
462101
  command: "scene init",
461864
462102
  startedAt,
@@ -461868,16 +462106,27 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
461868
462106
  aspect,
461869
462107
  duration,
461870
462108
  visualStyle: visualStyle?.name ?? null,
462109
+ profile,
461871
462110
  created: result.created,
461872
462111
  merged: result.merged,
461873
462112
  skipped: result.skipped,
462113
+ groups: result.groups,
461874
462114
  skillFiles: skillResult.files,
461875
462115
  skillBundleVersion: skillResult.bundleVersion
461876
462116
  }
461877
462117
  });
461878
462118
  return;
461879
462119
  }
461880
- spinner2?.succeed(source_default.green(`Scene project ready: ${dir}`));
462120
+ spinner2?.succeed(source_default.green(`Video project ready: ${dir}`));
462121
+ console.log();
462122
+ console.log(source_default.bold.cyan("Edit first"));
462123
+ console.log(source_default.dim("\u2500".repeat(60)));
462124
+ console.log(` ${source_default.bold("STORYBOARD.md")} ${source_default.dim("# beats: narration, backdrop, minimum duration")}`);
462125
+ console.log(` ${source_default.bold("DESIGN.md")} ${source_default.dim("# palette, typography, motion rules")}`);
462126
+ console.log();
462127
+ console.log(source_default.bold.cyan("Profile"));
462128
+ console.log(source_default.dim("\u2500".repeat(60)));
462129
+ console.log(` ${source_default.bold(profile)} ${source_default.dim(formatSceneInitProfile(profile))}`);
461881
462130
  console.log();
461882
462131
  console.log(source_default.bold.cyan("Files"));
461883
462132
  console.log(source_default.dim("\u2500".repeat(60)));
@@ -461888,7 +462137,7 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
461888
462137
  const skillSkipped = skillResult.files.filter((f) => f.status === "skipped-exists");
461889
462138
  if (skillWritten.length + skillSkipped.length > 0) {
461890
462139
  console.log();
461891
- console.log(source_default.bold.cyan("Hyperframes skill"));
462140
+ console.log(source_default.bold.cyan("Composition rules"));
461892
462141
  console.log(source_default.dim("\u2500".repeat(60)));
461893
462142
  for (const f of skillWritten) console.log(source_default.green(" +"), f.path);
461894
462143
  for (const f of skillSkipped) console.log(source_default.dim(" \xB7"), f.path, source_default.dim("(kept existing)"));
@@ -461902,16 +462151,39 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
461902
462151
  } else {
461903
462152
  console.log(` ${source_default.cyan("vibe scene styles")} ${source_default.dim("# pick a named style for DESIGN.md")}`);
461904
462153
  }
461905
- console.log(` ${source_default.dim("Your agent now has Hyperframes rules in")} ${source_default.cyan("SKILL.md")} ${source_default.dim("\u2014 ask it to author scene HTML directly.")}`);
462154
+ if (profile === "agent" || profile === "full") {
462155
+ console.log(` ${source_default.dim("Your agent now has composition rules in")} ${source_default.cyan("SKILL.md")} ${source_default.dim("\u2014 ask it to author scene HTML directly.")}`);
462156
+ } else {
462157
+ console.log(` ${source_default.cyan("vibe scene install-skill")} ${source_default.dim("# add agent authoring rules later")}`);
462158
+ }
461906
462159
  console.log(` ${source_default.cyan("vibe scene add")} <name> ${source_default.dim("# fallback: 5-preset emit (no agent)")}`);
461907
- console.log(` ${source_default.cyan("vibe scene lint")} ${source_default.dim("# validate HTML")}`);
461908
- console.log(` ${source_default.cyan("vibe scene render")} ${source_default.dim("# render to MP4")}`);
462160
+ console.log(` ${source_default.cyan("vibe build")} ${source_default.dim("# build STORYBOARD.md into scenes/assets")}`);
462161
+ console.log(` ${source_default.cyan("vibe render")} ${source_default.dim("# render to video")}`);
461909
462162
  } catch (error) {
461910
462163
  spinner2?.fail("Failed to scaffold scene project");
461911
462164
  const msg = error instanceof Error ? error.message : String(error);
461912
462165
  exitWithError(generalError(`Failed to scaffold: ${msg}`));
461913
462166
  }
461914
462167
  });
462168
+ function printSceneInitDryRun(opts) {
462169
+ console.log();
462170
+ console.log(source_default.bold.cyan("VibeFrame Scene Init - dry run"));
462171
+ console.log(source_default.dim("-".repeat(60)));
462172
+ console.log(` Project: ${source_default.bold(opts.dir)}`);
462173
+ console.log(` Name: ${source_default.bold(opts.name)}`);
462174
+ console.log(` Profile: ${source_default.bold(opts.profile)} ${source_default.dim(formatSceneInitProfile(opts.profile))}`);
462175
+ console.log(` Aspect: ${opts.aspect}`);
462176
+ console.log(` Duration: ${opts.duration}s`);
462177
+ console.log(` Visual style: ${opts.visualStyleName ?? "none"}`);
462178
+ console.log();
462179
+ console.log(source_default.bold.cyan("Files that would be prepared"));
462180
+ console.log(source_default.dim("-".repeat(60)));
462181
+ for (const file of opts.groups.authoring) console.log(` authoring ${file}`);
462182
+ for (const file of opts.groups.agent) console.log(` agent ${file}`);
462183
+ for (const file of opts.groups.render) console.log(` render ${file}`);
462184
+ console.log();
462185
+ console.log(source_default.dim("No files were written."));
462186
+ }
461915
462187
  var VALID_INSTALL_SKILL_HOSTS = ["claude-code", "cursor", "auto", "all"];
461916
462188
  sceneCommand.command("install-skill").description("Install the Hyperframes skill into a scene project so the host agent can read it (Phase H1)").argument("[project-dir]", "Project directory containing STORYBOARD.md / DESIGN.md", ".").option("--host <id>", `Host layout target: ${VALID_INSTALL_SKILL_HOSTS.join(" | ")}`, "auto").option("--force", "Overwrite existing skill files (default: skip-on-exist)").option("--dry-run", "Preview which files would be written without changing anything").action(async (projectDirArg, options) => {
461917
462189
  const startedAt = Date.now();
@@ -462457,7 +462729,7 @@ async function executeSceneAdd(opts) {
462457
462729
  transcriptWordCount
462458
462730
  };
462459
462731
  }
462460
- sceneCommand.command("lint").description("Validate scene HTML against Hyperframes rules (in-process, no Chrome required)").argument("[root]", "Root composition file relative to --project", "index.html").option("--project <dir>", "Project directory", ".").option("--fix", 'Apply mechanical auto-fixes (currently: missing class="clip")').action(async (root2, options) => {
462732
+ sceneCommand.command("lint").description("Validate scene HTML against composition rules (in-process, no Chrome required)").argument("[root]", "Root composition file relative to --project", "index.html").option("--project <dir>", "Project directory", ".").option("--fix", 'Apply mechanical auto-fixes (currently: missing class="clip")').action(async (root2, options) => {
462461
462733
  const startedAt = Date.now();
462462
462734
  const projectDir = resolve21(options.project);
462463
462735
  if (!await rootExists(projectDir, root2)) {
@@ -462551,7 +462823,7 @@ function validateWorkers(value) {
462551
462823
  }
462552
462824
  return n;
462553
462825
  }
462554
- sceneCommand.command("render").description("Render a scene project to MP4/WebM/MOV via the Hyperframes producer (requires Chrome)").argument("[root]", "Root composition file relative to --project", "index.html").option("--project <dir>", "Project directory", ".").option("-o, --out <path>", "Output file (default: renders/<name>-<timestamp>.<format>)").option("--fps <n>", `Frames per second: ${VALID_FPS.join("|")}`, "30").option("--quality <q>", `Quality preset: ${VALID_QUALITIES.join("|")}`, "standard").option("--format <f>", `Output container: ${VALID_FORMATS.join("|")}`, "mp4").option("--workers <n>", "Capture workers (1-16, default 1)", "1").option("--dry-run", "Preview parameters without rendering").action(async (root2, options) => {
462826
+ sceneCommand.command("render", { hidden: true }).description("Render a scene project to MP4/WebM/MOV via the Hyperframes producer (requires Chrome) [legacy \u2014 prefer `vibe render`]").argument("[root]", "Root composition file relative to --project", "index.html").option("--project <dir>", "Project directory", ".").option("-o, --out <path>", "Output file (default: renders/<name>-<timestamp>.<format>)").option("--fps <n>", `Frames per second: ${VALID_FPS.join("|")}`, "30").option("--quality <q>", `Quality preset: ${VALID_QUALITIES.join("|")}`, "standard").option("--format <f>", `Output container: ${VALID_FORMATS.join("|")}`, "mp4").option("--workers <n>", "Capture workers (1-16, default 1)", "1").option("--dry-run", "Preview parameters without rendering").action(async (root2, options) => {
462555
462827
  const startedAt = Date.now();
462556
462828
  const fps = validateFps(options.fps);
462557
462829
  const quality = validateQuality(options.quality);
@@ -462627,7 +462899,7 @@ sceneCommand.command("render").description("Render a scene project to MP4/WebM/M
462627
462899
  }
462628
462900
  }
462629
462901
  });
462630
- sceneCommand.command("build").description("One-shot: read STORYBOARD.md cues, dispatch TTS + image-gen per beat, compose, render to MP4 (v0.60)").argument("[project-dir]", "Project directory containing STORYBOARD.md", ".").option("--mode <mode>", "Build mode: agent (host-agent authors HTML) | batch (CLI's internal LLM authors HTML) | auto (agent if any host detected) [Plan H \u2014 Phase 3]", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "LLM that composes scene HTML in batch mode: claude|openai|gemini (default: auto-resolve from available API keys, claude > gemini > openai)").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|kokoro (overrides frontmatter)").option("--voice <id>", "Voice id (provider-specific \u2014 overrides frontmatter)").option("--image-provider <name>", "Image provider: openai (only one supported in v0.60)").option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").action(async (projectDirArg, options) => {
462902
+ sceneCommand.command("build", { hidden: true }).description("One-shot: read STORYBOARD.md cues, dispatch TTS + image-gen per beat, compose, render to MP4 [legacy \u2014 prefer `vibe build`]").argument("[project-dir]", "Project directory containing STORYBOARD.md", ".").option("--mode <mode>", "Build mode: agent (host-agent authors HTML) | batch (CLI's internal LLM authors HTML) | auto (agent if any host detected) [Plan H \u2014 Phase 3]", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "LLM that composes scene HTML in batch mode: claude|openai|gemini (default: auto-resolve from available API keys, claude > gemini > openai)").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|kokoro (overrides frontmatter)").option("--voice <id>", "Voice id (provider-specific \u2014 overrides frontmatter)").option("--image-provider <name>", "Image provider: openai (only one supported in v0.60)").option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").action(async (projectDirArg, options) => {
462631
462903
  const startedAt = Date.now();
462632
462904
  const projectDir = resolve21(projectDirArg);
462633
462905
  if (options.dryRun) {
@@ -462744,7 +463016,7 @@ sceneCommand.command("build").description("One-shot: read STORYBOARD.md cues, di
462744
463016
  }
462745
463017
  }
462746
463018
  console.log();
462747
- console.log(source_default.dim("Once you've authored each beat's HTML, re-run `vibe scene build` to lint + render."));
463019
+ console.log(source_default.dim("Once you've authored each beat's HTML, re-run `vibe build` to lint + render."));
462748
463020
  console.log(source_default.dim("Or pass `--mode batch` to use the internal LLM compose path instead."));
462749
463021
  return;
462750
463022
  }
@@ -462883,7 +463155,7 @@ var sceneInitTool = defineTool({
462883
463155
  name: "scene_init",
462884
463156
  category: "scene",
462885
463157
  cost: "free",
462886
- description: "Scaffold a new bilingual VibeFrame + Hyperframes scene project. Creates index.html, hyperframes.json, vibe.project.yaml, compositions/, assets/, .gitignore, and a project-local CLAUDE.md. Idempotent: re-running on an existing Hyperframes project merges hyperframes.json instead of overwriting. No API keys required.",
463158
+ description: "Scaffold a new VibeFrame video scene project. Supports minimal, agent, and full profiles; full includes the current HTML render backend metadata. Idempotent: re-running keeps user-authored files and merges backend config instead of overwriting. No API keys required.",
462887
463159
  schema: sceneInitSchema,
462888
463160
  async execute(args, ctx) {
462889
463161
  const dir = resolve23(ctx.workingDirectory, args.dir);
@@ -465041,38 +465313,44 @@ function findUnresolvedRefs(params, availableStepIds) {
465041
465313
 
465042
465314
  // ../cli/src/pipeline/executor.ts
465043
465315
  init_output();
465044
- var ACTION_TO_COMMAND = {
465045
- "generate-image": "generate image",
465046
- "generate-video": "generate video",
465047
- "generate-tts": "generate speech",
465048
- "generate-sfx": "generate sound-effect",
465049
- "generate-music": "generate music",
465050
- "generate-storyboard": "generate storyboard",
465051
- "generate-motion": "generate motion",
465052
- "edit-silence-cut": "edit silence-cut",
465053
- "edit-jump-cut": "edit jump-cut",
465054
- "edit-caption": "edit caption",
465055
- "edit-noise-reduce": "edit noise-reduce",
465056
- "edit-fade": "edit fade",
465057
- "edit-translate-srt": "edit translate-srt",
465058
- "edit-text-overlay": "edit text-overlay",
465059
- "edit-grade": "edit grade",
465060
- "edit-speed-ramp": "edit speed-ramp",
465061
- "edit-reframe": "edit reframe",
465062
- "edit-interpolate": "edit interpolate",
465063
- "edit-upscale": "edit upscale-video",
465064
- "edit-image": "edit image",
465065
- "audio-transcribe": "audio transcribe",
465066
- "detect-scenes": "detect scenes",
465067
- "detect-silence": "detect silence",
465068
- "detect-beats": "detect beats",
465069
- "analyze-media": "analyze media",
465070
- "analyze-video": "analyze video",
465071
- "review-video": "analyze review",
465072
- "compose-scenes-with-skills": "compose scenes with skills"
465316
+ var ACTION_METADATA = {
465317
+ "generate-image": { id: "generate-image", title: "Generate image", category: "generate", command: "generate image", outputs: ["image"] },
465318
+ "generate-video": { id: "generate-video", title: "Generate video", category: "generate", command: "generate video", outputs: ["video"], requiredKeys: ["provider-dependent"] },
465319
+ "generate-tts": { id: "generate-tts", title: "Generate speech", category: "generate", command: "generate speech", outputs: ["audio"] },
465320
+ "generate-sfx": { id: "generate-sfx", title: "Generate sound effect", category: "generate", command: "generate sound-effect", outputs: ["audio"] },
465321
+ "generate-music": { id: "generate-music", title: "Generate music", category: "generate", command: "generate music", outputs: ["audio"] },
465322
+ "generate-storyboard": { id: "generate-storyboard", title: "Generate storyboard", category: "generate", command: "generate storyboard", outputs: ["storyboard"] },
465323
+ "generate-motion": { id: "generate-motion", title: "Generate motion", category: "generate", command: "generate motion", outputs: ["code", "video"] },
465324
+ "edit-silence-cut": { id: "edit-silence-cut", title: "Cut silence", category: "edit", command: "edit silence-cut", outputs: ["video"] },
465325
+ "edit-jump-cut": { id: "edit-jump-cut", title: "Jump cut", category: "edit", command: "edit jump-cut", outputs: ["video"] },
465326
+ "edit-caption": { id: "edit-caption", title: "Caption video", category: "edit", command: "edit caption", outputs: ["video", "srt"] },
465327
+ "edit-noise-reduce": { id: "edit-noise-reduce", title: "Reduce noise", category: "edit", command: "edit noise-reduce", outputs: ["video"] },
465328
+ "edit-fade": { id: "edit-fade", title: "Add fade", category: "edit", command: "edit fade", outputs: ["video"] },
465329
+ "edit-translate-srt": { id: "edit-translate-srt", title: "Translate subtitles", category: "edit", command: "edit translate-srt", outputs: ["srt"] },
465330
+ "edit-text-overlay": { id: "edit-text-overlay", title: "Add text overlay", category: "edit", command: "edit text-overlay", outputs: ["video"] },
465331
+ "edit-grade": { id: "edit-grade", title: "Color grade", category: "edit", command: "edit grade", outputs: ["video"] },
465332
+ "edit-speed-ramp": { id: "edit-speed-ramp", title: "Speed ramp", category: "edit", command: "edit speed-ramp", outputs: ["video"] },
465333
+ "edit-reframe": { id: "edit-reframe", title: "Reframe video", category: "edit", command: "edit reframe", outputs: ["video"] },
465334
+ "edit-interpolate": { id: "edit-interpolate", title: "Interpolate frames", category: "edit", command: "edit interpolate", outputs: ["video"] },
465335
+ "edit-upscale": { id: "edit-upscale", title: "Upscale video", category: "edit", command: "edit upscale-video", outputs: ["video"] },
465336
+ "edit-image": { id: "edit-image", title: "Edit image", category: "edit", command: "edit image", outputs: ["image"] },
465337
+ "audio-transcribe": { id: "audio-transcribe", title: "Transcribe audio", category: "audio", command: "audio transcribe", outputs: ["transcript", "srt"] },
465338
+ "audio-isolate": { id: "audio-isolate", title: "Isolate audio", category: "audio", outputs: ["audio"] },
465339
+ "audio-dub": { id: "audio-dub", title: "Dub audio", category: "audio", outputs: ["audio", "video"] },
465340
+ "audio-duck": { id: "audio-duck", title: "Duck audio", category: "audio", outputs: ["video"] },
465341
+ "detect-scenes": { id: "detect-scenes", title: "Detect scenes", category: "detect", command: "detect scenes", outputs: ["json"] },
465342
+ "detect-silence": { id: "detect-silence", title: "Detect silence", category: "detect", command: "detect silence", outputs: ["json"] },
465343
+ "detect-beats": { id: "detect-beats", title: "Detect beats", category: "detect", command: "detect beats", outputs: ["json"] },
465344
+ "analyze-media": { id: "analyze-media", title: "Analyze media", category: "analyze", command: "analyze media", outputs: ["json"] },
465345
+ "analyze-video": { id: "analyze-video", title: "Analyze video", category: "analyze", command: "analyze video", outputs: ["json"] },
465346
+ "review-video": { id: "review-video", title: "Review video", category: "analyze", command: "analyze review", outputs: ["json"] },
465347
+ "compose-scenes-with-skills": { id: "compose-scenes-with-skills", title: "Compose scenes with skills", category: "scene", command: "compose scenes with skills", outputs: ["html"] },
465348
+ "scene-build": { id: "scene-build", title: "Build scene project", category: "scene", outputs: ["video", "html", "assets"] },
465349
+ "scene-render": { id: "scene-render", title: "Render scene project", category: "scene", outputs: ["video"] },
465350
+ export: { id: "export", title: "Export project", category: "export", outputs: ["video"] }
465073
465351
  };
465074
465352
  function maxCostFor(action) {
465075
- const cmd = ACTION_TO_COMMAND[action];
465353
+ const cmd = ACTION_METADATA[action]?.command;
465076
465354
  if (!cmd) return 0;
465077
465355
  return COST_ESTIMATES[cmd]?.max ?? 0;
465078
465356
  }
@@ -465097,7 +465375,15 @@ async function ensureActionsRegistered() {
465097
465375
  registerAction("generate-video", async (params, outputDir) => {
465098
465376
  const { executeVideoGenerate: executeVideoGenerate2 } = await Promise.resolve().then(() => (init_ai_video(), ai_video_exports));
465099
465377
  const output3 = getOutput(params, outputDir, "video.mp4");
465100
- const r = await executeVideoGenerate2({ prompt: params.prompt, provider: params.provider, image: params.image, duration: params.duration, ratio: params.ratio, output: output3, wait: true });
465378
+ const r = await executeVideoGenerate2({
465379
+ prompt: params.prompt,
465380
+ provider: params.provider,
465381
+ image: params.image,
465382
+ duration: params.duration,
465383
+ ratio: params.ratio,
465384
+ output: output3,
465385
+ wait: true
465386
+ });
465101
465387
  return { id: "", action: "generate-video", success: r.success, output: r.outputPath || r.videoUrl, data: { taskId: r.taskId, provider: r.provider, videoUrl: r.videoUrl }, error: r.error };
465102
465388
  });
465103
465389
  registerAction("generate-tts", async (params, outputDir) => {
@@ -466333,6 +466619,25 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
466333
466619
  spinner2.text = `Encoding... ${progress}%`;
466334
466620
  });
466335
466621
  spinner2.succeed(source_default.green(`Exported: ${outputPath}`));
466622
+ if (isJsonMode()) {
466623
+ outputSuccess({
466624
+ command: "export",
466625
+ startedAt,
466626
+ data: {
466627
+ outputPath,
466628
+ backend: "ffmpeg",
466629
+ format: options.format,
466630
+ preset: options.preset,
466631
+ resolution: presetSettings.resolution,
466632
+ duration: summary.duration,
466633
+ clipCount: summary.clipCount,
466634
+ bitrate: options.bitrate ?? null,
466635
+ fps: options.fps ?? null,
466636
+ codec: options.codec ?? null
466637
+ }
466638
+ });
466639
+ return;
466640
+ }
466336
466641
  console.log();
466337
466642
  console.log(source_default.dim(" Duration:"), `${summary.duration.toFixed(1)}s`);
466338
466643
  console.log(source_default.dim(" Clips:"), summary.clipCount);
@@ -467808,7 +468113,7 @@ A scene project is a directory that is **bilingual**: it works with both
467808
468113
  and a paused GSAP timeline. Cheap to edit, cheap to lint, expensive only
467809
468114
  at render.
467810
468115
 
467811
- \`vibe scene build\` (v0.60+) is the supported one-shot driver from a
468116
+ \`vibe build\` (v0.60+) is the supported one-shot driver from a
467812
468117
  written storyboard to an MP4. Plan H (v0.70) added \`--mode agent\` so the
467813
468118
  host agent itself authors the per-beat HTML \u2014 no internal LLM call.
467814
468119
 
@@ -467816,19 +468121,19 @@ host agent itself authors the per-beat HTML \u2014 no internal LLM call.
467816
468121
 
467817
468122
  | Path | Command | When to use |
467818
468123
  |---|---|---|
467819
- | **One-shot (default, v0.60+)** | \`vibe scene build [project-dir]\` | STORYBOARD.md has YAML frontmatter + per-beat cues |
467820
- | **High-craft (manual)** | \`DESIGN.md\` + Hyperframes skill in your agent | Maximum control: hand-author each scene |
468124
+ | **One-shot (default, v0.60+)** | \`vibe build [project-dir]\` | STORYBOARD.md has YAML frontmatter + per-beat cues |
468125
+ | **High-craft (manual)** | \`DESIGN.md\` + local composition rules in your agent | Maximum control: hand-author each scene |
467821
468126
  | **Quick draft** | \`vibe scene add --style <preset>\` | No agent or no API keys; fast iteration |
467822
468127
 
467823
- Recommend \`vibe scene build\` whenever the user has a STORYBOARD with
468128
+ Recommend \`vibe build\` whenever the user has a STORYBOARD with
467824
468129
  narration / backdrop intent.
467825
468130
 
467826
468131
  ## High-craft path
467827
468132
 
467828
- 1. \`vibe scene init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
468133
+ 1. \`vibe init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
467829
468134
  \`DESIGN.md\` (palette, typography, motion, transitions) plus the
467830
468135
  \`vibe.project.yaml\` / \`hyperframes.json\` / \`index.html\` scaffold.
467831
- In Plan H this **also installs the Hyperframes skill** at the
468136
+ In Plan H this **also installs local composition rules** at the
467832
468137
  right place for your host (\`.claude/skills/hyperframes/\` for Claude
467833
468138
  Code, \`.cursor/rules/hyperframes.mdc\` for Cursor, universal
467834
468139
  \`SKILL.md\` for everyone else).
@@ -467839,7 +468144,7 @@ narration / backdrop intent.
467839
468144
  4. Author each scene HTML directly under \`compositions/scene-<id>.html\`
467840
468145
  using the rules from steps 2 and 3. The skill enforces the visual
467841
468146
  identity contract \u2014 scenes that contradict DESIGN.md fail lint.
467842
- 5. \`vibe scene lint --fix\` for mechanical issues, \`vibe scene render\`
468147
+ 5. \`vibe scene lint --fix\` for mechanical issues, \`vibe render my-promo\`
467843
468148
  to MP4.
467844
468149
 
467845
468150
  ## Quick-draft path
@@ -467849,7 +468154,7 @@ vibe scene init my-promo -r 16:9 -d 30
467849
468154
  vibe scene add intro --style announcement \\
467850
468155
  --headline "Ship videos, not clicks"
467851
468156
  vibe scene lint
467852
- vibe scene render
468157
+ vibe render my-promo
467853
468158
  \`\`\`
467854
468159
 
467855
468160
  \`vibe scene init\` is **idempotent** \u2014 running it on an existing
@@ -467861,12 +468166,12 @@ Safe to invoke on user-provided projects.
467861
468166
  \`\`\`bash
467862
468167
  vibe scene init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
467863
468168
  vibe scene styles [<name>] # list / show vendored visual identities
467864
- vibe scene install-skill [<dir>] [--host all] # retroactive Hyperframes-skill install
468169
+ vibe scene install-skill [<dir>] [--host all] # retroactive composition-rules install
467865
468170
  vibe scene add <name> --style <preset> [...]
467866
468171
  vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
467867
468172
  vibe scene lint [<root>] [--json] [--fix]
467868
468173
  vibe scene render [<root>] [--fps 30] [--quality standard] [--format mp4]
467869
- vibe scene build [<dir>] [--mode agent|batch|auto] # H3 dispatch
468174
+ vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
467870
468175
  \`\`\`
467871
468176
 
467872
468177
  ## Style presets (for \`vibe scene add --style\`)
@@ -467884,18 +468189,18 @@ from the generated TTS audio.
467884
468189
  ## STORYBOARD-to-MP4 (one command, v0.60+)
467885
468190
 
467886
468191
  \`\`\`bash
467887
- vibe scene init my-promo --visual-style "Swiss Pulse" -d 12
468192
+ vibe init my-promo --visual-style "Swiss Pulse" -d 12
467888
468193
  # (edit STORYBOARD.md with per-beat YAML cues \u2014 narration, backdrop, duration)
467889
- vibe scene build my-promo
468194
+ vibe build my-promo
467890
468195
  \`\`\`
467891
468196
 
467892
- \`vibe scene build\` reads the STORYBOARD frontmatter + per-beat cues,
468197
+ \`vibe build\` reads the STORYBOARD frontmatter + per-beat cues,
467893
468198
  dispatches TTS + image-gen per beat, then either:
467894
468199
 
467895
468200
  - **\`--mode agent\`** (default when an agent host is detected) \u2014 emits a
467896
468201
  \`needs-author\` plan via \`vibe scene compose-prompts\`. The host agent
467897
468202
  authors each \`compositions/scene-<id>.html\` itself, then re-invoking
467898
- \`vibe scene build\` proceeds to lint + render.
468203
+ \`vibe build\` proceeds to lint + render.
467899
468204
  - **\`--mode batch\`** \u2014 VibeFrame runs an internal LLM (Claude / OpenAI /
467900
468205
  Gemini) to compose the HTML, then renders.
467901
468206
 
@@ -467918,9 +468223,9 @@ and surface the error to the user.
467918
468223
  | Task | Tool |
467919
468224
  |------|------|
467920
468225
  | Generate narration + image, then author scene | \`vibe scene add\` |
467921
- | Generate a full scenes project from a STORYBOARD | \`vibe scene build\` |
468226
+ | Generate a full scenes project from a STORYBOARD | \`vibe build\` |
467922
468227
  | Hand-tweak a single scene's animation | edit \`compositions/<file>.html\` directly |
467923
- | Render the project | \`vibe scene render\` *or* \`npx hyperframes render\` (equivalent) |
468228
+ | Render the project | \`vibe render\` *or* \`vibe scene render\` for lower-level control |
467924
468229
  | Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
467925
468230
 
467926
468231
  The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
@@ -468004,8 +468309,8 @@ Checkpoints land next to the YAML: \`pipeline.yaml.checkpoint.json\`.
468004
468309
 
468005
468310
  ## Authoring tips
468006
468311
 
468007
- 1. **Start from examples** \u2014 \`examples/demo-pipeline.yaml\` (FFmpeg-only,
468008
- no keys), \`examples/promo-video.yaml\` (AI providers).
468312
+ 1. **Start from a tiny YAML** \u2014 keep it in your project directory and run
468313
+ \`vibe run pipeline.yaml --dry-run\` before spending provider budget.
468009
468314
  2. **Dry-run first** \u2014 you see estimated cost and resolved variable
468010
468315
  graph before spending API credits.
468011
468316
  3. **Keep step ids short and descriptive** (\`intro\`, \`scene1\`, \`voice\`,
@@ -468040,18 +468345,18 @@ var META = {
468040
468345
  title: "Scene authoring with vibe",
468041
468346
  summary: "Author per-scene HTML compositions and render to MP4 (BUILD flow)",
468042
468347
  steps: [
468043
- 'Run `vibe scene init <dir> --visual-style "<style name>"` to scaffold the project + install the Hyperframes skill (Plan H).',
468348
+ 'Run `vibe init <dir> --visual-style "<style name>"` to scaffold the project + install local composition rules.',
468044
468349
  "Edit `STORYBOARD.md` with per-beat YAML cues (narration / backdrop / duration).",
468045
468350
  "Read `SKILL.md` for the framework rules and `DESIGN.md` for the visual-identity hard-gate.",
468046
- "Run `vibe scene build <dir>`. With an agent host detected, the CLI emits a `needs-author` plan; the host agent authors each `compositions/scene-<id>.html` and re-invokes to render.",
468047
- "Run `vibe scene lint --fix` to validate, then `vibe scene render` to produce the MP4."
468351
+ "Run `vibe build <dir>`. With an agent host detected, the CLI emits a `needs-author` plan; the host agent authors each `compositions/scene-<id>.html` and re-invokes to render.",
468352
+ "Run `vibe scene lint --fix` to validate, then `vibe render <dir>` to produce the MP4."
468048
468353
  ],
468049
468354
  relatedCommands: [
468050
- "vibe scene init",
468355
+ "vibe init",
468051
468356
  "vibe scene styles",
468052
468357
  "vibe scene install-skill",
468053
468358
  "vibe scene compose-prompts",
468054
- "vibe scene build",
468359
+ "vibe build",
468055
468360
  "vibe scene lint",
468056
468361
  "vibe scene render",
468057
468362
  "vibe scene add"