@vibeframe/mcp-server 0.72.0 → 0.73.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 +10 -10
  2. package/dist/index.js +773 -481
  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
  });
@@ -449463,57 +449997,6 @@ var init_output = __esm({
449463
449997
  }
449464
449998
  });
449465
449999
 
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();
449514
- }
449515
- });
449516
-
449517
450000
  // ../cli/src/utils/subtitle.ts
449518
450001
  function detectFormat(outputPath, explicitFormat) {
449519
450002
  if (explicitFormat) {
@@ -459164,9 +459647,14 @@ var init_ai_script_pipeline = __esm({
459164
459647
  import { resolve as resolve49 } from "node:path";
459165
459648
  import { readFile as readFile25, writeFile as writeFile33 } from "node:fs/promises";
459166
459649
  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", `
459650
+ 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(
459651
+ "-d, --duration <sec>",
459652
+ "Duration in seconds. Seedance accepts 4-15 (`fal` alias supported); Kling accepts 5 or 10; Veo maps to 6 or 8.",
459653
+ "5"
459654
+ ).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
459655
  Examples:
459169
- $ vibe generate video "dancing cat" -o cat.mp4 # Grok (default)
459656
+ $ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
459657
+ $ vibe gen vid "cinematic city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
459170
459658
  $ vibe gen vid "city timelapse" -o city.mp4 -p kling # Kling
459171
459659
  $ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
459172
459660
  $ vibe gen vid "ocean waves" -o waves.mp4 -p veo --resolution 1080p # Veo
@@ -459192,12 +459680,13 @@ Examples:
459192
459680
  if (options.output) {
459193
459681
  validateOutputPath(options.output);
459194
459682
  }
459195
- const validProviders = ["runway", "kling", "veo", "grok", "fal"];
459683
+ const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
459196
459684
  const videoEnvMap = {
459197
459685
  grok: "XAI_API_KEY",
459198
459686
  veo: "GOOGLE_API_KEY",
459199
459687
  kling: "KLING_API_KEY",
459200
459688
  runway: "RUNWAY_API_SECRET",
459689
+ seedance: "FAL_KEY",
459201
459690
  fal: "FAL_KEY"
459202
459691
  };
459203
459692
  let provider;
@@ -459207,7 +459696,7 @@ Examples:
459207
459696
  exitWithError(
459208
459697
  usageError(
459209
459698
  `Invalid provider: ${provider}`,
459210
- `Available providers: ${validProviders.join(", ")}`
459699
+ "Available providers: seedance, grok, kling, runway, veo. `fal` is a backwards-compatible alias for seedance."
459211
459700
  )
459212
459701
  );
459213
459702
  }
@@ -459270,14 +459759,15 @@ Examples:
459270
459759
  data: {
459271
459760
  params: {
459272
459761
  prompt: prompt3,
459273
- provider,
459762
+ provider: provider === "fal" ? "seedance" : provider,
459274
459763
  duration: options.duration,
459275
459764
  ratio: options.ratio,
459276
459765
  image: options.image,
459277
459766
  mode: options.mode,
459278
459767
  negative: options.negative,
459279
459768
  resolution: options.resolution,
459280
- veoModel: options.veoModel
459769
+ veoModel: options.veoModel,
459770
+ seedanceModel: options.seedanceModel
459281
459771
  }
459282
459772
  }
459283
459773
  });
@@ -459288,6 +459778,7 @@ Examples:
459288
459778
  kling: "KLING_API_KEY",
459289
459779
  veo: "GOOGLE_API_KEY",
459290
459780
  grok: "XAI_API_KEY",
459781
+ seedance: "FAL_KEY",
459291
459782
  fal: "FAL_KEY"
459292
459783
  };
459293
459784
  const providerNameMap = {
@@ -459295,7 +459786,8 @@ Examples:
459295
459786
  kling: "Kling",
459296
459787
  veo: "Veo",
459297
459788
  grok: "Grok",
459298
- fal: "fal.ai (Seedance 2.0)"
459789
+ seedance: "Seedance 2.0 via fal.ai",
459790
+ fal: "Seedance 2.0 via fal.ai"
459299
459791
  };
459300
459792
  const envKey = envKeyMap[provider];
459301
459793
  const providerName = providerNameMap[provider];
@@ -459514,15 +460006,15 @@ Examples:
459514
460006
  },
459515
460007
  3e5
459516
460008
  );
459517
- } else if (provider === "fal") {
460009
+ } else if (provider === "fal" || provider === "seedance") {
459518
460010
  const fal = new FalProvider();
459519
460011
  await fal.initialize({ apiKey });
459520
460012
  let falImage = referenceImage;
459521
460013
  if (falImage && falImage.startsWith("data:")) {
459522
- spinner2.text = "Uploading image to ImgBB for fal...";
460014
+ spinner2.text = "Uploading image to ImgBB for Seedance...";
459523
460015
  const imgbbKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
459524
460016
  if (!imgbbKey) {
459525
- spinner2.fail("ImgBB API key required for fal image-to-video");
460017
+ spinner2.fail("ImgBB API key required for Seedance image-to-video");
459526
460018
  exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
459527
460019
  }
459528
460020
  const base64Data = falImage.split(",")[1];
@@ -459534,8 +460026,9 @@ Examples:
459534
460026
  }
459535
460027
  falImage = uploadResult.url;
459536
460028
  }
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";
460029
+ spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
460030
+ const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
460031
+ const falModel = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
459539
460032
  result = await fal.generateVideo(prompt3, {
459540
460033
  prompt: prompt3,
459541
460034
  referenceImage: falImage,
@@ -459660,7 +460153,8 @@ var init_generate = __esm({
459660
460153
  Examples:
459661
460154
  $ vibe generate image "a sunset over the ocean" -o sunset.png
459662
460155
  $ vibe generate image "logo design" -o logo.png -p openai
459663
- $ vibe generate video "dancing cat" -o cat.mp4 # Grok (default, native audio)
460156
+ $ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
460157
+ $ vibe generate video "city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
459664
460158
  $ vibe generate video "city timelapse" -o city.mp4 -p kling # Kling
459665
460159
  $ vibe generate video "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
459666
460160
  $ vibe generate speech "Hello world" -o hello.mp3
@@ -459670,7 +460164,8 @@ Examples:
459670
460164
  API Keys (per provider):
459671
460165
  GOOGLE_API_KEY Image (default), Veo video
459672
460166
  OPENAI_API_KEY Image (-p openai)
459673
- XAI_API_KEY Grok image/video (default video)
460167
+ FAL_KEY Seedance video (-p seedance, default video)
460168
+ XAI_API_KEY Grok image/video
459674
460169
  KLING_API_KEY Kling video (-p kling)
459675
460170
  RUNWAY_API_SECRET Runway video (-p runway)
459676
460171
  ELEVENLABS_API_KEY Speech, sound effects, music
@@ -459718,12 +460213,20 @@ async function executeVideoGenerate(options) {
459718
460213
  negative,
459719
460214
  resolution,
459720
460215
  veoModel = "3.1-fast",
460216
+ seedanceModel = "quality",
459721
460217
  output: output3,
459722
460218
  wait = true,
459723
460219
  apiKey
459724
460220
  } = options;
459725
460221
  try {
459726
- const envKeyMap = { grok: "XAI_API_KEY", runway: "RUNWAY_API_SECRET", kling: "KLING_API_KEY", veo: "GOOGLE_API_KEY" };
460222
+ const envKeyMap = {
460223
+ grok: "XAI_API_KEY",
460224
+ runway: "RUNWAY_API_SECRET",
460225
+ kling: "KLING_API_KEY",
460226
+ veo: "GOOGLE_API_KEY",
460227
+ seedance: "FAL_KEY",
460228
+ fal: "FAL_KEY"
460229
+ };
459727
460230
  const key2 = apiKey || process.env[envKeyMap[provider] || ""];
459728
460231
  if (!key2) return { success: false, error: `${envKeyMap[provider]} required for ${provider}` };
459729
460232
  let referenceImage;
@@ -459735,7 +460238,36 @@ async function executeVideoGenerate(options) {
459735
460238
  const mimeType = mimeTypes[ext || "png"] || "image/png";
459736
460239
  referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
459737
460240
  }
459738
- if (provider === "runway") {
460241
+ if (provider === "seedance" || provider === "fal") {
460242
+ const fal = new FalProvider();
460243
+ await fal.initialize({ apiKey: key2 });
460244
+ let falImage = referenceImage;
460245
+ if (falImage && falImage.startsWith("data:")) {
460246
+ const imgbbKey = process.env.IMGBB_API_KEY;
460247
+ if (!imgbbKey) return { success: false, error: "IMGBB_API_KEY required for Seedance image-to-video" };
460248
+ const base64Data = falImage.split(",")[1];
460249
+ const uploadResult = await uploadToImgbb(Buffer.from(base64Data, "base64"), imgbbKey);
460250
+ if (!uploadResult.success || !uploadResult.url) return { success: false, error: `ImgBB upload failed: ${uploadResult.error}` };
460251
+ falImage = uploadResult.url;
460252
+ }
460253
+ const model = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
460254
+ const result = await fal.generateVideo(prompt3, {
460255
+ prompt: prompt3,
460256
+ referenceImage: falImage,
460257
+ duration,
460258
+ aspectRatio: ratio,
460259
+ negativePrompt: negative,
460260
+ model
460261
+ });
460262
+ if (result.status === "failed") return { success: false, error: result.error || "Seedance generation failed" };
460263
+ let outputPath;
460264
+ if (output3 && result.videoUrl) {
460265
+ const buffer = await downloadVideo(result.videoUrl, key2);
460266
+ outputPath = resolve50(process.cwd(), output3);
460267
+ await writeFile34(outputPath, buffer);
460268
+ }
460269
+ return { success: true, taskId: result.id, status: "completed", videoUrl: result.videoUrl, outputPath, provider: "seedance" };
460270
+ } else if (provider === "runway") {
459739
460271
  const runway = new RunwayProvider();
459740
460272
  await runway.initialize({ apiKey: key2 });
459741
460273
  const result = await runway.generateVideo(prompt3, {
@@ -460950,334 +461482,8 @@ function visualStyleNames() {
460950
461482
  return STYLES.map((s) => `"${s.name}"`).join(", ");
460951
461483
  }
460952
461484
 
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
- }
461485
+ // ../cli/src/tools/manifest/scene.ts
461486
+ init_scene_project();
461281
461487
 
461282
461488
  // ../cli/src/commands/scene.ts
461283
461489
  init_esm();
@@ -461286,6 +461492,7 @@ init_ora();
461286
461492
  var import_yaml5 = __toESM(require_dist(), 1);
461287
461493
  init_dist();
461288
461494
  init_tts_resolve();
461495
+ init_scene_project();
461289
461496
  import { basename as basename6, resolve as resolve21, relative as relative7, dirname as dirname17 } from "node:path";
461290
461497
  import { mkdir as mkdir10, readFile as readFile10, writeFile as writeFile10, access as access4, copyFile as copyFile2 } from "node:fs/promises";
461291
461498
  import { existsSync as existsSync28 } from "node:fs";
@@ -461777,6 +461984,7 @@ function deriveInstallHosts(detected) {
461777
461984
  // ../cli/src/commands/scene.ts
461778
461985
  init_compose_prompts();
461779
461986
  var VALID_ASPECTS2 = ["16:9", "9:16", "1:1", "4:5"];
461987
+ var VALID_SCENE_INIT_PROFILES = ["minimal", "agent", "full"];
461780
461988
  function validateAspect(value) {
461781
461989
  if (!VALID_ASPECTS2.includes(value)) {
461782
461990
  exitWithError(usageError(`Invalid aspect ratio: ${value}`, `Valid: ${VALID_ASPECTS2.join(", ")}`));
@@ -461808,7 +462016,12 @@ function validateVisualStyle(value) {
461808
462016
  }
461809
462017
  return found;
461810
462018
  }
461811
- var sceneCommand = new Command("scene").description("Author and render per-scene HTML compositions (Hyperframes backend)").addHelpText("after", `
462019
+ function formatSceneInitProfile(profile) {
462020
+ if (profile === "minimal") return "authoring files only; build will add render scaffold when needed";
462021
+ if (profile === "agent") return "authoring files plus local composition rules for host agents";
462022
+ return "complete authoring, agent, and render scaffold";
462023
+ }
462024
+ var sceneCommand = new Command("scene").description("Advanced scene commands for VibeFrame video projects").addHelpText("after", `
461812
462025
  Examples:
461813
462026
  $ vibe scene init my-video # Scaffold a new project
461814
462027
  $ vibe scene init my-video -r 9:16 -d 30 # Vertical 30s project
@@ -461816,22 +462029,32 @@ Examples:
461816
462029
  --headline "Welcome to VibeFrame" # Headline-only scene
461817
462030
  $ vibe scene add overview --narration "VibeFrame turns scripts into video." \\
461818
462031
  --visuals "studio desk, soft lighting" # AI narration + image
461819
- $ vibe scene lint # Validate every scene against Hyperframes rules
462032
+ $ vibe scene lint # Validate every scene against composition rules
461820
462033
  $ vibe scene lint --fix # Auto-fix mechanical issues (e.g. missing class="clip")
461821
462034
  $ vibe scene lint --json # Structured output for agent loops
461822
462035
  $ vibe scene render # Render to renders/<name>-<timestamp>.mp4
461823
462036
  $ vibe scene render -o demo.mp4 --quality high # Custom output path + quality
461824
462037
  $ vibe scene render --fps 60 --format webm # 60fps WebM render
461825
462038
 
461826
- A scene project is bilingual: it works with both \`vibe\` and \`npx hyperframes\`.
462039
+ Most users can start with \`vibe init\`, \`vibe build\`, and \`vibe render\`.
462040
+ This namespace exposes lower-level scene authoring and rendering controls.
461827
462041
  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) => {
462042
+ sceneCommand.command("init").description("Scaffold a new scene project (or safely augment an existing 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("--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
462043
  const startedAt = Date.now();
461830
462044
  const aspect = validateAspect(options.ratio);
461831
462045
  const duration = validateDuration(options.duration);
461832
462046
  const name = options.name ?? basename6(dir.replace(/\/+$/, ""));
461833
462047
  const visualStyle = options.visualStyle ? validateVisualStyle(options.visualStyle) : void 0;
462048
+ const profile = String(options.profile ?? "agent");
462049
+ if (!isSceneScaffoldProfile(profile)) {
462050
+ exitWithError(usageError(`Invalid --profile: ${profile}`, `Must be one of: ${VALID_SCENE_INIT_PROFILES.join(", ")}`));
462051
+ }
462052
+ const groups = describeSceneScaffold({ dir, profile });
461834
462053
  if (options.dryRun) {
462054
+ if (!isJsonMode() && !isQuietMode()) {
462055
+ printSceneInitDryRun({ dir, name, aspect, duration, visualStyleName: visualStyle?.name ?? null, profile, groups });
462056
+ return;
462057
+ }
461835
462058
  outputSuccess({
461836
462059
  command: "scene init",
461837
462060
  startedAt,
@@ -461842,23 +462065,25 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
461842
462065
  name,
461843
462066
  aspect,
461844
462067
  duration,
461845
- visualStyle: visualStyle?.name ?? null
461846
- }
462068
+ visualStyle: visualStyle?.name ?? null,
462069
+ profile
462070
+ },
462071
+ groups
461847
462072
  }
461848
462073
  });
461849
462074
  return;
461850
462075
  }
461851
- const spinner2 = isJsonMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
462076
+ const spinner2 = isJsonMode() || isQuietMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
461852
462077
  try {
461853
- const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle });
462078
+ const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle, profile });
461854
462079
  const detectedIds = detectedAgentHosts().map((h) => h.id);
461855
462080
  const skillHosts = deriveInstallHosts(detectedIds);
461856
462081
  const projectAbs = resolve21(dir);
461857
- const skillResult = await installHyperframesSkill({
462082
+ const skillResult = profile === "agent" || profile === "full" ? await installHyperframesSkill({
461858
462083
  projectDir: projectAbs,
461859
462084
  hosts: skillHosts
461860
- });
461861
- if (isJsonMode()) {
462085
+ }) : { success: true, files: [], bundleVersion: "not-installed" };
462086
+ if (isJsonMode() || isQuietMode()) {
461862
462087
  outputSuccess({
461863
462088
  command: "scene init",
461864
462089
  startedAt,
@@ -461868,16 +462093,27 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
461868
462093
  aspect,
461869
462094
  duration,
461870
462095
  visualStyle: visualStyle?.name ?? null,
462096
+ profile,
461871
462097
  created: result.created,
461872
462098
  merged: result.merged,
461873
462099
  skipped: result.skipped,
462100
+ groups: result.groups,
461874
462101
  skillFiles: skillResult.files,
461875
462102
  skillBundleVersion: skillResult.bundleVersion
461876
462103
  }
461877
462104
  });
461878
462105
  return;
461879
462106
  }
461880
- spinner2?.succeed(source_default.green(`Scene project ready: ${dir}`));
462107
+ spinner2?.succeed(source_default.green(`Video project ready: ${dir}`));
462108
+ console.log();
462109
+ console.log(source_default.bold.cyan("Edit first"));
462110
+ console.log(source_default.dim("\u2500".repeat(60)));
462111
+ console.log(` ${source_default.bold("STORYBOARD.md")} ${source_default.dim("# beats: narration, backdrop, minimum duration")}`);
462112
+ console.log(` ${source_default.bold("DESIGN.md")} ${source_default.dim("# palette, typography, motion rules")}`);
462113
+ console.log();
462114
+ console.log(source_default.bold.cyan("Profile"));
462115
+ console.log(source_default.dim("\u2500".repeat(60)));
462116
+ console.log(` ${source_default.bold(profile)} ${source_default.dim(formatSceneInitProfile(profile))}`);
461881
462117
  console.log();
461882
462118
  console.log(source_default.bold.cyan("Files"));
461883
462119
  console.log(source_default.dim("\u2500".repeat(60)));
@@ -461888,7 +462124,7 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
461888
462124
  const skillSkipped = skillResult.files.filter((f) => f.status === "skipped-exists");
461889
462125
  if (skillWritten.length + skillSkipped.length > 0) {
461890
462126
  console.log();
461891
- console.log(source_default.bold.cyan("Hyperframes skill"));
462127
+ console.log(source_default.bold.cyan("Composition rules"));
461892
462128
  console.log(source_default.dim("\u2500".repeat(60)));
461893
462129
  for (const f of skillWritten) console.log(source_default.green(" +"), f.path);
461894
462130
  for (const f of skillSkipped) console.log(source_default.dim(" \xB7"), f.path, source_default.dim("(kept existing)"));
@@ -461902,16 +462138,39 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
461902
462138
  } else {
461903
462139
  console.log(` ${source_default.cyan("vibe scene styles")} ${source_default.dim("# pick a named style for DESIGN.md")}`);
461904
462140
  }
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.")}`);
462141
+ if (profile === "agent" || profile === "full") {
462142
+ 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.")}`);
462143
+ } else {
462144
+ console.log(` ${source_default.cyan("vibe scene install-skill")} ${source_default.dim("# add agent authoring rules later")}`);
462145
+ }
461906
462146
  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")}`);
462147
+ console.log(` ${source_default.cyan("vibe build")} ${source_default.dim("# build STORYBOARD.md into scenes/assets")}`);
462148
+ console.log(` ${source_default.cyan("vibe render")} ${source_default.dim("# render to video")}`);
461909
462149
  } catch (error) {
461910
462150
  spinner2?.fail("Failed to scaffold scene project");
461911
462151
  const msg = error instanceof Error ? error.message : String(error);
461912
462152
  exitWithError(generalError(`Failed to scaffold: ${msg}`));
461913
462153
  }
461914
462154
  });
462155
+ function printSceneInitDryRun(opts) {
462156
+ console.log();
462157
+ console.log(source_default.bold.cyan("VibeFrame Scene Init - dry run"));
462158
+ console.log(source_default.dim("-".repeat(60)));
462159
+ console.log(` Project: ${source_default.bold(opts.dir)}`);
462160
+ console.log(` Name: ${source_default.bold(opts.name)}`);
462161
+ console.log(` Profile: ${source_default.bold(opts.profile)} ${source_default.dim(formatSceneInitProfile(opts.profile))}`);
462162
+ console.log(` Aspect: ${opts.aspect}`);
462163
+ console.log(` Duration: ${opts.duration}s`);
462164
+ console.log(` Visual style: ${opts.visualStyleName ?? "none"}`);
462165
+ console.log();
462166
+ console.log(source_default.bold.cyan("Files that would be prepared"));
462167
+ console.log(source_default.dim("-".repeat(60)));
462168
+ for (const file of opts.groups.authoring) console.log(` authoring ${file}`);
462169
+ for (const file of opts.groups.agent) console.log(` agent ${file}`);
462170
+ for (const file of opts.groups.render) console.log(` render ${file}`);
462171
+ console.log();
462172
+ console.log(source_default.dim("No files were written."));
462173
+ }
461915
462174
  var VALID_INSTALL_SKILL_HOSTS = ["claude-code", "cursor", "auto", "all"];
461916
462175
  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
462176
  const startedAt = Date.now();
@@ -462457,7 +462716,7 @@ async function executeSceneAdd(opts) {
462457
462716
  transcriptWordCount
462458
462717
  };
462459
462718
  }
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) => {
462719
+ 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
462720
  const startedAt = Date.now();
462462
462721
  const projectDir = resolve21(options.project);
462463
462722
  if (!await rootExists(projectDir, root2)) {
@@ -462744,7 +463003,7 @@ sceneCommand.command("build").description("One-shot: read STORYBOARD.md cues, di
462744
463003
  }
462745
463004
  }
462746
463005
  console.log();
462747
- console.log(source_default.dim("Once you've authored each beat's HTML, re-run `vibe scene build` to lint + render."));
463006
+ console.log(source_default.dim("Once you've authored each beat's HTML, re-run `vibe build` to lint + render."));
462748
463007
  console.log(source_default.dim("Or pass `--mode batch` to use the internal LLM compose path instead."));
462749
463008
  return;
462750
463009
  }
@@ -462883,7 +463142,7 @@ var sceneInitTool = defineTool({
462883
463142
  name: "scene_init",
462884
463143
  category: "scene",
462885
463144
  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.",
463145
+ 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
463146
  schema: sceneInitSchema,
462888
463147
  async execute(args, ctx) {
462889
463148
  const dir = resolve23(ctx.workingDirectory, args.dir);
@@ -465041,38 +465300,44 @@ function findUnresolvedRefs(params, availableStepIds) {
465041
465300
 
465042
465301
  // ../cli/src/pipeline/executor.ts
465043
465302
  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"
465303
+ var ACTION_METADATA = {
465304
+ "generate-image": { id: "generate-image", title: "Generate image", category: "generate", command: "generate image", outputs: ["image"] },
465305
+ "generate-video": { id: "generate-video", title: "Generate video", category: "generate", command: "generate video", outputs: ["video"], requiredKeys: ["provider-dependent"] },
465306
+ "generate-tts": { id: "generate-tts", title: "Generate speech", category: "generate", command: "generate speech", outputs: ["audio"] },
465307
+ "generate-sfx": { id: "generate-sfx", title: "Generate sound effect", category: "generate", command: "generate sound-effect", outputs: ["audio"] },
465308
+ "generate-music": { id: "generate-music", title: "Generate music", category: "generate", command: "generate music", outputs: ["audio"] },
465309
+ "generate-storyboard": { id: "generate-storyboard", title: "Generate storyboard", category: "generate", command: "generate storyboard", outputs: ["storyboard"] },
465310
+ "generate-motion": { id: "generate-motion", title: "Generate motion", category: "generate", command: "generate motion", outputs: ["code", "video"] },
465311
+ "edit-silence-cut": { id: "edit-silence-cut", title: "Cut silence", category: "edit", command: "edit silence-cut", outputs: ["video"] },
465312
+ "edit-jump-cut": { id: "edit-jump-cut", title: "Jump cut", category: "edit", command: "edit jump-cut", outputs: ["video"] },
465313
+ "edit-caption": { id: "edit-caption", title: "Caption video", category: "edit", command: "edit caption", outputs: ["video", "srt"] },
465314
+ "edit-noise-reduce": { id: "edit-noise-reduce", title: "Reduce noise", category: "edit", command: "edit noise-reduce", outputs: ["video"] },
465315
+ "edit-fade": { id: "edit-fade", title: "Add fade", category: "edit", command: "edit fade", outputs: ["video"] },
465316
+ "edit-translate-srt": { id: "edit-translate-srt", title: "Translate subtitles", category: "edit", command: "edit translate-srt", outputs: ["srt"] },
465317
+ "edit-text-overlay": { id: "edit-text-overlay", title: "Add text overlay", category: "edit", command: "edit text-overlay", outputs: ["video"] },
465318
+ "edit-grade": { id: "edit-grade", title: "Color grade", category: "edit", command: "edit grade", outputs: ["video"] },
465319
+ "edit-speed-ramp": { id: "edit-speed-ramp", title: "Speed ramp", category: "edit", command: "edit speed-ramp", outputs: ["video"] },
465320
+ "edit-reframe": { id: "edit-reframe", title: "Reframe video", category: "edit", command: "edit reframe", outputs: ["video"] },
465321
+ "edit-interpolate": { id: "edit-interpolate", title: "Interpolate frames", category: "edit", command: "edit interpolate", outputs: ["video"] },
465322
+ "edit-upscale": { id: "edit-upscale", title: "Upscale video", category: "edit", command: "edit upscale-video", outputs: ["video"] },
465323
+ "edit-image": { id: "edit-image", title: "Edit image", category: "edit", command: "edit image", outputs: ["image"] },
465324
+ "audio-transcribe": { id: "audio-transcribe", title: "Transcribe audio", category: "audio", command: "audio transcribe", outputs: ["transcript", "srt"] },
465325
+ "audio-isolate": { id: "audio-isolate", title: "Isolate audio", category: "audio", outputs: ["audio"] },
465326
+ "audio-dub": { id: "audio-dub", title: "Dub audio", category: "audio", outputs: ["audio", "video"] },
465327
+ "audio-duck": { id: "audio-duck", title: "Duck audio", category: "audio", outputs: ["video"] },
465328
+ "detect-scenes": { id: "detect-scenes", title: "Detect scenes", category: "detect", command: "detect scenes", outputs: ["json"] },
465329
+ "detect-silence": { id: "detect-silence", title: "Detect silence", category: "detect", command: "detect silence", outputs: ["json"] },
465330
+ "detect-beats": { id: "detect-beats", title: "Detect beats", category: "detect", command: "detect beats", outputs: ["json"] },
465331
+ "analyze-media": { id: "analyze-media", title: "Analyze media", category: "analyze", command: "analyze media", outputs: ["json"] },
465332
+ "analyze-video": { id: "analyze-video", title: "Analyze video", category: "analyze", command: "analyze video", outputs: ["json"] },
465333
+ "review-video": { id: "review-video", title: "Review video", category: "analyze", command: "analyze review", outputs: ["json"] },
465334
+ "compose-scenes-with-skills": { id: "compose-scenes-with-skills", title: "Compose scenes with skills", category: "scene", command: "compose scenes with skills", outputs: ["html"] },
465335
+ "scene-build": { id: "scene-build", title: "Build scene project", category: "scene", outputs: ["video", "html", "assets"] },
465336
+ "scene-render": { id: "scene-render", title: "Render scene project", category: "scene", outputs: ["video"] },
465337
+ export: { id: "export", title: "Export project", category: "export", outputs: ["video"] }
465073
465338
  };
465074
465339
  function maxCostFor(action) {
465075
- const cmd = ACTION_TO_COMMAND[action];
465340
+ const cmd = ACTION_METADATA[action]?.command;
465076
465341
  if (!cmd) return 0;
465077
465342
  return COST_ESTIMATES[cmd]?.max ?? 0;
465078
465343
  }
@@ -465097,7 +465362,15 @@ async function ensureActionsRegistered() {
465097
465362
  registerAction("generate-video", async (params, outputDir) => {
465098
465363
  const { executeVideoGenerate: executeVideoGenerate2 } = await Promise.resolve().then(() => (init_ai_video(), ai_video_exports));
465099
465364
  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 });
465365
+ const r = await executeVideoGenerate2({
465366
+ prompt: params.prompt,
465367
+ provider: params.provider,
465368
+ image: params.image,
465369
+ duration: params.duration,
465370
+ ratio: params.ratio,
465371
+ output: output3,
465372
+ wait: true
465373
+ });
465101
465374
  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
465375
  });
465103
465376
  registerAction("generate-tts", async (params, outputDir) => {
@@ -466333,6 +466606,25 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
466333
466606
  spinner2.text = `Encoding... ${progress}%`;
466334
466607
  });
466335
466608
  spinner2.succeed(source_default.green(`Exported: ${outputPath}`));
466609
+ if (isJsonMode()) {
466610
+ outputSuccess({
466611
+ command: "export",
466612
+ startedAt,
466613
+ data: {
466614
+ outputPath,
466615
+ backend: "ffmpeg",
466616
+ format: options.format,
466617
+ preset: options.preset,
466618
+ resolution: presetSettings.resolution,
466619
+ duration: summary.duration,
466620
+ clipCount: summary.clipCount,
466621
+ bitrate: options.bitrate ?? null,
466622
+ fps: options.fps ?? null,
466623
+ codec: options.codec ?? null
466624
+ }
466625
+ });
466626
+ return;
466627
+ }
466336
466628
  console.log();
466337
466629
  console.log(source_default.dim(" Duration:"), `${summary.duration.toFixed(1)}s`);
466338
466630
  console.log(source_default.dim(" Clips:"), summary.clipCount);
@@ -467808,7 +468100,7 @@ A scene project is a directory that is **bilingual**: it works with both
467808
468100
  and a paused GSAP timeline. Cheap to edit, cheap to lint, expensive only
467809
468101
  at render.
467810
468102
 
467811
- \`vibe scene build\` (v0.60+) is the supported one-shot driver from a
468103
+ \`vibe build\` (v0.60+) is the supported one-shot driver from a
467812
468104
  written storyboard to an MP4. Plan H (v0.70) added \`--mode agent\` so the
467813
468105
  host agent itself authors the per-beat HTML \u2014 no internal LLM call.
467814
468106
 
@@ -467816,19 +468108,19 @@ host agent itself authors the per-beat HTML \u2014 no internal LLM call.
467816
468108
 
467817
468109
  | Path | Command | When to use |
467818
468110
  |---|---|---|
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 |
468111
+ | **One-shot (default, v0.60+)** | \`vibe build [project-dir]\` | STORYBOARD.md has YAML frontmatter + per-beat cues |
468112
+ | **High-craft (manual)** | \`DESIGN.md\` + local composition rules in your agent | Maximum control: hand-author each scene |
467821
468113
  | **Quick draft** | \`vibe scene add --style <preset>\` | No agent or no API keys; fast iteration |
467822
468114
 
467823
- Recommend \`vibe scene build\` whenever the user has a STORYBOARD with
468115
+ Recommend \`vibe build\` whenever the user has a STORYBOARD with
467824
468116
  narration / backdrop intent.
467825
468117
 
467826
468118
  ## High-craft path
467827
468119
 
467828
- 1. \`vibe scene init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
468120
+ 1. \`vibe init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
467829
468121
  \`DESIGN.md\` (palette, typography, motion, transitions) plus the
467830
468122
  \`vibe.project.yaml\` / \`hyperframes.json\` / \`index.html\` scaffold.
467831
- In Plan H this **also installs the Hyperframes skill** at the
468123
+ In Plan H this **also installs local composition rules** at the
467832
468124
  right place for your host (\`.claude/skills/hyperframes/\` for Claude
467833
468125
  Code, \`.cursor/rules/hyperframes.mdc\` for Cursor, universal
467834
468126
  \`SKILL.md\` for everyone else).
@@ -467839,7 +468131,7 @@ narration / backdrop intent.
467839
468131
  4. Author each scene HTML directly under \`compositions/scene-<id>.html\`
467840
468132
  using the rules from steps 2 and 3. The skill enforces the visual
467841
468133
  identity contract \u2014 scenes that contradict DESIGN.md fail lint.
467842
- 5. \`vibe scene lint --fix\` for mechanical issues, \`vibe scene render\`
468134
+ 5. \`vibe scene lint --fix\` for mechanical issues, \`vibe render my-promo\`
467843
468135
  to MP4.
467844
468136
 
467845
468137
  ## Quick-draft path
@@ -467849,7 +468141,7 @@ vibe scene init my-promo -r 16:9 -d 30
467849
468141
  vibe scene add intro --style announcement \\
467850
468142
  --headline "Ship videos, not clicks"
467851
468143
  vibe scene lint
467852
- vibe scene render
468144
+ vibe render my-promo
467853
468145
  \`\`\`
467854
468146
 
467855
468147
  \`vibe scene init\` is **idempotent** \u2014 running it on an existing
@@ -467861,12 +468153,12 @@ Safe to invoke on user-provided projects.
467861
468153
  \`\`\`bash
467862
468154
  vibe scene init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
467863
468155
  vibe scene styles [<name>] # list / show vendored visual identities
467864
- vibe scene install-skill [<dir>] [--host all] # retroactive Hyperframes-skill install
468156
+ vibe scene install-skill [<dir>] [--host all] # retroactive composition-rules install
467865
468157
  vibe scene add <name> --style <preset> [...]
467866
468158
  vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
467867
468159
  vibe scene lint [<root>] [--json] [--fix]
467868
468160
  vibe scene render [<root>] [--fps 30] [--quality standard] [--format mp4]
467869
- vibe scene build [<dir>] [--mode agent|batch|auto] # H3 dispatch
468161
+ vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
467870
468162
  \`\`\`
467871
468163
 
467872
468164
  ## Style presets (for \`vibe scene add --style\`)
@@ -467884,18 +468176,18 @@ from the generated TTS audio.
467884
468176
  ## STORYBOARD-to-MP4 (one command, v0.60+)
467885
468177
 
467886
468178
  \`\`\`bash
467887
- vibe scene init my-promo --visual-style "Swiss Pulse" -d 12
468179
+ vibe init my-promo --visual-style "Swiss Pulse" -d 12
467888
468180
  # (edit STORYBOARD.md with per-beat YAML cues \u2014 narration, backdrop, duration)
467889
- vibe scene build my-promo
468181
+ vibe build my-promo
467890
468182
  \`\`\`
467891
468183
 
467892
- \`vibe scene build\` reads the STORYBOARD frontmatter + per-beat cues,
468184
+ \`vibe build\` reads the STORYBOARD frontmatter + per-beat cues,
467893
468185
  dispatches TTS + image-gen per beat, then either:
467894
468186
 
467895
468187
  - **\`--mode agent\`** (default when an agent host is detected) \u2014 emits a
467896
468188
  \`needs-author\` plan via \`vibe scene compose-prompts\`. The host agent
467897
468189
  authors each \`compositions/scene-<id>.html\` itself, then re-invoking
467898
- \`vibe scene build\` proceeds to lint + render.
468190
+ \`vibe build\` proceeds to lint + render.
467899
468191
  - **\`--mode batch\`** \u2014 VibeFrame runs an internal LLM (Claude / OpenAI /
467900
468192
  Gemini) to compose the HTML, then renders.
467901
468193
 
@@ -467918,9 +468210,9 @@ and surface the error to the user.
467918
468210
  | Task | Tool |
467919
468211
  |------|------|
467920
468212
  | Generate narration + image, then author scene | \`vibe scene add\` |
467921
- | Generate a full scenes project from a STORYBOARD | \`vibe scene build\` |
468213
+ | Generate a full scenes project from a STORYBOARD | \`vibe build\` |
467922
468214
  | Hand-tweak a single scene's animation | edit \`compositions/<file>.html\` directly |
467923
- | Render the project | \`vibe scene render\` *or* \`npx hyperframes render\` (equivalent) |
468215
+ | Render the project | \`vibe render\` *or* \`vibe scene render\` for lower-level control |
467924
468216
  | Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
467925
468217
 
467926
468218
  The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
@@ -468004,8 +468296,8 @@ Checkpoints land next to the YAML: \`pipeline.yaml.checkpoint.json\`.
468004
468296
 
468005
468297
  ## Authoring tips
468006
468298
 
468007
- 1. **Start from examples** \u2014 \`examples/demo-pipeline.yaml\` (FFmpeg-only,
468008
- no keys), \`examples/promo-video.yaml\` (AI providers).
468299
+ 1. **Start from a tiny YAML** \u2014 keep it in your project directory and run
468300
+ \`vibe run pipeline.yaml --dry-run\` before spending provider budget.
468009
468301
  2. **Dry-run first** \u2014 you see estimated cost and resolved variable
468010
468302
  graph before spending API credits.
468011
468303
  3. **Keep step ids short and descriptive** (\`intro\`, \`scene1\`, \`voice\`,
@@ -468040,18 +468332,18 @@ var META = {
468040
468332
  title: "Scene authoring with vibe",
468041
468333
  summary: "Author per-scene HTML compositions and render to MP4 (BUILD flow)",
468042
468334
  steps: [
468043
- 'Run `vibe scene init <dir> --visual-style "<style name>"` to scaffold the project + install the Hyperframes skill (Plan H).',
468335
+ 'Run `vibe init <dir> --visual-style "<style name>"` to scaffold the project + install local composition rules.',
468044
468336
  "Edit `STORYBOARD.md` with per-beat YAML cues (narration / backdrop / duration).",
468045
468337
  "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."
468338
+ "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.",
468339
+ "Run `vibe scene lint --fix` to validate, then `vibe render <dir>` to produce the MP4."
468048
468340
  ],
468049
468341
  relatedCommands: [
468050
- "vibe scene init",
468342
+ "vibe init",
468051
468343
  "vibe scene styles",
468052
468344
  "vibe scene install-skill",
468053
468345
  "vibe scene compose-prompts",
468054
- "vibe scene build",
468346
+ "vibe build",
468055
468347
  "vibe scene lint",
468056
468348
  "vibe scene render",
468057
468349
  "vibe scene add"