@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.
- package/README.md +10 -10
- package/dist/index.js +773 -481
- 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
|
|
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/
|
|
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
|
|
23795
|
-
"generate video -p
|
|
23796
|
-
"generate video -p
|
|
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
|
|
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>\`
|
|
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(
|
|
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
|
|
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=")(
|
|
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,
|
|
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 #
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 #
|
|
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
|
-
|
|
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 = {
|
|
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 === "
|
|
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/
|
|
460954
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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(`
|
|
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("
|
|
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
|
-
|
|
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
|
|
461908
|
-
console.log(` ${source_default.cyan("vibe
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"
|
|
465067
|
-
"
|
|
465068
|
-
"
|
|
465069
|
-
"
|
|
465070
|
-
"
|
|
465071
|
-
"
|
|
465072
|
-
"
|
|
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 =
|
|
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({
|
|
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
|
|
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
|
|
467820
|
-
| **High-craft (manual)** | \`DESIGN.md\` +
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
468181
|
+
vibe build my-promo
|
|
467890
468182
|
\`\`\`
|
|
467891
468183
|
|
|
467892
|
-
\`vibe
|
|
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
|
|
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
|
|
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
|
|
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
|
|
468008
|
-
|
|
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
|
|
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
|
|
468047
|
-
"Run `vibe scene lint --fix` to validate, then `vibe
|
|
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
|
|
468342
|
+
"vibe init",
|
|
468051
468343
|
"vibe scene styles",
|
|
468052
468344
|
"vibe scene install-skill",
|
|
468053
468345
|
"vibe scene compose-prompts",
|
|
468054
|
-
"vibe
|
|
468346
|
+
"vibe build",
|
|
468055
468347
|
"vibe scene lint",
|
|
468056
468348
|
"vibe scene render",
|
|
468057
468349
|
"vibe scene add"
|