@vibeframe/mcp-server 0.71.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 +38 -10
- package/dist/index.js +1587 -986
- 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
|
});
|
|
@@ -449232,6 +449766,7 @@ __export(output_exports, {
|
|
|
449232
449766
|
notFoundError: () => notFoundError,
|
|
449233
449767
|
outputError: () => outputError,
|
|
449234
449768
|
outputResult: () => outputResult,
|
|
449769
|
+
outputSuccess: () => outputSuccess,
|
|
449235
449770
|
spinner: () => spinner,
|
|
449236
449771
|
suggestNext: () => suggestNext,
|
|
449237
449772
|
usageError: () => usageError
|
|
@@ -449290,6 +449825,40 @@ function formatCost(min, max, unit) {
|
|
|
449290
449825
|
if (min === max) return `~$${min.toFixed(2)} ${unit}`;
|
|
449291
449826
|
return `~$${min.toFixed(2)}-$${max.toFixed(2)} ${unit}`;
|
|
449292
449827
|
}
|
|
449828
|
+
function lookupCostEstimateUpperBound(command3) {
|
|
449829
|
+
return COST_ESTIMATES[command3]?.max ?? 0;
|
|
449830
|
+
}
|
|
449831
|
+
function outputSuccess(opts) {
|
|
449832
|
+
const elapsedMs = Math.max(0, Date.now() - opts.startedAt);
|
|
449833
|
+
const costUsd = opts.costUsd ?? (opts.dryRun ? lookupCostEstimateUpperBound(opts.command) : 0);
|
|
449834
|
+
const envelope = {
|
|
449835
|
+
command: opts.command,
|
|
449836
|
+
...opts.dryRun ? { dryRun: true } : {},
|
|
449837
|
+
elapsedMs,
|
|
449838
|
+
costUsd,
|
|
449839
|
+
warnings: opts.warnings ?? [],
|
|
449840
|
+
data: opts.data
|
|
449841
|
+
};
|
|
449842
|
+
if (isJsonMode()) {
|
|
449843
|
+
const fields = process.env.VIBE_OUTPUT_FIELDS;
|
|
449844
|
+
if (fields) {
|
|
449845
|
+
const keys2 = fields.split(",").map((k) => k.trim());
|
|
449846
|
+
const data = opts.data;
|
|
449847
|
+
const filteredData = {};
|
|
449848
|
+
for (const key2 of keys2) {
|
|
449849
|
+
if (key2 in data) filteredData[key2] = data[key2];
|
|
449850
|
+
}
|
|
449851
|
+
envelope.data = filteredData;
|
|
449852
|
+
}
|
|
449853
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
449854
|
+
return;
|
|
449855
|
+
}
|
|
449856
|
+
if (isQuietMode()) {
|
|
449857
|
+
const data = opts.data;
|
|
449858
|
+
const primary = data.outputPath ?? data.output ?? data.path ?? data.url ?? data.id;
|
|
449859
|
+
if (primary !== void 0) console.log(String(primary));
|
|
449860
|
+
}
|
|
449861
|
+
}
|
|
449293
449862
|
function outputResult(result) {
|
|
449294
449863
|
if (result.dryRun && result.command && typeof result.command === "string") {
|
|
449295
449864
|
const cost = COST_ESTIMATES[result.command];
|
|
@@ -449428,57 +449997,6 @@ var init_output = __esm({
|
|
|
449428
449997
|
}
|
|
449429
449998
|
});
|
|
449430
449999
|
|
|
449431
|
-
// ../cli/src/utils/audio.ts
|
|
449432
|
-
async function getAudioDuration(filePath) {
|
|
449433
|
-
try {
|
|
449434
|
-
return await ffprobeDuration(filePath);
|
|
449435
|
-
} catch (error) {
|
|
449436
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
449437
|
-
throw new Error(`Failed to get audio duration: ${message}`);
|
|
449438
|
-
}
|
|
449439
|
-
}
|
|
449440
|
-
async function getVideoDuration(filePath) {
|
|
449441
|
-
try {
|
|
449442
|
-
return await ffprobeDuration(filePath);
|
|
449443
|
-
} catch (error) {
|
|
449444
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
449445
|
-
throw new Error(`Failed to get video duration: ${message}`);
|
|
449446
|
-
}
|
|
449447
|
-
}
|
|
449448
|
-
async function extendVideoNaturally(videoPath, targetDuration, outputPath) {
|
|
449449
|
-
const videoDuration = await getVideoDuration(videoPath);
|
|
449450
|
-
const ratio = targetDuration / videoDuration;
|
|
449451
|
-
if (ratio <= 1) {
|
|
449452
|
-
const { copyFile: copyFile5 } = await import("node:fs/promises");
|
|
449453
|
-
await copyFile5(videoPath, outputPath);
|
|
449454
|
-
return;
|
|
449455
|
-
}
|
|
449456
|
-
if (ratio <= 1.15) {
|
|
449457
|
-
const slowFactor = (1 / ratio).toFixed(4);
|
|
449458
|
-
await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS`, "-an", outputPath]);
|
|
449459
|
-
} else if (ratio <= 1.4) {
|
|
449460
|
-
const slowFactor = (1 / ratio).toFixed(4);
|
|
449461
|
-
await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
|
|
449462
|
-
} else {
|
|
449463
|
-
const slowRatio = 0.7;
|
|
449464
|
-
const slowedDuration = videoDuration / slowRatio;
|
|
449465
|
-
const freezeDuration = targetDuration - slowedDuration;
|
|
449466
|
-
if (freezeDuration <= 0) {
|
|
449467
|
-
const slowFactor = (1 / ratio).toFixed(4);
|
|
449468
|
-
await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
|
|
449469
|
-
} else {
|
|
449470
|
-
const slowFactor = (1 / slowRatio).toFixed(4);
|
|
449471
|
-
await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS,tpad=stop_mode=clone:stop_duration=${freezeDuration.toFixed(2)}`, "-an", outputPath]);
|
|
449472
|
-
}
|
|
449473
|
-
}
|
|
449474
|
-
}
|
|
449475
|
-
var init_audio = __esm({
|
|
449476
|
-
"../cli/src/utils/audio.ts"() {
|
|
449477
|
-
"use strict";
|
|
449478
|
-
init_exec_safe();
|
|
449479
|
-
}
|
|
449480
|
-
});
|
|
449481
|
-
|
|
449482
450000
|
// ../cli/src/utils/subtitle.ts
|
|
449483
450001
|
function detectFormat(outputPath, explicitFormat) {
|
|
449484
450002
|
if (explicitFormat) {
|
|
@@ -451881,6 +452399,7 @@ Examples:
|
|
|
451881
452399
|
$ vibe ed sc video.mp4 --dry-run --json
|
|
451882
452400
|
|
|
451883
452401
|
No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires GOOGLE_API_KEY).`).action(async (videoPath, options) => {
|
|
452402
|
+
const startedAt = Date.now();
|
|
451884
452403
|
try {
|
|
451885
452404
|
if (options.output) {
|
|
451886
452405
|
validateOutputPath(options.output);
|
|
@@ -451897,16 +452416,19 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
451897
452416
|
const outputPath = options.output || `${name}-cut${ext}`;
|
|
451898
452417
|
const useGemini = options.useGemini || false;
|
|
451899
452418
|
if (options.dryRun) {
|
|
451900
|
-
|
|
451901
|
-
dryRun: true,
|
|
452419
|
+
outputSuccess({
|
|
451902
452420
|
command: "edit silence-cut",
|
|
451903
|
-
|
|
451904
|
-
|
|
451905
|
-
|
|
451906
|
-
|
|
451907
|
-
|
|
451908
|
-
|
|
451909
|
-
|
|
452421
|
+
startedAt,
|
|
452422
|
+
dryRun: true,
|
|
452423
|
+
data: {
|
|
452424
|
+
params: {
|
|
452425
|
+
videoPath: absVideoPath,
|
|
452426
|
+
noiseThreshold: parseFloat(options.noise),
|
|
452427
|
+
minDuration: parseFloat(options.minDuration),
|
|
452428
|
+
padding: parseFloat(options.padding),
|
|
452429
|
+
useGemini,
|
|
452430
|
+
analyzeOnly: options.analyzeOnly || false
|
|
452431
|
+
}
|
|
451910
452432
|
}
|
|
451911
452433
|
});
|
|
451912
452434
|
return;
|
|
@@ -451931,13 +452453,16 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
451931
452453
|
}
|
|
451932
452454
|
spinner2.succeed(source_default.green("Silence detection complete"));
|
|
451933
452455
|
if (isJsonMode()) {
|
|
451934
|
-
|
|
451935
|
-
|
|
451936
|
-
|
|
451937
|
-
|
|
451938
|
-
|
|
451939
|
-
|
|
451940
|
-
|
|
452456
|
+
outputSuccess({
|
|
452457
|
+
command: "edit silence-cut",
|
|
452458
|
+
startedAt,
|
|
452459
|
+
data: {
|
|
452460
|
+
method: result.method,
|
|
452461
|
+
totalDuration: result.totalDuration,
|
|
452462
|
+
silentPeriods: result.silentPeriods,
|
|
452463
|
+
silentDuration: result.silentDuration,
|
|
452464
|
+
outputPath: result.outputPath
|
|
452465
|
+
}
|
|
451941
452466
|
});
|
|
451942
452467
|
return;
|
|
451943
452468
|
}
|
|
@@ -451975,6 +452500,7 @@ Examples:
|
|
|
451975
452500
|
$ vibe ed cap video.mp4 --dry-run --json
|
|
451976
452501
|
|
|
451977
452502
|
Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoPath, options) => {
|
|
452503
|
+
const startedAt = Date.now();
|
|
451978
452504
|
try {
|
|
451979
452505
|
if (options.output) {
|
|
451980
452506
|
validateOutputPath(options.output);
|
|
@@ -451987,16 +452513,19 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
451987
452513
|
exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
|
|
451988
452514
|
}
|
|
451989
452515
|
if (options.dryRun) {
|
|
451990
|
-
|
|
451991
|
-
dryRun: true,
|
|
452516
|
+
outputSuccess({
|
|
451992
452517
|
command: "edit caption",
|
|
451993
|
-
|
|
451994
|
-
|
|
451995
|
-
|
|
451996
|
-
|
|
451997
|
-
|
|
451998
|
-
|
|
451999
|
-
|
|
452518
|
+
startedAt,
|
|
452519
|
+
dryRun: true,
|
|
452520
|
+
data: {
|
|
452521
|
+
params: {
|
|
452522
|
+
videoPath: absVideoPath,
|
|
452523
|
+
style: options.style,
|
|
452524
|
+
fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
|
|
452525
|
+
fontColor: options.color,
|
|
452526
|
+
language: options.language,
|
|
452527
|
+
position: options.position
|
|
452528
|
+
}
|
|
452000
452529
|
}
|
|
452001
452530
|
});
|
|
452002
452531
|
return;
|
|
@@ -452025,12 +452554,15 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452025
452554
|
}
|
|
452026
452555
|
spinner2.succeed(source_default.green("Captions applied"));
|
|
452027
452556
|
if (isJsonMode()) {
|
|
452028
|
-
|
|
452029
|
-
|
|
452030
|
-
|
|
452031
|
-
|
|
452032
|
-
|
|
452033
|
-
|
|
452557
|
+
outputSuccess({
|
|
452558
|
+
command: "edit caption",
|
|
452559
|
+
startedAt,
|
|
452560
|
+
data: {
|
|
452561
|
+
segmentCount: result.segmentCount,
|
|
452562
|
+
style: options.style || "bold",
|
|
452563
|
+
outputPath: result.outputPath,
|
|
452564
|
+
srtPath: result.srtPath
|
|
452565
|
+
}
|
|
452034
452566
|
});
|
|
452035
452567
|
return;
|
|
452036
452568
|
}
|
|
@@ -452049,6 +452581,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452049
452581
|
}
|
|
452050
452582
|
});
|
|
452051
452583
|
aiCommand.command("noise-reduce").description("Remove background noise from audio/video using FFmpeg (no API key needed)").argument("<input>", "Audio or video file path").option("-o, --output <path>", "Output file path (default: <name>-denoised.<ext>)").option("-s, --strength <level>", "Noise reduction strength: low, medium, high (default: medium)", "medium").option("-n, --noise-floor <dB>", "Custom noise floor in dB (overrides strength preset)").option("--dry-run", "Preview parameters without executing").action(async (inputPath, options) => {
|
|
452584
|
+
const startedAt = Date.now();
|
|
452052
452585
|
try {
|
|
452053
452586
|
if (options.output) {
|
|
452054
452587
|
validateOutputPath(options.output);
|
|
@@ -452061,13 +452594,16 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452061
452594
|
exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
|
|
452062
452595
|
}
|
|
452063
452596
|
if (options.dryRun) {
|
|
452064
|
-
|
|
452065
|
-
dryRun: true,
|
|
452597
|
+
outputSuccess({
|
|
452066
452598
|
command: "edit noise-reduce",
|
|
452067
|
-
|
|
452068
|
-
|
|
452069
|
-
|
|
452070
|
-
|
|
452599
|
+
startedAt,
|
|
452600
|
+
dryRun: true,
|
|
452601
|
+
data: {
|
|
452602
|
+
params: {
|
|
452603
|
+
inputPath: absInputPath,
|
|
452604
|
+
strength: options.strength,
|
|
452605
|
+
noiseFloor: options.noiseFloor ? parseFloat(options.noiseFloor) : void 0
|
|
452606
|
+
}
|
|
452071
452607
|
}
|
|
452072
452608
|
});
|
|
452073
452609
|
return;
|
|
@@ -452088,11 +452624,14 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452088
452624
|
}
|
|
452089
452625
|
spinner2.succeed(source_default.green("Noise reduction complete"));
|
|
452090
452626
|
if (isJsonMode()) {
|
|
452091
|
-
|
|
452092
|
-
|
|
452093
|
-
|
|
452094
|
-
|
|
452095
|
-
|
|
452627
|
+
outputSuccess({
|
|
452628
|
+
command: "edit noise-reduce",
|
|
452629
|
+
startedAt,
|
|
452630
|
+
data: {
|
|
452631
|
+
inputDuration: result.inputDuration,
|
|
452632
|
+
strength: options.strength || "medium",
|
|
452633
|
+
outputPath: result.outputPath
|
|
452634
|
+
}
|
|
452096
452635
|
});
|
|
452097
452636
|
return;
|
|
452098
452637
|
}
|
|
@@ -452108,6 +452647,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452108
452647
|
}
|
|
452109
452648
|
});
|
|
452110
452649
|
aiCommand.command("fade").description("Apply fade in/out effects to video (FFmpeg only, no API key needed)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-faded.<ext>)").option("--fade-in <seconds>", "Fade-in duration in seconds (default: 1)", "1").option("--fade-out <seconds>", "Fade-out duration in seconds (default: 1)", "1").option("--audio-only", "Apply fade to audio only (video stream copied)").option("--video-only", "Apply fade to video only (audio stream copied)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
452650
|
+
const startedAt = Date.now();
|
|
452111
452651
|
try {
|
|
452112
452652
|
if (options.output) {
|
|
452113
452653
|
validateOutputPath(options.output);
|
|
@@ -452120,15 +452660,18 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452120
452660
|
exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
|
|
452121
452661
|
}
|
|
452122
452662
|
if (options.dryRun) {
|
|
452123
|
-
|
|
452124
|
-
dryRun: true,
|
|
452663
|
+
outputSuccess({
|
|
452125
452664
|
command: "edit fade",
|
|
452126
|
-
|
|
452127
|
-
|
|
452128
|
-
|
|
452129
|
-
|
|
452130
|
-
|
|
452131
|
-
|
|
452665
|
+
startedAt,
|
|
452666
|
+
dryRun: true,
|
|
452667
|
+
data: {
|
|
452668
|
+
params: {
|
|
452669
|
+
videoPath: absVideoPath,
|
|
452670
|
+
fadeIn: parseFloat(options.fadeIn),
|
|
452671
|
+
fadeOut: parseFloat(options.fadeOut),
|
|
452672
|
+
audioOnly: options.audioOnly || false,
|
|
452673
|
+
videoOnly: options.videoOnly || false
|
|
452674
|
+
}
|
|
452132
452675
|
}
|
|
452133
452676
|
});
|
|
452134
452677
|
return;
|
|
@@ -452151,12 +452694,15 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452151
452694
|
}
|
|
452152
452695
|
spinner2.succeed(source_default.green("Fade effects applied"));
|
|
452153
452696
|
if (isJsonMode()) {
|
|
452154
|
-
|
|
452155
|
-
|
|
452156
|
-
|
|
452157
|
-
|
|
452158
|
-
|
|
452159
|
-
|
|
452697
|
+
outputSuccess({
|
|
452698
|
+
command: "edit fade",
|
|
452699
|
+
startedAt,
|
|
452700
|
+
data: {
|
|
452701
|
+
totalDuration: result.totalDuration,
|
|
452702
|
+
fadeInApplied: result.fadeInApplied,
|
|
452703
|
+
fadeOutApplied: result.fadeOutApplied,
|
|
452704
|
+
outputPath: result.outputPath
|
|
452705
|
+
}
|
|
452160
452706
|
});
|
|
452161
452707
|
return;
|
|
452162
452708
|
}
|
|
@@ -452173,6 +452719,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452173
452719
|
}
|
|
452174
452720
|
});
|
|
452175
452721
|
aiCommand.command("translate-srt").description("Translate SRT subtitle file to another language (Claude/OpenAI)").argument("<srt>", "SRT file path").option("-t, --target <language>", "Target language (e.g., ko, es, fr, ja, zh)").option("-o, --output <path>", "Output file path (default: <name>-<target>.srt)").option("-p, --provider <provider>", "Translation provider: claude, openai (default: claude)", "claude").option("--source <language>", "Source language (auto-detected if omitted)").option("-k, --api-key <key>", "API key (or set ANTHROPIC_API_KEY / OPENAI_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (srtPath, options) => {
|
|
452722
|
+
const startedAt = Date.now();
|
|
452176
452723
|
try {
|
|
452177
452724
|
if (options.output) {
|
|
452178
452725
|
validateOutputPath(options.output);
|
|
@@ -452185,14 +452732,17 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452185
452732
|
exitWithError(notFoundError(absSrtPath));
|
|
452186
452733
|
}
|
|
452187
452734
|
if (options.dryRun) {
|
|
452188
|
-
|
|
452189
|
-
dryRun: true,
|
|
452735
|
+
outputSuccess({
|
|
452190
452736
|
command: "edit translate-srt",
|
|
452191
|
-
|
|
452192
|
-
|
|
452193
|
-
|
|
452194
|
-
|
|
452195
|
-
|
|
452737
|
+
startedAt,
|
|
452738
|
+
dryRun: true,
|
|
452739
|
+
data: {
|
|
452740
|
+
params: {
|
|
452741
|
+
srtPath: absSrtPath,
|
|
452742
|
+
targetLanguage: options.target,
|
|
452743
|
+
provider: options.provider || "claude",
|
|
452744
|
+
sourceLanguage: options.source
|
|
452745
|
+
}
|
|
452196
452746
|
}
|
|
452197
452747
|
});
|
|
452198
452748
|
return;
|
|
@@ -452222,12 +452772,15 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452222
452772
|
}
|
|
452223
452773
|
spinner2.succeed(source_default.green("Translation complete"));
|
|
452224
452774
|
if (isJsonMode()) {
|
|
452225
|
-
|
|
452226
|
-
|
|
452227
|
-
|
|
452228
|
-
|
|
452229
|
-
|
|
452230
|
-
|
|
452775
|
+
outputSuccess({
|
|
452776
|
+
command: "edit translate-srt",
|
|
452777
|
+
startedAt,
|
|
452778
|
+
data: {
|
|
452779
|
+
segmentCount: result.segmentCount,
|
|
452780
|
+
sourceLanguage: result.sourceLanguage,
|
|
452781
|
+
targetLanguage: result.targetLanguage,
|
|
452782
|
+
outputPath: result.outputPath
|
|
452783
|
+
}
|
|
452231
452784
|
});
|
|
452232
452785
|
return;
|
|
452233
452786
|
}
|
|
@@ -452244,6 +452797,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452244
452797
|
}
|
|
452245
452798
|
});
|
|
452246
452799
|
aiCommand.command("jump-cut").description("Remove filler words (um, uh, like, etc.) from video using Whisper word-level timestamps").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-jumpcut.<ext>)").option("--fillers <words>", "Comma-separated filler words to detect").option("--padding <seconds>", "Padding around cuts in seconds (default: 0.05)", "0.05").option("-l, --language <lang>", "Language code for transcription (e.g., en, ko)").option("--analyze-only", "Only detect fillers, don't cut").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
452800
|
+
const startedAt = Date.now();
|
|
452247
452801
|
try {
|
|
452248
452802
|
if (options.output) {
|
|
452249
452803
|
validateOutputPath(options.output);
|
|
@@ -452258,15 +452812,18 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452258
452812
|
}
|
|
452259
452813
|
if (options.dryRun) {
|
|
452260
452814
|
const fillers2 = options.fillers ? options.fillers.split(",").map((f) => f.trim()) : void 0;
|
|
452261
|
-
|
|
452262
|
-
dryRun: true,
|
|
452815
|
+
outputSuccess({
|
|
452263
452816
|
command: "edit jump-cut",
|
|
452264
|
-
|
|
452265
|
-
|
|
452266
|
-
|
|
452267
|
-
|
|
452268
|
-
|
|
452269
|
-
|
|
452817
|
+
startedAt,
|
|
452818
|
+
dryRun: true,
|
|
452819
|
+
data: {
|
|
452820
|
+
params: {
|
|
452821
|
+
videoPath: absVideoPath,
|
|
452822
|
+
fillers: fillers2,
|
|
452823
|
+
padding: parseFloat(options.padding),
|
|
452824
|
+
language: options.language,
|
|
452825
|
+
analyzeOnly: options.analyzeOnly || false
|
|
452826
|
+
}
|
|
452270
452827
|
}
|
|
452271
452828
|
});
|
|
452272
452829
|
return;
|
|
@@ -452295,13 +452852,16 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
452295
452852
|
}
|
|
452296
452853
|
spinner2.succeed(source_default.green("Filler detection complete"));
|
|
452297
452854
|
if (isJsonMode()) {
|
|
452298
|
-
|
|
452299
|
-
|
|
452300
|
-
|
|
452301
|
-
|
|
452302
|
-
|
|
452303
|
-
|
|
452304
|
-
|
|
452855
|
+
outputSuccess({
|
|
452856
|
+
command: "edit jump-cut",
|
|
452857
|
+
startedAt,
|
|
452858
|
+
data: {
|
|
452859
|
+
totalDuration: result.totalDuration,
|
|
452860
|
+
fillerCount: result.fillerCount,
|
|
452861
|
+
fillerDuration: result.fillerDuration,
|
|
452862
|
+
fillers: result.fillers,
|
|
452863
|
+
outputPath: result.outputPath
|
|
452864
|
+
}
|
|
452305
452865
|
});
|
|
452306
452866
|
return;
|
|
452307
452867
|
}
|
|
@@ -453446,6 +454006,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453446
454006
|
registerEditCommands(editCommand);
|
|
453447
454007
|
registerFillGapsCommand(editCommand);
|
|
453448
454008
|
editCommand.command("grade").description("Apply AI-generated color grading (Claude + FFmpeg)").argument("<video>", "Video file path").option("-s, --style <prompt>", "Style description (e.g., 'cinematic warm')").option("--preset <name>", "Built-in preset: film-noir, vintage, cinematic-warm, cool-tones, high-contrast, pastel, cyberpunk, horror").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show filter without applying").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
454009
|
+
const startedAt = Date.now();
|
|
453449
454010
|
try {
|
|
453450
454011
|
if (options.style) rejectControlChars(options.style);
|
|
453451
454012
|
if (options.output) {
|
|
@@ -453461,13 +454022,16 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453461
454022
|
exitWithError(notFoundError("FFmpeg not found. Install with: brew install ffmpeg"));
|
|
453462
454023
|
}
|
|
453463
454024
|
if (options.dryRun) {
|
|
453464
|
-
|
|
453465
|
-
dryRun: true,
|
|
454025
|
+
outputSuccess({
|
|
453466
454026
|
command: "edit grade",
|
|
453467
|
-
|
|
453468
|
-
|
|
453469
|
-
|
|
453470
|
-
|
|
454027
|
+
startedAt,
|
|
454028
|
+
dryRun: true,
|
|
454029
|
+
data: {
|
|
454030
|
+
params: {
|
|
454031
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
454032
|
+
style: options.style || options.preset,
|
|
454033
|
+
analyzeOnly: options.analyzeOnly || false
|
|
454034
|
+
}
|
|
453471
454035
|
}
|
|
453472
454036
|
});
|
|
453473
454037
|
return;
|
|
@@ -453493,12 +454057,15 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453493
454057
|
if (isJsonMode()) {
|
|
453494
454058
|
const absPath2 = resolve29(process.cwd(), videoPath);
|
|
453495
454059
|
const gradeOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath2.replace(/(\.[^.]+)$/, "-graded$1");
|
|
453496
|
-
|
|
453497
|
-
|
|
453498
|
-
|
|
453499
|
-
|
|
453500
|
-
|
|
453501
|
-
|
|
454060
|
+
outputSuccess({
|
|
454061
|
+
command: "edit grade",
|
|
454062
|
+
startedAt,
|
|
454063
|
+
data: {
|
|
454064
|
+
style: options.preset || options.style,
|
|
454065
|
+
description: gradeResult.description,
|
|
454066
|
+
ffmpegFilter: gradeResult.ffmpegFilter,
|
|
454067
|
+
outputPath: options.analyzeOnly ? void 0 : gradeOutputPath
|
|
454068
|
+
}
|
|
453502
454069
|
});
|
|
453503
454070
|
return;
|
|
453504
454071
|
}
|
|
@@ -453527,6 +454094,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453527
454094
|
}
|
|
453528
454095
|
});
|
|
453529
454096
|
editCommand.command("text-overlay").description("Apply text overlays to video (FFmpeg drawtext)").argument("<video>", "Video file path").option("-t, --text <texts...>", "Text lines to overlay (repeat for multiple)").option("-s, --style <style>", "Overlay style: lower-third, center-bold, subtitle, minimal", "lower-third").option("--font-size <size>", "Font size in pixels (auto-calculated if omitted)").option("--font-color <color>", "Font color (default: white)", "white").option("--fade <seconds>", "Fade in/out duration in seconds", "0.3").option("--start <seconds>", "Start time in seconds", "0").option("--end <seconds>", "End time in seconds (default: video duration)").option("-o, --output <path>", "Output video file path").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
454097
|
+
const startedAt = Date.now();
|
|
453530
454098
|
try {
|
|
453531
454099
|
if (!options.text || options.text.length === 0) {
|
|
453532
454100
|
exitWithError(usageError("At least one --text option is required", 'Example: vibe edit text-overlay video.mp4 -t "NEXUS AI" --style center-bold'));
|
|
@@ -453539,18 +454107,21 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453539
454107
|
exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
|
|
453540
454108
|
}
|
|
453541
454109
|
if (options.dryRun) {
|
|
453542
|
-
|
|
453543
|
-
dryRun: true,
|
|
454110
|
+
outputSuccess({
|
|
453544
454111
|
command: "edit text-overlay",
|
|
453545
|
-
|
|
453546
|
-
|
|
453547
|
-
|
|
453548
|
-
|
|
453549
|
-
|
|
453550
|
-
|
|
453551
|
-
|
|
453552
|
-
|
|
453553
|
-
|
|
454112
|
+
startedAt,
|
|
454113
|
+
dryRun: true,
|
|
454114
|
+
data: {
|
|
454115
|
+
params: {
|
|
454116
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
454117
|
+
texts: options.text,
|
|
454118
|
+
style: options.style,
|
|
454119
|
+
fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
|
|
454120
|
+
fontColor: options.fontColor,
|
|
454121
|
+
fade: parseFloat(options.fade),
|
|
454122
|
+
start: parseFloat(options.start),
|
|
454123
|
+
end: options.end ? parseFloat(options.end) : void 0
|
|
454124
|
+
}
|
|
453554
454125
|
}
|
|
453555
454126
|
});
|
|
453556
454127
|
return;
|
|
@@ -453575,11 +454146,14 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453575
454146
|
}
|
|
453576
454147
|
spinner2.succeed(source_default.green("Text overlays applied"));
|
|
453577
454148
|
if (isJsonMode()) {
|
|
453578
|
-
|
|
453579
|
-
|
|
453580
|
-
|
|
453581
|
-
|
|
453582
|
-
|
|
454149
|
+
outputSuccess({
|
|
454150
|
+
command: "edit text-overlay",
|
|
454151
|
+
startedAt,
|
|
454152
|
+
data: {
|
|
454153
|
+
style: options.style,
|
|
454154
|
+
texts: options.text,
|
|
454155
|
+
outputPath: result.outputPath
|
|
454156
|
+
}
|
|
453583
454157
|
});
|
|
453584
454158
|
return;
|
|
453585
454159
|
}
|
|
@@ -453596,6 +454170,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453596
454170
|
}
|
|
453597
454171
|
});
|
|
453598
454172
|
editCommand.command("speed-ramp").description("Apply content-aware speed ramping (Whisper + Claude + FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output video file path").option("-s, --style <style>", "Style: dramatic, smooth, action", "dramatic").option("--min-speed <factor>", "Minimum speed factor", "0.25").option("--max-speed <factor>", "Maximum speed factor", "4.0").option("--analyze-only", "Show keyframes without applying").option("-l, --language <lang>", "Language code for transcription").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
454173
|
+
const startedAt = Date.now();
|
|
453599
454174
|
try {
|
|
453600
454175
|
if (options.output) {
|
|
453601
454176
|
validateOutputPath(options.output);
|
|
@@ -453604,15 +454179,18 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453604
454179
|
exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
|
|
453605
454180
|
}
|
|
453606
454181
|
if (options.dryRun) {
|
|
453607
|
-
|
|
453608
|
-
dryRun: true,
|
|
454182
|
+
outputSuccess({
|
|
453609
454183
|
command: "edit speed-ramp",
|
|
453610
|
-
|
|
453611
|
-
|
|
453612
|
-
|
|
453613
|
-
|
|
453614
|
-
|
|
453615
|
-
|
|
454184
|
+
startedAt,
|
|
454185
|
+
dryRun: true,
|
|
454186
|
+
data: {
|
|
454187
|
+
params: {
|
|
454188
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
454189
|
+
style: options.style,
|
|
454190
|
+
minSpeed: parseFloat(options.minSpeed),
|
|
454191
|
+
maxSpeed: parseFloat(options.maxSpeed),
|
|
454192
|
+
analyzeOnly: options.analyzeOnly || false
|
|
454193
|
+
}
|
|
453616
454194
|
}
|
|
453617
454195
|
});
|
|
453618
454196
|
return;
|
|
@@ -453665,11 +454243,14 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453665
454243
|
if (isJsonMode()) {
|
|
453666
454244
|
const avgSpeed2 = speedResult.keyframes.reduce((sum, kf) => sum + kf.speed, 0) / speedResult.keyframes.length;
|
|
453667
454245
|
const speedRampOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
|
|
453668
|
-
|
|
453669
|
-
|
|
453670
|
-
|
|
453671
|
-
|
|
453672
|
-
|
|
454246
|
+
outputSuccess({
|
|
454247
|
+
command: "edit speed-ramp",
|
|
454248
|
+
startedAt,
|
|
454249
|
+
data: {
|
|
454250
|
+
keyframes: speedResult.keyframes,
|
|
454251
|
+
avgSpeed: avgSpeed2,
|
|
454252
|
+
outputPath: options.analyzeOnly ? void 0 : speedRampOutputPath
|
|
454253
|
+
}
|
|
453673
454254
|
});
|
|
453674
454255
|
return;
|
|
453675
454256
|
}
|
|
@@ -453709,6 +454290,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453709
454290
|
}
|
|
453710
454291
|
});
|
|
453711
454292
|
editCommand.command("reframe").description("Auto-reframe video to different aspect ratio (Claude Vision + FFmpeg)").argument("<video>", "Video file path").option("-a, --aspect <ratio>", "Target aspect ratio: 9:16, 1:1, 4:5", "9:16").option("-f, --focus <mode>", "Focus mode: auto, face, center, action", "auto").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show crop regions without applying").option("--keyframes <path>", "Export keyframes to JSON file").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
454293
|
+
const startedAt = Date.now();
|
|
453712
454294
|
try {
|
|
453713
454295
|
if (options.output) {
|
|
453714
454296
|
validateOutputPath(options.output);
|
|
@@ -453717,14 +454299,17 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453717
454299
|
exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
|
|
453718
454300
|
}
|
|
453719
454301
|
if (options.dryRun) {
|
|
453720
|
-
|
|
453721
|
-
dryRun: true,
|
|
454302
|
+
outputSuccess({
|
|
453722
454303
|
command: "edit reframe",
|
|
453723
|
-
|
|
453724
|
-
|
|
453725
|
-
|
|
453726
|
-
|
|
453727
|
-
|
|
454304
|
+
startedAt,
|
|
454305
|
+
dryRun: true,
|
|
454306
|
+
data: {
|
|
454307
|
+
params: {
|
|
454308
|
+
videoPath: resolve29(process.cwd(), videoPath),
|
|
454309
|
+
aspect: options.aspect,
|
|
454310
|
+
focus: options.focus,
|
|
454311
|
+
analyzeOnly: options.analyzeOnly || false
|
|
454312
|
+
}
|
|
453728
454313
|
}
|
|
453729
454314
|
});
|
|
453730
454315
|
return;
|
|
@@ -453792,13 +454377,16 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453792
454377
|
spinner2.succeed(source_default.green(`Analyzed ${cropKeyframes.length} keyframes`));
|
|
453793
454378
|
if (isJsonMode()) {
|
|
453794
454379
|
const reframeOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
|
|
453795
|
-
|
|
453796
|
-
|
|
453797
|
-
|
|
453798
|
-
|
|
453799
|
-
|
|
453800
|
-
|
|
453801
|
-
|
|
454380
|
+
outputSuccess({
|
|
454381
|
+
command: "edit reframe",
|
|
454382
|
+
startedAt,
|
|
454383
|
+
data: {
|
|
454384
|
+
sourceWidth,
|
|
454385
|
+
sourceHeight,
|
|
454386
|
+
aspect: options.aspect,
|
|
454387
|
+
cropKeyframes,
|
|
454388
|
+
outputPath: options.analyzeOnly ? void 0 : reframeOutputPath
|
|
454389
|
+
}
|
|
453802
454390
|
});
|
|
453803
454391
|
return;
|
|
453804
454392
|
}
|
|
@@ -453849,6 +454437,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453849
454437
|
}
|
|
453850
454438
|
});
|
|
453851
454439
|
editCommand.command("image").description("Edit image(s) using AI (Gemini/OpenAI/Grok)").argument("<images...>", "Input image file(s) followed by edit prompt").option("-p, --provider <provider>", "Provider: gemini (default), openai, grok", "gemini").option("-k, --api-key <key>", "API key (or set env variable)").option("-o, --output <path>", "Output file path", "edited.png").option("-m, --model <model>", "Model: flash/3.1-flash/latest/pro (Gemini only)", "flash").option("-r, --ratio <ratio>", "Output aspect ratio").option("-s, --size <resolution>", "Resolution: 1K, 2K, 4K (Gemini Pro only)").option("--dry-run", "Preview parameters without executing").action(async (args, options) => {
|
|
454440
|
+
const startedAt = Date.now();
|
|
453852
454441
|
try {
|
|
453853
454442
|
if (args.length < 2) {
|
|
453854
454443
|
exitWithError(usageError("Need at least one image and a prompt"));
|
|
@@ -453864,16 +454453,19 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453864
454453
|
exitWithError(usageError("Grok supports only 1 input image for editing.", "Use -p gemini (up to 14 images) or -p openai (up to 16 images) for multi-image editing."));
|
|
453865
454454
|
}
|
|
453866
454455
|
if (options.dryRun) {
|
|
453867
|
-
|
|
453868
|
-
dryRun: true,
|
|
454456
|
+
outputSuccess({
|
|
453869
454457
|
command: "edit image",
|
|
453870
|
-
|
|
453871
|
-
|
|
453872
|
-
|
|
453873
|
-
|
|
453874
|
-
|
|
453875
|
-
|
|
453876
|
-
|
|
454458
|
+
startedAt,
|
|
454459
|
+
dryRun: true,
|
|
454460
|
+
data: {
|
|
454461
|
+
params: {
|
|
454462
|
+
imagePaths: imagePaths.map((p) => resolve29(process.cwd(), p)),
|
|
454463
|
+
prompt: prompt3,
|
|
454464
|
+
provider,
|
|
454465
|
+
model: options.model,
|
|
454466
|
+
ratio: options.ratio,
|
|
454467
|
+
size: options.size
|
|
454468
|
+
}
|
|
453877
454469
|
}
|
|
453878
454470
|
});
|
|
453879
454471
|
return;
|
|
@@ -453951,11 +454543,14 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453951
454543
|
};
|
|
453952
454544
|
const resultModel = result.model;
|
|
453953
454545
|
if (isJsonMode()) {
|
|
453954
|
-
|
|
453955
|
-
|
|
453956
|
-
|
|
453957
|
-
|
|
453958
|
-
|
|
454546
|
+
outputSuccess({
|
|
454547
|
+
command: "edit image",
|
|
454548
|
+
startedAt,
|
|
454549
|
+
data: {
|
|
454550
|
+
provider,
|
|
454551
|
+
model: resultModel || options.model,
|
|
454552
|
+
outputPath
|
|
454553
|
+
}
|
|
453959
454554
|
});
|
|
453960
454555
|
await saveImage();
|
|
453961
454556
|
return;
|
|
@@ -453971,6 +454566,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453971
454566
|
}
|
|
453972
454567
|
});
|
|
453973
454568
|
editCommand.command("interpolate").description("Create slow motion with frame interpolation (FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("-f, --factor <number>", "Slow motion factor: 2, 4, or 8", "2").option("--fps <number>", "Target output FPS").option("--quality <mode>", "Quality: fast or quality", "quality").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
454569
|
+
const startedAt = Date.now();
|
|
453974
454570
|
try {
|
|
453975
454571
|
if (options.output) {
|
|
453976
454572
|
validateOutputPath(options.output);
|
|
@@ -453981,14 +454577,17 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
453981
454577
|
exitWithError(usageError("Factor must be 2, 4, or 8"));
|
|
453982
454578
|
}
|
|
453983
454579
|
if (options.dryRun) {
|
|
453984
|
-
|
|
453985
|
-
dryRun: true,
|
|
454580
|
+
outputSuccess({
|
|
453986
454581
|
command: "edit interpolate",
|
|
453987
|
-
|
|
453988
|
-
|
|
453989
|
-
|
|
453990
|
-
|
|
453991
|
-
|
|
454582
|
+
startedAt,
|
|
454583
|
+
dryRun: true,
|
|
454584
|
+
data: {
|
|
454585
|
+
params: {
|
|
454586
|
+
videoPath: absPath,
|
|
454587
|
+
factor,
|
|
454588
|
+
fps: options.fps ? parseInt(options.fps) : void 0,
|
|
454589
|
+
quality: options.quality
|
|
454590
|
+
}
|
|
453992
454591
|
}
|
|
453993
454592
|
});
|
|
453994
454593
|
return;
|
|
@@ -454015,12 +454614,15 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
454015
454614
|
await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `minterpolate='${mi}:fps=${targetFps}',setpts=${factor}*PTS`, "-an", outputPath, "-y"], { timeout: 6e5 });
|
|
454016
454615
|
spinner2.succeed(source_default.green(`Created ${factor}x slow motion`));
|
|
454017
454616
|
if (isJsonMode()) {
|
|
454018
|
-
|
|
454019
|
-
|
|
454020
|
-
|
|
454021
|
-
|
|
454022
|
-
|
|
454023
|
-
|
|
454617
|
+
outputSuccess({
|
|
454618
|
+
command: "edit interpolate",
|
|
454619
|
+
startedAt,
|
|
454620
|
+
data: {
|
|
454621
|
+
originalFps,
|
|
454622
|
+
targetFps,
|
|
454623
|
+
factor,
|
|
454624
|
+
outputPath
|
|
454625
|
+
}
|
|
454024
454626
|
});
|
|
454025
454627
|
return;
|
|
454026
454628
|
}
|
|
@@ -454046,6 +454648,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
454046
454648
|
}
|
|
454047
454649
|
});
|
|
454048
454650
|
editCommand.command("upscale-video").description("Upscale video resolution using AI or FFmpeg").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("-s, --scale <factor>", "Scale factor: 2 or 4", "2").option("-m, --model <model>", "Model: real-esrgan, topaz", "real-esrgan").option("--ffmpeg", "Use FFmpeg lanczos (free, no API)").option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)").option("--no-wait", "Start processing and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
454651
|
+
const startedAt = Date.now();
|
|
454049
454652
|
try {
|
|
454050
454653
|
if (options.output) {
|
|
454051
454654
|
validateOutputPath(options.output);
|
|
@@ -454056,14 +454659,17 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
454056
454659
|
exitWithError(usageError("Scale must be 2 or 4"));
|
|
454057
454660
|
}
|
|
454058
454661
|
if (options.dryRun) {
|
|
454059
|
-
|
|
454060
|
-
dryRun: true,
|
|
454662
|
+
outputSuccess({
|
|
454061
454663
|
command: "edit upscale-video",
|
|
454062
|
-
|
|
454063
|
-
|
|
454064
|
-
|
|
454065
|
-
|
|
454066
|
-
|
|
454664
|
+
startedAt,
|
|
454665
|
+
dryRun: true,
|
|
454666
|
+
data: {
|
|
454667
|
+
params: {
|
|
454668
|
+
videoPath: absPath,
|
|
454669
|
+
scale,
|
|
454670
|
+
model: options.model,
|
|
454671
|
+
ffmpeg: options.ffmpeg || false
|
|
454672
|
+
}
|
|
454067
454673
|
}
|
|
454068
454674
|
});
|
|
454069
454675
|
return;
|
|
@@ -454089,10 +454695,13 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
454089
454695
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", `scale=${newWidth}:${newHeight}:flags=lanczos`, "-c:a", "copy", outputPath, "-y"]);
|
|
454090
454696
|
spinner3.succeed(source_default.green(`Upscaled to ${newWidth}x${newHeight}`));
|
|
454091
454697
|
if (isJsonMode()) {
|
|
454092
|
-
|
|
454093
|
-
|
|
454094
|
-
|
|
454095
|
-
|
|
454698
|
+
outputSuccess({
|
|
454699
|
+
command: "edit upscale-video",
|
|
454700
|
+
startedAt,
|
|
454701
|
+
data: {
|
|
454702
|
+
dimensions: `${newWidth}x${newHeight}`,
|
|
454703
|
+
outputPath
|
|
454704
|
+
}
|
|
454096
454705
|
});
|
|
454097
454706
|
return;
|
|
454098
454707
|
}
|
|
@@ -454797,21 +455406,25 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
454797
455406
|
}
|
|
454798
455407
|
function registerReviewCommand(aiCommand) {
|
|
454799
455408
|
aiCommand.command("review").description("Review video quality using Gemini AI and optionally auto-fix issues").argument("<video>", "Video file path").option("-s, --storyboard <path>", "Storyboard JSON file for context").option("--auto-apply", "Automatically apply fixable corrections").option("--verify", "Run verification pass after applying fixes").option("-m, --model <model>", "Gemini model: flash (default), flash-2.5, pro", "flash").option("-o, --output <path>", "Output video file path (for auto-apply)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
455409
|
+
const startedAt = Date.now();
|
|
454800
455410
|
try {
|
|
454801
455411
|
if (options.output) {
|
|
454802
455412
|
validateOutputPath(options.output);
|
|
454803
455413
|
}
|
|
454804
455414
|
if (options.dryRun) {
|
|
454805
|
-
|
|
454806
|
-
dryRun: true,
|
|
455415
|
+
outputSuccess({
|
|
454807
455416
|
command: "ai review",
|
|
454808
|
-
|
|
454809
|
-
|
|
454810
|
-
|
|
454811
|
-
|
|
454812
|
-
|
|
454813
|
-
|
|
454814
|
-
|
|
455417
|
+
startedAt,
|
|
455418
|
+
dryRun: true,
|
|
455419
|
+
data: {
|
|
455420
|
+
params: {
|
|
455421
|
+
videoPath,
|
|
455422
|
+
storyboard: options.storyboard,
|
|
455423
|
+
autoApply: options.autoApply ?? false,
|
|
455424
|
+
verify: options.verify ?? false,
|
|
455425
|
+
model: options.model,
|
|
455426
|
+
output: options.output
|
|
455427
|
+
}
|
|
454815
455428
|
}
|
|
454816
455429
|
});
|
|
454817
455430
|
return;
|
|
@@ -455079,27 +455692,31 @@ Use this image analysis to inform the color palette, typography placement, and o
|
|
|
455079
455692
|
}
|
|
455080
455693
|
function registerMotionCommand(aiCommand) {
|
|
455081
455694
|
aiCommand.command("motion").description("Generate motion graphics using Claude + Remotion (render & composite)").argument("<description>", "Natural language description of the motion graphic").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output file path", "motion.tsx").option("-d, --duration <sec>", "Duration in seconds", "5").option("-w, --width <px>", "Width in pixels", "1920").option("-h, --height <px>", "Height in pixels", "1080").option("--fps <fps>", "Frame rate", "30").option("-s, --style <style>", "Style preset: minimal, corporate, playful, cinematic").option("--render", "Render the generated code with Remotion (output .webm)").option("--video <path>", "Base video to composite the motion graphic onto").option("--image <path>", "Image to analyze with Gemini \u2014 color/mood fed into Claude prompt").option("--from-tsx <path>", "Refine an existing TSX file instead of generating from scratch").option("-m, --model <alias>", "LLM model: sonnet (default), opus, gemini, gemini-3.1-pro", "sonnet").option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
|
|
455695
|
+
const startedAt = Date.now();
|
|
455082
455696
|
try {
|
|
455083
455697
|
if (options.output) {
|
|
455084
455698
|
validateOutputPath(options.output);
|
|
455085
455699
|
}
|
|
455086
455700
|
if (options.dryRun) {
|
|
455087
|
-
|
|
455701
|
+
outputSuccess({
|
|
455702
|
+
command: "generate motion",
|
|
455703
|
+
startedAt,
|
|
455088
455704
|
dryRun: true,
|
|
455089
|
-
|
|
455090
|
-
|
|
455091
|
-
|
|
455092
|
-
|
|
455093
|
-
|
|
455094
|
-
|
|
455095
|
-
|
|
455096
|
-
|
|
455097
|
-
|
|
455098
|
-
|
|
455099
|
-
|
|
455100
|
-
|
|
455101
|
-
|
|
455102
|
-
|
|
455705
|
+
data: {
|
|
455706
|
+
params: {
|
|
455707
|
+
description: description.slice(0, 200),
|
|
455708
|
+
duration: options.duration,
|
|
455709
|
+
width: options.width,
|
|
455710
|
+
height: options.height,
|
|
455711
|
+
fps: options.fps,
|
|
455712
|
+
style: options.style,
|
|
455713
|
+
render: options.render ?? false,
|
|
455714
|
+
video: options.video,
|
|
455715
|
+
image: options.image,
|
|
455716
|
+
fromTsx: options.fromTsx,
|
|
455717
|
+
model: options.model,
|
|
455718
|
+
output: options.output
|
|
455719
|
+
}
|
|
455103
455720
|
}
|
|
455104
455721
|
});
|
|
455105
455722
|
return;
|
|
@@ -455213,20 +455830,24 @@ async function executeSoundEffect(options) {
|
|
|
455213
455830
|
}
|
|
455214
455831
|
function registerSoundEffectCommand(parent) {
|
|
455215
455832
|
parent.command("sound-effect").description("Generate sound effect using ElevenLabs").argument("<prompt>", "Description of the sound effect").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "sound-effect.mp3").option("-d, --duration <seconds>", "Duration in seconds (0.5-22, default: auto)").option("--prompt-influence <value>", "Prompt influence (0-1, default: 0.3)").option("--dry-run", "Preview parameters without executing").action(async (prompt3, options) => {
|
|
455833
|
+
const startedAt = Date.now();
|
|
455216
455834
|
try {
|
|
455217
455835
|
rejectControlChars(prompt3);
|
|
455218
455836
|
if (options.output) {
|
|
455219
455837
|
validateOutputPath(options.output);
|
|
455220
455838
|
}
|
|
455221
455839
|
if (options.dryRun) {
|
|
455222
|
-
|
|
455223
|
-
dryRun: true,
|
|
455840
|
+
outputSuccess({
|
|
455224
455841
|
command: "generate sound-effect",
|
|
455225
|
-
|
|
455226
|
-
|
|
455227
|
-
|
|
455228
|
-
|
|
455229
|
-
|
|
455842
|
+
startedAt,
|
|
455843
|
+
dryRun: true,
|
|
455844
|
+
data: {
|
|
455845
|
+
params: {
|
|
455846
|
+
prompt: prompt3,
|
|
455847
|
+
duration: options.duration,
|
|
455848
|
+
promptInfluence: options.promptInfluence,
|
|
455849
|
+
output: options.output
|
|
455850
|
+
}
|
|
455230
455851
|
}
|
|
455231
455852
|
});
|
|
455232
455853
|
return;
|
|
@@ -455253,7 +455874,11 @@ function registerSoundEffectCommand(parent) {
|
|
|
455253
455874
|
await writeFile22(outputPath, result.audioBuffer);
|
|
455254
455875
|
spinner2.succeed(source_default.green("Sound effect generated"));
|
|
455255
455876
|
if (isJsonMode()) {
|
|
455256
|
-
|
|
455877
|
+
outputSuccess({
|
|
455878
|
+
command: "generate sound-effect",
|
|
455879
|
+
startedAt,
|
|
455880
|
+
data: { outputPath }
|
|
455881
|
+
});
|
|
455257
455882
|
return;
|
|
455258
455883
|
}
|
|
455259
455884
|
console.log(source_default.green(`Saved to: ${outputPath}`));
|
|
@@ -455303,6 +455928,7 @@ async function executeMusicStatus(options) {
|
|
|
455303
455928
|
}
|
|
455304
455929
|
function registerMusicStatusCommand(parent) {
|
|
455305
455930
|
parent.command("music-status", { hidden: true }).description("Check music generation status").argument("<task-id>", "Task ID from music generation").option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)").action(async (taskId, options) => {
|
|
455931
|
+
const startedAt = Date.now();
|
|
455306
455932
|
try {
|
|
455307
455933
|
const apiKey = await requireApiKey(
|
|
455308
455934
|
"REPLICATE_API_TOKEN",
|
|
@@ -455314,12 +455940,15 @@ function registerMusicStatusCommand(parent) {
|
|
|
455314
455940
|
const result = await replicate.getMusicStatus(taskId);
|
|
455315
455941
|
if (isJsonMode()) {
|
|
455316
455942
|
const status = result.audioUrl ? "completed" : result.error ? "failed" : "processing";
|
|
455317
|
-
|
|
455318
|
-
|
|
455319
|
-
|
|
455320
|
-
|
|
455321
|
-
|
|
455322
|
-
|
|
455943
|
+
outputSuccess({
|
|
455944
|
+
command: "generate music-status",
|
|
455945
|
+
startedAt,
|
|
455946
|
+
data: {
|
|
455947
|
+
taskId,
|
|
455948
|
+
status,
|
|
455949
|
+
audioUrl: result.audioUrl,
|
|
455950
|
+
error: result.error
|
|
455951
|
+
}
|
|
455323
455952
|
});
|
|
455324
455953
|
return;
|
|
455325
455954
|
}
|
|
@@ -455357,6 +455986,7 @@ var init_music_status = __esm({
|
|
|
455357
455986
|
// ../cli/src/commands/generate/video-cancel.ts
|
|
455358
455987
|
function registerVideoCancelCommand(parent) {
|
|
455359
455988
|
parent.command("video-cancel", { hidden: true }).description("Cancel video generation (Grok or Runway)").argument("<task-id>", "Task ID to cancel").option("-p, --provider <provider>", "Provider: grok, runway", "grok").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET env)").action(async (taskId, options) => {
|
|
455989
|
+
const startedAt = Date.now();
|
|
455360
455990
|
try {
|
|
455361
455991
|
const provider = (options.provider || "grok").toLowerCase();
|
|
455362
455992
|
let success = false;
|
|
@@ -455369,7 +455999,11 @@ function registerVideoCancelCommand(parent) {
|
|
|
455369
455999
|
if (success) {
|
|
455370
456000
|
spinner2.succeed(source_default.green("Generation cancelled"));
|
|
455371
456001
|
if (isJsonMode()) {
|
|
455372
|
-
|
|
456002
|
+
outputSuccess({
|
|
456003
|
+
command: "generate video-cancel",
|
|
456004
|
+
startedAt,
|
|
456005
|
+
data: { taskId, provider: "grok", cancelled: true }
|
|
456006
|
+
});
|
|
455373
456007
|
return;
|
|
455374
456008
|
}
|
|
455375
456009
|
} else {
|
|
@@ -455389,7 +456023,11 @@ function registerVideoCancelCommand(parent) {
|
|
|
455389
456023
|
if (success) {
|
|
455390
456024
|
spinner2.succeed(source_default.green("Generation cancelled"));
|
|
455391
456025
|
if (isJsonMode()) {
|
|
455392
|
-
|
|
456026
|
+
outputSuccess({
|
|
456027
|
+
command: "generate video-cancel",
|
|
456028
|
+
startedAt,
|
|
456029
|
+
data: { taskId, provider: "runway", cancelled: true }
|
|
456030
|
+
});
|
|
455393
456031
|
return;
|
|
455394
456032
|
}
|
|
455395
456033
|
} else {
|
|
@@ -455462,16 +456100,18 @@ async function executeBackground(options) {
|
|
|
455462
456100
|
}
|
|
455463
456101
|
function registerBackgroundCommand(parent) {
|
|
455464
456102
|
parent.command("background").description("Generate video background using DALL-E").argument("<description>", "Background description").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("-o, --output <path>", "Output file path (downloads image)").option("-a, --aspect <ratio>", "Aspect ratio: 16:9, 9:16, 1:1", "16:9").option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
|
|
456103
|
+
const startedAt = Date.now();
|
|
455465
456104
|
try {
|
|
455466
456105
|
rejectControlChars(description);
|
|
455467
456106
|
if (options.output) {
|
|
455468
456107
|
validateOutputPath(options.output);
|
|
455469
456108
|
}
|
|
455470
456109
|
if (options.dryRun) {
|
|
455471
|
-
|
|
455472
|
-
dryRun: true,
|
|
456110
|
+
outputSuccess({
|
|
455473
456111
|
command: "generate background",
|
|
455474
|
-
|
|
456112
|
+
startedAt,
|
|
456113
|
+
dryRun: true,
|
|
456114
|
+
data: { params: { description, aspect: options.aspect, output: options.output } }
|
|
455475
456115
|
});
|
|
455476
456116
|
return;
|
|
455477
456117
|
}
|
|
@@ -455506,7 +456146,11 @@ function registerBackgroundCommand(parent) {
|
|
|
455506
456146
|
await mkdir18(dirname25(outputPath), { recursive: true });
|
|
455507
456147
|
await writeFile23(outputPath, buffer);
|
|
455508
456148
|
}
|
|
455509
|
-
|
|
456149
|
+
outputSuccess({
|
|
456150
|
+
command: "generate background",
|
|
456151
|
+
startedAt,
|
|
456152
|
+
data: { imageUrl: img.url, outputPath }
|
|
456153
|
+
});
|
|
455510
456154
|
return;
|
|
455511
456155
|
}
|
|
455512
456156
|
console.log();
|
|
@@ -455603,6 +456247,7 @@ async function executeStoryboard(options) {
|
|
|
455603
456247
|
}
|
|
455604
456248
|
function registerStoryboardCommand(parent) {
|
|
455605
456249
|
parent.command("storyboard").description("Generate video storyboard from content using Claude").argument("<content>", "Content to analyze (text or file path)").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output JSON file path").option("-d, --duration <sec>", "Target total duration in seconds").option("-f, --file", "Treat content argument as file path").option("-c, --creativity <level>", "Creativity level: low (default, consistent) or high (varied, unexpected)", "low").option("--dry-run", "Preview parameters without executing").action(async (content, options) => {
|
|
456250
|
+
const startedAt = Date.now();
|
|
455606
456251
|
try {
|
|
455607
456252
|
rejectControlChars(content);
|
|
455608
456253
|
if (options.output) {
|
|
@@ -455618,13 +456263,16 @@ function registerStoryboardCommand(parent) {
|
|
|
455618
456263
|
textContent2 = await readFile23(filePath, "utf-8");
|
|
455619
456264
|
}
|
|
455620
456265
|
if (options.dryRun) {
|
|
455621
|
-
|
|
455622
|
-
dryRun: true,
|
|
456266
|
+
outputSuccess({
|
|
455623
456267
|
command: "generate storyboard",
|
|
455624
|
-
|
|
455625
|
-
|
|
455626
|
-
|
|
455627
|
-
|
|
456268
|
+
startedAt,
|
|
456269
|
+
dryRun: true,
|
|
456270
|
+
data: {
|
|
456271
|
+
params: {
|
|
456272
|
+
content: textContent2.substring(0, 200),
|
|
456273
|
+
duration: options.duration,
|
|
456274
|
+
creativity
|
|
456275
|
+
}
|
|
455628
456276
|
}
|
|
455629
456277
|
});
|
|
455630
456278
|
return;
|
|
@@ -455656,16 +456304,23 @@ function registerStoryboardCommand(parent) {
|
|
|
455656
456304
|
const outputPath = resolve39(process.cwd(), options.output);
|
|
455657
456305
|
await writeFile24(outputPath, JSON.stringify(segments, null, 2), "utf-8");
|
|
455658
456306
|
if (isJsonMode()) {
|
|
455659
|
-
|
|
455660
|
-
|
|
455661
|
-
|
|
455662
|
-
|
|
455663
|
-
|
|
456307
|
+
outputSuccess({
|
|
456308
|
+
command: "generate storyboard",
|
|
456309
|
+
startedAt,
|
|
456310
|
+
data: {
|
|
456311
|
+
segmentCount: segments.length,
|
|
456312
|
+
segments,
|
|
456313
|
+
outputPath
|
|
456314
|
+
}
|
|
455664
456315
|
});
|
|
455665
456316
|
return;
|
|
455666
456317
|
}
|
|
455667
456318
|
} else if (isJsonMode()) {
|
|
455668
|
-
|
|
456319
|
+
outputSuccess({
|
|
456320
|
+
command: "generate storyboard",
|
|
456321
|
+
startedAt,
|
|
456322
|
+
data: { segmentCount: segments.length, segments }
|
|
456323
|
+
});
|
|
455669
456324
|
return;
|
|
455670
456325
|
}
|
|
455671
456326
|
console.log();
|
|
@@ -455797,6 +456452,7 @@ async function executeSpeech(options) {
|
|
|
455797
456452
|
}
|
|
455798
456453
|
function registerSpeechCommand(parent) {
|
|
455799
456454
|
parent.command("speech").alias("tts").description("Generate speech from text using ElevenLabs").argument("[text]", "Text to convert to speech (interactive if omitted)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "output.mp3").option("-v, --voice <id>", "Voice ID (default: Rachel)", "21m00Tcm4TlvDq8ikWAM").option("--list-voices", "List available voices").option("--fit-duration <seconds>", "Speed up audio to fit target duration (via FFmpeg atempo)", parseFloat).option("--dry-run", "Preview parameters without executing").action(async (text, options) => {
|
|
456455
|
+
const startedAt = Date.now();
|
|
455800
456456
|
try {
|
|
455801
456457
|
if (!text) {
|
|
455802
456458
|
if (hasTTY()) {
|
|
@@ -455818,10 +456474,11 @@ function registerSpeechCommand(parent) {
|
|
|
455818
456474
|
validateOutputPath(options.output);
|
|
455819
456475
|
}
|
|
455820
456476
|
if (options.dryRun) {
|
|
455821
|
-
|
|
455822
|
-
dryRun: true,
|
|
456477
|
+
outputSuccess({
|
|
455823
456478
|
command: "generate speech",
|
|
455824
|
-
|
|
456479
|
+
startedAt,
|
|
456480
|
+
dryRun: true,
|
|
456481
|
+
data: { params: { text, voice: options.voice, output: options.output } }
|
|
455825
456482
|
});
|
|
455826
456483
|
return;
|
|
455827
456484
|
}
|
|
@@ -455910,10 +456567,13 @@ function registerSpeechCommand(parent) {
|
|
|
455910
456567
|
}
|
|
455911
456568
|
}
|
|
455912
456569
|
if (isJsonMode()) {
|
|
455913
|
-
|
|
455914
|
-
|
|
455915
|
-
|
|
455916
|
-
|
|
456570
|
+
outputSuccess({
|
|
456571
|
+
command: "generate speech",
|
|
456572
|
+
startedAt,
|
|
456573
|
+
data: {
|
|
456574
|
+
characterCount: result.characterCount,
|
|
456575
|
+
outputPath
|
|
456576
|
+
}
|
|
455917
456577
|
});
|
|
455918
456578
|
return;
|
|
455919
456579
|
}
|
|
@@ -456002,6 +456662,7 @@ async function executeMusic(options) {
|
|
|
456002
456662
|
}
|
|
456003
456663
|
function registerMusicCommand(parent) {
|
|
456004
456664
|
parent.command("music").description("Generate background music from a text prompt (ElevenLabs or Replicate MusicGen)").argument("<prompt>", "Description of the music to generate").option("-p, --provider <provider>", "Provider: elevenlabs (default, up to 10min), replicate (MusicGen, max 30s)", "elevenlabs").option("-k, --api-key <key>", "API key (or set ELEVENLABS_API_KEY / REPLICATE_API_TOKEN env)").option("-d, --duration <seconds>", "Duration in seconds (elevenlabs: 3-600, replicate: 1-30)", "8").option("--instrumental", "Force instrumental music, no vocals (ElevenLabs only)").option("-m, --melody <file>", "Reference melody audio file for conditioning (Replicate only)").option("--model <model>", "Model variant (Replicate only): large, stereo-large, melody-large, stereo-melody-large", "stereo-large").option("-o, --output <path>", "Output audio file path", "music.mp3").option("--no-wait", "Don't wait for generation to complete (Replicate async mode)").option("--dry-run", "Preview parameters without executing").action(async (prompt3, options) => {
|
|
456665
|
+
const startedAt = Date.now();
|
|
456005
456666
|
try {
|
|
456006
456667
|
rejectControlChars(prompt3);
|
|
456007
456668
|
if (options.output) {
|
|
@@ -456009,16 +456670,19 @@ function registerMusicCommand(parent) {
|
|
|
456009
456670
|
}
|
|
456010
456671
|
const provider = (options.provider || "elevenlabs").toLowerCase();
|
|
456011
456672
|
if (options.dryRun) {
|
|
456012
|
-
|
|
456013
|
-
dryRun: true,
|
|
456673
|
+
outputSuccess({
|
|
456014
456674
|
command: "generate music",
|
|
456015
|
-
|
|
456016
|
-
|
|
456017
|
-
|
|
456018
|
-
|
|
456019
|
-
|
|
456020
|
-
|
|
456021
|
-
|
|
456675
|
+
startedAt,
|
|
456676
|
+
dryRun: true,
|
|
456677
|
+
data: {
|
|
456678
|
+
params: {
|
|
456679
|
+
prompt: prompt3,
|
|
456680
|
+
provider,
|
|
456681
|
+
duration: options.duration,
|
|
456682
|
+
model: options.model,
|
|
456683
|
+
output: options.output,
|
|
456684
|
+
instrumental: options.instrumental
|
|
456685
|
+
}
|
|
456022
456686
|
}
|
|
456023
456687
|
});
|
|
456024
456688
|
return;
|
|
@@ -456045,11 +456709,14 @@ function registerMusicCommand(parent) {
|
|
|
456045
456709
|
await writeFile26(outputPath, result.audioBuffer);
|
|
456046
456710
|
spinner2.succeed(source_default.green("Music generated successfully"));
|
|
456047
456711
|
if (isJsonMode()) {
|
|
456048
|
-
|
|
456049
|
-
|
|
456050
|
-
|
|
456051
|
-
|
|
456052
|
-
|
|
456712
|
+
outputSuccess({
|
|
456713
|
+
command: "generate music",
|
|
456714
|
+
startedAt,
|
|
456715
|
+
data: {
|
|
456716
|
+
provider: "elevenlabs",
|
|
456717
|
+
outputPath,
|
|
456718
|
+
duration
|
|
456719
|
+
}
|
|
456053
456720
|
});
|
|
456054
456721
|
return;
|
|
456055
456722
|
}
|
|
@@ -456117,12 +456784,15 @@ function registerMusicCommand(parent) {
|
|
|
456117
456784
|
await writeFile26(outputPath, audioBuffer);
|
|
456118
456785
|
spinner2.succeed(source_default.green("Music generated successfully"));
|
|
456119
456786
|
if (isJsonMode()) {
|
|
456120
|
-
|
|
456121
|
-
|
|
456122
|
-
|
|
456123
|
-
|
|
456124
|
-
|
|
456125
|
-
|
|
456787
|
+
outputSuccess({
|
|
456788
|
+
command: "generate music",
|
|
456789
|
+
startedAt,
|
|
456790
|
+
data: {
|
|
456791
|
+
provider: "replicate",
|
|
456792
|
+
taskId: result.taskId,
|
|
456793
|
+
audioUrl: finalResult.audioUrl,
|
|
456794
|
+
outputPath
|
|
456795
|
+
}
|
|
456126
456796
|
});
|
|
456127
456797
|
return;
|
|
456128
456798
|
}
|
|
@@ -456157,6 +456827,7 @@ import { existsSync as existsSync47 } from "node:fs";
|
|
|
456157
456827
|
import { writeFile as writeFile27, mkdir as mkdir19 } from "node:fs/promises";
|
|
456158
456828
|
function registerThumbnailCommand(parent) {
|
|
456159
456829
|
parent.command("thumbnail").description("Generate video thumbnail (DALL-E) or extract best frame from video (Gemini)").argument("[description]", "Thumbnail description (for DALL-E generation)").option("-k, --api-key <key>", "API key (OpenAI for generation, Google for best-frame)").option("-o, --output <path>", "Output file path").option("-s, --style <style>", "Platform style: youtube, instagram, tiktok, twitter").option("--best-frame <video>", "Extract best thumbnail frame from video using Gemini AI").option("--prompt <prompt>", "Custom prompt for best-frame analysis").option("--model <model>", "Gemini model: flash, latest, pro (default: flash)", "flash").action(async (description, options) => {
|
|
456830
|
+
const startedAt = Date.now();
|
|
456160
456831
|
try {
|
|
456161
456832
|
if (description) rejectControlChars(description);
|
|
456162
456833
|
if (options.output) {
|
|
@@ -456192,11 +456863,14 @@ function registerThumbnailCommand(parent) {
|
|
|
456192
456863
|
}
|
|
456193
456864
|
spinner3.succeed(source_default.green("Best frame extracted"));
|
|
456194
456865
|
if (isJsonMode()) {
|
|
456195
|
-
|
|
456196
|
-
|
|
456197
|
-
|
|
456198
|
-
|
|
456199
|
-
|
|
456866
|
+
outputSuccess({
|
|
456867
|
+
command: "generate thumbnail",
|
|
456868
|
+
startedAt,
|
|
456869
|
+
data: {
|
|
456870
|
+
timestamp: result2.timestamp,
|
|
456871
|
+
reason: result2.reason,
|
|
456872
|
+
outputPath: result2.outputPath
|
|
456873
|
+
}
|
|
456200
456874
|
});
|
|
456201
456875
|
return;
|
|
456202
456876
|
}
|
|
@@ -456244,7 +456918,11 @@ function registerThumbnailCommand(parent) {
|
|
|
456244
456918
|
await mkdir19(dirname26(outputPath), { recursive: true });
|
|
456245
456919
|
await writeFile27(outputPath, buffer);
|
|
456246
456920
|
}
|
|
456247
|
-
|
|
456921
|
+
outputSuccess({
|
|
456922
|
+
command: "generate thumbnail",
|
|
456923
|
+
startedAt,
|
|
456924
|
+
data: { imageUrl: img.url, outputPath }
|
|
456925
|
+
});
|
|
456248
456926
|
return;
|
|
456249
456927
|
}
|
|
456250
456928
|
console.log();
|
|
@@ -456315,6 +456993,7 @@ function getStatusColor(status) {
|
|
|
456315
456993
|
}
|
|
456316
456994
|
function registerVideoStatusCommand(parent) {
|
|
456317
456995
|
parent.command("video-status", { hidden: true }).description("Check video generation status (Grok, Runway, or Kling)").argument("<task-id>", "Task ID from video generation").option("-p, --provider <provider>", "Provider: grok, runway, kling", "grok").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY env)").option("-t, --type <type>", "Task type: text2video or image2video (Kling only)", "text2video").option("-w, --wait", "Wait for completion").option("-o, --output <path>", "Download video when complete").action(async (taskId, options) => {
|
|
456996
|
+
const startedAt = Date.now();
|
|
456318
456997
|
try {
|
|
456319
456998
|
const provider = (options.provider || "grok").toLowerCase();
|
|
456320
456999
|
if (provider === "grok") {
|
|
@@ -456337,14 +457016,17 @@ function registerVideoStatusCommand(parent) {
|
|
|
456337
457016
|
outputPath = resolve44(process.cwd(), options.output);
|
|
456338
457017
|
await writeFile28(outputPath, buffer);
|
|
456339
457018
|
}
|
|
456340
|
-
|
|
456341
|
-
|
|
456342
|
-
|
|
456343
|
-
|
|
456344
|
-
|
|
456345
|
-
|
|
456346
|
-
|
|
456347
|
-
|
|
457019
|
+
outputSuccess({
|
|
457020
|
+
command: "generate video-status",
|
|
457021
|
+
startedAt,
|
|
457022
|
+
data: {
|
|
457023
|
+
taskId,
|
|
457024
|
+
provider: "grok",
|
|
457025
|
+
status: result.status,
|
|
457026
|
+
videoUrl: result.videoUrl,
|
|
457027
|
+
error: result.error,
|
|
457028
|
+
outputPath
|
|
457029
|
+
}
|
|
456348
457030
|
});
|
|
456349
457031
|
return;
|
|
456350
457032
|
}
|
|
@@ -456402,15 +457084,18 @@ function registerVideoStatusCommand(parent) {
|
|
|
456402
457084
|
outputPath = resolve44(process.cwd(), options.output);
|
|
456403
457085
|
await writeFile28(outputPath, buffer);
|
|
456404
457086
|
}
|
|
456405
|
-
|
|
456406
|
-
|
|
456407
|
-
|
|
456408
|
-
|
|
456409
|
-
|
|
456410
|
-
|
|
456411
|
-
|
|
456412
|
-
|
|
456413
|
-
|
|
457087
|
+
outputSuccess({
|
|
457088
|
+
command: "generate video-status",
|
|
457089
|
+
startedAt,
|
|
457090
|
+
data: {
|
|
457091
|
+
taskId,
|
|
457092
|
+
provider: "runway",
|
|
457093
|
+
status: result.status,
|
|
457094
|
+
videoUrl: result.videoUrl,
|
|
457095
|
+
progress: result.progress,
|
|
457096
|
+
error: result.error,
|
|
457097
|
+
outputPath
|
|
457098
|
+
}
|
|
456414
457099
|
});
|
|
456415
457100
|
return;
|
|
456416
457101
|
}
|
|
@@ -456466,15 +457151,18 @@ function registerVideoStatusCommand(parent) {
|
|
|
456466
457151
|
outputPath = resolve44(process.cwd(), options.output);
|
|
456467
457152
|
await writeFile28(outputPath, buffer);
|
|
456468
457153
|
}
|
|
456469
|
-
|
|
456470
|
-
|
|
456471
|
-
|
|
456472
|
-
|
|
456473
|
-
|
|
456474
|
-
|
|
456475
|
-
|
|
456476
|
-
|
|
456477
|
-
|
|
457154
|
+
outputSuccess({
|
|
457155
|
+
command: "generate video-status",
|
|
457156
|
+
startedAt,
|
|
457157
|
+
data: {
|
|
457158
|
+
taskId,
|
|
457159
|
+
provider: "kling",
|
|
457160
|
+
status: result.status,
|
|
457161
|
+
videoUrl: result.videoUrl,
|
|
457162
|
+
duration: result.duration,
|
|
457163
|
+
error: result.error,
|
|
457164
|
+
outputPath
|
|
457165
|
+
}
|
|
456478
457166
|
});
|
|
456479
457167
|
return;
|
|
456480
457168
|
}
|
|
@@ -456538,22 +457226,26 @@ import { resolve as resolve45 } from "node:path";
|
|
|
456538
457226
|
import { writeFile as writeFile29 } from "node:fs/promises";
|
|
456539
457227
|
function registerVideoExtendCommand(parent) {
|
|
456540
457228
|
parent.command("video-extend", { hidden: true }).description("Extend video duration (Kling by video ID, Veo by operation name)").argument("<id>", "Kling video ID or Veo operation name").option("-p, --provider <provider>", "Provider: kling, veo", "kling").option("-k, --api-key <key>", "API key (KLING_API_KEY or GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path").option("--prompt <text>", "Continuation prompt").option("-d, --duration <sec>", "Duration: 5 or 10 (Kling), 4/6/8 (Veo)", "5").option("-n, --negative <prompt>", "Negative prompt (what to avoid, Kling only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast", "3.1").option("--no-wait", "Start extension and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (id, options) => {
|
|
457229
|
+
const startedAt = Date.now();
|
|
456541
457230
|
try {
|
|
456542
457231
|
const provider = (options.provider || "kling").toLowerCase();
|
|
456543
457232
|
if (options.output) {
|
|
456544
457233
|
validateOutputPath(options.output);
|
|
456545
457234
|
}
|
|
456546
457235
|
if (options.dryRun) {
|
|
456547
|
-
|
|
456548
|
-
dryRun: true,
|
|
457236
|
+
outputSuccess({
|
|
456549
457237
|
command: "generate video-extend",
|
|
456550
|
-
|
|
456551
|
-
|
|
456552
|
-
|
|
456553
|
-
|
|
456554
|
-
|
|
456555
|
-
|
|
456556
|
-
|
|
457238
|
+
startedAt,
|
|
457239
|
+
dryRun: true,
|
|
457240
|
+
data: {
|
|
457241
|
+
params: {
|
|
457242
|
+
id,
|
|
457243
|
+
provider,
|
|
457244
|
+
prompt: options.prompt,
|
|
457245
|
+
duration: options.duration,
|
|
457246
|
+
negative: options.negative,
|
|
457247
|
+
veoModel: options.veoModel
|
|
457248
|
+
}
|
|
456557
457249
|
}
|
|
456558
457250
|
});
|
|
456559
457251
|
return;
|
|
@@ -456610,13 +457302,16 @@ function registerVideoExtendCommand(parent) {
|
|
|
456610
457302
|
outputPath = resolve45(process.cwd(), options.output);
|
|
456611
457303
|
await writeFile29(outputPath, buffer);
|
|
456612
457304
|
}
|
|
456613
|
-
|
|
456614
|
-
|
|
456615
|
-
|
|
456616
|
-
|
|
456617
|
-
|
|
456618
|
-
|
|
456619
|
-
|
|
457305
|
+
outputSuccess({
|
|
457306
|
+
command: "generate video-extend",
|
|
457307
|
+
startedAt,
|
|
457308
|
+
data: {
|
|
457309
|
+
provider: "kling",
|
|
457310
|
+
taskId: result.id,
|
|
457311
|
+
videoUrl: finalResult.videoUrl,
|
|
457312
|
+
duration: finalResult.duration,
|
|
457313
|
+
outputPath
|
|
457314
|
+
}
|
|
456620
457315
|
});
|
|
456621
457316
|
return;
|
|
456622
457317
|
}
|
|
@@ -456696,13 +457391,16 @@ function registerVideoExtendCommand(parent) {
|
|
|
456696
457391
|
outputPath = resolve45(process.cwd(), options.output);
|
|
456697
457392
|
await writeFile29(outputPath, buffer);
|
|
456698
457393
|
}
|
|
456699
|
-
|
|
456700
|
-
|
|
456701
|
-
|
|
456702
|
-
|
|
456703
|
-
|
|
456704
|
-
|
|
456705
|
-
|
|
457394
|
+
outputSuccess({
|
|
457395
|
+
command: "generate video-extend",
|
|
457396
|
+
startedAt,
|
|
457397
|
+
data: {
|
|
457398
|
+
provider: "veo",
|
|
457399
|
+
taskId: result.id,
|
|
457400
|
+
videoUrl: finalResult.videoUrl,
|
|
457401
|
+
duration: finalResult.duration,
|
|
457402
|
+
outputPath
|
|
457403
|
+
}
|
|
456706
457404
|
});
|
|
456707
457405
|
return;
|
|
456708
457406
|
}
|
|
@@ -456817,6 +457515,7 @@ Examples:
|
|
|
456817
457515
|
$ vibe gen img "landscape photo" -o wide.png -r 16:9
|
|
456818
457516
|
$ vibe gen img "portrait" -o portrait.png -p gemini -m pro
|
|
456819
457517
|
$ vibe gen img "product shot" --dry-run --json`).action(async (prompt3, options) => {
|
|
457518
|
+
const startedAt = Date.now();
|
|
456820
457519
|
try {
|
|
456821
457520
|
if (!prompt3) {
|
|
456822
457521
|
if (hasTTY()) {
|
|
@@ -456875,18 +457574,21 @@ Examples:
|
|
|
456875
457574
|
provider = resolved?.name ?? "gemini";
|
|
456876
457575
|
}
|
|
456877
457576
|
if (options.dryRun) {
|
|
456878
|
-
|
|
456879
|
-
dryRun: true,
|
|
457577
|
+
outputSuccess({
|
|
456880
457578
|
command: "generate image",
|
|
456881
|
-
|
|
456882
|
-
|
|
456883
|
-
|
|
456884
|
-
|
|
456885
|
-
|
|
456886
|
-
|
|
456887
|
-
|
|
456888
|
-
|
|
456889
|
-
|
|
457579
|
+
startedAt,
|
|
457580
|
+
dryRun: true,
|
|
457581
|
+
data: {
|
|
457582
|
+
params: {
|
|
457583
|
+
prompt: prompt3,
|
|
457584
|
+
provider,
|
|
457585
|
+
model: options.model,
|
|
457586
|
+
ratio: options.ratio,
|
|
457587
|
+
size: options.size,
|
|
457588
|
+
quality: options.quality,
|
|
457589
|
+
count: options.count,
|
|
457590
|
+
output: options.output
|
|
457591
|
+
}
|
|
456890
457592
|
}
|
|
456891
457593
|
});
|
|
456892
457594
|
return;
|
|
@@ -456924,14 +457626,17 @@ Examples:
|
|
|
456924
457626
|
await mkdir20(dirname27(outputPath), { recursive: true });
|
|
456925
457627
|
await writeFile30(outputPath, buffer);
|
|
456926
457628
|
}
|
|
456927
|
-
|
|
456928
|
-
|
|
456929
|
-
|
|
456930
|
-
|
|
456931
|
-
|
|
456932
|
-
|
|
456933
|
-
|
|
456934
|
-
|
|
457629
|
+
outputSuccess({
|
|
457630
|
+
command: "generate image",
|
|
457631
|
+
startedAt,
|
|
457632
|
+
data: {
|
|
457633
|
+
provider: "openai",
|
|
457634
|
+
images: result.images.map((img) => ({
|
|
457635
|
+
url: img.url,
|
|
457636
|
+
revisedPrompt: img.revisedPrompt
|
|
457637
|
+
})),
|
|
457638
|
+
outputPath
|
|
457639
|
+
}
|
|
456935
457640
|
});
|
|
456936
457641
|
return;
|
|
456937
457642
|
}
|
|
@@ -457043,13 +457748,18 @@ Examples:
|
|
|
457043
457748
|
await mkdir20(dirname27(outputPath), { recursive: true });
|
|
457044
457749
|
await writeFile30(outputPath, buffer);
|
|
457045
457750
|
}
|
|
457046
|
-
|
|
457047
|
-
|
|
457048
|
-
|
|
457049
|
-
|
|
457050
|
-
|
|
457051
|
-
|
|
457052
|
-
|
|
457751
|
+
outputSuccess({
|
|
457752
|
+
command: "generate image",
|
|
457753
|
+
startedAt,
|
|
457754
|
+
warnings: usedLabel.includes("fallback") ? [`Model "${options.model}" failed; fell back to flash`] : [],
|
|
457755
|
+
data: {
|
|
457756
|
+
provider: "gemini",
|
|
457757
|
+
model: usedLabel,
|
|
457758
|
+
images: result.images.map((img) => ({
|
|
457759
|
+
mimeType: img.mimeType
|
|
457760
|
+
})),
|
|
457761
|
+
outputPath
|
|
457762
|
+
}
|
|
457053
457763
|
});
|
|
457054
457764
|
return;
|
|
457055
457765
|
}
|
|
@@ -457133,11 +457843,14 @@ Examples:
|
|
|
457133
457843
|
await mkdir20(dirname27(outputPath), { recursive: true });
|
|
457134
457844
|
await writeFile30(outputPath, buffer);
|
|
457135
457845
|
}
|
|
457136
|
-
|
|
457137
|
-
|
|
457138
|
-
|
|
457139
|
-
|
|
457140
|
-
|
|
457846
|
+
outputSuccess({
|
|
457847
|
+
command: "generate image",
|
|
457848
|
+
startedAt,
|
|
457849
|
+
data: {
|
|
457850
|
+
provider: "grok",
|
|
457851
|
+
images: result.images.map((img) => ({ url: img.url })),
|
|
457852
|
+
outputPath
|
|
457853
|
+
}
|
|
457141
457854
|
});
|
|
457142
457855
|
return;
|
|
457143
457856
|
}
|
|
@@ -457208,11 +457921,14 @@ Examples:
|
|
|
457208
457921
|
proc.on("close", (code) => {
|
|
457209
457922
|
if (code === 0) {
|
|
457210
457923
|
if (isJsonMode()) {
|
|
457211
|
-
|
|
457212
|
-
|
|
457213
|
-
|
|
457214
|
-
|
|
457215
|
-
|
|
457924
|
+
outputSuccess({
|
|
457925
|
+
command: "generate image",
|
|
457926
|
+
startedAt,
|
|
457927
|
+
data: {
|
|
457928
|
+
provider: "runway",
|
|
457929
|
+
images: [{ format: "file" }],
|
|
457930
|
+
outputPath
|
|
457931
|
+
}
|
|
457216
457932
|
});
|
|
457217
457933
|
} else {
|
|
457218
457934
|
spinner2.succeed(source_default.green("Generated image with Runway"));
|
|
@@ -458931,13 +459647,19 @@ var init_ai_script_pipeline = __esm({
|
|
|
458931
459647
|
import { resolve as resolve49 } from "node:path";
|
|
458932
459648
|
import { readFile as readFile25, writeFile as writeFile33 } from "node:fs/promises";
|
|
458933
459649
|
function registerVideoCommand(parent) {
|
|
458934
|
-
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", `
|
|
458935
459655
|
Examples:
|
|
458936
|
-
$ 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
|
|
458937
459658
|
$ vibe gen vid "city timelapse" -o city.mp4 -p kling # Kling
|
|
458938
459659
|
$ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
|
|
458939
459660
|
$ vibe gen vid "ocean waves" -o waves.mp4 -p veo --resolution 1080p # Veo
|
|
458940
459661
|
$ vibe gen vid "sunset" -o sun.mp4 -d 10 --dry-run --json`).action(async (prompt3, options) => {
|
|
459662
|
+
const startedAt = Date.now();
|
|
458941
459663
|
try {
|
|
458942
459664
|
if (!prompt3) {
|
|
458943
459665
|
if (hasTTY()) {
|
|
@@ -458958,12 +459680,13 @@ Examples:
|
|
|
458958
459680
|
if (options.output) {
|
|
458959
459681
|
validateOutputPath(options.output);
|
|
458960
459682
|
}
|
|
458961
|
-
const validProviders = ["runway", "kling", "veo", "grok", "fal"];
|
|
459683
|
+
const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
|
|
458962
459684
|
const videoEnvMap = {
|
|
458963
459685
|
grok: "XAI_API_KEY",
|
|
458964
459686
|
veo: "GOOGLE_API_KEY",
|
|
458965
459687
|
kling: "KLING_API_KEY",
|
|
458966
459688
|
runway: "RUNWAY_API_SECRET",
|
|
459689
|
+
seedance: "FAL_KEY",
|
|
458967
459690
|
fal: "FAL_KEY"
|
|
458968
459691
|
};
|
|
458969
459692
|
let provider;
|
|
@@ -458973,7 +459696,7 @@ Examples:
|
|
|
458973
459696
|
exitWithError(
|
|
458974
459697
|
usageError(
|
|
458975
459698
|
`Invalid provider: ${provider}`,
|
|
458976
|
-
|
|
459699
|
+
"Available providers: seedance, grok, kling, runway, veo. `fal` is a backwards-compatible alias for seedance."
|
|
458977
459700
|
)
|
|
458978
459701
|
);
|
|
458979
459702
|
}
|
|
@@ -459029,19 +459752,23 @@ Examples:
|
|
|
459029
459752
|
options.ratio = "16:9";
|
|
459030
459753
|
}
|
|
459031
459754
|
if (options.dryRun) {
|
|
459032
|
-
|
|
459033
|
-
dryRun: true,
|
|
459755
|
+
outputSuccess({
|
|
459034
459756
|
command: "generate video",
|
|
459035
|
-
|
|
459036
|
-
|
|
459037
|
-
|
|
459038
|
-
|
|
459039
|
-
|
|
459040
|
-
|
|
459041
|
-
|
|
459042
|
-
|
|
459043
|
-
|
|
459044
|
-
|
|
459757
|
+
startedAt,
|
|
459758
|
+
dryRun: true,
|
|
459759
|
+
data: {
|
|
459760
|
+
params: {
|
|
459761
|
+
prompt: prompt3,
|
|
459762
|
+
provider: provider === "fal" ? "seedance" : provider,
|
|
459763
|
+
duration: options.duration,
|
|
459764
|
+
ratio: options.ratio,
|
|
459765
|
+
image: options.image,
|
|
459766
|
+
mode: options.mode,
|
|
459767
|
+
negative: options.negative,
|
|
459768
|
+
resolution: options.resolution,
|
|
459769
|
+
veoModel: options.veoModel,
|
|
459770
|
+
seedanceModel: options.seedanceModel
|
|
459771
|
+
}
|
|
459045
459772
|
}
|
|
459046
459773
|
});
|
|
459047
459774
|
return;
|
|
@@ -459051,6 +459778,7 @@ Examples:
|
|
|
459051
459778
|
kling: "KLING_API_KEY",
|
|
459052
459779
|
veo: "GOOGLE_API_KEY",
|
|
459053
459780
|
grok: "XAI_API_KEY",
|
|
459781
|
+
seedance: "FAL_KEY",
|
|
459054
459782
|
fal: "FAL_KEY"
|
|
459055
459783
|
};
|
|
459056
459784
|
const providerNameMap = {
|
|
@@ -459058,7 +459786,8 @@ Examples:
|
|
|
459058
459786
|
kling: "Kling",
|
|
459059
459787
|
veo: "Veo",
|
|
459060
459788
|
grok: "Grok",
|
|
459061
|
-
|
|
459789
|
+
seedance: "Seedance 2.0 via fal.ai",
|
|
459790
|
+
fal: "Seedance 2.0 via fal.ai"
|
|
459062
459791
|
};
|
|
459063
459792
|
const envKey = envKeyMap[provider];
|
|
459064
459793
|
const providerName = providerNameMap[provider];
|
|
@@ -459277,15 +460006,15 @@ Examples:
|
|
|
459277
460006
|
},
|
|
459278
460007
|
3e5
|
|
459279
460008
|
);
|
|
459280
|
-
} else if (provider === "fal") {
|
|
460009
|
+
} else if (provider === "fal" || provider === "seedance") {
|
|
459281
460010
|
const fal = new FalProvider();
|
|
459282
460011
|
await fal.initialize({ apiKey });
|
|
459283
460012
|
let falImage = referenceImage;
|
|
459284
460013
|
if (falImage && falImage.startsWith("data:")) {
|
|
459285
|
-
spinner2.text = "Uploading image to ImgBB for
|
|
460014
|
+
spinner2.text = "Uploading image to ImgBB for Seedance...";
|
|
459286
460015
|
const imgbbKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
|
|
459287
460016
|
if (!imgbbKey) {
|
|
459288
|
-
spinner2.fail("ImgBB API key required for
|
|
460017
|
+
spinner2.fail("ImgBB API key required for Seedance image-to-video");
|
|
459289
460018
|
exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
|
|
459290
460019
|
}
|
|
459291
460020
|
const base64Data = falImage.split(",")[1];
|
|
@@ -459297,8 +460026,9 @@ Examples:
|
|
|
459297
460026
|
}
|
|
459298
460027
|
falImage = uploadResult.url;
|
|
459299
460028
|
}
|
|
459300
|
-
spinner2.text = "Generating video with Seedance 2.0 (this may take 1-3 minutes)...";
|
|
459301
|
-
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";
|
|
459302
460032
|
result = await fal.generateVideo(prompt3, {
|
|
459303
460033
|
prompt: prompt3,
|
|
459304
460034
|
referenceImage: falImage,
|
|
@@ -459321,13 +460051,16 @@ Examples:
|
|
|
459321
460051
|
outputPath = resolve49(process.cwd(), options.output);
|
|
459322
460052
|
await writeFile33(outputPath, buffer);
|
|
459323
460053
|
}
|
|
459324
|
-
|
|
459325
|
-
|
|
459326
|
-
|
|
459327
|
-
|
|
459328
|
-
|
|
459329
|
-
|
|
459330
|
-
|
|
460054
|
+
outputSuccess({
|
|
460055
|
+
command: "generate video",
|
|
460056
|
+
startedAt,
|
|
460057
|
+
data: {
|
|
460058
|
+
provider,
|
|
460059
|
+
taskId: result?.id,
|
|
460060
|
+
videoUrl: finalResult.videoUrl,
|
|
460061
|
+
duration: finalResult.duration,
|
|
460062
|
+
outputPath
|
|
460063
|
+
}
|
|
459331
460064
|
});
|
|
459332
460065
|
return;
|
|
459333
460066
|
}
|
|
@@ -459420,7 +460153,8 @@ var init_generate = __esm({
|
|
|
459420
460153
|
Examples:
|
|
459421
460154
|
$ vibe generate image "a sunset over the ocean" -o sunset.png
|
|
459422
460155
|
$ vibe generate image "logo design" -o logo.png -p openai
|
|
459423
|
-
$ 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
|
|
459424
460158
|
$ vibe generate video "city timelapse" -o city.mp4 -p kling # Kling
|
|
459425
460159
|
$ vibe generate video "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
|
|
459426
460160
|
$ vibe generate speech "Hello world" -o hello.mp3
|
|
@@ -459430,7 +460164,8 @@ Examples:
|
|
|
459430
460164
|
API Keys (per provider):
|
|
459431
460165
|
GOOGLE_API_KEY Image (default), Veo video
|
|
459432
460166
|
OPENAI_API_KEY Image (-p openai)
|
|
459433
|
-
|
|
460167
|
+
FAL_KEY Seedance video (-p seedance, default video)
|
|
460168
|
+
XAI_API_KEY Grok image/video
|
|
459434
460169
|
KLING_API_KEY Kling video (-p kling)
|
|
459435
460170
|
RUNWAY_API_SECRET Runway video (-p runway)
|
|
459436
460171
|
ELEVENLABS_API_KEY Speech, sound effects, music
|
|
@@ -459478,12 +460213,20 @@ async function executeVideoGenerate(options) {
|
|
|
459478
460213
|
negative,
|
|
459479
460214
|
resolution,
|
|
459480
460215
|
veoModel = "3.1-fast",
|
|
460216
|
+
seedanceModel = "quality",
|
|
459481
460217
|
output: output3,
|
|
459482
460218
|
wait = true,
|
|
459483
460219
|
apiKey
|
|
459484
460220
|
} = options;
|
|
459485
460221
|
try {
|
|
459486
|
-
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
|
+
};
|
|
459487
460230
|
const key2 = apiKey || process.env[envKeyMap[provider] || ""];
|
|
459488
460231
|
if (!key2) return { success: false, error: `${envKeyMap[provider]} required for ${provider}` };
|
|
459489
460232
|
let referenceImage;
|
|
@@ -459495,7 +460238,36 @@ async function executeVideoGenerate(options) {
|
|
|
459495
460238
|
const mimeType = mimeTypes[ext || "png"] || "image/png";
|
|
459496
460239
|
referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
|
|
459497
460240
|
}
|
|
459498
|
-
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") {
|
|
459499
460271
|
const runway = new RunwayProvider();
|
|
459500
460272
|
await runway.initialize({ apiKey: key2 });
|
|
459501
460273
|
const result = await runway.generateVideo(prompt3, {
|
|
@@ -459892,20 +460664,24 @@ var init_detect = __esm({
|
|
|
459892
460664
|
init_validate();
|
|
459893
460665
|
detectCommand = new Command("detect").description("Auto-detect scenes, beats, and silences in media");
|
|
459894
460666
|
detectCommand.command("scenes").description("Detect scene changes in video").argument("<video>", "Video file path").option("-t, --threshold <value>", "Scene change threshold (0-1)", "0.3").option("-o, --output <path>", "Output JSON file with timestamps").option("-p, --project <path>", "Add scenes as clips to project").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
460667
|
+
const startedAt = Date.now();
|
|
459895
460668
|
const spinner2 = ora("Detecting scenes...").start();
|
|
459896
460669
|
try {
|
|
459897
460670
|
if (options.output) {
|
|
459898
460671
|
validateOutputPath(options.output);
|
|
459899
460672
|
}
|
|
459900
460673
|
if (options.dryRun) {
|
|
459901
|
-
|
|
459902
|
-
dryRun: true,
|
|
460674
|
+
outputSuccess({
|
|
459903
460675
|
command: "detect scenes",
|
|
459904
|
-
|
|
459905
|
-
|
|
459906
|
-
|
|
459907
|
-
|
|
459908
|
-
|
|
460676
|
+
startedAt,
|
|
460677
|
+
dryRun: true,
|
|
460678
|
+
data: {
|
|
460679
|
+
params: {
|
|
460680
|
+
video: videoPath,
|
|
460681
|
+
threshold: options.threshold,
|
|
460682
|
+
output: options.output || null,
|
|
460683
|
+
project: options.project || null
|
|
460684
|
+
}
|
|
459909
460685
|
}
|
|
459910
460686
|
});
|
|
459911
460687
|
return;
|
|
@@ -460007,20 +460783,24 @@ var init_detect = __esm({
|
|
|
460007
460783
|
}
|
|
460008
460784
|
});
|
|
460009
460785
|
detectCommand.command("silence").description("Detect silence in audio/video").argument("<media>", "Media file path").option("-n, --noise <dB>", "Noise threshold in dB", "-30").option("-d, --duration <sec>", "Minimum silence duration", "0.5").option("-o, --output <path>", "Output JSON file with timestamps").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
|
|
460786
|
+
const startedAt = Date.now();
|
|
460010
460787
|
const spinner2 = ora("Detecting silence...").start();
|
|
460011
460788
|
try {
|
|
460012
460789
|
if (options.output) {
|
|
460013
460790
|
validateOutputPath(options.output);
|
|
460014
460791
|
}
|
|
460015
460792
|
if (options.dryRun) {
|
|
460016
|
-
|
|
460017
|
-
dryRun: true,
|
|
460793
|
+
outputSuccess({
|
|
460018
460794
|
command: "detect silence",
|
|
460019
|
-
|
|
460020
|
-
|
|
460021
|
-
|
|
460022
|
-
|
|
460023
|
-
|
|
460795
|
+
startedAt,
|
|
460796
|
+
dryRun: true,
|
|
460797
|
+
data: {
|
|
460798
|
+
params: {
|
|
460799
|
+
media: mediaPath,
|
|
460800
|
+
noise: options.noise,
|
|
460801
|
+
duration: options.duration,
|
|
460802
|
+
output: options.output || null
|
|
460803
|
+
}
|
|
460024
460804
|
}
|
|
460025
460805
|
});
|
|
460026
460806
|
return;
|
|
@@ -460089,18 +460869,22 @@ var init_detect = __esm({
|
|
|
460089
460869
|
}
|
|
460090
460870
|
});
|
|
460091
460871
|
detectCommand.command("beats").description("Detect beats in audio (for music sync)").argument("<audio>", "Audio file path").option("-o, --output <path>", "Output JSON file with timestamps").option("--dry-run", "Preview parameters without executing").action(async (audioPath, options) => {
|
|
460872
|
+
const startedAt = Date.now();
|
|
460092
460873
|
const spinner2 = ora("Detecting beats...").start();
|
|
460093
460874
|
try {
|
|
460094
460875
|
if (options.output) {
|
|
460095
460876
|
validateOutputPath(options.output);
|
|
460096
460877
|
}
|
|
460097
460878
|
if (options.dryRun) {
|
|
460098
|
-
|
|
460099
|
-
dryRun: true,
|
|
460879
|
+
outputSuccess({
|
|
460100
460880
|
command: "detect beats",
|
|
460101
|
-
|
|
460102
|
-
|
|
460103
|
-
|
|
460881
|
+
startedAt,
|
|
460882
|
+
dryRun: true,
|
|
460883
|
+
data: {
|
|
460884
|
+
params: {
|
|
460885
|
+
audio: audioPath,
|
|
460886
|
+
output: options.output || null
|
|
460887
|
+
}
|
|
460104
460888
|
}
|
|
460105
460889
|
});
|
|
460106
460890
|
return;
|
|
@@ -460698,334 +461482,8 @@ function visualStyleNames() {
|
|
|
460698
461482
|
return STYLES.map((s) => `"${s.name}"`).join(", ");
|
|
460699
461483
|
}
|
|
460700
461484
|
|
|
460701
|
-
// ../cli/src/
|
|
460702
|
-
|
|
460703
|
-
import { mkdir, readFile, writeFile, access } from "node:fs/promises";
|
|
460704
|
-
import { resolve, basename } from "node:path";
|
|
460705
|
-
var ASPECT_DIMS = {
|
|
460706
|
-
"16:9": { width: 1920, height: 1080 },
|
|
460707
|
-
"9:16": { width: 1080, height: 1920 },
|
|
460708
|
-
"1:1": { width: 1080, height: 1080 },
|
|
460709
|
-
"4:5": { width: 1080, height: 1350 }
|
|
460710
|
-
};
|
|
460711
|
-
function aspectToDims(aspect) {
|
|
460712
|
-
return ASPECT_DIMS[aspect];
|
|
460713
|
-
}
|
|
460714
|
-
function defaultVibeProjectConfig(name) {
|
|
460715
|
-
return {
|
|
460716
|
-
name,
|
|
460717
|
-
aspect: "16:9",
|
|
460718
|
-
defaultSceneDuration: 5,
|
|
460719
|
-
providers: { image: null, tts: null, transcribe: null },
|
|
460720
|
-
budget: { maxUsd: 0 }
|
|
460721
|
-
};
|
|
460722
|
-
}
|
|
460723
|
-
function buildHyperframesConfig() {
|
|
460724
|
-
return {
|
|
460725
|
-
$schema: "https://hyperframes.heygen.com/schema/hyperframes.json",
|
|
460726
|
-
registry: "https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry",
|
|
460727
|
-
paths: {
|
|
460728
|
-
blocks: "compositions",
|
|
460729
|
-
components: "compositions/components",
|
|
460730
|
-
assets: "assets"
|
|
460731
|
-
}
|
|
460732
|
-
};
|
|
460733
|
-
}
|
|
460734
|
-
function buildHyperframesMeta(name, now = /* @__PURE__ */ new Date()) {
|
|
460735
|
-
return { id: name, name, createdAt: now.toISOString() };
|
|
460736
|
-
}
|
|
460737
|
-
function mergeHyperframesConfig(existing, defaults) {
|
|
460738
|
-
const out = { ...defaults, ...existing };
|
|
460739
|
-
if (existing.paths || defaults.paths) {
|
|
460740
|
-
out.paths = { ...defaults.paths ?? {}, ...existing.paths ?? {} };
|
|
460741
|
-
}
|
|
460742
|
-
return out;
|
|
460743
|
-
}
|
|
460744
|
-
function buildEmptyRootHtml(opts) {
|
|
460745
|
-
const { width, height } = ASPECT_DIMS[opts.aspect];
|
|
460746
|
-
return `<!doctype html>
|
|
460747
|
-
<html lang="en">
|
|
460748
|
-
<head>
|
|
460749
|
-
<meta charset="UTF-8" />
|
|
460750
|
-
<meta name="viewport" content="width=${width}, height=${height}" />
|
|
460751
|
-
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
460752
|
-
<style>
|
|
460753
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
460754
|
-
html, body {
|
|
460755
|
-
margin: 0;
|
|
460756
|
-
width: ${width}px;
|
|
460757
|
-
height: ${height}px;
|
|
460758
|
-
overflow: hidden;
|
|
460759
|
-
background: #000;
|
|
460760
|
-
}
|
|
460761
|
-
</style>
|
|
460762
|
-
</head>
|
|
460763
|
-
<body>
|
|
460764
|
-
<div
|
|
460765
|
-
id="root"
|
|
460766
|
-
data-composition-id="main"
|
|
460767
|
-
data-start="0"
|
|
460768
|
-
data-duration="${opts.duration}"
|
|
460769
|
-
data-width="${width}"
|
|
460770
|
-
data-height="${height}"
|
|
460771
|
-
>
|
|
460772
|
-
<!-- Scenes added via \`vibe scene add\` are inserted here. -->
|
|
460773
|
-
<!-- Each scene reference: data-composition-id, data-composition-src, data-start, data-duration, data-track-index. -->
|
|
460774
|
-
<!-- See compositions/*.html for sub-composition contents. -->
|
|
460775
|
-
|
|
460776
|
-
</div>
|
|
460777
|
-
|
|
460778
|
-
<script>
|
|
460779
|
-
window.__timelines = window.__timelines || {};
|
|
460780
|
-
window.__timelines["main"] = gsap.timeline({ paused: true });
|
|
460781
|
-
</script>
|
|
460782
|
-
</body>
|
|
460783
|
-
</html>
|
|
460784
|
-
`;
|
|
460785
|
-
}
|
|
460786
|
-
function buildDesignMd(opts) {
|
|
460787
|
-
const { name, style } = opts;
|
|
460788
|
-
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.`;
|
|
460789
|
-
const moodLine = style ? `**Mood:** ${style.mood} \xB7 **Best for:** ${style.bestFor}` : `**Mood:** _(one line \u2014 what should the viewer FEEL?)_`;
|
|
460790
|
-
const palette = style ? `${style.palette.map((c) => `- \`${c}\``).join("\n")}
|
|
460791
|
-
|
|
460792
|
-
${style.paletteNotes}` : `- _hex_ \u2014 primary
|
|
460793
|
-
- _hex_ \u2014 accent
|
|
460794
|
-
|
|
460795
|
-
_2\u20133 colours max. Declare explicit hex values; never name colours abstractly._`;
|
|
460796
|
-
const typography = style ? style.typography : `_One family, two weights. State the role of each (headline / label / body)._`;
|
|
460797
|
-
const composition = style ? style.composition : `_Grid? Centered? Layered? How does negative space behave?_`;
|
|
460798
|
-
const motion = style ? `${style.motion}
|
|
460799
|
-
|
|
460800
|
-
**GSAP signature:** ${style.gsapSignature}` : `_How fast? Snappy or fluid? Overshoot or precision?_
|
|
460801
|
-
|
|
460802
|
-
**GSAP signature:** _e.g. \`expo.out\`, \`sine.inOut\`, \`back.out(1.8)\`_`;
|
|
460803
|
-
const transition = style ? style.transition : `_Which Hyperframes shader matches the energy? (Cinematic Zoom, Cross-Warp Morph, Glitch, Domain Warp, \u2026)_`;
|
|
460804
|
-
const avoid = style ? style.avoid.map((a) => `- ${a}`).join("\n") : `- _anti-pattern 1_
|
|
460805
|
-
- _anti-pattern 2_
|
|
460806
|
-
- _anti-pattern 3_`;
|
|
460807
|
-
return `# ${name} \u2014 Design
|
|
460808
|
-
|
|
460809
|
-
> **Hard-gate.** This file defines the visual identity of every scene.
|
|
460810
|
-
> Author it before generating any HTML, backdrop image, or motion.
|
|
460811
|
-
> The Hyperframes \`hyperframes\` skill enforces this: scenes that
|
|
460812
|
-
> contradict DESIGN.md are rejected.
|
|
460813
|
-
|
|
460814
|
-
${intro}
|
|
460815
|
-
|
|
460816
|
-
## Style
|
|
460817
|
-
|
|
460818
|
-
${moodLine}
|
|
460819
|
-
|
|
460820
|
-
## Palette
|
|
460821
|
-
|
|
460822
|
-
${palette}
|
|
460823
|
-
|
|
460824
|
-
## Typography
|
|
460825
|
-
|
|
460826
|
-
${typography}
|
|
460827
|
-
|
|
460828
|
-
## Composition
|
|
460829
|
-
|
|
460830
|
-
${composition}
|
|
460831
|
-
|
|
460832
|
-
## Motion
|
|
460833
|
-
|
|
460834
|
-
${motion}
|
|
460835
|
-
|
|
460836
|
-
## Transition
|
|
460837
|
-
|
|
460838
|
-
${transition}
|
|
460839
|
-
|
|
460840
|
-
## What NOT to do
|
|
460841
|
-
|
|
460842
|
-
${avoid}
|
|
460843
|
-
|
|
460844
|
-
---
|
|
460845
|
-
|
|
460846
|
-
_Browse other named styles: \`vibe scene styles\`_
|
|
460847
|
-
${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>"\`._`}
|
|
460848
|
-
`;
|
|
460849
|
-
}
|
|
460850
|
-
function buildProjectClaudeMd(name) {
|
|
460851
|
-
return `# ${name} \u2014 Scene Authoring Project
|
|
460852
|
-
|
|
460853
|
-
This project is **bilingual**: it works with both VibeFrame (\`vibe\`) and
|
|
460854
|
-
HeyGen Hyperframes (\`hyperframes\`). You can run either CLI inside this
|
|
460855
|
-
directory.
|
|
460856
|
-
|
|
460857
|
-
## Visual identity hard-gate
|
|
460858
|
-
|
|
460859
|
-
**Author \`DESIGN.md\` before any scene HTML.** It defines palette,
|
|
460860
|
-
typography, motion, and transition rules. Both the agent-driven path and
|
|
460861
|
-
the fallback emit reference it; scenes that contradict DESIGN.md are
|
|
460862
|
-
rejected by the Hyperframes \`hyperframes\` skill.
|
|
460863
|
-
|
|
460864
|
-
Browse named styles: \`vibe scene styles\`. Re-seed from one with
|
|
460865
|
-
\`vibe scene init . --visual-style "Swiss Pulse"\` (idempotent).
|
|
460866
|
-
|
|
460867
|
-
## Skills \u2014 USE THESE FIRST
|
|
460868
|
-
|
|
460869
|
-
**Always invoke the relevant skill before authoring scenes.** Skills encode
|
|
460870
|
-
framework-specific patterns (GSAP timeline registration, data-attribute
|
|
460871
|
-
semantics, VibeFrame pipeline conventions) that are NOT in generic web docs.
|
|
460872
|
-
|
|
460873
|
-
| Skill | Command | When to use |
|
|
460874
|
-
| ----------------- | ---------------- | ------------------------------------------------------------------------------------- |
|
|
460875
|
-
| **hyperframes** | \`/hyperframes\` | Cinematic-quality composition \u2014 DESIGN.md hard-gate, named styles, motion principles |
|
|
460876
|
-
| **vibe-scene** | \`/vibe-scene\` | VibeFrame's authoring loop, AI assets, lint feedback, pipeline integration |
|
|
460877
|
-
| **gsap** | \`/gsap\` | GSAP tweens, timelines, easing |
|
|
460878
|
-
|
|
460879
|
-
Install the Hyperframes skills once per machine:
|
|
460880
|
-
|
|
460881
|
-
\`\`\`bash
|
|
460882
|
-
npx skills add heygen-com/hyperframes
|
|
460883
|
-
\`\`\`
|
|
460884
|
-
|
|
460885
|
-
Restart your agent session (or reload the skill list) after installing.
|
|
460886
|
-
If skills aren't available, follow the **Key Rules** below \u2014 they cover
|
|
460887
|
-
the framework-level minimum, not the cinematic craft layer.
|
|
460888
|
-
|
|
460889
|
-
## Project structure
|
|
460890
|
-
|
|
460891
|
-
- \`DESIGN.md\` \u2014 visual identity contract (palette, type, motion, transitions)
|
|
460892
|
-
- \`index.html\` \u2014 root composition (timeline)
|
|
460893
|
-
- \`compositions/scene-*.html\` \u2014 per-scene HTML authored by you or the agent
|
|
460894
|
-
- \`assets/\` \u2014 shared media (narration audio, images, video)
|
|
460895
|
-
- \`transcript.json\` \u2014 Whisper word-level transcript (if narration exists)
|
|
460896
|
-
- \`hyperframes.json\` \u2014 HF registry config (speak to both toolchains)
|
|
460897
|
-
- \`vibe.project.yaml\` \u2014 VibeFrame config (providers, budget)
|
|
460898
|
-
- \`renders/\` \u2014 output MP4s
|
|
460899
|
-
|
|
460900
|
-
## Commands
|
|
460901
|
-
|
|
460902
|
-
\`\`\`bash
|
|
460903
|
-
vibe scene add <name> --narration "..." --visuals "..." # Author a new scene via AI
|
|
460904
|
-
vibe scene lint # Validate scenes (in-process HF linter)
|
|
460905
|
-
vibe scene render # Render to MP4
|
|
460906
|
-
|
|
460907
|
-
# Hyperframes CLI (if installed \u2014 works in this project too)
|
|
460908
|
-
npx hyperframes preview
|
|
460909
|
-
npx hyperframes render
|
|
460910
|
-
\`\`\`
|
|
460911
|
-
|
|
460912
|
-
## Key Rules (for hand-authored scene HTML)
|
|
460913
|
-
|
|
460914
|
-
1. Every timed element needs \`data-start\`, \`data-duration\`, and \`data-track-index\`.
|
|
460915
|
-
2. Elements with timing **MUST** have \`class="clip"\` \u2014 the framework uses this for visibility control.
|
|
460916
|
-
3. Timelines must be paused and registered on \`window.__timelines\`:
|
|
460917
|
-
\`\`\`js
|
|
460918
|
-
window.__timelines = window.__timelines || {};
|
|
460919
|
-
window.__timelines["composition-id"] = gsap.timeline({ paused: true });
|
|
460920
|
-
\`\`\`
|
|
460921
|
-
4. Videos use \`muted\` with a separate \`<audio>\` element for the audio track.
|
|
460922
|
-
5. Sub-compositions use \`data-composition-src="compositions/file.html"\`.
|
|
460923
|
-
6. Only deterministic logic \u2014 no \`Date.now()\`, \`Math.random()\`, or network fetches.
|
|
460924
|
-
|
|
460925
|
-
## Linting \u2014 run after changes
|
|
460926
|
-
|
|
460927
|
-
\`\`\`bash
|
|
460928
|
-
vibe scene lint # preferred \u2014 in-process, no network
|
|
460929
|
-
vibe scene lint --fix # auto-fix mechanical issues
|
|
460930
|
-
vibe scene lint --json # structured output for agent loops
|
|
460931
|
-
\`\`\`
|
|
460932
|
-
`;
|
|
460933
|
-
}
|
|
460934
|
-
function buildSceneGitignore() {
|
|
460935
|
-
return `# VibeFrame caches
|
|
460936
|
-
.vibeframe/cache/
|
|
460937
|
-
.vibeframe/checkpoints/
|
|
460938
|
-
|
|
460939
|
-
# Render outputs
|
|
460940
|
-
renders/*.mp4
|
|
460941
|
-
tmp/
|
|
460942
|
-
|
|
460943
|
-
# OS / editor
|
|
460944
|
-
.DS_Store
|
|
460945
|
-
*.log
|
|
460946
|
-
`;
|
|
460947
|
-
}
|
|
460948
|
-
async function pathExists(p) {
|
|
460949
|
-
try {
|
|
460950
|
-
await access(p);
|
|
460951
|
-
return true;
|
|
460952
|
-
} catch {
|
|
460953
|
-
return false;
|
|
460954
|
-
}
|
|
460955
|
-
}
|
|
460956
|
-
async function scaffoldSceneProject(opts) {
|
|
460957
|
-
const dir = resolve(opts.dir);
|
|
460958
|
-
const name = opts.name ?? basename(dir);
|
|
460959
|
-
const aspect = opts.aspect ?? "16:9";
|
|
460960
|
-
const duration = opts.duration ?? 10;
|
|
460961
|
-
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
460962
|
-
await mkdir(dir, { recursive: true });
|
|
460963
|
-
await mkdir(resolve(dir, "compositions"), { recursive: true });
|
|
460964
|
-
await mkdir(resolve(dir, "assets"), { recursive: true });
|
|
460965
|
-
const created = [];
|
|
460966
|
-
const skipped2 = [];
|
|
460967
|
-
const merged = [];
|
|
460968
|
-
const hfPath = resolve(dir, "hyperframes.json");
|
|
460969
|
-
const hfDefaults = buildHyperframesConfig();
|
|
460970
|
-
if (await pathExists(hfPath)) {
|
|
460971
|
-
const existingRaw = await readFile(hfPath, "utf-8");
|
|
460972
|
-
const existing = JSON.parse(existingRaw);
|
|
460973
|
-
const mergedConfig = mergeHyperframesConfig(existing, hfDefaults);
|
|
460974
|
-
await writeFile(hfPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf-8");
|
|
460975
|
-
merged.push(hfPath);
|
|
460976
|
-
} else {
|
|
460977
|
-
await writeFile(hfPath, JSON.stringify(hfDefaults, null, 2) + "\n", "utf-8");
|
|
460978
|
-
created.push(hfPath);
|
|
460979
|
-
}
|
|
460980
|
-
const metaPath = resolve(dir, "meta.json");
|
|
460981
|
-
if (await pathExists(metaPath)) {
|
|
460982
|
-
skipped2.push(metaPath);
|
|
460983
|
-
} else {
|
|
460984
|
-
await writeFile(metaPath, JSON.stringify(buildHyperframesMeta(name, now), null, 2) + "\n", "utf-8");
|
|
460985
|
-
created.push(metaPath);
|
|
460986
|
-
}
|
|
460987
|
-
const rootPath = resolve(dir, "index.html");
|
|
460988
|
-
if (await pathExists(rootPath)) {
|
|
460989
|
-
skipped2.push(rootPath);
|
|
460990
|
-
} else {
|
|
460991
|
-
await writeFile(rootPath, buildEmptyRootHtml({ aspect, duration }), "utf-8");
|
|
460992
|
-
created.push(rootPath);
|
|
460993
|
-
}
|
|
460994
|
-
const vibePath = resolve(dir, "vibe.project.yaml");
|
|
460995
|
-
if (await pathExists(vibePath)) {
|
|
460996
|
-
skipped2.push(vibePath);
|
|
460997
|
-
} else {
|
|
460998
|
-
const cfg = { ...defaultVibeProjectConfig(name), aspect };
|
|
460999
|
-
await writeFile(vibePath, (0, import_yaml.stringify)(cfg), "utf-8");
|
|
461000
|
-
created.push(vibePath);
|
|
461001
|
-
}
|
|
461002
|
-
const claudePath = resolve(dir, "CLAUDE.md");
|
|
461003
|
-
if (await pathExists(claudePath)) {
|
|
461004
|
-
skipped2.push(claudePath);
|
|
461005
|
-
} else {
|
|
461006
|
-
await writeFile(claudePath, buildProjectClaudeMd(name), "utf-8");
|
|
461007
|
-
created.push(claudePath);
|
|
461008
|
-
}
|
|
461009
|
-
const designPath = resolve(dir, "DESIGN.md");
|
|
461010
|
-
if (await pathExists(designPath)) {
|
|
461011
|
-
skipped2.push(designPath);
|
|
461012
|
-
} else {
|
|
461013
|
-
await writeFile(
|
|
461014
|
-
designPath,
|
|
461015
|
-
buildDesignMd({ name, style: opts.visualStyle }),
|
|
461016
|
-
"utf-8"
|
|
461017
|
-
);
|
|
461018
|
-
created.push(designPath);
|
|
461019
|
-
}
|
|
461020
|
-
const gitignorePath = resolve(dir, ".gitignore");
|
|
461021
|
-
if (await pathExists(gitignorePath)) {
|
|
461022
|
-
skipped2.push(gitignorePath);
|
|
461023
|
-
} else {
|
|
461024
|
-
await writeFile(gitignorePath, buildSceneGitignore(), "utf-8");
|
|
461025
|
-
created.push(gitignorePath);
|
|
461026
|
-
}
|
|
461027
|
-
return { created, skipped: skipped2, merged };
|
|
461028
|
-
}
|
|
461485
|
+
// ../cli/src/tools/manifest/scene.ts
|
|
461486
|
+
init_scene_project();
|
|
461029
461487
|
|
|
461030
461488
|
// ../cli/src/commands/scene.ts
|
|
461031
461489
|
init_esm();
|
|
@@ -461034,6 +461492,7 @@ init_ora();
|
|
|
461034
461492
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
461035
461493
|
init_dist();
|
|
461036
461494
|
init_tts_resolve();
|
|
461495
|
+
init_scene_project();
|
|
461037
461496
|
import { basename as basename6, resolve as resolve21, relative as relative7, dirname as dirname17 } from "node:path";
|
|
461038
461497
|
import { mkdir as mkdir10, readFile as readFile10, writeFile as writeFile10, access as access4, copyFile as copyFile2 } from "node:fs/promises";
|
|
461039
461498
|
import { existsSync as existsSync28 } from "node:fs";
|
|
@@ -461525,6 +461984,7 @@ function deriveInstallHosts(detected) {
|
|
|
461525
461984
|
// ../cli/src/commands/scene.ts
|
|
461526
461985
|
init_compose_prompts();
|
|
461527
461986
|
var VALID_ASPECTS2 = ["16:9", "9:16", "1:1", "4:5"];
|
|
461987
|
+
var VALID_SCENE_INIT_PROFILES = ["minimal", "agent", "full"];
|
|
461528
461988
|
function validateAspect(value) {
|
|
461529
461989
|
if (!VALID_ASPECTS2.includes(value)) {
|
|
461530
461990
|
exitWithError(usageError(`Invalid aspect ratio: ${value}`, `Valid: ${VALID_ASPECTS2.join(", ")}`));
|
|
@@ -461556,7 +462016,12 @@ function validateVisualStyle(value) {
|
|
|
461556
462016
|
}
|
|
461557
462017
|
return found;
|
|
461558
462018
|
}
|
|
461559
|
-
|
|
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", `
|
|
461560
462025
|
Examples:
|
|
461561
462026
|
$ vibe scene init my-video # Scaffold a new project
|
|
461562
462027
|
$ vibe scene init my-video -r 9:16 -d 30 # Vertical 30s project
|
|
@@ -461564,62 +462029,91 @@ Examples:
|
|
|
461564
462029
|
--headline "Welcome to VibeFrame" # Headline-only scene
|
|
461565
462030
|
$ vibe scene add overview --narration "VibeFrame turns scripts into video." \\
|
|
461566
462031
|
--visuals "studio desk, soft lighting" # AI narration + image
|
|
461567
|
-
$ vibe scene lint # Validate every scene against
|
|
462032
|
+
$ vibe scene lint # Validate every scene against composition rules
|
|
461568
462033
|
$ vibe scene lint --fix # Auto-fix mechanical issues (e.g. missing class="clip")
|
|
461569
462034
|
$ vibe scene lint --json # Structured output for agent loops
|
|
461570
462035
|
$ vibe scene render # Render to renders/<name>-<timestamp>.mp4
|
|
461571
462036
|
$ vibe scene render -o demo.mp4 --quality high # Custom output path + quality
|
|
461572
462037
|
$ vibe scene render --fps 60 --format webm # 60fps WebM render
|
|
461573
462038
|
|
|
461574
|
-
|
|
462039
|
+
Most users can start with \`vibe init\`, \`vibe build\`, and \`vibe render\`.
|
|
462040
|
+
This namespace exposes lower-level scene authoring and rendering controls.
|
|
461575
462041
|
Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
461576
|
-
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) => {
|
|
462043
|
+
const startedAt = Date.now();
|
|
461577
462044
|
const aspect = validateAspect(options.ratio);
|
|
461578
462045
|
const duration = validateDuration(options.duration);
|
|
461579
462046
|
const name = options.name ?? basename6(dir.replace(/\/+$/, ""));
|
|
461580
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 });
|
|
461581
462053
|
if (options.dryRun) {
|
|
461582
|
-
|
|
461583
|
-
|
|
462054
|
+
if (!isJsonMode() && !isQuietMode()) {
|
|
462055
|
+
printSceneInitDryRun({ dir, name, aspect, duration, visualStyleName: visualStyle?.name ?? null, profile, groups });
|
|
462056
|
+
return;
|
|
462057
|
+
}
|
|
462058
|
+
outputSuccess({
|
|
461584
462059
|
command: "scene init",
|
|
461585
|
-
|
|
461586
|
-
|
|
461587
|
-
|
|
461588
|
-
|
|
461589
|
-
|
|
461590
|
-
|
|
462060
|
+
startedAt,
|
|
462061
|
+
dryRun: true,
|
|
462062
|
+
data: {
|
|
462063
|
+
params: {
|
|
462064
|
+
dir,
|
|
462065
|
+
name,
|
|
462066
|
+
aspect,
|
|
462067
|
+
duration,
|
|
462068
|
+
visualStyle: visualStyle?.name ?? null,
|
|
462069
|
+
profile
|
|
462070
|
+
},
|
|
462071
|
+
groups
|
|
461591
462072
|
}
|
|
461592
462073
|
});
|
|
461593
462074
|
return;
|
|
461594
462075
|
}
|
|
461595
|
-
const spinner2 = isJsonMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
|
|
462076
|
+
const spinner2 = isJsonMode() || isQuietMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
|
|
461596
462077
|
try {
|
|
461597
|
-
const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle });
|
|
462078
|
+
const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle, profile });
|
|
461598
462079
|
const detectedIds = detectedAgentHosts().map((h) => h.id);
|
|
461599
462080
|
const skillHosts = deriveInstallHosts(detectedIds);
|
|
461600
462081
|
const projectAbs = resolve21(dir);
|
|
461601
|
-
const skillResult = await installHyperframesSkill({
|
|
462082
|
+
const skillResult = profile === "agent" || profile === "full" ? await installHyperframesSkill({
|
|
461602
462083
|
projectDir: projectAbs,
|
|
461603
462084
|
hosts: skillHosts
|
|
461604
|
-
});
|
|
461605
|
-
if (isJsonMode()) {
|
|
461606
|
-
|
|
461607
|
-
success: true,
|
|
462085
|
+
}) : { success: true, files: [], bundleVersion: "not-installed" };
|
|
462086
|
+
if (isJsonMode() || isQuietMode()) {
|
|
462087
|
+
outputSuccess({
|
|
461608
462088
|
command: "scene init",
|
|
461609
|
-
|
|
461610
|
-
|
|
461611
|
-
|
|
461612
|
-
|
|
461613
|
-
|
|
461614
|
-
|
|
461615
|
-
|
|
461616
|
-
|
|
461617
|
-
|
|
461618
|
-
|
|
462089
|
+
startedAt,
|
|
462090
|
+
data: {
|
|
462091
|
+
dir,
|
|
462092
|
+
name,
|
|
462093
|
+
aspect,
|
|
462094
|
+
duration,
|
|
462095
|
+
visualStyle: visualStyle?.name ?? null,
|
|
462096
|
+
profile,
|
|
462097
|
+
created: result.created,
|
|
462098
|
+
merged: result.merged,
|
|
462099
|
+
skipped: result.skipped,
|
|
462100
|
+
groups: result.groups,
|
|
462101
|
+
skillFiles: skillResult.files,
|
|
462102
|
+
skillBundleVersion: skillResult.bundleVersion
|
|
462103
|
+
}
|
|
461619
462104
|
});
|
|
461620
462105
|
return;
|
|
461621
462106
|
}
|
|
461622
|
-
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))}`);
|
|
461623
462117
|
console.log();
|
|
461624
462118
|
console.log(source_default.bold.cyan("Files"));
|
|
461625
462119
|
console.log(source_default.dim("\u2500".repeat(60)));
|
|
@@ -461630,7 +462124,7 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
|
|
|
461630
462124
|
const skillSkipped = skillResult.files.filter((f) => f.status === "skipped-exists");
|
|
461631
462125
|
if (skillWritten.length + skillSkipped.length > 0) {
|
|
461632
462126
|
console.log();
|
|
461633
|
-
console.log(source_default.bold.cyan("
|
|
462127
|
+
console.log(source_default.bold.cyan("Composition rules"));
|
|
461634
462128
|
console.log(source_default.dim("\u2500".repeat(60)));
|
|
461635
462129
|
for (const f of skillWritten) console.log(source_default.green(" +"), f.path);
|
|
461636
462130
|
for (const f of skillSkipped) console.log(source_default.dim(" \xB7"), f.path, source_default.dim("(kept existing)"));
|
|
@@ -461644,18 +462138,42 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
|
|
|
461644
462138
|
} else {
|
|
461645
462139
|
console.log(` ${source_default.cyan("vibe scene styles")} ${source_default.dim("# pick a named style for DESIGN.md")}`);
|
|
461646
462140
|
}
|
|
461647
|
-
|
|
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
|
+
}
|
|
461648
462146
|
console.log(` ${source_default.cyan("vibe scene add")} <name> ${source_default.dim("# fallback: 5-preset emit (no agent)")}`);
|
|
461649
|
-
console.log(` ${source_default.cyan("vibe
|
|
461650
|
-
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")}`);
|
|
461651
462149
|
} catch (error) {
|
|
461652
462150
|
spinner2?.fail("Failed to scaffold scene project");
|
|
461653
462151
|
const msg = error instanceof Error ? error.message : String(error);
|
|
461654
462152
|
exitWithError(generalError(`Failed to scaffold: ${msg}`));
|
|
461655
462153
|
}
|
|
461656
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
|
+
}
|
|
461657
462174
|
var VALID_INSTALL_SKILL_HOSTS = ["claude-code", "cursor", "auto", "all"];
|
|
461658
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) => {
|
|
462176
|
+
const startedAt = Date.now();
|
|
461659
462177
|
const hostFlag = options.host ?? "auto";
|
|
461660
462178
|
if (!VALID_INSTALL_SKILL_HOSTS.includes(hostFlag)) {
|
|
461661
462179
|
exitWithError(usageError(`Invalid --host: ${hostFlag}`, `Valid: ${VALID_INSTALL_SKILL_HOSTS.join(", ")}`));
|
|
@@ -461675,15 +462193,17 @@ sceneCommand.command("install-skill").description("Install the Hyperframes skill
|
|
|
461675
462193
|
dryRun: options.dryRun ?? false
|
|
461676
462194
|
});
|
|
461677
462195
|
if (isJsonMode()) {
|
|
461678
|
-
|
|
461679
|
-
success: true,
|
|
462196
|
+
outputSuccess({
|
|
461680
462197
|
command: "scene install-skill",
|
|
461681
|
-
|
|
461682
|
-
|
|
461683
|
-
|
|
461684
|
-
|
|
461685
|
-
|
|
461686
|
-
|
|
462198
|
+
startedAt,
|
|
462199
|
+
dryRun: options.dryRun ?? false,
|
|
462200
|
+
data: {
|
|
462201
|
+
projectDir,
|
|
462202
|
+
host: hostFlag,
|
|
462203
|
+
resolvedHosts: hosts,
|
|
462204
|
+
bundleVersion: result.bundleVersion,
|
|
462205
|
+
files: result.files
|
|
462206
|
+
}
|
|
461687
462207
|
});
|
|
461688
462208
|
return;
|
|
461689
462209
|
}
|
|
@@ -461705,6 +462225,7 @@ sceneCommand.command("install-skill").description("Install the Hyperframes skill
|
|
|
461705
462225
|
}
|
|
461706
462226
|
});
|
|
461707
462227
|
sceneCommand.command("compose-prompts").description("Emit the per-beat compose plan for the host agent to author HTML itself (Phase H2 \u2014 no LLM call)").argument("[project-dir]", "Project directory containing STORYBOARD.md / DESIGN.md", ".").option("--beat <id>", "Restrict the plan to a single beat by id (e.g. 'hook', '1')").action(async (projectDirArg, options) => {
|
|
462228
|
+
const startedAt = Date.now();
|
|
461708
462229
|
const projectDir = resolve21(projectDirArg);
|
|
461709
462230
|
const result = await getComposePrompts({
|
|
461710
462231
|
projectDir,
|
|
@@ -461712,18 +462233,21 @@ sceneCommand.command("compose-prompts").description("Emit the per-beat compose p
|
|
|
461712
462233
|
});
|
|
461713
462234
|
if (!result.success) {
|
|
461714
462235
|
if (isJsonMode()) {
|
|
461715
|
-
|
|
462236
|
+
outputSuccess({
|
|
461716
462237
|
command: "scene compose-prompts",
|
|
461717
|
-
|
|
462238
|
+
startedAt,
|
|
462239
|
+
data: { ...result }
|
|
461718
462240
|
});
|
|
461719
|
-
process.
|
|
462241
|
+
process.exitCode = 1;
|
|
462242
|
+
return;
|
|
461720
462243
|
}
|
|
461721
462244
|
exitWithError(generalError(result.error ?? "compose-prompts failed"));
|
|
461722
462245
|
}
|
|
461723
462246
|
if (isJsonMode()) {
|
|
461724
|
-
|
|
462247
|
+
outputSuccess({
|
|
461725
462248
|
command: "scene compose-prompts",
|
|
461726
|
-
|
|
462249
|
+
startedAt,
|
|
462250
|
+
data: { ...result }
|
|
461727
462251
|
});
|
|
461728
462252
|
return;
|
|
461729
462253
|
}
|
|
@@ -461757,20 +462281,23 @@ sceneCommand.command("compose-prompts").description("Emit the per-beat compose p
|
|
|
461757
462281
|
console.log(source_default.dim("Re-run with --json to get the full per-beat userPrompt + cues for direct consumption."));
|
|
461758
462282
|
});
|
|
461759
462283
|
sceneCommand.command("styles").description("List vendored visual styles (or show one) for DESIGN.md seeding").argument("[name]", "Style name to inspect (omit to list all)").action((name) => {
|
|
462284
|
+
const startedAt = Date.now();
|
|
461760
462285
|
if (!name) {
|
|
461761
462286
|
const all = listVisualStyles();
|
|
461762
462287
|
if (isJsonMode()) {
|
|
461763
|
-
|
|
461764
|
-
success: true,
|
|
462288
|
+
outputSuccess({
|
|
461765
462289
|
command: "scene styles",
|
|
461766
|
-
|
|
461767
|
-
|
|
461768
|
-
|
|
461769
|
-
|
|
461770
|
-
|
|
461771
|
-
|
|
461772
|
-
|
|
461773
|
-
|
|
462290
|
+
startedAt,
|
|
462291
|
+
data: {
|
|
462292
|
+
count: all.length,
|
|
462293
|
+
styles: all.map((s) => ({
|
|
462294
|
+
name: s.name,
|
|
462295
|
+
slug: s.slug,
|
|
462296
|
+
designer: s.designer,
|
|
462297
|
+
mood: s.mood,
|
|
462298
|
+
bestFor: s.bestFor
|
|
462299
|
+
}))
|
|
462300
|
+
}
|
|
461774
462301
|
});
|
|
461775
462302
|
return;
|
|
461776
462303
|
}
|
|
@@ -461798,7 +462325,11 @@ sceneCommand.command("styles").description("List vendored visual styles (or show
|
|
|
461798
462325
|
return;
|
|
461799
462326
|
}
|
|
461800
462327
|
if (isJsonMode()) {
|
|
461801
|
-
|
|
462328
|
+
outputSuccess({
|
|
462329
|
+
command: "scene styles",
|
|
462330
|
+
startedAt,
|
|
462331
|
+
data: { style }
|
|
462332
|
+
});
|
|
461802
462333
|
return;
|
|
461803
462334
|
}
|
|
461804
462335
|
console.log();
|
|
@@ -461820,6 +462351,7 @@ sceneCommand.command("styles").description("List vendored visual styles (or show
|
|
|
461820
462351
|
console.log(source_default.dim("Seed DESIGN.md:"), source_default.cyan(`vibe scene init <dir> --visual-style "${style.name}"`));
|
|
461821
462352
|
});
|
|
461822
462353
|
sceneCommand.command("add").description("Add a new scene to a project: AI narration + image + per-scene HTML").argument("<name>", "Scene name (slugified into the composition id)").option("--style <preset>", `Style preset: ${SCENE_PRESETS.join(", ")}`, "simple").option("--narration <text>", "Narration text (or path to a .txt file). Drives TTS + scene duration.").option("--narration-file <path>", "Existing narration audio file (.wav/.mp3). Skips TTS \u2014 useful with hyperframes tts, Mac say, or other external tools.").option("-d, --duration <sec>", "Explicit scene duration in seconds (overrides narration audio)").option("--visuals <prompt>", "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider").option("--headline <text>", "Visible headline (defaults to the humanised scene name)").option("--kicker <text>", "Small label above the headline (explainer / product-shot)").option("--insert-into <path>", "Root composition file to update", "index.html").option("--project <dir>", "Project directory", ".").option("--image-provider <name>", "Image provider: gemini, openai", "gemini").option("--tts <provider>", "TTS provider: auto, elevenlabs, kokoro (default auto \u2014 picks ElevenLabs when key set, else Kokoro local)", "auto").option("--voice <id>", "Voice id (ElevenLabs name/id, or Kokoro id like af_heart, am_michael)").option("--no-audio", "Skip TTS even when --narration is provided (useful for tests/agent dry runs)").option("--no-image", "Skip image generation even when --visuals is provided").option("--no-transcribe", "Skip Whisper word-level transcribe step (no transcript-<id>.json emitted)").option("--transcribe-language <code>", "BCP-47 language code passed to Whisper (e.g. en, ko)").option("--force", "Overwrite an existing compositions/scene-<id>.html").option("--dry-run", "Preview parameters without writing files or calling APIs").action(async (name, options) => {
|
|
462354
|
+
const startedAt = Date.now();
|
|
461823
462355
|
if (options.style) options.style = validatePreset(options.style);
|
|
461824
462356
|
if (options.duration !== void 0) options.duration = validateDuration(options.duration);
|
|
461825
462357
|
let tts;
|
|
@@ -461830,25 +462362,28 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
|
|
|
461830
462362
|
}
|
|
461831
462363
|
if (options.dryRun) {
|
|
461832
462364
|
const id = slugifySceneName(name);
|
|
461833
|
-
|
|
461834
|
-
dryRun: true,
|
|
462365
|
+
outputSuccess({
|
|
461835
462366
|
command: "scene add",
|
|
461836
|
-
|
|
461837
|
-
|
|
461838
|
-
|
|
461839
|
-
|
|
461840
|
-
|
|
461841
|
-
|
|
461842
|
-
|
|
461843
|
-
|
|
461844
|
-
|
|
461845
|
-
|
|
461846
|
-
|
|
461847
|
-
|
|
461848
|
-
|
|
461849
|
-
|
|
461850
|
-
|
|
461851
|
-
|
|
462367
|
+
startedAt,
|
|
462368
|
+
dryRun: true,
|
|
462369
|
+
data: {
|
|
462370
|
+
params: {
|
|
462371
|
+
name,
|
|
462372
|
+
id,
|
|
462373
|
+
preset: options.style,
|
|
462374
|
+
narration: !!options.narration,
|
|
462375
|
+
visuals: !!options.visuals,
|
|
462376
|
+
duration: options.duration,
|
|
462377
|
+
headline: options.headline,
|
|
462378
|
+
kicker: options.kicker,
|
|
462379
|
+
project: options.project,
|
|
462380
|
+
insertInto: options.insertInto,
|
|
462381
|
+
imageProvider: options.imageProvider,
|
|
462382
|
+
tts,
|
|
462383
|
+
audio: options.audio,
|
|
462384
|
+
// commander sets `audio: false` when --no-audio is passed
|
|
462385
|
+
image: options.image
|
|
462386
|
+
}
|
|
461852
462387
|
}
|
|
461853
462388
|
});
|
|
461854
462389
|
return;
|
|
@@ -461883,9 +462418,10 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
|
|
|
461883
462418
|
exitWithError(generalError(result.error ?? "Scene add failed"));
|
|
461884
462419
|
}
|
|
461885
462420
|
if (isJsonMode()) {
|
|
461886
|
-
|
|
462421
|
+
outputSuccess({
|
|
461887
462422
|
command: "scene add",
|
|
461888
|
-
|
|
462423
|
+
startedAt,
|
|
462424
|
+
data: { ...result }
|
|
461889
462425
|
});
|
|
461890
462426
|
return;
|
|
461891
462427
|
}
|
|
@@ -462180,7 +462716,8 @@ async function executeSceneAdd(opts) {
|
|
|
462180
462716
|
transcriptWordCount
|
|
462181
462717
|
};
|
|
462182
462718
|
}
|
|
462183
|
-
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) => {
|
|
462720
|
+
const startedAt = Date.now();
|
|
462184
462721
|
const projectDir = resolve21(options.project);
|
|
462185
462722
|
if (!await rootExists(projectDir, root2)) {
|
|
462186
462723
|
exitWithError(generalError(
|
|
@@ -462198,11 +462735,12 @@ sceneCommand.command("lint").description("Validate scene HTML against Hyperframe
|
|
|
462198
462735
|
exitWithError(generalError(`Lint failed: ${msg}`));
|
|
462199
462736
|
}
|
|
462200
462737
|
if (isJsonMode()) {
|
|
462201
|
-
|
|
462738
|
+
outputSuccess({
|
|
462202
462739
|
command: "scene lint",
|
|
462203
|
-
|
|
462740
|
+
startedAt,
|
|
462741
|
+
data: { ...result }
|
|
462204
462742
|
});
|
|
462205
|
-
if (!result.ok) process.
|
|
462743
|
+
if (!result.ok) process.exitCode = 1;
|
|
462206
462744
|
return;
|
|
462207
462745
|
}
|
|
462208
462746
|
if (result.ok && result.warningCount === 0 && result.infoCount === 0) {
|
|
@@ -462236,7 +462774,7 @@ sceneCommand.command("lint").description("Validate scene HTML against Hyperframe
|
|
|
462236
462774
|
console.log(` ${source_default.green("\u2714")} ${fx.file} ${source_default.dim(fx.codes.join(", "))}`);
|
|
462237
462775
|
}
|
|
462238
462776
|
}
|
|
462239
|
-
if (!result.ok) process.
|
|
462777
|
+
if (!result.ok) process.exitCode = 1;
|
|
462240
462778
|
});
|
|
462241
462779
|
function severityTag(severity) {
|
|
462242
462780
|
if (severity === "error") return source_default.red("\u2718 error ");
|
|
@@ -462273,23 +462811,27 @@ function validateWorkers(value) {
|
|
|
462273
462811
|
return n;
|
|
462274
462812
|
}
|
|
462275
462813
|
sceneCommand.command("render").description("Render a scene project to MP4/WebM/MOV via the Hyperframes producer (requires Chrome)").argument("[root]", "Root composition file relative to --project", "index.html").option("--project <dir>", "Project directory", ".").option("-o, --out <path>", "Output file (default: renders/<name>-<timestamp>.<format>)").option("--fps <n>", `Frames per second: ${VALID_FPS.join("|")}`, "30").option("--quality <q>", `Quality preset: ${VALID_QUALITIES.join("|")}`, "standard").option("--format <f>", `Output container: ${VALID_FORMATS.join("|")}`, "mp4").option("--workers <n>", "Capture workers (1-16, default 1)", "1").option("--dry-run", "Preview parameters without rendering").action(async (root2, options) => {
|
|
462814
|
+
const startedAt = Date.now();
|
|
462276
462815
|
const fps = validateFps(options.fps);
|
|
462277
462816
|
const quality = validateQuality(options.quality);
|
|
462278
462817
|
const format4 = validateFormat(options.format);
|
|
462279
462818
|
const workers = validateWorkers(options.workers);
|
|
462280
462819
|
const projectDir = resolve21(options.project);
|
|
462281
462820
|
if (options.dryRun) {
|
|
462282
|
-
|
|
462283
|
-
dryRun: true,
|
|
462821
|
+
outputSuccess({
|
|
462284
462822
|
command: "scene render",
|
|
462285
|
-
|
|
462286
|
-
|
|
462287
|
-
|
|
462288
|
-
|
|
462289
|
-
|
|
462290
|
-
|
|
462291
|
-
|
|
462292
|
-
|
|
462823
|
+
startedAt,
|
|
462824
|
+
dryRun: true,
|
|
462825
|
+
data: {
|
|
462826
|
+
params: {
|
|
462827
|
+
projectDir,
|
|
462828
|
+
root: root2,
|
|
462829
|
+
output: options.out,
|
|
462830
|
+
fps,
|
|
462831
|
+
quality,
|
|
462832
|
+
format: format4,
|
|
462833
|
+
workers
|
|
462834
|
+
}
|
|
462293
462835
|
}
|
|
462294
462836
|
});
|
|
462295
462837
|
return;
|
|
@@ -462310,13 +462852,22 @@ sceneCommand.command("render").description("Render a scene project to MP4/WebM/M
|
|
|
462310
462852
|
if (!result.success) {
|
|
462311
462853
|
spinner2?.fail("Render failed");
|
|
462312
462854
|
if (isJsonMode()) {
|
|
462313
|
-
|
|
462314
|
-
|
|
462855
|
+
outputSuccess({
|
|
462856
|
+
command: "scene render",
|
|
462857
|
+
startedAt,
|
|
462858
|
+
data: { ...result }
|
|
462859
|
+
});
|
|
462860
|
+
process.exitCode = 1;
|
|
462861
|
+
return;
|
|
462315
462862
|
}
|
|
462316
462863
|
exitWithError(generalError(result.error ?? "Render failed"));
|
|
462317
462864
|
}
|
|
462318
462865
|
if (isJsonMode()) {
|
|
462319
|
-
|
|
462866
|
+
outputSuccess({
|
|
462867
|
+
command: "scene render",
|
|
462868
|
+
startedAt,
|
|
462869
|
+
data: { ...result }
|
|
462870
|
+
});
|
|
462320
462871
|
return;
|
|
462321
462872
|
}
|
|
462322
462873
|
spinner2?.succeed(source_default.green(`Render complete: ${result.outputPath}`));
|
|
@@ -462336,25 +462887,29 @@ sceneCommand.command("render").description("Render a scene project to MP4/WebM/M
|
|
|
462336
462887
|
}
|
|
462337
462888
|
});
|
|
462338
462889
|
sceneCommand.command("build").description("One-shot: read STORYBOARD.md cues, dispatch TTS + image-gen per beat, compose, render to MP4 (v0.60)").argument("[project-dir]", "Project directory containing STORYBOARD.md", ".").option("--mode <mode>", "Build mode: agent (host-agent authors HTML) | batch (CLI's internal LLM authors HTML) | auto (agent if any host detected) [Plan H \u2014 Phase 3]", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "LLM that composes scene HTML in batch mode: claude|openai|gemini (default: auto-resolve from available API keys, claude > gemini > openai)").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|kokoro (overrides frontmatter)").option("--voice <id>", "Voice id (provider-specific \u2014 overrides frontmatter)").option("--image-provider <name>", "Image provider: openai (only one supported in v0.60)").option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").action(async (projectDirArg, options) => {
|
|
462890
|
+
const startedAt = Date.now();
|
|
462339
462891
|
const projectDir = resolve21(projectDirArg);
|
|
462340
462892
|
if (options.dryRun) {
|
|
462341
|
-
|
|
462342
|
-
dryRun: true,
|
|
462893
|
+
outputSuccess({
|
|
462343
462894
|
command: "scene build",
|
|
462344
|
-
|
|
462345
|
-
|
|
462346
|
-
|
|
462347
|
-
|
|
462348
|
-
|
|
462349
|
-
|
|
462350
|
-
|
|
462351
|
-
|
|
462352
|
-
|
|
462353
|
-
|
|
462354
|
-
|
|
462355
|
-
|
|
462356
|
-
|
|
462357
|
-
|
|
462895
|
+
startedAt,
|
|
462896
|
+
dryRun: true,
|
|
462897
|
+
data: {
|
|
462898
|
+
params: {
|
|
462899
|
+
projectDir,
|
|
462900
|
+
mode: options.mode,
|
|
462901
|
+
effort: options.effort,
|
|
462902
|
+
composer: options.composer,
|
|
462903
|
+
skipNarration: options.skipNarration ?? false,
|
|
462904
|
+
skipBackdrop: options.skipBackdrop ?? false,
|
|
462905
|
+
skipRender: options.skipRender ?? false,
|
|
462906
|
+
ttsProvider: options.tts,
|
|
462907
|
+
voice: options.voice,
|
|
462908
|
+
imageProvider: options.imageProvider,
|
|
462909
|
+
imageQuality: options.quality,
|
|
462910
|
+
imageSize: options.imageSize,
|
|
462911
|
+
force: options.force ?? false
|
|
462912
|
+
}
|
|
462358
462913
|
}
|
|
462359
462914
|
});
|
|
462360
462915
|
return;
|
|
@@ -462408,13 +462963,22 @@ sceneCommand.command("build").description("One-shot: read STORYBOARD.md cues, di
|
|
|
462408
462963
|
if (!result.success) {
|
|
462409
462964
|
spinner2?.fail(`Build failed: ${result.error}`);
|
|
462410
462965
|
if (isJsonMode()) {
|
|
462411
|
-
|
|
462412
|
-
|
|
462966
|
+
outputSuccess({
|
|
462967
|
+
command: "scene build",
|
|
462968
|
+
startedAt,
|
|
462969
|
+
data: { ...result }
|
|
462970
|
+
});
|
|
462971
|
+
process.exitCode = 1;
|
|
462972
|
+
return;
|
|
462413
462973
|
}
|
|
462414
462974
|
exitWithError(generalError(result.error ?? "Build failed"));
|
|
462415
462975
|
}
|
|
462416
462976
|
if (isJsonMode()) {
|
|
462417
|
-
|
|
462977
|
+
outputSuccess({
|
|
462978
|
+
command: "scene build",
|
|
462979
|
+
startedAt,
|
|
462980
|
+
data: { ...result }
|
|
462981
|
+
});
|
|
462418
462982
|
return;
|
|
462419
462983
|
}
|
|
462420
462984
|
if (result.phase === "needs-author") {
|
|
@@ -462439,7 +463003,7 @@ sceneCommand.command("build").description("One-shot: read STORYBOARD.md cues, di
|
|
|
462439
463003
|
}
|
|
462440
463004
|
}
|
|
462441
463005
|
console.log();
|
|
462442
|
-
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."));
|
|
462443
463007
|
console.log(source_default.dim("Or pass `--mode batch` to use the internal LLM compose path instead."));
|
|
462444
463008
|
return;
|
|
462445
463009
|
}
|
|
@@ -462578,7 +463142,7 @@ var sceneInitTool = defineTool({
|
|
|
462578
463142
|
name: "scene_init",
|
|
462579
463143
|
category: "scene",
|
|
462580
463144
|
cost: "free",
|
|
462581
|
-
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.",
|
|
462582
463146
|
schema: sceneInitSchema,
|
|
462583
463147
|
async execute(args, ctx) {
|
|
462584
463148
|
const dir = resolve23(ctx.workingDirectory, args.dir);
|
|
@@ -464736,38 +465300,44 @@ function findUnresolvedRefs(params, availableStepIds) {
|
|
|
464736
465300
|
|
|
464737
465301
|
// ../cli/src/pipeline/executor.ts
|
|
464738
465302
|
init_output();
|
|
464739
|
-
var
|
|
464740
|
-
"generate-image": "generate image",
|
|
464741
|
-
"generate-video": "generate video",
|
|
464742
|
-
"generate-tts": "generate speech",
|
|
464743
|
-
"generate-sfx": "generate sound-effect",
|
|
464744
|
-
"generate-music": "generate music",
|
|
464745
|
-
"generate-storyboard": "generate storyboard",
|
|
464746
|
-
"generate-motion": "generate motion",
|
|
464747
|
-
"edit-silence-cut": "edit silence-cut",
|
|
464748
|
-
"edit-jump-cut": "edit jump-cut",
|
|
464749
|
-
"edit-caption": "edit caption",
|
|
464750
|
-
"edit-noise-reduce": "edit noise-reduce",
|
|
464751
|
-
"edit-fade": "edit fade",
|
|
464752
|
-
"edit-translate-srt": "edit translate-srt",
|
|
464753
|
-
"edit-text-overlay": "edit text-overlay",
|
|
464754
|
-
"edit-grade": "edit grade",
|
|
464755
|
-
"edit-speed-ramp": "edit speed-ramp",
|
|
464756
|
-
"edit-reframe": "edit reframe",
|
|
464757
|
-
"edit-interpolate": "edit interpolate",
|
|
464758
|
-
"edit-upscale": "edit upscale-video",
|
|
464759
|
-
"edit-image": "edit image",
|
|
464760
|
-
"audio-transcribe": "audio transcribe",
|
|
464761
|
-
"
|
|
464762
|
-
"
|
|
464763
|
-
"
|
|
464764
|
-
"
|
|
464765
|
-
"
|
|
464766
|
-
"
|
|
464767
|
-
"
|
|
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"] }
|
|
464768
465338
|
};
|
|
464769
465339
|
function maxCostFor(action) {
|
|
464770
|
-
const cmd =
|
|
465340
|
+
const cmd = ACTION_METADATA[action]?.command;
|
|
464771
465341
|
if (!cmd) return 0;
|
|
464772
465342
|
return COST_ESTIMATES[cmd]?.max ?? 0;
|
|
464773
465343
|
}
|
|
@@ -464792,7 +465362,15 @@ async function ensureActionsRegistered() {
|
|
|
464792
465362
|
registerAction("generate-video", async (params, outputDir) => {
|
|
464793
465363
|
const { executeVideoGenerate: executeVideoGenerate2 } = await Promise.resolve().then(() => (init_ai_video(), ai_video_exports));
|
|
464794
465364
|
const output3 = getOutput(params, outputDir, "video.mp4");
|
|
464795
|
-
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
|
+
});
|
|
464796
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 };
|
|
464797
465375
|
});
|
|
464798
465376
|
registerAction("generate-tts", async (params, outputDir) => {
|
|
@@ -465924,6 +466502,7 @@ Cost: Free (no API keys needed). Requires FFmpeg.
|
|
|
465924
466502
|
GIF format: 15fps, no audio, looping. Good for previews and sharing.
|
|
465925
466503
|
Custom flags (--bitrate, --fps, --resolution, --codec) override preset values.
|
|
465926
466504
|
Run 'vibe schema export' for structured parameter info.`).action(async (projectPath, options) => {
|
|
466505
|
+
const startedAt = Date.now();
|
|
465927
466506
|
const spinner2 = ora("Checking FFmpeg...").start();
|
|
465928
466507
|
try {
|
|
465929
466508
|
if (options.output) {
|
|
@@ -465941,27 +466520,30 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
|
|
|
465941
466520
|
exitWithError(usageError(overrideError));
|
|
465942
466521
|
}
|
|
465943
466522
|
if (options.dryRun) {
|
|
465944
|
-
|
|
465945
|
-
dryRun: true,
|
|
466523
|
+
outputSuccess({
|
|
465946
466524
|
command: "export",
|
|
465947
|
-
|
|
465948
|
-
|
|
465949
|
-
|
|
465950
|
-
|
|
465951
|
-
|
|
465952
|
-
|
|
465953
|
-
|
|
465954
|
-
|
|
465955
|
-
|
|
465956
|
-
|
|
465957
|
-
|
|
465958
|
-
|
|
466525
|
+
startedAt,
|
|
466526
|
+
dryRun: true,
|
|
466527
|
+
data: {
|
|
466528
|
+
params: {
|
|
466529
|
+
project: projectPath,
|
|
466530
|
+
output: options.output || null,
|
|
466531
|
+
format: options.format,
|
|
466532
|
+
preset: options.preset,
|
|
466533
|
+
overwrite: options.overwrite,
|
|
466534
|
+
gapFill: options.gapFill,
|
|
466535
|
+
backend: options.backend,
|
|
466536
|
+
bitrate: options.bitrate ?? null,
|
|
466537
|
+
fps: customOverrides.fps ?? null,
|
|
466538
|
+
resolution: options.resolution ?? null,
|
|
466539
|
+
codec: options.codec ?? null
|
|
466540
|
+
}
|
|
465959
466541
|
}
|
|
465960
466542
|
});
|
|
465961
466543
|
return;
|
|
465962
466544
|
}
|
|
465963
466545
|
if (options.backend === "hyperframes") {
|
|
465964
|
-
await runHyperframesExport(projectPath, options, spinner2);
|
|
466546
|
+
await runHyperframesExport(projectPath, options, spinner2, startedAt);
|
|
465965
466547
|
return;
|
|
465966
466548
|
}
|
|
465967
466549
|
const ffmpegPath = await findFFmpeg();
|
|
@@ -466024,6 +466606,25 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
|
|
|
466024
466606
|
spinner2.text = `Encoding... ${progress}%`;
|
|
466025
466607
|
});
|
|
466026
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
|
+
}
|
|
466027
466628
|
console.log();
|
|
466028
466629
|
console.log(source_default.dim(" Duration:"), `${summary.duration.toFixed(1)}s`);
|
|
466029
466630
|
console.log(source_default.dim(" Clips:"), summary.clipCount);
|
|
@@ -466548,13 +467149,13 @@ function getPresetSettings(preset, aspectRatio) {
|
|
|
466548
467149
|
}
|
|
466549
467150
|
return settings;
|
|
466550
467151
|
}
|
|
466551
|
-
async function runHyperframesExport(projectPath, options, spinner2) {
|
|
467152
|
+
async function runHyperframesExport(projectPath, options, spinner2, startedAt) {
|
|
466552
467153
|
spinner2.text = "Loading project...";
|
|
466553
467154
|
const { readFile: readFile34 } = await import("node:fs/promises");
|
|
466554
467155
|
const { resolve: resolve64, basename: basename18 } = await import("node:path");
|
|
466555
467156
|
const { Project: Project2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
466556
467157
|
const { createHyperframesBackend: createHyperframesBackend2 } = await Promise.resolve().then(() => (init_hyperframes(), hyperframes_exports));
|
|
466557
|
-
const { exitWithError: exitWithError2, generalError: generalError2,
|
|
467158
|
+
const { exitWithError: exitWithError2, generalError: generalError2, outputSuccess: outputSuccess2 } = await Promise.resolve().then(() => (init_output(), output_exports));
|
|
466558
467159
|
const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
|
|
466559
467160
|
const filePath = resolve64(process.cwd(), projectPath);
|
|
466560
467161
|
const content = await readFile34(filePath, "utf-8");
|
|
@@ -466580,10 +467181,10 @@ async function runHyperframesExport(projectPath, options, spinner2) {
|
|
|
466580
467181
|
return;
|
|
466581
467182
|
}
|
|
466582
467183
|
spinner2.succeed(chalk2.green(`Exported: ${result.outputPath}`));
|
|
466583
|
-
|
|
466584
|
-
success: true,
|
|
467184
|
+
outputSuccess2({
|
|
466585
467185
|
command: "export",
|
|
466586
|
-
|
|
467186
|
+
startedAt,
|
|
467187
|
+
data: {
|
|
466587
467188
|
outputPath: result.outputPath,
|
|
466588
467189
|
backend: "hyperframes",
|
|
466589
467190
|
durationMs: result.durationMs,
|
|
@@ -467499,7 +468100,7 @@ A scene project is a directory that is **bilingual**: it works with both
|
|
|
467499
468100
|
and a paused GSAP timeline. Cheap to edit, cheap to lint, expensive only
|
|
467500
468101
|
at render.
|
|
467501
468102
|
|
|
467502
|
-
\`vibe
|
|
468103
|
+
\`vibe build\` (v0.60+) is the supported one-shot driver from a
|
|
467503
468104
|
written storyboard to an MP4. Plan H (v0.70) added \`--mode agent\` so the
|
|
467504
468105
|
host agent itself authors the per-beat HTML \u2014 no internal LLM call.
|
|
467505
468106
|
|
|
@@ -467507,19 +468108,19 @@ host agent itself authors the per-beat HTML \u2014 no internal LLM call.
|
|
|
467507
468108
|
|
|
467508
468109
|
| Path | Command | When to use |
|
|
467509
468110
|
|---|---|---|
|
|
467510
|
-
| **One-shot (default, v0.60+)** | \`vibe
|
|
467511
|
-
| **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 |
|
|
467512
468113
|
| **Quick draft** | \`vibe scene add --style <preset>\` | No agent or no API keys; fast iteration |
|
|
467513
468114
|
|
|
467514
|
-
Recommend \`vibe
|
|
468115
|
+
Recommend \`vibe build\` whenever the user has a STORYBOARD with
|
|
467515
468116
|
narration / backdrop intent.
|
|
467516
468117
|
|
|
467517
468118
|
## High-craft path
|
|
467518
468119
|
|
|
467519
|
-
1. \`vibe
|
|
468120
|
+
1. \`vibe init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
|
|
467520
468121
|
\`DESIGN.md\` (palette, typography, motion, transitions) plus the
|
|
467521
468122
|
\`vibe.project.yaml\` / \`hyperframes.json\` / \`index.html\` scaffold.
|
|
467522
|
-
In Plan H this **also installs
|
|
468123
|
+
In Plan H this **also installs local composition rules** at the
|
|
467523
468124
|
right place for your host (\`.claude/skills/hyperframes/\` for Claude
|
|
467524
468125
|
Code, \`.cursor/rules/hyperframes.mdc\` for Cursor, universal
|
|
467525
468126
|
\`SKILL.md\` for everyone else).
|
|
@@ -467530,7 +468131,7 @@ narration / backdrop intent.
|
|
|
467530
468131
|
4. Author each scene HTML directly under \`compositions/scene-<id>.html\`
|
|
467531
468132
|
using the rules from steps 2 and 3. The skill enforces the visual
|
|
467532
468133
|
identity contract \u2014 scenes that contradict DESIGN.md fail lint.
|
|
467533
|
-
5. \`vibe scene lint --fix\` for mechanical issues, \`vibe
|
|
468134
|
+
5. \`vibe scene lint --fix\` for mechanical issues, \`vibe render my-promo\`
|
|
467534
468135
|
to MP4.
|
|
467535
468136
|
|
|
467536
468137
|
## Quick-draft path
|
|
@@ -467540,7 +468141,7 @@ vibe scene init my-promo -r 16:9 -d 30
|
|
|
467540
468141
|
vibe scene add intro --style announcement \\
|
|
467541
468142
|
--headline "Ship videos, not clicks"
|
|
467542
468143
|
vibe scene lint
|
|
467543
|
-
vibe
|
|
468144
|
+
vibe render my-promo
|
|
467544
468145
|
\`\`\`
|
|
467545
468146
|
|
|
467546
468147
|
\`vibe scene init\` is **idempotent** \u2014 running it on an existing
|
|
@@ -467552,12 +468153,12 @@ Safe to invoke on user-provided projects.
|
|
|
467552
468153
|
\`\`\`bash
|
|
467553
468154
|
vibe scene init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
|
|
467554
468155
|
vibe scene styles [<name>] # list / show vendored visual identities
|
|
467555
|
-
vibe scene install-skill [<dir>] [--host all] # retroactive
|
|
468156
|
+
vibe scene install-skill [<dir>] [--host all] # retroactive composition-rules install
|
|
467556
468157
|
vibe scene add <name> --style <preset> [...]
|
|
467557
468158
|
vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
|
|
467558
468159
|
vibe scene lint [<root>] [--json] [--fix]
|
|
467559
468160
|
vibe scene render [<root>] [--fps 30] [--quality standard] [--format mp4]
|
|
467560
|
-
vibe
|
|
468161
|
+
vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
|
|
467561
468162
|
\`\`\`
|
|
467562
468163
|
|
|
467563
468164
|
## Style presets (for \`vibe scene add --style\`)
|
|
@@ -467575,18 +468176,18 @@ from the generated TTS audio.
|
|
|
467575
468176
|
## STORYBOARD-to-MP4 (one command, v0.60+)
|
|
467576
468177
|
|
|
467577
468178
|
\`\`\`bash
|
|
467578
|
-
vibe
|
|
468179
|
+
vibe init my-promo --visual-style "Swiss Pulse" -d 12
|
|
467579
468180
|
# (edit STORYBOARD.md with per-beat YAML cues \u2014 narration, backdrop, duration)
|
|
467580
|
-
vibe
|
|
468181
|
+
vibe build my-promo
|
|
467581
468182
|
\`\`\`
|
|
467582
468183
|
|
|
467583
|
-
\`vibe
|
|
468184
|
+
\`vibe build\` reads the STORYBOARD frontmatter + per-beat cues,
|
|
467584
468185
|
dispatches TTS + image-gen per beat, then either:
|
|
467585
468186
|
|
|
467586
468187
|
- **\`--mode agent\`** (default when an agent host is detected) \u2014 emits a
|
|
467587
468188
|
\`needs-author\` plan via \`vibe scene compose-prompts\`. The host agent
|
|
467588
468189
|
authors each \`compositions/scene-<id>.html\` itself, then re-invoking
|
|
467589
|
-
\`vibe
|
|
468190
|
+
\`vibe build\` proceeds to lint + render.
|
|
467590
468191
|
- **\`--mode batch\`** \u2014 VibeFrame runs an internal LLM (Claude / OpenAI /
|
|
467591
468192
|
Gemini) to compose the HTML, then renders.
|
|
467592
468193
|
|
|
@@ -467609,9 +468210,9 @@ and surface the error to the user.
|
|
|
467609
468210
|
| Task | Tool |
|
|
467610
468211
|
|------|------|
|
|
467611
468212
|
| Generate narration + image, then author scene | \`vibe scene add\` |
|
|
467612
|
-
| Generate a full scenes project from a STORYBOARD | \`vibe
|
|
468213
|
+
| Generate a full scenes project from a STORYBOARD | \`vibe build\` |
|
|
467613
468214
|
| Hand-tweak a single scene's animation | edit \`compositions/<file>.html\` directly |
|
|
467614
|
-
| Render the project | \`vibe
|
|
468215
|
+
| Render the project | \`vibe render\` *or* \`vibe scene render\` for lower-level control |
|
|
467615
468216
|
| Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
|
|
467616
468217
|
|
|
467617
468218
|
The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
|
|
@@ -467695,8 +468296,8 @@ Checkpoints land next to the YAML: \`pipeline.yaml.checkpoint.json\`.
|
|
|
467695
468296
|
|
|
467696
468297
|
## Authoring tips
|
|
467697
468298
|
|
|
467698
|
-
1. **Start from
|
|
467699
|
-
|
|
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.
|
|
467700
468301
|
2. **Dry-run first** \u2014 you see estimated cost and resolved variable
|
|
467701
468302
|
graph before spending API credits.
|
|
467702
468303
|
3. **Keep step ids short and descriptive** (\`intro\`, \`scene1\`, \`voice\`,
|
|
@@ -467731,18 +468332,18 @@ var META = {
|
|
|
467731
468332
|
title: "Scene authoring with vibe",
|
|
467732
468333
|
summary: "Author per-scene HTML compositions and render to MP4 (BUILD flow)",
|
|
467733
468334
|
steps: [
|
|
467734
|
-
'Run `vibe
|
|
468335
|
+
'Run `vibe init <dir> --visual-style "<style name>"` to scaffold the project + install local composition rules.',
|
|
467735
468336
|
"Edit `STORYBOARD.md` with per-beat YAML cues (narration / backdrop / duration).",
|
|
467736
468337
|
"Read `SKILL.md` for the framework rules and `DESIGN.md` for the visual-identity hard-gate.",
|
|
467737
|
-
"Run `vibe
|
|
467738
|
-
"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."
|
|
467739
468340
|
],
|
|
467740
468341
|
relatedCommands: [
|
|
467741
|
-
"vibe
|
|
468342
|
+
"vibe init",
|
|
467742
468343
|
"vibe scene styles",
|
|
467743
468344
|
"vibe scene install-skill",
|
|
467744
468345
|
"vibe scene compose-prompts",
|
|
467745
|
-
"vibe
|
|
468346
|
+
"vibe build",
|
|
467746
468347
|
"vibe scene lint",
|
|
467747
468348
|
"vibe scene render",
|
|
467748
468349
|
"vibe scene add"
|