@vibeframe/cli 0.112.1 → 0.113.2
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/CONTEXT.md +59 -10
- package/dist/commands/_shared/build-cache.d.ts +10 -1
- package/dist/commands/_shared/build-cache.test.d.ts +1 -0
- package/dist/commands/_shared/init-templates.d.ts +1 -1
- package/dist/commands/_shared/project-config.d.ts +1 -1
- package/dist/commands/_shared/render-inspect.d.ts +4 -1
- package/dist/commands/_shared/review-report.d.ts +50 -1
- package/dist/commands/_shared/review-report.test.d.ts +1 -0
- package/dist/commands/_shared/scene-build.d.ts +14 -1
- package/dist/commands/_shared/scene-html-emit.d.ts +4 -2
- package/dist/commands/_shared/scene-inspect.d.ts +2 -1
- package/dist/commands/_shared/scene-project.d.ts +1 -1
- package/dist/commands/_shared/status-jobs.d.ts +3 -0
- package/dist/commands/_shared/storyboard-edit.d.ts +15 -1
- package/dist/commands/_shared/storyboard-parse.d.ts +34 -0
- package/dist/commands/_shared/tts-resolve.d.ts +11 -7
- package/dist/commands/error-envelopes.test.d.ts +26 -0
- package/dist/commands/review-actions-cli.test.d.ts +1 -0
- package/dist/commands/scene.d.ts +12 -0
- package/dist/index.js +1667 -320
- package/dist/tools/_shared/elicit.d.ts +1 -1
- package/dist/tools/manifest/edit.d.ts +6 -6
- package/dist/tools/manifest/index.js +1742 -516
- package/dist/tools/manifest/scene.d.ts +6 -3
- package/dist/tools/manifest/storyboard.d.ts +3 -3
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -7066,8 +7066,8 @@ var init_api_keys = __esm({
|
|
|
7066
7066
|
envVar: "OPENAI_API_KEY",
|
|
7067
7067
|
label: "OpenAI",
|
|
7068
7068
|
showInSetup: true,
|
|
7069
|
-
setupDescription: "gpt-image-2 image gen ($, default since v0.56), Whisper transcribe, Agent",
|
|
7070
|
-
envExampleComment: "OpenAI API Key (Whisper transcription, gpt-image-2 \u2014 default text-to-image since v0.56)",
|
|
7069
|
+
setupDescription: "gpt-image-2 image gen ($, default since v0.56), gpt-4o-mini-tts narration, Whisper transcribe, Agent",
|
|
7070
|
+
envExampleComment: "OpenAI API Key (Whisper transcription, gpt-4o-mini-tts narration, gpt-image-2 \u2014 default text-to-image since v0.56)",
|
|
7071
7071
|
envExampleUrl: "https://platform.openai.com/api-keys",
|
|
7072
7072
|
keyFormat: { prefix: /^sk-/, example: "sk-..." }
|
|
7073
7073
|
});
|
|
@@ -9174,8 +9174,8 @@ var init_openai = __esm({
|
|
|
9174
9174
|
id: "openai",
|
|
9175
9175
|
label: "OpenAI",
|
|
9176
9176
|
apiKey: "openai",
|
|
9177
|
-
kinds: ["llm", "image", "transcription"],
|
|
9178
|
-
resolverPriority: { image: 1 },
|
|
9177
|
+
kinds: ["llm", "image", "transcription", "speech"],
|
|
9178
|
+
resolverPriority: { image: 1, speech: 2 },
|
|
9179
9179
|
commandsUnlocked: [
|
|
9180
9180
|
"agent -p openai",
|
|
9181
9181
|
"generate image -p openai",
|
|
@@ -11597,16 +11597,19 @@ var init_elevenlabs = __esm({
|
|
|
11597
11597
|
});
|
|
11598
11598
|
|
|
11599
11599
|
// ../ai-providers/dist/kokoro/KokoroProvider.js
|
|
11600
|
-
async function
|
|
11600
|
+
async function loadKokoroRuntime() {
|
|
11601
11601
|
if (factoryOverride)
|
|
11602
|
-
return factoryOverride;
|
|
11602
|
+
return { factory: factoryOverride, device: "cpu" };
|
|
11603
11603
|
try {
|
|
11604
11604
|
const mod = await import("kokoro-js");
|
|
11605
|
-
return mod.KokoroTTS;
|
|
11605
|
+
return { factory: mod.KokoroTTS, device: "cpu" };
|
|
11606
11606
|
} catch (err) {
|
|
11607
|
-
const
|
|
11608
|
-
if (
|
|
11609
|
-
return
|
|
11607
|
+
const workspace = await loadKokoroFromWorkspace();
|
|
11608
|
+
if (workspace)
|
|
11609
|
+
return { factory: workspace, device: "cpu" };
|
|
11610
|
+
const bundled = await loadBundledKokoroRuntime();
|
|
11611
|
+
if (bundled)
|
|
11612
|
+
return bundled;
|
|
11610
11613
|
throw mapKokoroImportError(err);
|
|
11611
11614
|
}
|
|
11612
11615
|
}
|
|
@@ -11626,10 +11629,51 @@ async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
|
|
|
11626
11629
|
return null;
|
|
11627
11630
|
}
|
|
11628
11631
|
}
|
|
11632
|
+
async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUNTIME) {
|
|
11633
|
+
const base = runtimeDir?.trim();
|
|
11634
|
+
if (!base)
|
|
11635
|
+
return null;
|
|
11636
|
+
try {
|
|
11637
|
+
const { readFile: readFile56, writeFile: writeFile61, mkdir: mkdir31 } = await import("node:fs/promises");
|
|
11638
|
+
const { join: join30, resolve: resolve86 } = await import("node:path");
|
|
11639
|
+
const { pathToFileURL } = await import("node:url");
|
|
11640
|
+
const { homedir: homedir8 } = await import("node:os");
|
|
11641
|
+
const transformersDir = resolve86(base, "node_modules", "@huggingface", "transformers");
|
|
11642
|
+
const ortDistDir = resolve86(base, "node_modules", "onnxruntime-web", "dist");
|
|
11643
|
+
const transformers = await import(pathToFileURL(join30(transformersDir, "dist", "transformers.web.js")).href);
|
|
11644
|
+
const cacheRoot = join30(homedir8(), ".cache", "vibeframe", "models");
|
|
11645
|
+
transformers.env.allowLocalModels = false;
|
|
11646
|
+
transformers.env.useCustomCache = true;
|
|
11647
|
+
transformers.env.customCache = {
|
|
11648
|
+
async match(key) {
|
|
11649
|
+
try {
|
|
11650
|
+
return new Response(await readFile56(join30(cacheRoot, encodeURIComponent(String(key)))));
|
|
11651
|
+
} catch {
|
|
11652
|
+
return void 0;
|
|
11653
|
+
}
|
|
11654
|
+
},
|
|
11655
|
+
async put(key, response) {
|
|
11656
|
+
try {
|
|
11657
|
+
const buf = Buffer.from(await response.arrayBuffer());
|
|
11658
|
+
await mkdir31(cacheRoot, { recursive: true });
|
|
11659
|
+
await writeFile61(join30(cacheRoot, encodeURIComponent(String(key))), buf);
|
|
11660
|
+
} catch {
|
|
11661
|
+
}
|
|
11662
|
+
}
|
|
11663
|
+
};
|
|
11664
|
+
transformers.env.backends.onnx.wasm.wasmPaths = pathToFileURL(ortDistDir + "/").href;
|
|
11665
|
+
const factory = await loadKokoroFromWorkspace(base);
|
|
11666
|
+
if (!factory)
|
|
11667
|
+
return null;
|
|
11668
|
+
return { factory, device: "cpu" };
|
|
11669
|
+
} catch {
|
|
11670
|
+
return null;
|
|
11671
|
+
}
|
|
11672
|
+
}
|
|
11629
11673
|
function mapKokoroImportError(err) {
|
|
11630
11674
|
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
11631
11675
|
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
|
|
11632
|
-
return new Error("Local Kokoro TTS is unavailable:
|
|
11676
|
+
return new Error("Local Kokoro TTS is unavailable: kokoro-js could not be loaded from the npm install, the workspace node_modules, or the bundled extension runtime. Set OPENAI_API_KEY or ELEVENLABS_API_KEY to use a cloud voice instead (ttsProvider: openai / elevenlabs), or install the engine locally with `npm i kokoro-js` in your workspace folder.");
|
|
11633
11677
|
}
|
|
11634
11678
|
return err instanceof Error ? err : new Error(String(err));
|
|
11635
11679
|
}
|
|
@@ -11637,10 +11681,12 @@ function loadModel(progress) {
|
|
|
11637
11681
|
if (modelPromise)
|
|
11638
11682
|
return modelPromise;
|
|
11639
11683
|
modelPromise = (async () => {
|
|
11640
|
-
const
|
|
11641
|
-
|
|
11684
|
+
const runtime = await loadKokoroRuntime();
|
|
11685
|
+
const override = process.env.VIBE_ONNX_DEVICE;
|
|
11686
|
+
const device = override === "cpu" || override === "wasm" ? override : runtime.device;
|
|
11687
|
+
return runtime.factory.from_pretrained(KOKORO_MODEL_ID, {
|
|
11642
11688
|
dtype: "q8",
|
|
11643
|
-
device
|
|
11689
|
+
device,
|
|
11644
11690
|
progress_callback: progress ? (raw) => progress(normaliseEvent(raw)) : void 0
|
|
11645
11691
|
});
|
|
11646
11692
|
})().catch((err) => {
|
|
@@ -11724,7 +11770,7 @@ var init_kokoro = __esm({
|
|
|
11724
11770
|
label: "Kokoro (local)",
|
|
11725
11771
|
apiKey: null,
|
|
11726
11772
|
kinds: ["speech"],
|
|
11727
|
-
resolverPriority: { speech:
|
|
11773
|
+
resolverPriority: { speech: 3 }
|
|
11728
11774
|
});
|
|
11729
11775
|
}
|
|
11730
11776
|
});
|
|
@@ -11996,12 +12042,119 @@ var init_openai_image = __esm({
|
|
|
11996
12042
|
}
|
|
11997
12043
|
});
|
|
11998
12044
|
|
|
12045
|
+
// ../ai-providers/dist/openai-tts/OpenAiTtsProvider.js
|
|
12046
|
+
var OPENAI_TTS_VOICES, DEFAULT_VOICE, DEFAULT_MODEL2, TTS_429_MAX_RETRIES2, TTS_429_RETRY_DELAY_MS2, OpenAiTtsProvider, openaiTtsProvider;
|
|
12047
|
+
var init_OpenAiTtsProvider = __esm({
|
|
12048
|
+
"../ai-providers/dist/openai-tts/OpenAiTtsProvider.js"() {
|
|
12049
|
+
"use strict";
|
|
12050
|
+
OPENAI_TTS_VOICES = [
|
|
12051
|
+
"alloy",
|
|
12052
|
+
"ash",
|
|
12053
|
+
"ballad",
|
|
12054
|
+
"cedar",
|
|
12055
|
+
"coral",
|
|
12056
|
+
"echo",
|
|
12057
|
+
"fable",
|
|
12058
|
+
"marin",
|
|
12059
|
+
"nova",
|
|
12060
|
+
"onyx",
|
|
12061
|
+
"sage",
|
|
12062
|
+
"shimmer",
|
|
12063
|
+
"verse"
|
|
12064
|
+
];
|
|
12065
|
+
DEFAULT_VOICE = "marin";
|
|
12066
|
+
DEFAULT_MODEL2 = "gpt-4o-mini-tts";
|
|
12067
|
+
TTS_429_MAX_RETRIES2 = 1;
|
|
12068
|
+
TTS_429_RETRY_DELAY_MS2 = 2e3;
|
|
12069
|
+
OpenAiTtsProvider = class {
|
|
12070
|
+
constructor() {
|
|
12071
|
+
this.id = "openai";
|
|
12072
|
+
this.name = "OpenAI TTS";
|
|
12073
|
+
this.description = "OpenAI cloud text-to-speech (gpt-4o-mini-tts)";
|
|
12074
|
+
this.capabilities = ["text-to-speech"];
|
|
12075
|
+
this.isAvailable = true;
|
|
12076
|
+
this.baseUrl = "https://api.openai.com/v1";
|
|
12077
|
+
}
|
|
12078
|
+
async initialize(config2) {
|
|
12079
|
+
this.apiKey = config2.apiKey;
|
|
12080
|
+
if (config2.baseUrl) {
|
|
12081
|
+
this.baseUrl = config2.baseUrl;
|
|
12082
|
+
}
|
|
12083
|
+
}
|
|
12084
|
+
isConfigured() {
|
|
12085
|
+
return !!this.apiKey;
|
|
12086
|
+
}
|
|
12087
|
+
async textToSpeech(text, options = {}) {
|
|
12088
|
+
if (!this.apiKey) {
|
|
12089
|
+
return { success: false, error: "OpenAI API key not configured" };
|
|
12090
|
+
}
|
|
12091
|
+
const voice = (options.voice ?? DEFAULT_VOICE).toLowerCase();
|
|
12092
|
+
if (!OPENAI_TTS_VOICES.includes(voice)) {
|
|
12093
|
+
return {
|
|
12094
|
+
success: false,
|
|
12095
|
+
error: `Unknown OpenAI voice "${options.voice}". Available voices: ${OPENAI_TTS_VOICES.join(", ")}.`
|
|
12096
|
+
};
|
|
12097
|
+
}
|
|
12098
|
+
try {
|
|
12099
|
+
let response;
|
|
12100
|
+
for (let attempt = 0; ; attempt++) {
|
|
12101
|
+
response = await fetch(`${this.baseUrl}/audio/speech`, {
|
|
12102
|
+
method: "POST",
|
|
12103
|
+
headers: {
|
|
12104
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
12105
|
+
"Content-Type": "application/json"
|
|
12106
|
+
},
|
|
12107
|
+
body: JSON.stringify({
|
|
12108
|
+
model: options.model ?? DEFAULT_MODEL2,
|
|
12109
|
+
input: text,
|
|
12110
|
+
voice,
|
|
12111
|
+
response_format: "mp3",
|
|
12112
|
+
...options.speed !== void 0 && { speed: options.speed },
|
|
12113
|
+
...options.instructions !== void 0 && { instructions: options.instructions }
|
|
12114
|
+
})
|
|
12115
|
+
});
|
|
12116
|
+
if (response.status === 429 && attempt < TTS_429_MAX_RETRIES2) {
|
|
12117
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, TTS_429_RETRY_DELAY_MS2 * (attempt + 1)));
|
|
12118
|
+
continue;
|
|
12119
|
+
}
|
|
12120
|
+
break;
|
|
12121
|
+
}
|
|
12122
|
+
if (!response.ok) {
|
|
12123
|
+
const error = await response.text();
|
|
12124
|
+
return { success: false, error: `OpenAI TTS failed: ${error}` };
|
|
12125
|
+
}
|
|
12126
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
12127
|
+
return {
|
|
12128
|
+
success: true,
|
|
12129
|
+
audioBuffer: Buffer.from(arrayBuffer),
|
|
12130
|
+
characterCount: text.length
|
|
12131
|
+
};
|
|
12132
|
+
} catch (error) {
|
|
12133
|
+
return {
|
|
12134
|
+
success: false,
|
|
12135
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
12136
|
+
};
|
|
12137
|
+
}
|
|
12138
|
+
}
|
|
12139
|
+
};
|
|
12140
|
+
openaiTtsProvider = new OpenAiTtsProvider();
|
|
12141
|
+
}
|
|
12142
|
+
});
|
|
12143
|
+
|
|
12144
|
+
// ../ai-providers/dist/openai-tts/index.js
|
|
12145
|
+
var init_openai_tts = __esm({
|
|
12146
|
+
"../ai-providers/dist/openai-tts/index.js"() {
|
|
12147
|
+
"use strict";
|
|
12148
|
+
init_OpenAiTtsProvider();
|
|
12149
|
+
}
|
|
12150
|
+
});
|
|
12151
|
+
|
|
11999
12152
|
// ../ai-providers/dist/runway/RunwayProvider.js
|
|
12000
|
-
var
|
|
12153
|
+
var DEFAULT_MODEL3, RunwayProvider, runwayProvider;
|
|
12001
12154
|
var init_RunwayProvider = __esm({
|
|
12002
12155
|
"../ai-providers/dist/runway/RunwayProvider.js"() {
|
|
12003
12156
|
"use strict";
|
|
12004
|
-
|
|
12157
|
+
DEFAULT_MODEL3 = "gen4.5";
|
|
12005
12158
|
RunwayProvider = class _RunwayProvider {
|
|
12006
12159
|
constructor() {
|
|
12007
12160
|
this.id = "runway";
|
|
@@ -12044,7 +12197,7 @@ var init_RunwayProvider = __esm({
|
|
|
12044
12197
|
"1:1": "960:960"
|
|
12045
12198
|
};
|
|
12046
12199
|
const apiRatio = ratioMap[options?.aspectRatio || "16:9"] || "1280:720";
|
|
12047
|
-
const model = options?.model ||
|
|
12200
|
+
const model = options?.model || DEFAULT_MODEL3;
|
|
12048
12201
|
if (!options?.referenceImage && model !== "gen4.5") {
|
|
12049
12202
|
return {
|
|
12050
12203
|
id: "",
|
|
@@ -12306,11 +12459,11 @@ var init_runway = __esm({
|
|
|
12306
12459
|
|
|
12307
12460
|
// ../ai-providers/dist/kling/KlingProvider.js
|
|
12308
12461
|
import { createHmac } from "node:crypto";
|
|
12309
|
-
var
|
|
12462
|
+
var DEFAULT_MODEL4, STD_MODE_MODELS, KlingProvider, klingProvider;
|
|
12310
12463
|
var init_KlingProvider = __esm({
|
|
12311
12464
|
"../ai-providers/dist/kling/KlingProvider.js"() {
|
|
12312
12465
|
"use strict";
|
|
12313
|
-
|
|
12466
|
+
DEFAULT_MODEL4 = "kling-v2-5-turbo";
|
|
12314
12467
|
STD_MODE_MODELS = ["kling-v2-5-turbo", "kling-v2-6", "kling-v3", "kling-v3-omni"];
|
|
12315
12468
|
KlingProvider = class {
|
|
12316
12469
|
constructor() {
|
|
@@ -12373,7 +12526,7 @@ var init_KlingProvider = __esm({
|
|
|
12373
12526
|
}
|
|
12374
12527
|
try {
|
|
12375
12528
|
const token = this.generateToken();
|
|
12376
|
-
const model = options?.model ||
|
|
12529
|
+
const model = options?.model || DEFAULT_MODEL4;
|
|
12377
12530
|
let mode = options?.mode || "std";
|
|
12378
12531
|
if (!STD_MODE_MODELS.includes(model) && mode === "std") {
|
|
12379
12532
|
mode = "pro";
|
|
@@ -12744,11 +12897,11 @@ var init_kling = __esm({
|
|
|
12744
12897
|
});
|
|
12745
12898
|
|
|
12746
12899
|
// ../ai-providers/dist/grok/GrokProvider.js
|
|
12747
|
-
var
|
|
12900
|
+
var DEFAULT_MODEL5, GrokProvider, grokProvider;
|
|
12748
12901
|
var init_GrokProvider = __esm({
|
|
12749
12902
|
"../ai-providers/dist/grok/GrokProvider.js"() {
|
|
12750
12903
|
"use strict";
|
|
12751
|
-
|
|
12904
|
+
DEFAULT_MODEL5 = "grok-imagine-video";
|
|
12752
12905
|
GrokProvider = class {
|
|
12753
12906
|
constructor() {
|
|
12754
12907
|
this.id = "grok";
|
|
@@ -12915,7 +13068,7 @@ var init_GrokProvider = __esm({
|
|
|
12915
13068
|
try {
|
|
12916
13069
|
const duration = Math.round(Math.min(15, Math.max(1, options?.duration || 5)));
|
|
12917
13070
|
const body = {
|
|
12918
|
-
model:
|
|
13071
|
+
model: DEFAULT_MODEL5,
|
|
12919
13072
|
prompt: prompt3,
|
|
12920
13073
|
duration,
|
|
12921
13074
|
aspect_ratio: options?.aspectRatio || "16:9"
|
|
@@ -17181,6 +17334,19 @@ var require_src = __commonJS({
|
|
|
17181
17334
|
});
|
|
17182
17335
|
|
|
17183
17336
|
// ../ai-providers/dist/fal/FalProvider.js
|
|
17337
|
+
function estimateSeedanceVideoCostUsd(opts) {
|
|
17338
|
+
const shortSide = SEEDANCE_SHORT_SIDE[opts.resolution ?? "720p"] ?? 720;
|
|
17339
|
+
const [a, b] = (opts.aspectRatio ?? "16:9").split(":").map(Number);
|
|
17340
|
+
const ratioFactor = a > 0 && b > 0 ? Math.max(a, b) / Math.min(a, b) : 16 / 9;
|
|
17341
|
+
const longSide = Math.round(shortSide * ratioFactor);
|
|
17342
|
+
const tokensPerSecond = shortSide * longSide * 24 / 1024;
|
|
17343
|
+
let usdPerSecond = tokensPerSecond / 1e3 * SEEDANCE_USD_PER_1K_TOKENS;
|
|
17344
|
+
if (opts.fast && shortSide <= 720)
|
|
17345
|
+
usdPerSecond *= SEEDANCE_FAST_FACTOR;
|
|
17346
|
+
if (opts.hasVideoReference)
|
|
17347
|
+
usdPerSecond *= SEEDANCE_VIDEO_REF_FACTOR;
|
|
17348
|
+
return Number((usdPerSecond * Math.max(0, opts.durationSec)).toFixed(2));
|
|
17349
|
+
}
|
|
17184
17350
|
function formatFalError(error) {
|
|
17185
17351
|
if (!(error instanceof Error))
|
|
17186
17352
|
return String(error);
|
|
@@ -17290,7 +17456,7 @@ function normaliseDuration(value) {
|
|
|
17290
17456
|
return "auto";
|
|
17291
17457
|
return Math.max(4, Math.min(15, Math.round(value)));
|
|
17292
17458
|
}
|
|
17293
|
-
var import_client, ENDPOINT_TEXT_TO_VIDEO, ENDPOINT_IMAGE_TO_VIDEO, ENDPOINT_REFERENCE_TO_VIDEO, DEFAULT_VARIANT, VALID_RESOLUTIONS, VALID_ASPECTS, FalProvider, falProvider;
|
|
17459
|
+
var import_client, ENDPOINT_TEXT_TO_VIDEO, ENDPOINT_IMAGE_TO_VIDEO, ENDPOINT_REFERENCE_TO_VIDEO, DEFAULT_VARIANT, VALID_RESOLUTIONS, VALID_ASPECTS, SEEDANCE_USD_PER_1K_TOKENS, SEEDANCE_FAST_FACTOR, SEEDANCE_VIDEO_REF_FACTOR, SEEDANCE_SHORT_SIDE, FalProvider, falProvider;
|
|
17294
17460
|
var init_FalProvider = __esm({
|
|
17295
17461
|
"../ai-providers/dist/fal/FalProvider.js"() {
|
|
17296
17462
|
"use strict";
|
|
@@ -17310,6 +17476,10 @@ var init_FalProvider = __esm({
|
|
|
17310
17476
|
DEFAULT_VARIANT = "seedance-2.0";
|
|
17311
17477
|
VALID_RESOLUTIONS = ["480p", "720p", "1080p"];
|
|
17312
17478
|
VALID_ASPECTS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "auto"];
|
|
17479
|
+
SEEDANCE_USD_PER_1K_TOKENS = 0.014;
|
|
17480
|
+
SEEDANCE_FAST_FACTOR = 0.8;
|
|
17481
|
+
SEEDANCE_VIDEO_REF_FACTOR = 0.6;
|
|
17482
|
+
SEEDANCE_SHORT_SIDE = { "480p": 480, "720p": 720, "1080p": 1080 };
|
|
17313
17483
|
FalProvider = class {
|
|
17314
17484
|
constructor() {
|
|
17315
17485
|
this.id = "seedance";
|
|
@@ -18122,9 +18292,11 @@ __export(dist_exports2, {
|
|
|
18122
18292
|
KOKORO_MODEL_ID: () => KOKORO_MODEL_ID,
|
|
18123
18293
|
KlingProvider: () => KlingProvider,
|
|
18124
18294
|
KokoroProvider: () => KokoroProvider,
|
|
18295
|
+
OPENAI_TTS_VOICES: () => OPENAI_TTS_VOICES,
|
|
18125
18296
|
OllamaProvider: () => OllamaProvider,
|
|
18126
18297
|
OpenAIImageProvider: () => OpenAIImageProvider,
|
|
18127
18298
|
OpenAIProvider: () => OpenAIProvider,
|
|
18299
|
+
OpenAiTtsProvider: () => OpenAiTtsProvider,
|
|
18128
18300
|
ReplicateProvider: () => ReplicateProvider,
|
|
18129
18301
|
RunwayProvider: () => RunwayProvider,
|
|
18130
18302
|
WhisperProvider: () => WhisperProvider,
|
|
@@ -18132,6 +18304,7 @@ __export(dist_exports2, {
|
|
|
18132
18304
|
defineApiKey: () => defineApiKey,
|
|
18133
18305
|
defineProvider: () => defineProvider,
|
|
18134
18306
|
elevenLabsProvider: () => elevenLabsProvider,
|
|
18307
|
+
estimateSeedanceVideoCostUsd: () => estimateSeedanceVideoCostUsd,
|
|
18135
18308
|
falProvider: () => falProvider,
|
|
18136
18309
|
geminiProvider: () => geminiProvider,
|
|
18137
18310
|
getAllApiKeys: () => getAllApiKeys,
|
|
@@ -18148,9 +18321,12 @@ __export(dist_exports2, {
|
|
|
18148
18321
|
isGeminiTextModelAlias: () => isGeminiTextModelAlias,
|
|
18149
18322
|
klingProvider: () => klingProvider,
|
|
18150
18323
|
kokoroProvider: () => kokoroProvider,
|
|
18324
|
+
loadBundledKokoroRuntime: () => loadBundledKokoroRuntime,
|
|
18325
|
+
loadKokoroFromWorkspace: () => loadKokoroFromWorkspace,
|
|
18151
18326
|
ollamaProvider: () => ollamaProvider,
|
|
18152
18327
|
openaiImageProvider: () => openaiImageProvider,
|
|
18153
18328
|
openaiProvider: () => openaiProvider,
|
|
18329
|
+
openaiTtsProvider: () => openaiTtsProvider,
|
|
18154
18330
|
providerRegistry: () => providerRegistry,
|
|
18155
18331
|
replicateProvider: () => replicateProvider,
|
|
18156
18332
|
resolveGeminiTextModel: () => resolveGeminiTextModel,
|
|
@@ -18173,6 +18349,7 @@ var init_dist2 = __esm({
|
|
|
18173
18349
|
init_elevenlabs();
|
|
18174
18350
|
init_kokoro();
|
|
18175
18351
|
init_openai_image();
|
|
18352
|
+
init_openai_tts();
|
|
18176
18353
|
init_runway();
|
|
18177
18354
|
init_kling();
|
|
18178
18355
|
init_grok();
|
|
@@ -26347,10 +26524,22 @@ var init_api_key = __esm({
|
|
|
26347
26524
|
|
|
26348
26525
|
// src/commands/_shared/tts-resolve.ts
|
|
26349
26526
|
async function resolveTtsProvider(preferred = "auto") {
|
|
26350
|
-
|
|
26527
|
+
let choice = preferred;
|
|
26528
|
+
if (choice === "auto") {
|
|
26529
|
+
if (await getConfiguredApiKey("ELEVENLABS_API_KEY")) {
|
|
26530
|
+
choice = "elevenlabs";
|
|
26531
|
+
} else if (await getConfiguredApiKey("OPENAI_API_KEY")) {
|
|
26532
|
+
choice = "openai";
|
|
26533
|
+
} else {
|
|
26534
|
+
choice = "kokoro";
|
|
26535
|
+
}
|
|
26536
|
+
}
|
|
26351
26537
|
if (choice === "elevenlabs") {
|
|
26352
26538
|
return buildElevenLabs();
|
|
26353
26539
|
}
|
|
26540
|
+
if (choice === "openai") {
|
|
26541
|
+
return buildOpenAi();
|
|
26542
|
+
}
|
|
26354
26543
|
return buildKokoro();
|
|
26355
26544
|
}
|
|
26356
26545
|
async function buildElevenLabs() {
|
|
@@ -26366,6 +26555,19 @@ async function buildElevenLabs() {
|
|
|
26366
26555
|
});
|
|
26367
26556
|
return { provider: "elevenlabs", audioExtension: "mp3", call };
|
|
26368
26557
|
}
|
|
26558
|
+
async function buildOpenAi() {
|
|
26559
|
+
const key = await getApiKey("OPENAI_API_KEY", "OpenAI");
|
|
26560
|
+
if (!key) {
|
|
26561
|
+
throw new TtsKeyMissingError("openai");
|
|
26562
|
+
}
|
|
26563
|
+
const provider = new OpenAiTtsProvider();
|
|
26564
|
+
await provider.initialize({ apiKey: key });
|
|
26565
|
+
const call = async (text, opts) => provider.textToSpeech(text, {
|
|
26566
|
+
voice: opts?.voice,
|
|
26567
|
+
speed: opts?.speed
|
|
26568
|
+
});
|
|
26569
|
+
return { provider: "openai", audioExtension: "mp3", call };
|
|
26570
|
+
}
|
|
26369
26571
|
async function buildKokoro() {
|
|
26370
26572
|
const provider = new KokoroProvider();
|
|
26371
26573
|
await provider.initialize({});
|
|
@@ -26378,11 +26580,11 @@ async function buildKokoro() {
|
|
|
26378
26580
|
}
|
|
26379
26581
|
function parseTtsProviderName(value) {
|
|
26380
26582
|
if (!value) return "auto";
|
|
26381
|
-
if (value === "auto" || value === "elevenlabs" || value === "kokoro") {
|
|
26583
|
+
if (value === "auto" || value === "elevenlabs" || value === "openai" || value === "kokoro") {
|
|
26382
26584
|
return value;
|
|
26383
26585
|
}
|
|
26384
26586
|
throw new Error(
|
|
26385
|
-
`Invalid --tts: ${value}. Valid: auto, elevenlabs, kokoro.`
|
|
26587
|
+
`Invalid --tts: ${value}. Valid: auto, elevenlabs, openai, kokoro.`
|
|
26386
26588
|
);
|
|
26387
26589
|
}
|
|
26388
26590
|
var TtsKeyMissingError;
|
|
@@ -26394,7 +26596,7 @@ var init_tts_resolve = __esm({
|
|
|
26394
26596
|
TtsKeyMissingError = class extends Error {
|
|
26395
26597
|
constructor(provider) {
|
|
26396
26598
|
super(
|
|
26397
|
-
provider === "elevenlabs" ? "ElevenLabs API key required (ELEVENLABS_API_KEY). Run 'vibe setup', set ELEVENLABS_API_KEY in .env, or pass --tts kokoro for local synthesis." : `Provider ${provider} is unavailable.`
|
|
26599
|
+
provider === "elevenlabs" ? "ElevenLabs API key required (ELEVENLABS_API_KEY). Run 'vibe setup', set ELEVENLABS_API_KEY in .env, or pass --tts kokoro for local synthesis." : provider === "openai" ? "OpenAI API key required (OPENAI_API_KEY). Run 'vibe setup', set OPENAI_API_KEY in .env, or pass --tts kokoro for local synthesis." : `Provider ${provider} is unavailable.`
|
|
26398
26600
|
);
|
|
26399
26601
|
this.provider = provider;
|
|
26400
26602
|
this.name = "TtsKeyMissingError";
|
|
@@ -26821,6 +27023,43 @@ vibe host setup all --write # write project/app config
|
|
|
26821
27023
|
vibe host doctor all --json # verify readiness
|
|
26822
27024
|
\`\`\`
|
|
26823
27025
|
|
|
27026
|
+
## Native host goal loop
|
|
27027
|
+
|
|
27028
|
+
For long-running work, use Codex Goal mode, Claude Code \`/goal\`, or the
|
|
27029
|
+
host's equivalent as the outer loop. VibeFrame should not run a competing
|
|
27030
|
+
project-level goal loop; it provides \`--json\` commands, dry runs, cost caps,
|
|
27031
|
+
\`build-report.json\`, \`review-report.json\`, \`nextActions\`, \`safeToAutoRun\`,
|
|
27032
|
+
\`requiresConfirmation\`, \`fixOwner\`, deterministic repair, and render
|
|
27033
|
+
inspection for the host agent to reason over.
|
|
27034
|
+
|
|
27035
|
+
Copy-paste examples:
|
|
27036
|
+
|
|
27037
|
+
\`\`\`text
|
|
27038
|
+
/goal Build this VibeFrame project into renders/final.mp4. Use --json for every
|
|
27039
|
+
vibe command, run --dry-run before paid operations, use --max-cost 5 for builds
|
|
27040
|
+
unless the user sets another budget, read build-report.json and
|
|
27041
|
+
review-report.json before deciding the next action, prefer nextActions before
|
|
27042
|
+
guessing, run only safeToAutoRun:true actions automatically, and ask before
|
|
27043
|
+
requiresConfirmation:true actions. Treat retryWith as the compatibility
|
|
27044
|
+
fallback, fixOwner:"vibe" as CLI repair work, and fixOwner:"host-agent" as
|
|
27045
|
+
STORYBOARD.md, DESIGN.md, or composition edits.
|
|
27046
|
+
Stop only when renders/final.mp4 exists, duration and aspect ratio match the
|
|
27047
|
+
brief, inspect render --cheap has no errors, any AI review score is >= 90 when AI review is requested, and every
|
|
27048
|
+
remaining host-agent issue is fixed, accepted with rationale, or reported as
|
|
27049
|
+
blocked.
|
|
27050
|
+
\`\`\`
|
|
27051
|
+
|
|
27052
|
+
\`\`\`text
|
|
27053
|
+
/goal Finish this VibeFrame render using Claude Code goal mode as the outer
|
|
27054
|
+
loop. Use vibe context/schema when unsure, --json everywhere, dry-run before
|
|
27055
|
+
paid operations, budget cap via --max-cost 5, nextActions before guessing,
|
|
27056
|
+
build-report.json/review-report.json as loop state, and safeToAutoRun,
|
|
27057
|
+
requiresConfirmation, and fixOwner to decide between vibe scene repair and
|
|
27058
|
+
host-agent edits. Stop only after final MP4,
|
|
27059
|
+
target duration/aspect ratio, clean render inspection, any AI review score >= 90 when AI review is requested, and
|
|
27060
|
+
no unresolved unacknowledged host-agent issues.
|
|
27061
|
+
\`\`\`
|
|
27062
|
+
|
|
26824
27063
|
## Skills \u2014 USE THESE FIRST
|
|
26825
27064
|
|
|
26826
27065
|
@SKILL.md
|
|
@@ -27316,6 +27555,9 @@ function clean(text) {
|
|
|
27316
27555
|
function humanise(id) {
|
|
27317
27556
|
return id.split(/[-_]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
27318
27557
|
}
|
|
27558
|
+
function sceneCompositionId(beatId) {
|
|
27559
|
+
return `scene-${beatId}`;
|
|
27560
|
+
}
|
|
27319
27561
|
function slugifySceneName(name) {
|
|
27320
27562
|
const normalised = name.normalize("NFKD").replace(/[̀-ͯ]/g, "");
|
|
27321
27563
|
const slug = normalised.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -27352,10 +27594,16 @@ function fitKineticFontSize(words) {
|
|
|
27352
27594
|
function kenBurnsTween(scope, dur, endScale = 1.08) {
|
|
27353
27595
|
return `tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: ${endScale}, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);`;
|
|
27354
27596
|
}
|
|
27597
|
+
function backdropMotionTweens(scope, dur, hasImage, endScale = 1.08) {
|
|
27598
|
+
const sweep = hasImage ? "" : `
|
|
27599
|
+
tl.fromTo('${scope} .ambient-sweep', { xPercent: -75, opacity: 0.18 }, { xPercent: 75, opacity: 0.62, duration: ${dur.toFixed(2)}, ease: 'sine.inOut' }, 0);`;
|
|
27600
|
+
return `${kenBurnsTween(scope, dur, endScale)}${sweep}`;
|
|
27601
|
+
}
|
|
27355
27602
|
function buildPreset(input) {
|
|
27356
27603
|
const id = input.id;
|
|
27357
27604
|
const scope = `[data-composition-id="${id}"]`;
|
|
27358
|
-
const
|
|
27605
|
+
const displayId = input.beatId ?? id;
|
|
27606
|
+
const headline = clean(input.headline) || humanise(displayId);
|
|
27359
27607
|
const subhead = clean(input.subhead);
|
|
27360
27608
|
const kicker = clean(input.kicker);
|
|
27361
27609
|
const dur = input.duration;
|
|
@@ -27378,8 +27626,17 @@ function buildPreset(input) {
|
|
|
27378
27626
|
position: absolute;
|
|
27379
27627
|
inset: 0;
|
|
27380
27628
|
background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 70%);
|
|
27629
|
+
overflow: hidden;
|
|
27630
|
+
}
|
|
27631
|
+
${scope} .ambient-sweep {
|
|
27632
|
+
position: absolute;
|
|
27633
|
+
inset: -22%;
|
|
27634
|
+
display: block;
|
|
27635
|
+
background: linear-gradient(115deg, rgba(255,255,255,0) 20%, rgba(0,201,255,0.34) 48%, rgba(142,45,226,0.26) 58%, rgba(255,255,255,0) 78%);
|
|
27636
|
+
filter: blur(12px);
|
|
27637
|
+
transform: rotate(8deg);
|
|
27381
27638
|
}`;
|
|
27382
|
-
const backdropMarkup = `<div class="backdrop"></div>`;
|
|
27639
|
+
const backdropMarkup = hasImage ? `<div class="backdrop"></div>` : `<div class="backdrop"><div class="ambient-sweep"></div></div>`;
|
|
27383
27640
|
switch (input.preset) {
|
|
27384
27641
|
case "simple": {
|
|
27385
27642
|
const transcript = input.transcript;
|
|
@@ -27411,7 +27668,7 @@ function buildPreset(input) {
|
|
|
27411
27668
|
body: `${backdropMarkup}
|
|
27412
27669
|
<div class="caption" id="caption">${captionInner}</div>`,
|
|
27413
27670
|
timeline: `${crossfadeTweens(scope, dur)}
|
|
27414
|
-
${
|
|
27671
|
+
${backdropMotionTweens(scope, dur, hasImage)}
|
|
27415
27672
|
${captionTween}`
|
|
27416
27673
|
};
|
|
27417
27674
|
}
|
|
@@ -27450,12 +27707,12 @@ function buildPreset(input) {
|
|
|
27450
27707
|
// scene's own fade-in handles the visual transition. Backdrop
|
|
27451
27708
|
// Ken-Burns fills what used to be 80% dead static frame.
|
|
27452
27709
|
timeline: `${crossfadeTweens(scope, dur)}
|
|
27453
|
-
${
|
|
27710
|
+
${backdropMotionTweens(scope, dur, hasImage)}
|
|
27454
27711
|
tl.from('${scope} #headline', { opacity: 0, scale: 0.92, duration: 0.55, ease: 'power3.out' }, 0.05);`
|
|
27455
27712
|
};
|
|
27456
27713
|
}
|
|
27457
27714
|
case "explainer": {
|
|
27458
|
-
const k = kicker || humanise(
|
|
27715
|
+
const k = kicker || humanise(displayId).toUpperCase();
|
|
27459
27716
|
const sub = subhead || "";
|
|
27460
27717
|
const transcript = input.transcript;
|
|
27461
27718
|
const useWordSync = !!(transcript && transcript.length > 0 && sub);
|
|
@@ -27497,7 +27754,7 @@ function buildPreset(input) {
|
|
|
27497
27754
|
<div class="subtitle" id="subtitle">${subtitleInner}</div>` : ""}
|
|
27498
27755
|
</div>`,
|
|
27499
27756
|
timeline: `${crossfadeTweens(scope, dur)}
|
|
27500
|
-
${
|
|
27757
|
+
${backdropMotionTweens(scope, dur, hasImage)}
|
|
27501
27758
|
tl.from('${scope} #kicker', { opacity: 0, y: 16, duration: 0.4, ease: 'power2.out' }, 0.1);
|
|
27502
27759
|
tl.from('${scope} #title', { opacity: 0, y: 60, duration: 0.7, ease: 'power3.out' }, 0.25);
|
|
27503
27760
|
${subtitleTween}`
|
|
@@ -27538,12 +27795,12 @@ function buildPreset(input) {
|
|
|
27538
27795
|
body: `${backdropMarkup}
|
|
27539
27796
|
<div class="kinetic">${wordSpans}</div>`,
|
|
27540
27797
|
timeline: `${crossfadeTweens(scope, dur)}
|
|
27541
|
-
${
|
|
27798
|
+
${backdropMotionTweens(scope, dur, hasImage)}
|
|
27542
27799
|
${tweens}`
|
|
27543
27800
|
};
|
|
27544
27801
|
}
|
|
27545
27802
|
case "product-shot": {
|
|
27546
|
-
const label = kicker || humanise(
|
|
27803
|
+
const label = kicker || humanise(displayId);
|
|
27547
27804
|
const headlineFontPx = fitFontSize(headline, 72, 40, 50);
|
|
27548
27805
|
return {
|
|
27549
27806
|
css: `${scope} {
|
|
@@ -27577,7 +27834,7 @@ function buildPreset(input) {
|
|
|
27577
27834
|
<div class="product-headline" id="headline">${esc(headline)}</div>${subhead ? `
|
|
27578
27835
|
<div class="product-sub" id="subhead">${esc(subhead)}</div>` : ""}`,
|
|
27579
27836
|
timeline: `${crossfadeTweens(scope, dur)}
|
|
27580
|
-
|
|
27837
|
+
${backdropMotionTweens(scope, dur, hasImage, 1.12)}
|
|
27581
27838
|
tl.from('${scope} #label', { opacity: 0, x: -30, duration: 0.5, ease: 'power3.out' }, 0.2);
|
|
27582
27839
|
tl.from('${scope} #headline', { opacity: 0, y: 40, duration: 0.6, ease: 'power3.out' }, 0.4);${subhead ? `
|
|
27583
27840
|
tl.from('${scope} #subhead', { opacity: 0, y: 20, duration: 0.5, ease: 'power3.out' }, 0.65);` : ""}`
|
|
@@ -27638,9 +27895,10 @@ function emitSceneHtml(input) {
|
|
|
27638
27895
|
if (input.width <= 0 || input.height <= 0) {
|
|
27639
27896
|
throw new Error(`Invalid canvas dims: ${input.width}x${input.height}`);
|
|
27640
27897
|
}
|
|
27641
|
-
const
|
|
27898
|
+
const beatId = input.id;
|
|
27899
|
+
const id = sceneCompositionId(beatId);
|
|
27642
27900
|
const dur = Number(input.duration.toFixed(3));
|
|
27643
|
-
const parts = buildPreset({ ...input, id, duration: dur });
|
|
27901
|
+
const parts = buildPreset({ ...input, id, beatId, duration: dur });
|
|
27644
27902
|
const audioBlock = input.audioPath ? `
|
|
27645
27903
|
<audio
|
|
27646
27904
|
id="narration"
|
|
@@ -27656,7 +27914,7 @@ function emitSceneHtml(input) {
|
|
|
27656
27914
|
${lottieLayer.markup}` : "";
|
|
27657
27915
|
const lottieScript = lottieLayer ? `
|
|
27658
27916
|
${lottieLayer.script}` : "";
|
|
27659
|
-
return `<template id="
|
|
27917
|
+
return `<template id="${id}-template">
|
|
27660
27918
|
<div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input.width}" data-height="${input.height}">
|
|
27661
27919
|
<style>
|
|
27662
27920
|
${parts.css}
|
|
@@ -27692,8 +27950,9 @@ function buildClipReference(opts) {
|
|
|
27692
27950
|
const duration = Number(opts.duration.toFixed(3));
|
|
27693
27951
|
const track = opts.trackIndex ?? 1;
|
|
27694
27952
|
const src = opts.src ?? `compositions/scene-${opts.id}.html`;
|
|
27953
|
+
const compositionId = sceneCompositionId(opts.id);
|
|
27695
27954
|
const zIndex = Math.max(1, 1e6 - Math.floor(start * 1e3));
|
|
27696
|
-
return `<div class="clip" data-composition-id="${esc(
|
|
27955
|
+
return `<div class="clip" data-composition-id="${esc(compositionId)}" data-composition-src="${esc(src)}" data-start="${start}" data-duration="${duration}" data-track-index="${track}" style="z-index: ${zIndex};"></div>`;
|
|
27697
27956
|
}
|
|
27698
27957
|
function insertClipIntoRoot(rootHtml, clip) {
|
|
27699
27958
|
const clipDiv = buildClipReference(clip);
|
|
@@ -29171,6 +29430,39 @@ var init_install_skill = __esm({
|
|
|
29171
29430
|
});
|
|
29172
29431
|
|
|
29173
29432
|
// src/commands/_shared/storyboard-parse.ts
|
|
29433
|
+
function resolveCharacters(frontmatter) {
|
|
29434
|
+
const pool = frontmatter?.characters;
|
|
29435
|
+
if (!pool || typeof pool !== "object") return [];
|
|
29436
|
+
const resolved = [];
|
|
29437
|
+
for (const [name, spec] of Object.entries(pool)) {
|
|
29438
|
+
if (typeof spec === "string") {
|
|
29439
|
+
const prompt3 = spec.trim();
|
|
29440
|
+
if (prompt3) resolved.push({ name, prompt: prompt3 });
|
|
29441
|
+
continue;
|
|
29442
|
+
}
|
|
29443
|
+
if (spec && typeof spec === "object") {
|
|
29444
|
+
const image = typeof spec.image === "string" ? spec.image.trim() : "";
|
|
29445
|
+
if (image) {
|
|
29446
|
+
resolved.push({ name, imagePath: image });
|
|
29447
|
+
continue;
|
|
29448
|
+
}
|
|
29449
|
+
const description = typeof spec.description === "string" ? spec.description.trim() : "";
|
|
29450
|
+
if (description) resolved.push({ name, prompt: description });
|
|
29451
|
+
}
|
|
29452
|
+
}
|
|
29453
|
+
return resolved;
|
|
29454
|
+
}
|
|
29455
|
+
function beatCharacterNames(cues) {
|
|
29456
|
+
const raw = cues?.characters;
|
|
29457
|
+
if (typeof raw === "string") {
|
|
29458
|
+
const name = raw.trim();
|
|
29459
|
+
return name ? [name] : [];
|
|
29460
|
+
}
|
|
29461
|
+
if (Array.isArray(raw)) {
|
|
29462
|
+
return raw.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim());
|
|
29463
|
+
}
|
|
29464
|
+
return [];
|
|
29465
|
+
}
|
|
29174
29466
|
function parseStoryboard(md) {
|
|
29175
29467
|
const normalized = md.replace(/\r\n/g, "\n");
|
|
29176
29468
|
const { frontmatter, remaining } = extractProjectFrontmatter(normalized);
|
|
@@ -29776,7 +30068,15 @@ ${finalDurationBullet}
|
|
|
29776
30068
|
itself. For multi-phase beats, drive phase changes with GSAP \`autoAlpha\`
|
|
29777
30069
|
inside full-window clips: animate phase A out (\`autoAlpha: 0\`), then
|
|
29778
30070
|
phase B in, on the same timeline.
|
|
29779
|
-
- If \`assets/
|
|
30071
|
+
- If \`assets/video-${ctx.beat.id}.mp4\` exists, it is an AI-generated clip for
|
|
30072
|
+
this beat \u2014 use it as the FULL-FRAME visual and prefer it over any backdrop
|
|
30073
|
+
image or CSS. Put one full-bleed \`<video>\` inside a full-window \`.clip\`:
|
|
30074
|
+
\`<video src="assets/video-${ctx.beat.id}.mp4" data-start="0" data-duration="${BEAT_DURATION}" data-media-start="0" muted playsinline style="width:100%;height:100%;object-fit:cover"></video>\`
|
|
30075
|
+
Use the exact path (no \`./\` or \`../\`). The framework seeks the clip to each
|
|
30076
|
+
timeline frame, so do NOT add transform tweens to the \`<video>\` or its
|
|
30077
|
+
ancestors; layer text/motion graphics on top instead.
|
|
30078
|
+
- If \`assets/backdrop-${ctx.beat.id}.png\` exists (and no video clip above),
|
|
30079
|
+
use that local file as the
|
|
29780
30080
|
full-frame visual backdrop. The exact path string is
|
|
29781
30081
|
\`assets/backdrop-${ctx.beat.id}.png\`; do NOT prefix it with \`../\` or \`./\`
|
|
29782
30082
|
because fragments are mounted from the project root.
|
|
@@ -30694,6 +30994,101 @@ function uniqueRetryWith(items) {
|
|
|
30694
30994
|
...new Set(items.filter((item) => typeof item === "string" && item.length > 0))
|
|
30695
30995
|
];
|
|
30696
30996
|
}
|
|
30997
|
+
function commandReviewAction(command, opts) {
|
|
30998
|
+
const classified = classifyReviewCommand(command);
|
|
30999
|
+
return stripUndefined({
|
|
31000
|
+
id: actionId("command", command),
|
|
31001
|
+
kind: "command",
|
|
31002
|
+
label: opts.label ?? command,
|
|
31003
|
+
command,
|
|
31004
|
+
fixOwner: opts.fixOwner ?? classified.fixOwner,
|
|
31005
|
+
costTier: opts.costTier ?? classified.costTier,
|
|
31006
|
+
safeToAutoRun: opts.safeToAutoRun ?? classified.safeToAutoRun,
|
|
31007
|
+
requiresConfirmation: opts.requiresConfirmation ?? classified.requiresConfirmation,
|
|
31008
|
+
reason: opts.reason,
|
|
31009
|
+
sourceIssueCodes: uniqueStrings(opts.sourceIssueCodes ?? [])
|
|
31010
|
+
});
|
|
31011
|
+
}
|
|
31012
|
+
function agentReviewAction(opts) {
|
|
31013
|
+
return stripUndefined({
|
|
31014
|
+
id: actionId("agent", opts.agentPrompt),
|
|
31015
|
+
kind: "agent",
|
|
31016
|
+
label: opts.label,
|
|
31017
|
+
agentPrompt: opts.agentPrompt,
|
|
31018
|
+
fixOwner: "host-agent",
|
|
31019
|
+
costTier: "unknown",
|
|
31020
|
+
safeToAutoRun: false,
|
|
31021
|
+
requiresConfirmation: false,
|
|
31022
|
+
reason: opts.reason,
|
|
31023
|
+
sourceIssueCodes: uniqueStrings(opts.sourceIssueCodes ?? [])
|
|
31024
|
+
});
|
|
31025
|
+
}
|
|
31026
|
+
function manualReviewAction(opts) {
|
|
31027
|
+
return stripUndefined({
|
|
31028
|
+
id: actionId("manual", opts.label),
|
|
31029
|
+
kind: "manual",
|
|
31030
|
+
label: opts.label,
|
|
31031
|
+
fixOwner: opts.fixOwner ?? "host-agent",
|
|
31032
|
+
costTier: "unknown",
|
|
31033
|
+
safeToAutoRun: false,
|
|
31034
|
+
requiresConfirmation: false,
|
|
31035
|
+
reason: opts.reason,
|
|
31036
|
+
sourceIssueCodes: uniqueStrings(opts.sourceIssueCodes ?? [])
|
|
31037
|
+
});
|
|
31038
|
+
}
|
|
31039
|
+
function reviewActionsFromRetryWith(retryWith, sourceIssueCodes = []) {
|
|
31040
|
+
return normalizeReviewActions(
|
|
31041
|
+
retryWith.map((item) => {
|
|
31042
|
+
if (/^codex\s+/i.test(item)) {
|
|
31043
|
+
return agentReviewAction({
|
|
31044
|
+
label: "Ask the host agent to fix review issues",
|
|
31045
|
+
agentPrompt: item,
|
|
31046
|
+
reason: "The review report contains host-agent-owned issues.",
|
|
31047
|
+
sourceIssueCodes
|
|
31048
|
+
});
|
|
31049
|
+
}
|
|
31050
|
+
return commandReviewAction(item, {
|
|
31051
|
+
reason: "Backward-compatible retryWith command.",
|
|
31052
|
+
sourceIssueCodes
|
|
31053
|
+
});
|
|
31054
|
+
})
|
|
31055
|
+
);
|
|
31056
|
+
}
|
|
31057
|
+
function normalizeReviewActions(actions) {
|
|
31058
|
+
const merged = /* @__PURE__ */ new Map();
|
|
31059
|
+
for (const action of actions) {
|
|
31060
|
+
const key = [action.kind, action.command ?? action.agentPrompt ?? action.label].join("\0");
|
|
31061
|
+
const existing = merged.get(key);
|
|
31062
|
+
if (!existing) {
|
|
31063
|
+
merged.set(key, {
|
|
31064
|
+
...action,
|
|
31065
|
+
sourceIssueCodes: uniqueStrings(action.sourceIssueCodes ?? [])
|
|
31066
|
+
});
|
|
31067
|
+
continue;
|
|
31068
|
+
}
|
|
31069
|
+
merged.set(key, {
|
|
31070
|
+
...existing,
|
|
31071
|
+
safeToAutoRun: existing.safeToAutoRun && action.safeToAutoRun,
|
|
31072
|
+
requiresConfirmation: existing.requiresConfirmation || action.requiresConfirmation,
|
|
31073
|
+
sourceIssueCodes: uniqueStrings([
|
|
31074
|
+
...existing.sourceIssueCodes ?? [],
|
|
31075
|
+
...action.sourceIssueCodes ?? []
|
|
31076
|
+
])
|
|
31077
|
+
});
|
|
31078
|
+
}
|
|
31079
|
+
return [...merged.values()].map(
|
|
31080
|
+
(action) => stripUndefined({
|
|
31081
|
+
...action,
|
|
31082
|
+
sourceIssueCodes: action.sourceIssueCodes && action.sourceIssueCodes.length > 0 ? action.sourceIssueCodes : void 0
|
|
31083
|
+
})
|
|
31084
|
+
);
|
|
31085
|
+
}
|
|
31086
|
+
function deriveNextReviewActions(opts) {
|
|
31087
|
+
return normalizeReviewActions([
|
|
31088
|
+
...opts.issues.flatMap((issue) => issue.actions ?? []),
|
|
31089
|
+
...reviewActionsFromRetryWith(opts.retryWith)
|
|
31090
|
+
]);
|
|
31091
|
+
}
|
|
30697
31092
|
function fixOwnerForIssue(issue) {
|
|
30698
31093
|
if (issue.fixOwner) return issue.fixOwner;
|
|
30699
31094
|
if (issue.code.startsWith("AI_REVIEW_")) return "host-agent";
|
|
@@ -30703,11 +31098,23 @@ function fixOwnerForIssue(issue) {
|
|
|
30703
31098
|
}
|
|
30704
31099
|
return "vibe";
|
|
30705
31100
|
}
|
|
30706
|
-
function normalizeReviewIssues(issues,
|
|
30707
|
-
|
|
30708
|
-
|
|
30709
|
-
|
|
30710
|
-
|
|
31101
|
+
function normalizeReviewIssues(issues, opts) {
|
|
31102
|
+
const fallbackOwner = typeof opts === "string" ? opts : opts?.fallbackOwner;
|
|
31103
|
+
const projectDir = typeof opts === "string" ? void 0 : opts?.projectDir;
|
|
31104
|
+
return issues.map((issue) => {
|
|
31105
|
+
const normalized = {
|
|
31106
|
+
...issue,
|
|
31107
|
+
fixOwner: issue.fixOwner ?? fallbackOwner ?? fixOwnerForIssue(issue)
|
|
31108
|
+
};
|
|
31109
|
+
const actions = normalizeReviewActions([
|
|
31110
|
+
...issue.actions ?? [],
|
|
31111
|
+
...projectDir ? defaultActionsForIssue(normalized, projectDir) : []
|
|
31112
|
+
]);
|
|
31113
|
+
return stripUndefined({
|
|
31114
|
+
...normalized,
|
|
31115
|
+
actions: actions.length > 0 ? actions : void 0
|
|
31116
|
+
});
|
|
31117
|
+
});
|
|
30711
31118
|
}
|
|
30712
31119
|
function summarizeReviewIssues(issues) {
|
|
30713
31120
|
const normalized = normalizeReviewIssues(issues);
|
|
@@ -30723,7 +31130,11 @@ function summarizeReviewIssues(issues) {
|
|
|
30723
31130
|
};
|
|
30724
31131
|
}
|
|
30725
31132
|
function buildReviewReport(opts) {
|
|
30726
|
-
const
|
|
31133
|
+
const retryWith = uniqueRetryWith(opts.retryWith);
|
|
31134
|
+
const issues = normalizeReviewIssues(opts.issues, {
|
|
31135
|
+
projectDir: opts.project,
|
|
31136
|
+
retryWith
|
|
31137
|
+
});
|
|
30727
31138
|
return stripUndefined({
|
|
30728
31139
|
schemaVersion: "1",
|
|
30729
31140
|
kind: "review",
|
|
@@ -30734,7 +31145,8 @@ function buildReviewReport(opts) {
|
|
|
30734
31145
|
score: opts.score,
|
|
30735
31146
|
issues,
|
|
30736
31147
|
summary: summarizeReviewIssues(issues),
|
|
30737
|
-
retryWith:
|
|
31148
|
+
nextActions: deriveNextReviewActions({ issues, retryWith, projectDir: opts.project }),
|
|
31149
|
+
retryWith,
|
|
30738
31150
|
sourceReports: opts.sourceReports ?? [],
|
|
30739
31151
|
reportPath: opts.reportPath
|
|
30740
31152
|
});
|
|
@@ -30748,6 +31160,306 @@ async function writeReviewReport(path2, report) {
|
|
|
30748
31160
|
function stripUndefined(value) {
|
|
30749
31161
|
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
|
|
30750
31162
|
}
|
|
31163
|
+
function defaultActionsForIssue(issue, projectDir) {
|
|
31164
|
+
const code = issue.code;
|
|
31165
|
+
const beat = issue.beatId ?? issue.scene;
|
|
31166
|
+
const beatFlag = beat ? ` --beat ${beat}` : "";
|
|
31167
|
+
const sourceIssueCodes = [code];
|
|
31168
|
+
if (code === "PROJECT_NOT_FOUND" || code === "MISSING_STORYBOARD") {
|
|
31169
|
+
return [
|
|
31170
|
+
commandReviewAction(`vibe init ${projectDir} --from "<brief>" --json`, {
|
|
31171
|
+
label: "Initialize the project from a brief",
|
|
31172
|
+
fixOwner: "host-agent",
|
|
31173
|
+
reason: "The project cannot be inspected until the core project files exist.",
|
|
31174
|
+
sourceIssueCodes
|
|
31175
|
+
})
|
|
31176
|
+
];
|
|
31177
|
+
}
|
|
31178
|
+
if (code === "MISSING_DESIGN" || code === "DESIGN_PLACEHOLDER_FIELD") {
|
|
31179
|
+
return [
|
|
31180
|
+
manualReviewAction({
|
|
31181
|
+
label: "Create or complete DESIGN.md",
|
|
31182
|
+
reason: "Visual direction needs host-agent or human input before VibeFrame can repair it.",
|
|
31183
|
+
sourceIssueCodes
|
|
31184
|
+
})
|
|
31185
|
+
];
|
|
31186
|
+
}
|
|
31187
|
+
if (code === "BEAT_NOT_FOUND" || code.startsWith("STORYBOARD_")) {
|
|
31188
|
+
const actions = [
|
|
31189
|
+
commandReviewAction(`vibe storyboard validate ${projectDir} --json`, {
|
|
31190
|
+
label: "Validate the storyboard",
|
|
31191
|
+
fixOwner: "host-agent",
|
|
31192
|
+
reason: "The storyboard issue needs structured validation before editing.",
|
|
31193
|
+
sourceIssueCodes
|
|
31194
|
+
})
|
|
31195
|
+
];
|
|
31196
|
+
if (code === "STORYBOARD_PLACEHOLDER_CUE") {
|
|
31197
|
+
actions.push(
|
|
31198
|
+
commandReviewAction(
|
|
31199
|
+
`vibe storyboard revise ${projectDir} --from "<make cues concrete>" --dry-run --json`,
|
|
31200
|
+
{
|
|
31201
|
+
label: "Preview a storyboard revision",
|
|
31202
|
+
fixOwner: "host-agent",
|
|
31203
|
+
reason: "Placeholder cues should be revised into concrete video intent.",
|
|
31204
|
+
sourceIssueCodes
|
|
31205
|
+
}
|
|
31206
|
+
)
|
|
31207
|
+
);
|
|
31208
|
+
}
|
|
31209
|
+
return actions;
|
|
31210
|
+
}
|
|
31211
|
+
if (code === "MISSING_ROOT_COMPOSITION" || code === "ROOT_SYNC_CHECK_FAILED") {
|
|
31212
|
+
return [
|
|
31213
|
+
commandReviewAction(`vibe build ${projectDir} --stage sync --json`, {
|
|
31214
|
+
label: "Sync the project root composition",
|
|
31215
|
+
reason: "The root composition is missing or could not be verified.",
|
|
31216
|
+
sourceIssueCodes
|
|
31217
|
+
})
|
|
31218
|
+
];
|
|
31219
|
+
}
|
|
31220
|
+
if (code.startsWith("ROOT_")) {
|
|
31221
|
+
return issue.fixOwner === "host-agent" ? [
|
|
31222
|
+
agentReviewAction({
|
|
31223
|
+
label: "Repair the root composition shell",
|
|
31224
|
+
agentPrompt: "Fix host-agent-owned root composition issues from review-report.json",
|
|
31225
|
+
reason: "The root composition cannot be repaired deterministically.",
|
|
31226
|
+
sourceIssueCodes
|
|
31227
|
+
})
|
|
31228
|
+
] : [
|
|
31229
|
+
commandReviewAction(`vibe scene repair ${projectDir} --json`, {
|
|
31230
|
+
label: "Repair deterministic scene/root issues",
|
|
31231
|
+
reason: "The root composition has deterministic sync issues.",
|
|
31232
|
+
sourceIssueCodes
|
|
31233
|
+
}),
|
|
31234
|
+
commandReviewAction(`vibe build ${projectDir} --stage sync --json`, {
|
|
31235
|
+
label: "Resync root composition after repair",
|
|
31236
|
+
reason: "The root composition should be synced after deterministic repair.",
|
|
31237
|
+
sourceIssueCodes
|
|
31238
|
+
})
|
|
31239
|
+
];
|
|
31240
|
+
}
|
|
31241
|
+
if (code === "MISSING_COMPOSITION") {
|
|
31242
|
+
return [
|
|
31243
|
+
commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage compose --json`, {
|
|
31244
|
+
label: beat ? `Compose beat ${beat}` : "Compose missing scenes",
|
|
31245
|
+
reason: "Missing scene compositions must be generated or authored before render.",
|
|
31246
|
+
sourceIssueCodes
|
|
31247
|
+
})
|
|
31248
|
+
];
|
|
31249
|
+
}
|
|
31250
|
+
if (code === "MISSING_BUILD_REPORT") {
|
|
31251
|
+
return [
|
|
31252
|
+
commandReviewAction(`vibe build ${projectDir} --dry-run --json`, {
|
|
31253
|
+
label: "Preview the build plan",
|
|
31254
|
+
reason: "A dry-run build report is needed before choosing a paid or mutating build step.",
|
|
31255
|
+
sourceIssueCodes
|
|
31256
|
+
})
|
|
31257
|
+
];
|
|
31258
|
+
}
|
|
31259
|
+
if (code === "BUILD_REPORT_BEAT_MISSING") {
|
|
31260
|
+
return [
|
|
31261
|
+
commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage sync --json`, {
|
|
31262
|
+
label: beat ? `Sync build report for beat ${beat}` : "Sync the build report",
|
|
31263
|
+
reason: "The build report does not include the inspected beat.",
|
|
31264
|
+
sourceIssueCodes
|
|
31265
|
+
})
|
|
31266
|
+
];
|
|
31267
|
+
}
|
|
31268
|
+
if (code === "MALFORMED_BUILD_REPORT") {
|
|
31269
|
+
return [
|
|
31270
|
+
commandReviewAction(`vibe build ${projectDir} --json`, {
|
|
31271
|
+
label: "Regenerate the build report",
|
|
31272
|
+
reason: "The existing build report cannot be parsed.",
|
|
31273
|
+
sourceIssueCodes
|
|
31274
|
+
})
|
|
31275
|
+
];
|
|
31276
|
+
}
|
|
31277
|
+
if (code === "MISSING_REPORTED_ASSET" || code === "STALE_ASSET" || code === "UNKNOWN_ASSET_FRESHNESS") {
|
|
31278
|
+
return [
|
|
31279
|
+
commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage assets --force --json`, {
|
|
31280
|
+
label: beat ? `Regenerate assets for beat ${beat}` : "Regenerate stale or missing assets",
|
|
31281
|
+
reason: "Asset generation may call providers, so the host should confirm first.",
|
|
31282
|
+
sourceIssueCodes
|
|
31283
|
+
})
|
|
31284
|
+
];
|
|
31285
|
+
}
|
|
31286
|
+
if (code === "MUSIC_CUE_NOT_READY") {
|
|
31287
|
+
return [
|
|
31288
|
+
commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage assets --json`, {
|
|
31289
|
+
label: beat ? `Generate music for beat ${beat}` : "Generate missing music assets",
|
|
31290
|
+
reason: "Music generation may call providers, so the host should confirm first.",
|
|
31291
|
+
sourceIssueCodes
|
|
31292
|
+
}),
|
|
31293
|
+
commandReviewAction(`vibe build ${projectDir} --stage sync --json`, {
|
|
31294
|
+
label: "Sync music into the root composition",
|
|
31295
|
+
reason: "Generated music needs root composition wiring.",
|
|
31296
|
+
sourceIssueCodes
|
|
31297
|
+
})
|
|
31298
|
+
];
|
|
31299
|
+
}
|
|
31300
|
+
if (code.startsWith("SCENE_LINT_")) {
|
|
31301
|
+
return [
|
|
31302
|
+
commandReviewAction(`vibe scene repair ${projectDir} --json`, {
|
|
31303
|
+
label: "Repair deterministic scene lint issues",
|
|
31304
|
+
reason: "Scene lint reported an issue VibeFrame may be able to repair mechanically.",
|
|
31305
|
+
sourceIssueCodes
|
|
31306
|
+
})
|
|
31307
|
+
];
|
|
31308
|
+
}
|
|
31309
|
+
if (code === "RENDER_NOT_FOUND" || code === "EMPTY_RENDER") {
|
|
31310
|
+
return [
|
|
31311
|
+
commandReviewAction(
|
|
31312
|
+
beat ? `vibe render ${projectDir} --beat ${beat} --json` : `vibe render ${projectDir} --json`,
|
|
31313
|
+
{
|
|
31314
|
+
label: beat ? `Render beat ${beat}` : "Render the project",
|
|
31315
|
+
reason: "A render file is required before render inspection can pass.",
|
|
31316
|
+
sourceIssueCodes
|
|
31317
|
+
}
|
|
31318
|
+
)
|
|
31319
|
+
];
|
|
31320
|
+
}
|
|
31321
|
+
if (code === "NO_AUDIO_STREAM" || code === "DURATION_DRIFT") {
|
|
31322
|
+
return [
|
|
31323
|
+
commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage sync --json`, {
|
|
31324
|
+
label: beat ? `Sync timing/audio for beat ${beat}` : "Sync timing and audio wiring",
|
|
31325
|
+
reason: "The render output no longer matches synced project state.",
|
|
31326
|
+
sourceIssueCodes
|
|
31327
|
+
}),
|
|
31328
|
+
commandReviewAction(
|
|
31329
|
+
beat ? `vibe render ${projectDir} --beat ${beat} --json` : `vibe render ${projectDir} --json`,
|
|
31330
|
+
{
|
|
31331
|
+
label: beat ? `Rerender beat ${beat}` : "Rerender the project",
|
|
31332
|
+
reason: "The render should be regenerated after sync repair.",
|
|
31333
|
+
sourceIssueCodes
|
|
31334
|
+
}
|
|
31335
|
+
)
|
|
31336
|
+
];
|
|
31337
|
+
}
|
|
31338
|
+
if (code === "BLACK_FRAME_SEGMENT") {
|
|
31339
|
+
return issue.fixOwner === "host-agent" ? [
|
|
31340
|
+
agentReviewAction({
|
|
31341
|
+
label: beat ? `Fix black frames in beat ${beat}` : "Fix black frame segment",
|
|
31342
|
+
agentPrompt: "Fix black-frame issues from review-report.json, then rerender.",
|
|
31343
|
+
reason: "The issue likely needs scene, storyboard, or visual cue changes.",
|
|
31344
|
+
sourceIssueCodes
|
|
31345
|
+
})
|
|
31346
|
+
] : [
|
|
31347
|
+
commandReviewAction(`vibe scene repair ${projectDir} --json`, {
|
|
31348
|
+
label: "Repair deterministic scene timing",
|
|
31349
|
+
reason: "Black frames outside a specific beat may be recoverable with scene repair.",
|
|
31350
|
+
sourceIssueCodes
|
|
31351
|
+
})
|
|
31352
|
+
];
|
|
31353
|
+
}
|
|
31354
|
+
if (code === "STATIC_FRAME_SEGMENT") {
|
|
31355
|
+
return [
|
|
31356
|
+
agentReviewAction({
|
|
31357
|
+
label: beat ? `Add motion to beat ${beat}` : "Fix static visual hold",
|
|
31358
|
+
agentPrompt: "Fix static-frame issues from review-report.json, then rerender.",
|
|
31359
|
+
reason: "Static segments usually need creative scene, storyboard, or asset changes.",
|
|
31360
|
+
sourceIssueCodes
|
|
31361
|
+
})
|
|
31362
|
+
];
|
|
31363
|
+
}
|
|
31364
|
+
if (code === "LONG_SILENCE") {
|
|
31365
|
+
return issue.fixOwner === "host-agent" ? [
|
|
31366
|
+
agentReviewAction({
|
|
31367
|
+
label: beat ? `Fix long silence in beat ${beat}` : "Fix long silence",
|
|
31368
|
+
agentPrompt: "Fix long-silence issues from review-report.json, then rerender.",
|
|
31369
|
+
reason: "The beat duration, narration, or music cue needs host-agent judgment.",
|
|
31370
|
+
sourceIssueCodes
|
|
31371
|
+
})
|
|
31372
|
+
] : [
|
|
31373
|
+
commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage sync --json`, {
|
|
31374
|
+
label: beat ? `Sync audio for beat ${beat}` : "Sync audio wiring",
|
|
31375
|
+
reason: "The silence may be caused by stale narration or music wiring.",
|
|
31376
|
+
sourceIssueCodes
|
|
31377
|
+
})
|
|
31378
|
+
];
|
|
31379
|
+
}
|
|
31380
|
+
if (code.startsWith("AI_REVIEW_")) {
|
|
31381
|
+
return [
|
|
31382
|
+
agentReviewAction({
|
|
31383
|
+
label: "Fix AI review findings",
|
|
31384
|
+
agentPrompt: "Fix host-agent-owned AI review issues from review-report.json, then rerender.",
|
|
31385
|
+
reason: "AI review findings require storyboard, design, or composition judgment.",
|
|
31386
|
+
sourceIssueCodes
|
|
31387
|
+
})
|
|
31388
|
+
];
|
|
31389
|
+
}
|
|
31390
|
+
if (code === "FFPROBE_UNAVAILABLE" || code === "FFMPEG_UNAVAILABLE") {
|
|
31391
|
+
return [
|
|
31392
|
+
manualReviewAction({
|
|
31393
|
+
label: "Install FFmpeg and rerun inspection",
|
|
31394
|
+
reason: "Local media inspection depends on ffmpeg/ffprobe being available.",
|
|
31395
|
+
fixOwner: "host-agent",
|
|
31396
|
+
sourceIssueCodes
|
|
31397
|
+
})
|
|
31398
|
+
];
|
|
31399
|
+
}
|
|
31400
|
+
if (code === "FFPROBE_FAILED" || code === "NO_VIDEO_STREAM") {
|
|
31401
|
+
return [
|
|
31402
|
+
commandReviewAction(
|
|
31403
|
+
beat ? `vibe render ${projectDir} --beat ${beat} --json` : `vibe render ${projectDir} --json`,
|
|
31404
|
+
{
|
|
31405
|
+
label: beat ? `Rerender beat ${beat}` : "Rerender the project",
|
|
31406
|
+
reason: "The rendered media could not be probed as a valid video.",
|
|
31407
|
+
sourceIssueCodes
|
|
31408
|
+
}
|
|
31409
|
+
)
|
|
31410
|
+
];
|
|
31411
|
+
}
|
|
31412
|
+
if (code === "ASPECT_MISMATCH") {
|
|
31413
|
+
return [
|
|
31414
|
+
manualReviewAction({
|
|
31415
|
+
label: "Check project aspect and render settings",
|
|
31416
|
+
reason: "Aspect mismatches can come from either config intent or render settings.",
|
|
31417
|
+
fixOwner: issue.fixOwner,
|
|
31418
|
+
sourceIssueCodes
|
|
31419
|
+
})
|
|
31420
|
+
];
|
|
31421
|
+
}
|
|
31422
|
+
return [];
|
|
31423
|
+
}
|
|
31424
|
+
function classifyReviewCommand(command) {
|
|
31425
|
+
const lower = command.trim().toLowerCase();
|
|
31426
|
+
if (lower.startsWith("vibe status ")) return safe("free");
|
|
31427
|
+
if (lower.startsWith("vibe scene repair")) return safe("free");
|
|
31428
|
+
if (lower.startsWith("vibe storyboard validate") || lower.startsWith("vibe storyboard get") || lower.startsWith("vibe storyboard list")) {
|
|
31429
|
+
return safe("free", "host-agent");
|
|
31430
|
+
}
|
|
31431
|
+
if (lower.startsWith("vibe inspect project")) return safe("free");
|
|
31432
|
+
if (lower.startsWith("vibe inspect render")) {
|
|
31433
|
+
if (lower.includes("--ai")) return unsafe("low");
|
|
31434
|
+
return safe("free");
|
|
31435
|
+
}
|
|
31436
|
+
if (lower.startsWith("vibe render ")) return safe("free");
|
|
31437
|
+
if (lower.startsWith("vibe build ")) {
|
|
31438
|
+
if (lower.includes("--dry-run")) return safe("free");
|
|
31439
|
+
if (lower.includes("--stage sync") || lower.includes("--stage render")) return safe("free");
|
|
31440
|
+
return unsafe("unknown");
|
|
31441
|
+
}
|
|
31442
|
+
if (lower.startsWith("vibe generate video") || lower.startsWith("vibe edit fill-gaps") || lower.startsWith("vibe remix ")) {
|
|
31443
|
+
return unsafe("very-high");
|
|
31444
|
+
}
|
|
31445
|
+
if (lower.startsWith("vibe storyboard revise")) return unsafe("unknown", "host-agent");
|
|
31446
|
+
return unsafe("unknown");
|
|
31447
|
+
}
|
|
31448
|
+
function safe(costTier, fixOwner = "vibe") {
|
|
31449
|
+
return { fixOwner, costTier, safeToAutoRun: true, requiresConfirmation: false };
|
|
31450
|
+
}
|
|
31451
|
+
function unsafe(costTier, fixOwner = "vibe") {
|
|
31452
|
+
return { fixOwner, costTier, safeToAutoRun: false, requiresConfirmation: true };
|
|
31453
|
+
}
|
|
31454
|
+
function actionId(kind, value) {
|
|
31455
|
+
const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
|
|
31456
|
+
return `${kind}:${slug || "action"}`;
|
|
31457
|
+
}
|
|
31458
|
+
function uniqueStrings(items) {
|
|
31459
|
+
return [
|
|
31460
|
+
...new Set(items.filter((item) => typeof item === "string" && item.length > 0))
|
|
31461
|
+
];
|
|
31462
|
+
}
|
|
30751
31463
|
var init_review_report = __esm({
|
|
30752
31464
|
"src/commands/_shared/review-report.ts"() {
|
|
30753
31465
|
"use strict";
|
|
@@ -30880,6 +31592,297 @@ var init_scene_repair = __esm({
|
|
|
30880
31592
|
}
|
|
30881
31593
|
});
|
|
30882
31594
|
|
|
31595
|
+
// src/commands/_shared/storyboard-edit.ts
|
|
31596
|
+
function validateStoryboardMarkdown(markdown) {
|
|
31597
|
+
const parsed = parseStoryboard(markdown);
|
|
31598
|
+
const sections = splitBeatSections(markdown);
|
|
31599
|
+
const issues = [];
|
|
31600
|
+
if (parsed.beats.length === 0) {
|
|
31601
|
+
issues.push({
|
|
31602
|
+
severity: "error",
|
|
31603
|
+
code: "NO_BEATS",
|
|
31604
|
+
message: "STORYBOARD.md must contain at least one `## Beat ...` heading."
|
|
31605
|
+
});
|
|
31606
|
+
}
|
|
31607
|
+
const seen = /* @__PURE__ */ new Map();
|
|
31608
|
+
for (const beat of parsed.beats) {
|
|
31609
|
+
seen.set(beat.id, (seen.get(beat.id) ?? 0) + 1);
|
|
31610
|
+
}
|
|
31611
|
+
for (const [id, count] of seen.entries()) {
|
|
31612
|
+
if (count > 1) {
|
|
31613
|
+
issues.push({
|
|
31614
|
+
severity: "error",
|
|
31615
|
+
code: "DUPLICATE_BEAT_ID",
|
|
31616
|
+
beatId: id,
|
|
31617
|
+
message: `Beat id "${id}" appears ${count} times. Beat ids must be unique.`
|
|
31618
|
+
});
|
|
31619
|
+
}
|
|
31620
|
+
}
|
|
31621
|
+
for (const section of sections) {
|
|
31622
|
+
const cueBlock = readLeadingCueBlock(section.body);
|
|
31623
|
+
if (cueBlock?.error) {
|
|
31624
|
+
issues.push({
|
|
31625
|
+
severity: "error",
|
|
31626
|
+
code: "MALFORMED_CUE_YAML",
|
|
31627
|
+
beatId: section.id,
|
|
31628
|
+
message: `Beat "${section.id}" has malformed YAML cues: ${cueBlock.error}`
|
|
31629
|
+
});
|
|
31630
|
+
}
|
|
31631
|
+
}
|
|
31632
|
+
const characterPool = new Set(
|
|
31633
|
+
Object.keys(
|
|
31634
|
+
parsed.frontmatter?.characters ?? {}
|
|
31635
|
+
)
|
|
31636
|
+
);
|
|
31637
|
+
for (const beat of parsed.beats) {
|
|
31638
|
+
const cues = beat.cues ?? {};
|
|
31639
|
+
for (const [key, value] of Object.entries(cues)) {
|
|
31640
|
+
if (!ALLOWED_CUE_KEYS.has(key)) {
|
|
31641
|
+
issues.push({
|
|
31642
|
+
severity: "warning",
|
|
31643
|
+
code: "UNKNOWN_CUE",
|
|
31644
|
+
beatId: beat.id,
|
|
31645
|
+
message: `Beat "${beat.id}" uses unknown cue "${key}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`
|
|
31646
|
+
});
|
|
31647
|
+
continue;
|
|
31648
|
+
}
|
|
31649
|
+
if (key === "characters") {
|
|
31650
|
+
const validShape = typeof value === "string" || Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
31651
|
+
if (!validShape) {
|
|
31652
|
+
issues.push({
|
|
31653
|
+
severity: "error",
|
|
31654
|
+
code: "INVALID_CHARACTERS_VALUE",
|
|
31655
|
+
beatId: beat.id,
|
|
31656
|
+
message: `Beat "${beat.id}" cue "characters" must be a character name or a list of names.`
|
|
31657
|
+
});
|
|
31658
|
+
continue;
|
|
31659
|
+
}
|
|
31660
|
+
for (const name of beatCharacterNames({ characters: value })) {
|
|
31661
|
+
if (!characterPool.has(name)) {
|
|
31662
|
+
issues.push({
|
|
31663
|
+
severity: "warning",
|
|
31664
|
+
code: "UNKNOWN_CHARACTER",
|
|
31665
|
+
beatId: beat.id,
|
|
31666
|
+
message: `Beat "${beat.id}" references character "${name}" not defined in the frontmatter \`characters:\` pool.`
|
|
31667
|
+
});
|
|
31668
|
+
}
|
|
31669
|
+
}
|
|
31670
|
+
continue;
|
|
31671
|
+
}
|
|
31672
|
+
if (key === "duration") {
|
|
31673
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
31674
|
+
issues.push({
|
|
31675
|
+
severity: "error",
|
|
31676
|
+
code: "INVALID_DURATION",
|
|
31677
|
+
beatId: beat.id,
|
|
31678
|
+
message: `Beat "${beat.id}" cue "duration" must be a positive number of seconds.`
|
|
31679
|
+
});
|
|
31680
|
+
}
|
|
31681
|
+
continue;
|
|
31682
|
+
}
|
|
31683
|
+
if (STRING_CUE_KEYS.has(key) && typeof value !== "string") {
|
|
31684
|
+
issues.push({
|
|
31685
|
+
severity: "error",
|
|
31686
|
+
code: "INVALID_CUE_VALUE",
|
|
31687
|
+
beatId: beat.id,
|
|
31688
|
+
message: `Beat "${beat.id}" cue "${key}" must be a string.`
|
|
31689
|
+
});
|
|
31690
|
+
}
|
|
31691
|
+
}
|
|
31692
|
+
}
|
|
31693
|
+
for (const beat of parsed.beats) {
|
|
31694
|
+
if (beat.duration !== void 0 && beat.duration > MAX_RECOMMENDED_BEAT_SEC) {
|
|
31695
|
+
issues.push({
|
|
31696
|
+
severity: "warning",
|
|
31697
|
+
code: "BEAT_DURATION_TOO_LONG",
|
|
31698
|
+
beatId: beat.id,
|
|
31699
|
+
message: `Beat "${beat.id}" is ${beat.duration}s \u2014 beats longer than ${MAX_RECOMMENDED_BEAT_SEC}s render static and overstuffed. Split it into 6-15s beats (a 90s video should have 6-8 beats).`
|
|
31700
|
+
});
|
|
31701
|
+
}
|
|
31702
|
+
}
|
|
31703
|
+
return {
|
|
31704
|
+
ok: !issues.some((i) => i.severity === "error"),
|
|
31705
|
+
beats: parsed.beats,
|
|
31706
|
+
issues
|
|
31707
|
+
};
|
|
31708
|
+
}
|
|
31709
|
+
function getStoryboardBeat(markdown, beatId) {
|
|
31710
|
+
return parseStoryboard(markdown).beats.find((beat) => beat.id === beatId) ?? null;
|
|
31711
|
+
}
|
|
31712
|
+
function upsertStoryboardBeat(markdown, opts) {
|
|
31713
|
+
const beatId = normalizeBeatId(opts.beatId);
|
|
31714
|
+
const sections = splitBeatSections(markdown);
|
|
31715
|
+
const existing = sections.find((section2) => section2.id === beatId);
|
|
31716
|
+
const cues = storyboardCuesFromInput(opts);
|
|
31717
|
+
if (existing) {
|
|
31718
|
+
let next = markdown;
|
|
31719
|
+
for (const [key, value] of Object.entries(cues)) {
|
|
31720
|
+
next = setStoryboardCue(next, { beatId, key, value });
|
|
31721
|
+
}
|
|
31722
|
+
return { markdown: next, action: "updated" };
|
|
31723
|
+
}
|
|
31724
|
+
const section = buildStoryboardBeatSection({ ...opts, beatId, cues });
|
|
31725
|
+
if (sections.length > 0 && isStarterStoryboard(markdown)) {
|
|
31726
|
+
const global2 = markdown.slice(0, sections[0].start).trimEnd();
|
|
31727
|
+
return {
|
|
31728
|
+
markdown: `${global2}
|
|
31729
|
+
|
|
31730
|
+
${section}`,
|
|
31731
|
+
action: "replaced-starter"
|
|
31732
|
+
};
|
|
31733
|
+
}
|
|
31734
|
+
const prefix = markdown.trimEnd();
|
|
31735
|
+
return {
|
|
31736
|
+
markdown: `${prefix}${prefix ? "\n\n" : ""}${section}`,
|
|
31737
|
+
action: "appended"
|
|
31738
|
+
};
|
|
31739
|
+
}
|
|
31740
|
+
function setStoryboardCue(markdown, opts) {
|
|
31741
|
+
const section = findBeatSection(markdown, opts.beatId);
|
|
31742
|
+
if (!section) {
|
|
31743
|
+
throw new Error(`Beat "${opts.beatId}" not found.`);
|
|
31744
|
+
}
|
|
31745
|
+
if (!ALLOWED_CUE_KEYS.has(opts.key)) {
|
|
31746
|
+
throw new Error(`Unsupported cue "${opts.key}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`);
|
|
31747
|
+
}
|
|
31748
|
+
const cue = readLeadingCueBlock(section.body);
|
|
31749
|
+
if (cue?.error) {
|
|
31750
|
+
throw new Error(`Cannot edit malformed YAML cues for beat "${opts.beatId}": ${cue.error}`);
|
|
31751
|
+
}
|
|
31752
|
+
const cues = cue?.value ? { ...cue.value } : {};
|
|
31753
|
+
if (opts.unset) {
|
|
31754
|
+
delete cues[opts.key];
|
|
31755
|
+
} else {
|
|
31756
|
+
cues[opts.key] = normalizeCueValue(opts.key, opts.value);
|
|
31757
|
+
}
|
|
31758
|
+
const remainingBody = cue ? section.body.slice(cue.full.length).replace(/^\s*\n/, "") : section.body.replace(/^\s*\n/, "");
|
|
31759
|
+
const cueBlock = Object.keys(cues).length > 0 ? "```yaml\n" + (0, import_yaml5.stringify)(cues, { lineWidth: 0 }).trimEnd() + "\n```\n\n" : "";
|
|
31760
|
+
const nextBody = cueBlock + remainingBody.trimStart();
|
|
31761
|
+
const nextSection = section.raw.slice(0, section.headingEnd - section.start) + "\n\n" + nextBody.trimEnd() + "\n";
|
|
31762
|
+
return markdown.slice(0, section.start) + nextSection + markdown.slice(section.end);
|
|
31763
|
+
}
|
|
31764
|
+
function moveStoryboardBeat(markdown, opts) {
|
|
31765
|
+
if (opts.beatId === opts.afterBeatId) return markdown;
|
|
31766
|
+
const sections = splitBeatSections(markdown);
|
|
31767
|
+
const movingIndex = sections.findIndex((section) => section.id === opts.beatId);
|
|
31768
|
+
if (movingIndex === -1) throw new Error(`Beat "${opts.beatId}" not found.`);
|
|
31769
|
+
const afterIndex = sections.findIndex((section) => section.id === opts.afterBeatId);
|
|
31770
|
+
if (afterIndex === -1) throw new Error(`Beat "${opts.afterBeatId}" not found.`);
|
|
31771
|
+
const global2 = sections.length > 0 ? markdown.slice(0, sections[0].start) : markdown;
|
|
31772
|
+
const chunks = sections.map((section) => section.raw);
|
|
31773
|
+
const [moving] = chunks.splice(movingIndex, 1);
|
|
31774
|
+
const adjustedAfterIndex = movingIndex < afterIndex ? afterIndex - 1 : afterIndex;
|
|
31775
|
+
chunks.splice(adjustedAfterIndex + 1, 0, moving);
|
|
31776
|
+
return global2 + normalizeBeatChunks(chunks);
|
|
31777
|
+
}
|
|
31778
|
+
function normalizeBeatChunks(chunks) {
|
|
31779
|
+
return chunks.map((chunk) => chunk.trim()).filter(Boolean).join("\n\n") + "\n";
|
|
31780
|
+
}
|
|
31781
|
+
function normalizeBeatId(id) {
|
|
31782
|
+
return deriveBeatId(id);
|
|
31783
|
+
}
|
|
31784
|
+
function storyboardCuesFromInput(opts) {
|
|
31785
|
+
const cues = {};
|
|
31786
|
+
if (opts.duration !== void 0) cues.duration = opts.duration;
|
|
31787
|
+
if (opts.narration && opts.narration.trim()) cues.narration = opts.narration.trim();
|
|
31788
|
+
if (opts.backdrop && opts.backdrop.trim()) cues.backdrop = opts.backdrop.trim();
|
|
31789
|
+
return cues;
|
|
31790
|
+
}
|
|
31791
|
+
function buildStoryboardBeatSection(opts) {
|
|
31792
|
+
const title = normalizeTitle(opts.title ?? humanizeBeatId(opts.beatId));
|
|
31793
|
+
const cueBlock = Object.keys(opts.cues).length > 0 ? "```yaml\n" + (0, import_yaml5.stringify)(opts.cues, { lineWidth: 0 }).trimEnd() + "\n```\n\n" : "";
|
|
31794
|
+
const body = opts.body?.trim() || "Scene created with `vibe scene add`. Edit this body if the build flow should recompose it.";
|
|
31795
|
+
return `## Beat ${opts.beatId} - ${title}
|
|
31796
|
+
|
|
31797
|
+
${cueBlock}${body.trimEnd()}
|
|
31798
|
+
`;
|
|
31799
|
+
}
|
|
31800
|
+
function normalizeTitle(value) {
|
|
31801
|
+
return value.replace(/\s+/g, " ").trim() || "Scene";
|
|
31802
|
+
}
|
|
31803
|
+
function humanizeBeatId(id) {
|
|
31804
|
+
return id.split(/[-_]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ") || "Scene";
|
|
31805
|
+
}
|
|
31806
|
+
function isStarterStoryboard(markdown) {
|
|
31807
|
+
const parsed = parseStoryboard(markdown);
|
|
31808
|
+
if (parsed.beats.length !== 3) return false;
|
|
31809
|
+
const ids = parsed.beats.map((beat) => beat.id);
|
|
31810
|
+
if (ids.join(",") !== "hook,proof,close") return false;
|
|
31811
|
+
return markdown.includes("Introduce the promise in one crisp sentence.") && markdown.includes("Show the mechanism or proof point that makes the promise believable.") && markdown.includes("Close with the action the viewer should remember.");
|
|
31812
|
+
}
|
|
31813
|
+
function normalizeCueValue(key, value) {
|
|
31814
|
+
if (key === "duration") {
|
|
31815
|
+
const n = typeof value === "number" ? value : Number.parseFloat(String(value ?? ""));
|
|
31816
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
31817
|
+
throw new Error("Cue duration must be a positive number.");
|
|
31818
|
+
}
|
|
31819
|
+
return n;
|
|
31820
|
+
}
|
|
31821
|
+
if (typeof value !== "string") return value;
|
|
31822
|
+
return value.trim();
|
|
31823
|
+
}
|
|
31824
|
+
function splitBeatSections(markdown) {
|
|
31825
|
+
const normalized = markdown.replace(/\r\n/g, "\n");
|
|
31826
|
+
const headings = [];
|
|
31827
|
+
HEADING_RE2.lastIndex = 0;
|
|
31828
|
+
let match;
|
|
31829
|
+
while ((match = HEADING_RE2.exec(normalized)) !== null) {
|
|
31830
|
+
headings.push({ start: match.index, end: match.index + match[0].length, line: match[1].trim() });
|
|
31831
|
+
}
|
|
31832
|
+
return headings.map((heading, index) => {
|
|
31833
|
+
const end = index + 1 < headings.length ? headings[index + 1].start : normalized.length;
|
|
31834
|
+
return {
|
|
31835
|
+
id: deriveBeatId(heading.line),
|
|
31836
|
+
heading: heading.line,
|
|
31837
|
+
start: heading.start,
|
|
31838
|
+
headingEnd: heading.end,
|
|
31839
|
+
end,
|
|
31840
|
+
raw: normalized.slice(heading.start, end),
|
|
31841
|
+
body: normalized.slice(heading.end, end).trim()
|
|
31842
|
+
};
|
|
31843
|
+
});
|
|
31844
|
+
}
|
|
31845
|
+
function findBeatSection(markdown, beatId) {
|
|
31846
|
+
return splitBeatSections(markdown).find((section) => section.id === beatId) ?? null;
|
|
31847
|
+
}
|
|
31848
|
+
function readLeadingCueBlock(body) {
|
|
31849
|
+
const match = body.match(LEADING_CUE_RE);
|
|
31850
|
+
if (!match) return null;
|
|
31851
|
+
try {
|
|
31852
|
+
const parsed = (0, import_yaml5.parse)(match[2]);
|
|
31853
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
31854
|
+
return { full: match[0], error: "cue block must parse to a YAML object" };
|
|
31855
|
+
}
|
|
31856
|
+
return { full: match[0], value: parsed };
|
|
31857
|
+
} catch (error) {
|
|
31858
|
+
return { full: match[0], error: error instanceof Error ? error.message : String(error) };
|
|
31859
|
+
}
|
|
31860
|
+
}
|
|
31861
|
+
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS, MAX_RECOMMENDED_BEAT_SEC;
|
|
31862
|
+
var init_storyboard_edit = __esm({
|
|
31863
|
+
"src/commands/_shared/storyboard-edit.ts"() {
|
|
31864
|
+
"use strict";
|
|
31865
|
+
import_yaml5 = __toESM(require_dist2(), 1);
|
|
31866
|
+
init_storyboard_parse();
|
|
31867
|
+
STORYBOARD_CUE_KEYS = [
|
|
31868
|
+
"duration",
|
|
31869
|
+
"narration",
|
|
31870
|
+
"backdrop",
|
|
31871
|
+
"video",
|
|
31872
|
+
"motion",
|
|
31873
|
+
"voice",
|
|
31874
|
+
"music",
|
|
31875
|
+
"asset",
|
|
31876
|
+
"characters"
|
|
31877
|
+
];
|
|
31878
|
+
HEADING_RE2 = /^##\s+(.+?)\s*$/gm;
|
|
31879
|
+
LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
|
|
31880
|
+
ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
|
|
31881
|
+
STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
|
|
31882
|
+
MAX_RECOMMENDED_BEAT_SEC = 15;
|
|
31883
|
+
}
|
|
31884
|
+
});
|
|
31885
|
+
|
|
30883
31886
|
// src/commands/scene.ts
|
|
30884
31887
|
import { resolve as resolve17, relative as relative6, dirname as dirname4 } from "node:path";
|
|
30885
31888
|
import { mkdir as mkdir7, readFile as readFile13, writeFile as writeFile12, access as access5, copyFile } from "node:fs/promises";
|
|
@@ -30929,6 +31932,21 @@ async function loadVibeProjectConfig(projectDir) {
|
|
|
30929
31932
|
}
|
|
30930
31933
|
};
|
|
30931
31934
|
}
|
|
31935
|
+
function escapeRegExp3(value) {
|
|
31936
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31937
|
+
}
|
|
31938
|
+
function readClipStart(rootHtml, beatId) {
|
|
31939
|
+
const compositionId = sceneCompositionId(beatId);
|
|
31940
|
+
const match = rootHtml.match(
|
|
31941
|
+
new RegExp(
|
|
31942
|
+
`<div\\s+class="clip"[^>]*\\bdata-composition-id="${escapeRegExp3(compositionId)}"[^>]*\\bdata-start="([\\d.]+)"`,
|
|
31943
|
+
"i"
|
|
31944
|
+
)
|
|
31945
|
+
);
|
|
31946
|
+
if (!match) return void 0;
|
|
31947
|
+
const start = Number.parseFloat(match[1]);
|
|
31948
|
+
return Number.isFinite(start) ? start : void 0;
|
|
31949
|
+
}
|
|
30932
31950
|
async function resolveNarrationText(value) {
|
|
30933
31951
|
if (!value) return void 0;
|
|
30934
31952
|
const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
|
|
@@ -30955,6 +31973,7 @@ async function executeSceneAdd(opts) {
|
|
|
30955
31973
|
const id = slugifySceneName(opts.name);
|
|
30956
31974
|
const sceneRel = `compositions/scene-${id}.html`;
|
|
30957
31975
|
const scenePath = resolve17(projectDir, sceneRel);
|
|
31976
|
+
const storyboardAbsPath = resolve17(projectDir, "STORYBOARD.md");
|
|
30958
31977
|
const errResult = (error) => ({
|
|
30959
31978
|
success: false,
|
|
30960
31979
|
id,
|
|
@@ -31013,7 +32032,7 @@ async function executeSceneAdd(opts) {
|
|
|
31013
32032
|
throw error;
|
|
31014
32033
|
}
|
|
31015
32034
|
opts.onProgress?.(
|
|
31016
|
-
resolution.provider === "kokoro" ? "Generating narration with Kokoro (local \u2014 first run downloads ~
|
|
32035
|
+
resolution.provider === "kokoro" ? "Generating narration with Kokoro (local \u2014 first run downloads a ~90MB model)..." : resolution.provider === "openai" ? "Generating narration with OpenAI (gpt-4o-mini-tts)..." : "Generating narration with ElevenLabs..."
|
|
31017
32036
|
);
|
|
31018
32037
|
const tts = await resolution.call(narrationText, {
|
|
31019
32038
|
voice: opts.voice,
|
|
@@ -31174,6 +32193,27 @@ async function executeSceneAdd(opts) {
|
|
|
31174
32193
|
duration = fallback2;
|
|
31175
32194
|
}
|
|
31176
32195
|
duration = Number(duration.toFixed(2));
|
|
32196
|
+
let storyboardPath2;
|
|
32197
|
+
let storyboardSynced = false;
|
|
32198
|
+
let storyboardAction = "skipped";
|
|
32199
|
+
const shouldSyncStoryboard = opts.syncStoryboard !== false && await pathExists2(storyboardAbsPath);
|
|
32200
|
+
if (shouldSyncStoryboard) {
|
|
32201
|
+
opts.onProgress?.("Syncing STORYBOARD.md...");
|
|
32202
|
+
const before = await readFile13(storyboardAbsPath, "utf-8");
|
|
32203
|
+
const synced = upsertStoryboardBeat(before, {
|
|
32204
|
+
beatId: id,
|
|
32205
|
+
title: opts.headline,
|
|
32206
|
+
duration,
|
|
32207
|
+
narration: narrationText && !opts.skipAudio ? narrationText : void 0,
|
|
32208
|
+
backdrop: opts.visuals && !opts.skipImage ? opts.visuals : void 0
|
|
32209
|
+
});
|
|
32210
|
+
if (synced.markdown !== before) {
|
|
32211
|
+
await writeFile12(storyboardAbsPath, synced.markdown, "utf-8");
|
|
32212
|
+
}
|
|
32213
|
+
storyboardPath2 = relative6(process.cwd(), storyboardAbsPath) || storyboardAbsPath;
|
|
32214
|
+
storyboardSynced = true;
|
|
32215
|
+
storyboardAction = synced.action;
|
|
32216
|
+
}
|
|
31177
32217
|
opts.onProgress?.("Emitting scene HTML...");
|
|
31178
32218
|
const sceneHtml = emitSceneHtml({
|
|
31179
32219
|
id,
|
|
@@ -31192,11 +32232,24 @@ async function executeSceneAdd(opts) {
|
|
|
31192
32232
|
await mkdir7(dirname4(scenePath), { recursive: true });
|
|
31193
32233
|
await writeFile12(scenePath, sceneHtml, "utf-8");
|
|
31194
32234
|
opts.onProgress?.("Updating root composition...");
|
|
31195
|
-
|
|
31196
|
-
|
|
31197
|
-
|
|
31198
|
-
|
|
31199
|
-
|
|
32235
|
+
let start = 0;
|
|
32236
|
+
if (storyboardSynced) {
|
|
32237
|
+
const syncPlan = await syncRootComposition({
|
|
32238
|
+
projectDir,
|
|
32239
|
+
rootRel,
|
|
32240
|
+
beats: await loadProjectRootSyncBeats(projectDir)
|
|
32241
|
+
});
|
|
32242
|
+
const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
|
|
32243
|
+
if (blockingIssue) return errResult(blockingIssue.message);
|
|
32244
|
+
const rootHtmlAfterSync = await readFile13(rootPath, "utf-8");
|
|
32245
|
+
start = readClipStart(rootHtmlAfterSync, id) ?? 0;
|
|
32246
|
+
} else {
|
|
32247
|
+
start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
|
|
32248
|
+
const existingClipCount = (rootHtmlBefore.match(/<div\s+class="clip"/g) || []).length;
|
|
32249
|
+
const trackIndex = existingClipCount % 2 + 1;
|
|
32250
|
+
const updated = insertClipIntoRoot(rootHtmlBefore, { id, start, duration, trackIndex });
|
|
32251
|
+
await writeFile12(rootPath, updated, "utf-8");
|
|
32252
|
+
}
|
|
31200
32253
|
const transcriptAbsPath = transcriptRelPath ? resolve17(projectDir, transcriptRelPath) : void 0;
|
|
31201
32254
|
return {
|
|
31202
32255
|
success: true,
|
|
@@ -31209,6 +32262,9 @@ async function executeSceneAdd(opts) {
|
|
|
31209
32262
|
audioPath: audioAbsPath ? relative6(process.cwd(), audioAbsPath) || audioAbsPath : void 0,
|
|
31210
32263
|
imagePath: imageAbsPath ? relative6(process.cwd(), imageAbsPath) || imageAbsPath : void 0,
|
|
31211
32264
|
lottiePath: lottieAbsPath ? relative6(process.cwd(), lottieAbsPath) || lottieAbsPath : void 0,
|
|
32265
|
+
storyboardPath: storyboardPath2,
|
|
32266
|
+
storyboardSynced,
|
|
32267
|
+
storyboardAction,
|
|
31212
32268
|
transcriptPath: transcriptAbsPath ? relative6(process.cwd(), transcriptAbsPath) || transcriptAbsPath : void 0,
|
|
31213
32269
|
transcriptWordCount
|
|
31214
32270
|
};
|
|
@@ -31250,6 +32306,8 @@ var init_scene = __esm({
|
|
|
31250
32306
|
init_compose_prompts();
|
|
31251
32307
|
init_scene_submit();
|
|
31252
32308
|
init_scene_repair();
|
|
32309
|
+
init_root_sync();
|
|
32310
|
+
init_storyboard_edit();
|
|
31253
32311
|
sceneCommand = new Command("scene").description(
|
|
31254
32312
|
"Lower-level scene authoring (add, lint, list-styles). For project flow use `vibe init` / `vibe build` / `vibe render`."
|
|
31255
32313
|
).addHelpText(
|
|
@@ -31534,11 +32592,14 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
|
|
|
31534
32592
|
).option("-d, --duration <sec>", "Explicit scene duration in seconds (overrides narration audio)").option(
|
|
31535
32593
|
"--visuals <prompt>",
|
|
31536
32594
|
"Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider"
|
|
31537
|
-
).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(
|
|
32595
|
+
).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(
|
|
32596
|
+
"--no-storyboard",
|
|
32597
|
+
"Do not sync STORYBOARD.md; insert this scene directly into the root composition only"
|
|
32598
|
+
).option("--image-provider <name>", "Image provider: gemini, openai", "gemini").option(
|
|
31538
32599
|
"--tts <provider>",
|
|
31539
|
-
"TTS provider: auto, elevenlabs, kokoro (default auto \u2014
|
|
32600
|
+
"TTS provider: auto, elevenlabs, openai, kokoro (default auto \u2014 ElevenLabs key > OpenAI key > Kokoro local)",
|
|
31540
32601
|
"auto"
|
|
31541
|
-
).option("--voice <id>", "Voice id (ElevenLabs name/id, or Kokoro id like af_heart
|
|
32602
|
+
).option("--voice <id>", "Voice id (ElevenLabs name/id, OpenAI voice like marin, or Kokoro id like af_heart)").option(
|
|
31542
32603
|
"--no-audio",
|
|
31543
32604
|
"Skip TTS even when --narration is provided (useful for tests/agent dry runs)"
|
|
31544
32605
|
).option("--no-image", "Skip image generation even when --visuals is provided").option(
|
|
@@ -31603,6 +32664,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
|
|
|
31603
32664
|
kicker: options.kicker,
|
|
31604
32665
|
project: options.project,
|
|
31605
32666
|
insertInto: options.insertInto,
|
|
32667
|
+
syncStoryboard: options.storyboard !== false,
|
|
31606
32668
|
imageProvider: options.imageProvider,
|
|
31607
32669
|
tts,
|
|
31608
32670
|
audio: options.audio,
|
|
@@ -31630,6 +32692,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
|
|
|
31630
32692
|
kicker: options.kicker,
|
|
31631
32693
|
projectDir: options.project,
|
|
31632
32694
|
insertInto: options.insertInto,
|
|
32695
|
+
syncStoryboard: options.storyboard !== false,
|
|
31633
32696
|
imageProvider: options.imageProvider,
|
|
31634
32697
|
tts,
|
|
31635
32698
|
voice: options.voice,
|
|
@@ -31669,6 +32732,13 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
|
|
|
31669
32732
|
if (result.audioPath) console.log(source_default.green(" +"), result.audioPath);
|
|
31670
32733
|
if (result.imagePath) console.log(source_default.green(" +"), result.imagePath);
|
|
31671
32734
|
if (result.transcriptPath) console.log(source_default.green(" +"), result.transcriptPath);
|
|
32735
|
+
if (result.storyboardPath && result.storyboardSynced) {
|
|
32736
|
+
console.log(
|
|
32737
|
+
result.storyboardAction === "updated" ? source_default.yellow(" ~") : source_default.green(" +"),
|
|
32738
|
+
result.storyboardPath,
|
|
32739
|
+
source_default.dim(`(${result.storyboardAction})`)
|
|
32740
|
+
);
|
|
32741
|
+
}
|
|
31672
32742
|
console.log(source_default.yellow(" ~"), result.rootPath, source_default.dim("(updated)"));
|
|
31673
32743
|
console.log();
|
|
31674
32744
|
console.log(source_default.bold.cyan("Composition"));
|
|
@@ -35477,6 +36547,10 @@ async function inspectProjectStatus(projectDirArg, opts = {}) {
|
|
|
35477
36547
|
...review?.retryWith ?? [],
|
|
35478
36548
|
...workflow.retryWith
|
|
35479
36549
|
];
|
|
36550
|
+
const nextActions = normalizeReviewActions([
|
|
36551
|
+
...review?.nextActions ?? [],
|
|
36552
|
+
...reviewActionsFromRetryWith([...build?.retryWith ?? [], ...workflow.retryWith])
|
|
36553
|
+
]);
|
|
35480
36554
|
return {
|
|
35481
36555
|
schemaVersion: "1",
|
|
35482
36556
|
kind: "project",
|
|
@@ -35488,6 +36562,7 @@ async function inspectProjectStatus(projectDirArg, opts = {}) {
|
|
|
35488
36562
|
review,
|
|
35489
36563
|
jobs,
|
|
35490
36564
|
warnings,
|
|
36565
|
+
nextActions,
|
|
35491
36566
|
retryWith: unique(retryWith)
|
|
35492
36567
|
};
|
|
35493
36568
|
}
|
|
@@ -35781,9 +36856,44 @@ async function readReviewSummary(projectDir) {
|
|
|
35781
36856
|
},
|
|
35782
36857
|
sourceReports: stringArray(report.sourceReports),
|
|
35783
36858
|
updatedAt: info?.mtime.toISOString(),
|
|
36859
|
+
nextActions: reviewActionsFromUnknown(report.nextActions),
|
|
35784
36860
|
retryWith: stringArray(report.retryWith)
|
|
35785
36861
|
});
|
|
35786
36862
|
}
|
|
36863
|
+
function reviewActionsFromUnknown(value) {
|
|
36864
|
+
if (!Array.isArray(value)) return [];
|
|
36865
|
+
return value.flatMap((item) => {
|
|
36866
|
+
if (!item || typeof item !== "object") return [];
|
|
36867
|
+
const record = item;
|
|
36868
|
+
const kind = stringEnum(record.kind, ["command", "agent", "manual"]);
|
|
36869
|
+
const fixOwner = stringEnum(record.fixOwner, ["vibe", "host-agent"]);
|
|
36870
|
+
const costTier = stringEnum(record.costTier, [
|
|
36871
|
+
"free",
|
|
36872
|
+
"low",
|
|
36873
|
+
"high",
|
|
36874
|
+
"very-high",
|
|
36875
|
+
"unknown"
|
|
36876
|
+
]);
|
|
36877
|
+
if (!kind || !fixOwner || !costTier || typeof record.id !== "string" || typeof record.label !== "string" || typeof record.safeToAutoRun !== "boolean" || typeof record.requiresConfirmation !== "boolean" || typeof record.reason !== "string") {
|
|
36878
|
+
return [];
|
|
36879
|
+
}
|
|
36880
|
+
return [
|
|
36881
|
+
stripUndefined2({
|
|
36882
|
+
id: record.id,
|
|
36883
|
+
kind,
|
|
36884
|
+
label: record.label,
|
|
36885
|
+
command: typeof record.command === "string" ? record.command : void 0,
|
|
36886
|
+
agentPrompt: typeof record.agentPrompt === "string" ? record.agentPrompt : void 0,
|
|
36887
|
+
fixOwner,
|
|
36888
|
+
costTier,
|
|
36889
|
+
safeToAutoRun: record.safeToAutoRun,
|
|
36890
|
+
requiresConfirmation: record.requiresConfirmation,
|
|
36891
|
+
reason: record.reason,
|
|
36892
|
+
sourceIssueCodes: stringArray(record.sourceIssueCodes)
|
|
36893
|
+
})
|
|
36894
|
+
];
|
|
36895
|
+
});
|
|
36896
|
+
}
|
|
35787
36897
|
function summarizeJobs(records) {
|
|
35788
36898
|
return {
|
|
35789
36899
|
total: records.length,
|
|
@@ -35995,6 +37105,9 @@ function numberOr(fallback2, value) {
|
|
|
35995
37105
|
function stringArray(value) {
|
|
35996
37106
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0) : [];
|
|
35997
37107
|
}
|
|
37108
|
+
function stringEnum(value, allowed) {
|
|
37109
|
+
return typeof value === "string" && allowed.includes(value) ? value : void 0;
|
|
37110
|
+
}
|
|
35998
37111
|
function isActiveStatus(status) {
|
|
35999
37112
|
return status === "queued" || status === "running";
|
|
36000
37113
|
}
|
|
@@ -36060,6 +37173,7 @@ var init_status_jobs = __esm({
|
|
|
36060
37173
|
init_ai_video();
|
|
36061
37174
|
init_music_status();
|
|
36062
37175
|
init_build_asset_metadata();
|
|
37176
|
+
init_review_report();
|
|
36063
37177
|
init_storyboard_parse();
|
|
36064
37178
|
LOCAL_JOB_ORPHAN_AFTER_MS = 9e4;
|
|
36065
37179
|
}
|
|
@@ -38941,10 +40055,18 @@ Examples:
|
|
|
38941
40055
|
options.ratio = "16:9";
|
|
38942
40056
|
}
|
|
38943
40057
|
if (options.dryRun) {
|
|
40058
|
+
const costUsd = provider === "seedance" || provider === "fal" ? estimateSeedanceVideoCostUsd({
|
|
40059
|
+
durationSec: Number(options.duration) || 5,
|
|
40060
|
+
resolution: options.resolution,
|
|
40061
|
+
aspectRatio: options.ratio,
|
|
40062
|
+
fast: options.seedanceModel === "fast",
|
|
40063
|
+
hasVideoReference: Boolean(options.refVideos)
|
|
40064
|
+
}) : void 0;
|
|
38944
40065
|
outputSuccess({
|
|
38945
40066
|
command: "generate video",
|
|
38946
40067
|
startedAt,
|
|
38947
40068
|
dryRun: true,
|
|
40069
|
+
costUsd,
|
|
38948
40070
|
data: {
|
|
38949
40071
|
params: {
|
|
38950
40072
|
prompt: prompt3,
|
|
@@ -44132,208 +45254,6 @@ var init_ai_review = __esm({
|
|
|
44132
45254
|
}
|
|
44133
45255
|
});
|
|
44134
45256
|
|
|
44135
|
-
// src/commands/_shared/storyboard-edit.ts
|
|
44136
|
-
function validateStoryboardMarkdown(markdown) {
|
|
44137
|
-
const parsed = parseStoryboard(markdown);
|
|
44138
|
-
const sections = splitBeatSections(markdown);
|
|
44139
|
-
const issues = [];
|
|
44140
|
-
if (parsed.beats.length === 0) {
|
|
44141
|
-
issues.push({
|
|
44142
|
-
severity: "error",
|
|
44143
|
-
code: "NO_BEATS",
|
|
44144
|
-
message: "STORYBOARD.md must contain at least one `## Beat ...` heading."
|
|
44145
|
-
});
|
|
44146
|
-
}
|
|
44147
|
-
const seen = /* @__PURE__ */ new Map();
|
|
44148
|
-
for (const beat of parsed.beats) {
|
|
44149
|
-
seen.set(beat.id, (seen.get(beat.id) ?? 0) + 1);
|
|
44150
|
-
}
|
|
44151
|
-
for (const [id, count] of seen.entries()) {
|
|
44152
|
-
if (count > 1) {
|
|
44153
|
-
issues.push({
|
|
44154
|
-
severity: "error",
|
|
44155
|
-
code: "DUPLICATE_BEAT_ID",
|
|
44156
|
-
beatId: id,
|
|
44157
|
-
message: `Beat id "${id}" appears ${count} times. Beat ids must be unique.`
|
|
44158
|
-
});
|
|
44159
|
-
}
|
|
44160
|
-
}
|
|
44161
|
-
for (const section of sections) {
|
|
44162
|
-
const cueBlock = readLeadingCueBlock(section.body);
|
|
44163
|
-
if (cueBlock?.error) {
|
|
44164
|
-
issues.push({
|
|
44165
|
-
severity: "error",
|
|
44166
|
-
code: "MALFORMED_CUE_YAML",
|
|
44167
|
-
beatId: section.id,
|
|
44168
|
-
message: `Beat "${section.id}" has malformed YAML cues: ${cueBlock.error}`
|
|
44169
|
-
});
|
|
44170
|
-
}
|
|
44171
|
-
}
|
|
44172
|
-
for (const beat of parsed.beats) {
|
|
44173
|
-
const cues = beat.cues ?? {};
|
|
44174
|
-
for (const [key, value] of Object.entries(cues)) {
|
|
44175
|
-
if (!ALLOWED_CUE_KEYS.has(key)) {
|
|
44176
|
-
issues.push({
|
|
44177
|
-
severity: "warning",
|
|
44178
|
-
code: "UNKNOWN_CUE",
|
|
44179
|
-
beatId: beat.id,
|
|
44180
|
-
message: `Beat "${beat.id}" uses unknown cue "${key}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`
|
|
44181
|
-
});
|
|
44182
|
-
continue;
|
|
44183
|
-
}
|
|
44184
|
-
if (key === "duration") {
|
|
44185
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
44186
|
-
issues.push({
|
|
44187
|
-
severity: "error",
|
|
44188
|
-
code: "INVALID_DURATION",
|
|
44189
|
-
beatId: beat.id,
|
|
44190
|
-
message: `Beat "${beat.id}" cue "duration" must be a positive number of seconds.`
|
|
44191
|
-
});
|
|
44192
|
-
}
|
|
44193
|
-
continue;
|
|
44194
|
-
}
|
|
44195
|
-
if (STRING_CUE_KEYS.has(key) && typeof value !== "string") {
|
|
44196
|
-
issues.push({
|
|
44197
|
-
severity: "error",
|
|
44198
|
-
code: "INVALID_CUE_VALUE",
|
|
44199
|
-
beatId: beat.id,
|
|
44200
|
-
message: `Beat "${beat.id}" cue "${key}" must be a string.`
|
|
44201
|
-
});
|
|
44202
|
-
}
|
|
44203
|
-
}
|
|
44204
|
-
}
|
|
44205
|
-
for (const beat of parsed.beats) {
|
|
44206
|
-
if (beat.duration !== void 0 && beat.duration > MAX_RECOMMENDED_BEAT_SEC) {
|
|
44207
|
-
issues.push({
|
|
44208
|
-
severity: "warning",
|
|
44209
|
-
code: "BEAT_DURATION_TOO_LONG",
|
|
44210
|
-
beatId: beat.id,
|
|
44211
|
-
message: `Beat "${beat.id}" is ${beat.duration}s \u2014 beats longer than ${MAX_RECOMMENDED_BEAT_SEC}s render static and overstuffed. Split it into 6-15s beats (a 90s video should have 6-8 beats).`
|
|
44212
|
-
});
|
|
44213
|
-
}
|
|
44214
|
-
}
|
|
44215
|
-
return {
|
|
44216
|
-
ok: !issues.some((i) => i.severity === "error"),
|
|
44217
|
-
beats: parsed.beats,
|
|
44218
|
-
issues
|
|
44219
|
-
};
|
|
44220
|
-
}
|
|
44221
|
-
function getStoryboardBeat(markdown, beatId) {
|
|
44222
|
-
return parseStoryboard(markdown).beats.find((beat) => beat.id === beatId) ?? null;
|
|
44223
|
-
}
|
|
44224
|
-
function setStoryboardCue(markdown, opts) {
|
|
44225
|
-
const section = findBeatSection(markdown, opts.beatId);
|
|
44226
|
-
if (!section) {
|
|
44227
|
-
throw new Error(`Beat "${opts.beatId}" not found.`);
|
|
44228
|
-
}
|
|
44229
|
-
if (!ALLOWED_CUE_KEYS.has(opts.key)) {
|
|
44230
|
-
throw new Error(`Unsupported cue "${opts.key}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`);
|
|
44231
|
-
}
|
|
44232
|
-
const cue = readLeadingCueBlock(section.body);
|
|
44233
|
-
if (cue?.error) {
|
|
44234
|
-
throw new Error(`Cannot edit malformed YAML cues for beat "${opts.beatId}": ${cue.error}`);
|
|
44235
|
-
}
|
|
44236
|
-
const cues = cue?.value ? { ...cue.value } : {};
|
|
44237
|
-
if (opts.unset) {
|
|
44238
|
-
delete cues[opts.key];
|
|
44239
|
-
} else {
|
|
44240
|
-
cues[opts.key] = normalizeCueValue(opts.key, opts.value);
|
|
44241
|
-
}
|
|
44242
|
-
const remainingBody = cue ? section.body.slice(cue.full.length).replace(/^\s*\n/, "") : section.body.replace(/^\s*\n/, "");
|
|
44243
|
-
const cueBlock = Object.keys(cues).length > 0 ? "```yaml\n" + (0, import_yaml5.stringify)(cues, { lineWidth: 0 }).trimEnd() + "\n```\n\n" : "";
|
|
44244
|
-
const nextBody = cueBlock + remainingBody.trimStart();
|
|
44245
|
-
const nextSection = section.raw.slice(0, section.headingEnd - section.start) + "\n\n" + nextBody.trimEnd() + "\n";
|
|
44246
|
-
return markdown.slice(0, section.start) + nextSection + markdown.slice(section.end);
|
|
44247
|
-
}
|
|
44248
|
-
function moveStoryboardBeat(markdown, opts) {
|
|
44249
|
-
if (opts.beatId === opts.afterBeatId) return markdown;
|
|
44250
|
-
const sections = splitBeatSections(markdown);
|
|
44251
|
-
const movingIndex = sections.findIndex((section) => section.id === opts.beatId);
|
|
44252
|
-
if (movingIndex === -1) throw new Error(`Beat "${opts.beatId}" not found.`);
|
|
44253
|
-
const afterIndex = sections.findIndex((section) => section.id === opts.afterBeatId);
|
|
44254
|
-
if (afterIndex === -1) throw new Error(`Beat "${opts.afterBeatId}" not found.`);
|
|
44255
|
-
const global2 = sections.length > 0 ? markdown.slice(0, sections[0].start) : markdown;
|
|
44256
|
-
const chunks = sections.map((section) => section.raw);
|
|
44257
|
-
const [moving] = chunks.splice(movingIndex, 1);
|
|
44258
|
-
const adjustedAfterIndex = movingIndex < afterIndex ? afterIndex - 1 : afterIndex;
|
|
44259
|
-
chunks.splice(adjustedAfterIndex + 1, 0, moving);
|
|
44260
|
-
return global2 + normalizeBeatChunks(chunks);
|
|
44261
|
-
}
|
|
44262
|
-
function normalizeBeatChunks(chunks) {
|
|
44263
|
-
return chunks.map((chunk) => chunk.trim()).filter(Boolean).join("\n\n") + "\n";
|
|
44264
|
-
}
|
|
44265
|
-
function normalizeCueValue(key, value) {
|
|
44266
|
-
if (key === "duration") {
|
|
44267
|
-
const n = typeof value === "number" ? value : Number.parseFloat(String(value ?? ""));
|
|
44268
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
44269
|
-
throw new Error("Cue duration must be a positive number.");
|
|
44270
|
-
}
|
|
44271
|
-
return n;
|
|
44272
|
-
}
|
|
44273
|
-
if (typeof value !== "string") return value;
|
|
44274
|
-
return value.trim();
|
|
44275
|
-
}
|
|
44276
|
-
function splitBeatSections(markdown) {
|
|
44277
|
-
const normalized = markdown.replace(/\r\n/g, "\n");
|
|
44278
|
-
const headings = [];
|
|
44279
|
-
HEADING_RE2.lastIndex = 0;
|
|
44280
|
-
let match;
|
|
44281
|
-
while ((match = HEADING_RE2.exec(normalized)) !== null) {
|
|
44282
|
-
headings.push({ start: match.index, end: match.index + match[0].length, line: match[1].trim() });
|
|
44283
|
-
}
|
|
44284
|
-
return headings.map((heading, index) => {
|
|
44285
|
-
const end = index + 1 < headings.length ? headings[index + 1].start : normalized.length;
|
|
44286
|
-
return {
|
|
44287
|
-
id: deriveBeatId(heading.line),
|
|
44288
|
-
heading: heading.line,
|
|
44289
|
-
start: heading.start,
|
|
44290
|
-
headingEnd: heading.end,
|
|
44291
|
-
end,
|
|
44292
|
-
raw: normalized.slice(heading.start, end),
|
|
44293
|
-
body: normalized.slice(heading.end, end).trim()
|
|
44294
|
-
};
|
|
44295
|
-
});
|
|
44296
|
-
}
|
|
44297
|
-
function findBeatSection(markdown, beatId) {
|
|
44298
|
-
return splitBeatSections(markdown).find((section) => section.id === beatId) ?? null;
|
|
44299
|
-
}
|
|
44300
|
-
function readLeadingCueBlock(body) {
|
|
44301
|
-
const match = body.match(LEADING_CUE_RE);
|
|
44302
|
-
if (!match) return null;
|
|
44303
|
-
try {
|
|
44304
|
-
const parsed = (0, import_yaml5.parse)(match[2]);
|
|
44305
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
44306
|
-
return { full: match[0], error: "cue block must parse to a YAML object" };
|
|
44307
|
-
}
|
|
44308
|
-
return { full: match[0], value: parsed };
|
|
44309
|
-
} catch (error) {
|
|
44310
|
-
return { full: match[0], error: error instanceof Error ? error.message : String(error) };
|
|
44311
|
-
}
|
|
44312
|
-
}
|
|
44313
|
-
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS, MAX_RECOMMENDED_BEAT_SEC;
|
|
44314
|
-
var init_storyboard_edit = __esm({
|
|
44315
|
-
"src/commands/_shared/storyboard-edit.ts"() {
|
|
44316
|
-
"use strict";
|
|
44317
|
-
import_yaml5 = __toESM(require_dist2(), 1);
|
|
44318
|
-
init_storyboard_parse();
|
|
44319
|
-
STORYBOARD_CUE_KEYS = [
|
|
44320
|
-
"duration",
|
|
44321
|
-
"narration",
|
|
44322
|
-
"backdrop",
|
|
44323
|
-
"video",
|
|
44324
|
-
"motion",
|
|
44325
|
-
"voice",
|
|
44326
|
-
"music",
|
|
44327
|
-
"asset"
|
|
44328
|
-
];
|
|
44329
|
-
HEADING_RE2 = /^##\s+(.+?)\s*$/gm;
|
|
44330
|
-
LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
|
|
44331
|
-
ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
|
|
44332
|
-
STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
|
|
44333
|
-
MAX_RECOMMENDED_BEAT_SEC = 15;
|
|
44334
|
-
}
|
|
44335
|
-
});
|
|
44336
|
-
|
|
44337
45257
|
// src/commands/_shared/scene-inspect.ts
|
|
44338
45258
|
import { existsSync as existsSync34 } from "node:fs";
|
|
44339
45259
|
import { readdir as readdir4, readFile as readFile29, stat as stat5 } from "node:fs/promises";
|
|
@@ -44566,7 +45486,11 @@ async function inspectProject(opts) {
|
|
|
44566
45486
|
);
|
|
44567
45487
|
}
|
|
44568
45488
|
function makeProjectResult(projectDir, issues, checks, retryWith, beatId) {
|
|
44569
|
-
const
|
|
45489
|
+
const normalizedRetryWith = uniqueRetryWith(retryWith);
|
|
45490
|
+
const normalizedIssues = normalizeReviewIssues(issues, {
|
|
45491
|
+
projectDir,
|
|
45492
|
+
retryWith: normalizedRetryWith
|
|
45493
|
+
});
|
|
44570
45494
|
const status = statusFromIssues(normalizedIssues);
|
|
44571
45495
|
return {
|
|
44572
45496
|
schemaVersion: "1",
|
|
@@ -44578,9 +45502,14 @@ function makeProjectResult(projectDir, issues, checks, retryWith, beatId) {
|
|
|
44578
45502
|
score: scoreIssues(normalizedIssues),
|
|
44579
45503
|
issues: normalizedIssues,
|
|
44580
45504
|
summary: summarizeReviewIssues(normalizedIssues),
|
|
45505
|
+
nextActions: deriveNextReviewActions({
|
|
45506
|
+
issues: normalizedIssues,
|
|
45507
|
+
retryWith: normalizedRetryWith,
|
|
45508
|
+
projectDir
|
|
45509
|
+
}),
|
|
44581
45510
|
sourceReports: projectSourceReports(checks),
|
|
44582
45511
|
checks,
|
|
44583
|
-
retryWith:
|
|
45512
|
+
retryWith: normalizedRetryWith
|
|
44584
45513
|
};
|
|
44585
45514
|
}
|
|
44586
45515
|
function storyboardPlaceholderIssues(storyboard) {
|
|
@@ -45122,6 +46051,8 @@ async function inspectRender(opts) {
|
|
|
45122
46051
|
severity: "error",
|
|
45123
46052
|
code: "RENDER_NOT_FOUND",
|
|
45124
46053
|
message: opts.beatId ? `No rendered video was found for beat "${opts.beatId}". Pass --video or render the beat first.` : "No rendered video was found. Pass --video or render the project first.",
|
|
46054
|
+
beatId: opts.beatId,
|
|
46055
|
+
scene: opts.beatId,
|
|
45125
46056
|
suggestedFix: opts.beatId ? "Run `vibe render <project> --beat <id> --json`." : "Run `vibe build --stage render --json` or `vibe render --json`."
|
|
45126
46057
|
});
|
|
45127
46058
|
retryWith.push(
|
|
@@ -45144,6 +46075,8 @@ async function inspectRender(opts) {
|
|
|
45144
46075
|
code: "EMPTY_RENDER",
|
|
45145
46076
|
message: "Rendered video file is empty.",
|
|
45146
46077
|
file: displayPath(videoPath),
|
|
46078
|
+
beatId: opts.beatId,
|
|
46079
|
+
scene: opts.beatId,
|
|
45147
46080
|
suggestedFix: opts.beatId ? "Render again with `vibe render --beat <id> --json`." : "Render again with `vibe render --json`."
|
|
45148
46081
|
});
|
|
45149
46082
|
retryWith.push(
|
|
@@ -45153,6 +46086,8 @@ async function inspectRender(opts) {
|
|
|
45153
46086
|
const beatTimings = await beatTimingsFromBuildReport(projectDir, opts.beatId);
|
|
45154
46087
|
const expectedDurationSec = beatTimings.length > 0 ? Number(beatTimings.reduce((sum, beat) => sum + beat.sceneDurationSec, 0).toFixed(3)) : await expectedDurationFromBuildReport(projectDir, opts.beatId);
|
|
45155
46088
|
if (expectedDurationSec !== void 0) checks.expectedDurationSec = expectedDurationSec;
|
|
46089
|
+
const expectedAudio = await renderExpectsAudio(projectDir, opts.beatId);
|
|
46090
|
+
checks.expectedAudio = expectedAudio;
|
|
45156
46091
|
if (!commandExists("ffprobe")) {
|
|
45157
46092
|
issues.push({
|
|
45158
46093
|
severity: "error",
|
|
@@ -45178,7 +46113,7 @@ async function inspectRender(opts) {
|
|
|
45178
46113
|
file: displayPath(videoPath)
|
|
45179
46114
|
});
|
|
45180
46115
|
}
|
|
45181
|
-
if (!audioStream) {
|
|
46116
|
+
if (!audioStream && expectedAudio) {
|
|
45182
46117
|
issues.push({
|
|
45183
46118
|
severity: "warning",
|
|
45184
46119
|
code: "NO_AUDIO_STREAM",
|
|
@@ -45383,8 +46318,86 @@ async function inspectRender(opts) {
|
|
|
45383
46318
|
);
|
|
45384
46319
|
return maybeWriteRenderReport(projectDir, opts, result);
|
|
45385
46320
|
}
|
|
46321
|
+
async function renderExpectsAudio(projectDir, beatId) {
|
|
46322
|
+
const root = resolve43(projectDir);
|
|
46323
|
+
const rootHtml = await readTextIfExists(join19(root, "index.html"));
|
|
46324
|
+
if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
|
|
46325
|
+
const buildReport = await readJsonIfExists(join19(root, "build-report.json"));
|
|
46326
|
+
if (buildReportExpectsAudio(buildReport, beatId)) return true;
|
|
46327
|
+
const storyboard = await readTextIfExists(join19(root, "STORYBOARD.md"));
|
|
46328
|
+
if (!storyboard) return false;
|
|
46329
|
+
try {
|
|
46330
|
+
const parsed = parseStoryboard(storyboard);
|
|
46331
|
+
const beats = beatId ? parsed.beats.filter((beat) => beat.id === beatId) : parsed.beats;
|
|
46332
|
+
return beats.some(
|
|
46333
|
+
(beat) => [
|
|
46334
|
+
`assets/narration-${beat.id}.mp3`,
|
|
46335
|
+
`assets/narration-${beat.id}.wav`,
|
|
46336
|
+
`assets/music-${beat.id}.mp3`,
|
|
46337
|
+
`assets/music-${beat.id}.wav`
|
|
46338
|
+
].some((asset) => existsSync35(join19(root, asset)))
|
|
46339
|
+
);
|
|
46340
|
+
} catch {
|
|
46341
|
+
return false;
|
|
46342
|
+
}
|
|
46343
|
+
}
|
|
46344
|
+
function rootHtmlExpectsAudio(html, beatId) {
|
|
46345
|
+
const audioTags = html.match(/<audio\b[^>]*>/gi) ?? [];
|
|
46346
|
+
if (!beatId) return audioTags.length > 0;
|
|
46347
|
+
const escaped = escapeRegExp4(beatId);
|
|
46348
|
+
const beatAudioRe = new RegExp(
|
|
46349
|
+
`(id="(?:narration|music)-${escaped}"|src="[^"]*(?:narration|music)-${escaped}\\.(?:mp3|wav)")`,
|
|
46350
|
+
"i"
|
|
46351
|
+
);
|
|
46352
|
+
return audioTags.some((tag) => beatAudioRe.test(tag));
|
|
46353
|
+
}
|
|
46354
|
+
function buildReportExpectsAudio(report, beatId) {
|
|
46355
|
+
if (!report || typeof report !== "object") return false;
|
|
46356
|
+
const beats = Array.isArray(report.beats) ? report.beats ?? [] : [];
|
|
46357
|
+
const selected = beatId === void 0 ? beats : beats.filter((beat) => {
|
|
46358
|
+
if (!beat || typeof beat !== "object") return false;
|
|
46359
|
+
return beat.id === beatId;
|
|
46360
|
+
});
|
|
46361
|
+
return selected.some((beat) => beatReportExpectsAudio(beat));
|
|
46362
|
+
}
|
|
46363
|
+
function beatReportExpectsAudio(beat) {
|
|
46364
|
+
if (!beat || typeof beat !== "object") return false;
|
|
46365
|
+
const record = beat;
|
|
46366
|
+
if (typeof record.narrationPath === "string" && record.narrationPath.length > 0) return true;
|
|
46367
|
+
if (typeof record.musicPath === "string" && record.musicPath.length > 0) return true;
|
|
46368
|
+
if (parseOptionalNumber(record.narrationDurationSec) !== void 0) return true;
|
|
46369
|
+
const narration = record.narration;
|
|
46370
|
+
if (narration && typeof narration === "object") {
|
|
46371
|
+
const n = narration;
|
|
46372
|
+
if (typeof n.path === "string" && n.path.length > 0) return true;
|
|
46373
|
+
if (parseOptionalNumber(n.durationSec) !== void 0) return true;
|
|
46374
|
+
}
|
|
46375
|
+
const music = record.music;
|
|
46376
|
+
if (music && typeof music === "object") {
|
|
46377
|
+
const m = music;
|
|
46378
|
+
if (typeof m.path === "string" && m.path.length > 0) return true;
|
|
46379
|
+
if (parseOptionalNumber(m.durationSec) !== void 0) return true;
|
|
46380
|
+
}
|
|
46381
|
+
return false;
|
|
46382
|
+
}
|
|
46383
|
+
async function readJsonIfExists(path2) {
|
|
46384
|
+
const text = await readTextIfExists(path2);
|
|
46385
|
+
if (!text) return null;
|
|
46386
|
+
try {
|
|
46387
|
+
return JSON.parse(text);
|
|
46388
|
+
} catch {
|
|
46389
|
+
return null;
|
|
46390
|
+
}
|
|
46391
|
+
}
|
|
46392
|
+
function escapeRegExp4(value) {
|
|
46393
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
46394
|
+
}
|
|
45386
46395
|
function makeRenderResult(projectDir, opts, videoPath, issues, checks, retryWith, score) {
|
|
45387
|
-
const
|
|
46396
|
+
const normalizedRetryWith = uniqueRetryWith(retryWith);
|
|
46397
|
+
const normalizedIssues = normalizeReviewIssues(issues, {
|
|
46398
|
+
projectDir,
|
|
46399
|
+
retryWith: normalizedRetryWith
|
|
46400
|
+
});
|
|
45388
46401
|
const status = statusFromIssues(normalizedIssues);
|
|
45389
46402
|
return {
|
|
45390
46403
|
schemaVersion: "1",
|
|
@@ -45397,9 +46410,14 @@ function makeRenderResult(projectDir, opts, videoPath, issues, checks, retryWith
|
|
|
45397
46410
|
score,
|
|
45398
46411
|
issues: normalizedIssues,
|
|
45399
46412
|
summary: summarizeReviewIssues(normalizedIssues),
|
|
46413
|
+
nextActions: deriveNextReviewActions({
|
|
46414
|
+
issues: normalizedIssues,
|
|
46415
|
+
retryWith: normalizedRetryWith,
|
|
46416
|
+
projectDir
|
|
46417
|
+
}),
|
|
45400
46418
|
sourceReports: renderSourceReports(projectDir, videoPath, checks),
|
|
45401
46419
|
checks,
|
|
45402
|
-
retryWith:
|
|
46420
|
+
retryWith: normalizedRetryWith
|
|
45403
46421
|
};
|
|
45404
46422
|
}
|
|
45405
46423
|
async function maybeWriteRenderReport(projectDir, opts, result) {
|
|
@@ -59403,6 +60421,7 @@ var init_build_asset_reference = __esm({
|
|
|
59403
60421
|
EXTENSIONS = {
|
|
59404
60422
|
narration: [".mp3", ".wav", ".m4a"],
|
|
59405
60423
|
backdrop: [".png", ".jpg", ".jpeg", ".webp"],
|
|
60424
|
+
character: [".png", ".jpg", ".jpeg", ".webp"],
|
|
59406
60425
|
video: [".mp4", ".mov", ".webm"],
|
|
59407
60426
|
music: [".mp3", ".wav", ".m4a"]
|
|
59408
60427
|
};
|
|
@@ -59440,6 +60459,16 @@ function backdropCacheDescriptor(opts) {
|
|
|
59440
60459
|
ext: "png"
|
|
59441
60460
|
});
|
|
59442
60461
|
}
|
|
60462
|
+
function characterCacheDescriptor(opts) {
|
|
60463
|
+
return cacheAssetDescriptor("character", {
|
|
60464
|
+
name: opts.name,
|
|
60465
|
+
cue: opts.cue,
|
|
60466
|
+
provider: opts.provider,
|
|
60467
|
+
quality: opts.quality,
|
|
60468
|
+
size: opts.size,
|
|
60469
|
+
ext: "png"
|
|
60470
|
+
});
|
|
60471
|
+
}
|
|
59443
60472
|
function imageRatioForSize(size) {
|
|
59444
60473
|
switch (size) {
|
|
59445
60474
|
case "1024x1024":
|
|
@@ -59459,6 +60488,8 @@ function videoCacheDescriptor(opts) {
|
|
|
59459
60488
|
provider: opts.provider,
|
|
59460
60489
|
duration: normalizeVideoDuration(opts.duration),
|
|
59461
60490
|
ratio: "16:9",
|
|
60491
|
+
// Omit when empty so existing (character-less) clips keep their cache key.
|
|
60492
|
+
characters: opts.characters && opts.characters.length > 0 ? opts.characters : void 0,
|
|
59462
60493
|
ext: "mp4"
|
|
59463
60494
|
});
|
|
59464
60495
|
}
|
|
@@ -59619,13 +60650,13 @@ async function createBuildPlan(opts) {
|
|
|
59619
60650
|
const backdropCue = backdropPrompt ?? backdropReference?.raw;
|
|
59620
60651
|
const videoCue = videoPrompt ?? videoReference?.raw;
|
|
59621
60652
|
const musicCue = musicPrompt ?? musicReference?.raw;
|
|
59622
|
-
const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : 0;
|
|
60653
|
+
const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : resolved.narration.resolved === "openai" ? OPENAI_NARRATION_COST_USD : 0;
|
|
59623
60654
|
const narrationCache = narrationText && !narrationReference ? narrationCacheDescriptor({
|
|
59624
60655
|
beatId: beat.id,
|
|
59625
60656
|
cue: narrationText,
|
|
59626
60657
|
provider: resolved.narration.resolved,
|
|
59627
60658
|
voice,
|
|
59628
|
-
ext: resolved.narration.resolved === "
|
|
60659
|
+
ext: resolved.narration.resolved === "kokoro" ? "wav" : "mp3"
|
|
59629
60660
|
}) : null;
|
|
59630
60661
|
const backdropCache = augmentedBackdropPrompt && !backdropReference ? backdropCacheDescriptor({
|
|
59631
60662
|
beatId: beat.id,
|
|
@@ -59686,7 +60717,11 @@ async function createBuildPlan(opts) {
|
|
|
59686
60717
|
reference: videoReference,
|
|
59687
60718
|
projectDir,
|
|
59688
60719
|
force: opts.force,
|
|
59689
|
-
cost:
|
|
60720
|
+
cost: resolved.video.resolved === "seedance" || resolved.video.resolved === "fal" ? estimateSeedanceVideoCostUsd({
|
|
60721
|
+
durationSec: normalizeVideoDuration(beat.duration),
|
|
60722
|
+
resolution: "720p",
|
|
60723
|
+
aspectRatio: "16:9"
|
|
60724
|
+
}) : VIDEO_COST_USD,
|
|
59690
60725
|
active: includeAssets
|
|
59691
60726
|
}) : null;
|
|
59692
60727
|
const music = musicCue && !opts.skipMusic ? assetPlan({
|
|
@@ -59797,6 +60832,21 @@ async function createBuildPlan(opts) {
|
|
|
59797
60832
|
retryWith.push(skipCommand);
|
|
59798
60833
|
}
|
|
59799
60834
|
}
|
|
60835
|
+
if (validationOk && includeAssets && !opts.skipVideo) {
|
|
60836
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
60837
|
+
for (const beat of sourceBeats) {
|
|
60838
|
+
for (const name of beatCharacterNames(beat.cues)) referenced.add(name);
|
|
60839
|
+
}
|
|
60840
|
+
if (referenced.size > 0) {
|
|
60841
|
+
const toGenerate = resolveCharacters(parsed.frontmatter).filter(
|
|
60842
|
+
(c) => referenced.has(c.name) && c.prompt && (opts.force || !existsSync47(join23(projectDir, `assets/character-${c.name}.png`)))
|
|
60843
|
+
);
|
|
60844
|
+
if (toGenerate.length > 0) {
|
|
60845
|
+
estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
|
|
60846
|
+
missing.add("assets");
|
|
60847
|
+
}
|
|
60848
|
+
}
|
|
60849
|
+
}
|
|
59800
60850
|
return finalizeBuildPlan({
|
|
59801
60851
|
projectDir,
|
|
59802
60852
|
config: config2,
|
|
@@ -60043,17 +61093,20 @@ function providerInput(opts) {
|
|
|
60043
61093
|
async function resolveNarrationProvider(input, projectDir) {
|
|
60044
61094
|
const requested = input.value.toLowerCase();
|
|
60045
61095
|
const hasElevenLabs = Boolean(await getApiKeyFromConfig("elevenlabs", { cwd: projectDir }));
|
|
60046
|
-
const
|
|
61096
|
+
const hasOpenAi = Boolean(await getApiKeyFromConfig("openai", { cwd: projectDir }));
|
|
61097
|
+
const resolved = requested === "elevenlabs" ? "elevenlabs" : requested === "openai" ? "openai" : requested === "kokoro" ? "kokoro" : hasElevenLabs ? "elevenlabs" : hasOpenAi ? "openai" : "kokoro";
|
|
61098
|
+
const requiresKey = resolved !== "kokoro";
|
|
61099
|
+
const hasKey = resolved === "elevenlabs" ? hasElevenLabs : resolved === "openai" ? hasOpenAi : true;
|
|
60047
61100
|
return {
|
|
60048
61101
|
kind: "narration",
|
|
60049
61102
|
requested: input.requested,
|
|
60050
61103
|
resolved,
|
|
60051
61104
|
source: input.source,
|
|
60052
|
-
requiresKey
|
|
60053
|
-
configured:
|
|
60054
|
-
...
|
|
60055
|
-
...
|
|
60056
|
-
retryWith:
|
|
61105
|
+
requiresKey,
|
|
61106
|
+
configured: hasKey,
|
|
61107
|
+
...requiresKey ? { configKey: resolved } : {},
|
|
61108
|
+
...requiresKey && !hasKey ? { missingKey: providerEnvVar(resolved) } : {},
|
|
61109
|
+
retryWith: requiresKey && !hasKey ? ["vibe setup --full"] : []
|
|
60057
61110
|
};
|
|
60058
61111
|
}
|
|
60059
61112
|
async function keyedProviderResolution(opts, projectDir) {
|
|
@@ -60149,7 +61202,7 @@ function loadPlanEnv(projectDir) {
|
|
|
60149
61202
|
dir = dirname21(dir);
|
|
60150
61203
|
}
|
|
60151
61204
|
}
|
|
60152
|
-
var import_dotenv3, VIDEO_COST_USD, MUSIC_COST_USD, ELEVENLABS_NARRATION_COST_USD, COMPOSE_COST_USD, SUPPORTED_BUILD_IMAGE_PROVIDERS;
|
|
61205
|
+
var import_dotenv3, VIDEO_COST_USD, MUSIC_COST_USD, ELEVENLABS_NARRATION_COST_USD, OPENAI_NARRATION_COST_USD, COMPOSE_COST_USD, SUPPORTED_BUILD_IMAGE_PROVIDERS;
|
|
60153
61206
|
var init_build_plan = __esm({
|
|
60154
61207
|
"src/commands/_shared/build-plan.ts"() {
|
|
60155
61208
|
"use strict";
|
|
@@ -60158,6 +61211,7 @@ var init_build_plan = __esm({
|
|
|
60158
61211
|
init_schema();
|
|
60159
61212
|
init_build_asset_reference();
|
|
60160
61213
|
init_build_cache();
|
|
61214
|
+
init_dist2();
|
|
60161
61215
|
init_build_asset_metadata();
|
|
60162
61216
|
init_build_backdrop_prompt();
|
|
60163
61217
|
init_composer_resolve();
|
|
@@ -60167,6 +61221,7 @@ var init_build_plan = __esm({
|
|
|
60167
61221
|
VIDEO_COST_USD = 5;
|
|
60168
61222
|
MUSIC_COST_USD = 0.5;
|
|
60169
61223
|
ELEVENLABS_NARRATION_COST_USD = 0.05;
|
|
61224
|
+
OPENAI_NARRATION_COST_USD = 0.01;
|
|
60170
61225
|
COMPOSE_COST_USD = 0.06;
|
|
60171
61226
|
SUPPORTED_BUILD_IMAGE_PROVIDERS = ["openai", "gemini", "grok"];
|
|
60172
61227
|
}
|
|
@@ -60984,7 +62039,7 @@ __export(scene_build_exports, {
|
|
|
60984
62039
|
executeSceneBuild: () => executeSceneBuild,
|
|
60985
62040
|
resolveSceneBuildMode: () => resolveSceneBuildMode
|
|
60986
62041
|
});
|
|
60987
|
-
import { existsSync as existsSync49 } from "node:fs";
|
|
62042
|
+
import { existsSync as existsSync49, readFileSync as readFileSync3 } from "node:fs";
|
|
60988
62043
|
import { copyFile as copyFile5, mkdir as mkdir26, readFile as readFile46, writeFile as writeFile50 } from "node:fs/promises";
|
|
60989
62044
|
import { dirname as dirname24, join as join25, resolve as resolve65 } from "node:path";
|
|
60990
62045
|
async function executeSceneBuild(opts) {
|
|
@@ -61151,8 +62206,32 @@ async function executeSceneBuild(opts) {
|
|
|
61151
62206
|
const voice = opts.voice ?? parsed.frontmatter?.voice;
|
|
61152
62207
|
let beatOutcomes = collectExistingBeatOutcomes(parsed.beats, projectDir);
|
|
61153
62208
|
let pendingJobs = [];
|
|
62209
|
+
let characterCostUsd = 0;
|
|
61154
62210
|
if (shouldRunStage(selectedStage, "assets")) {
|
|
61155
62211
|
onProgress({ type: "phase-start", phase: "primitives" });
|
|
62212
|
+
const characterPaths = /* @__PURE__ */ new Map();
|
|
62213
|
+
if (!(opts.skipVideo ?? false)) {
|
|
62214
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
62215
|
+
for (const beat of activeBeats) {
|
|
62216
|
+
for (const name of beatCharacterNames(beat.cues)) referenced.add(name);
|
|
62217
|
+
}
|
|
62218
|
+
if (referenced.size > 0) {
|
|
62219
|
+
const charResult = await buildCharacters(
|
|
62220
|
+
resolveCharacters(parsed.frontmatter).filter((c) => referenced.has(c.name)),
|
|
62221
|
+
{
|
|
62222
|
+
projectDir,
|
|
62223
|
+
imageProvider,
|
|
62224
|
+
imageSize: opts.imageSize ?? "1536x1024",
|
|
62225
|
+
imageQuality: opts.imageQuality ?? "hd",
|
|
62226
|
+
force: opts.force ?? false,
|
|
62227
|
+
onProgress
|
|
62228
|
+
}
|
|
62229
|
+
);
|
|
62230
|
+
for (const [name, path2] of charResult.paths) characterPaths.set(name, path2);
|
|
62231
|
+
characterCostUsd = charResult.costUsd;
|
|
62232
|
+
warnings.push(...charResult.failures);
|
|
62233
|
+
}
|
|
62234
|
+
}
|
|
61156
62235
|
const primitiveResults = await mapWithConcurrency(
|
|
61157
62236
|
activeBeats,
|
|
61158
62237
|
BEAT_PRIMITIVES_CONCURRENCY,
|
|
@@ -61170,7 +62249,8 @@ async function executeSceneBuild(opts) {
|
|
|
61170
62249
|
skipVideo: opts.skipVideo ?? false,
|
|
61171
62250
|
skipMusic: opts.skipMusic ?? false,
|
|
61172
62251
|
force: opts.force ?? false,
|
|
61173
|
-
onProgress
|
|
62252
|
+
onProgress,
|
|
62253
|
+
characterPaths
|
|
61174
62254
|
})
|
|
61175
62255
|
);
|
|
61176
62256
|
beatOutcomes = primitiveResults.map((result) => result.outcome);
|
|
@@ -61179,10 +62259,7 @@ async function executeSceneBuild(opts) {
|
|
|
61179
62259
|
(beat) => beat.narrationStatus === "failed" || beat.backdropStatus === "failed" || beat.videoStatus === "failed" || beat.musicStatus === "failed"
|
|
61180
62260
|
);
|
|
61181
62261
|
stageReports.assets.status = assetFailed ? "failed" : pendingJobs.length > 0 ? "pending-jobs" : "done";
|
|
61182
|
-
stageReports.assets.costUsd = estimateActualAssetCost(
|
|
61183
|
-
beatOutcomes,
|
|
61184
|
-
opts.imageQuality ?? "hd"
|
|
61185
|
-
);
|
|
62262
|
+
stageReports.assets.costUsd = estimateActualAssetCost(beatOutcomes, opts.imageQuality ?? "hd") + characterCostUsd;
|
|
61186
62263
|
stageReports.assets.retryWith = pendingJobs.map(
|
|
61187
62264
|
(job) => `vibe status job ${job.id} --project ${projectDir} --json`
|
|
61188
62265
|
);
|
|
@@ -61347,6 +62424,18 @@ async function executeSceneBuild(opts) {
|
|
|
61347
62424
|
stageReports.compose.status = "skipped";
|
|
61348
62425
|
}
|
|
61349
62426
|
if (stageReports.compose.status === "done") {
|
|
62427
|
+
for (const beat of activeBeats) {
|
|
62428
|
+
const videoRel = `assets/video-${beat.id}.mp4`;
|
|
62429
|
+
const compositionAbs = join25(projectDir, "compositions", `scene-${beat.id}.html`);
|
|
62430
|
+
if (!existsSync49(join25(projectDir, videoRel)) || !existsSync49(compositionAbs)) continue;
|
|
62431
|
+
const html = readFileSync3(compositionAbs, "utf-8");
|
|
62432
|
+
if (!html.includes(videoRel)) {
|
|
62433
|
+
warnings.push(
|
|
62434
|
+
`Beat "${beat.id}" has a generated clip (${videoRel}) but its composition does not reference it, so the clip will not appear. Recompose the beat.`
|
|
62435
|
+
);
|
|
62436
|
+
retryWith.push(`vibe build ${projectDir} --beat ${beat.id} --stage compose --force --json`);
|
|
62437
|
+
}
|
|
62438
|
+
}
|
|
61350
62439
|
const repair = await runBuildSceneRepair(projectDir, "compose", false);
|
|
61351
62440
|
sceneRepair = mergeSceneRepairSummaries(sceneRepair, repair);
|
|
61352
62441
|
applySceneRepairToStage(stageReports.compose, repair);
|
|
@@ -61767,6 +62856,120 @@ async function dispatchNarration(beat, ctx) {
|
|
|
61767
62856
|
freshness: "fresh"
|
|
61768
62857
|
};
|
|
61769
62858
|
}
|
|
62859
|
+
function characterSheetPrompt(name, description) {
|
|
62860
|
+
return [
|
|
62861
|
+
`Character turnaround reference sheet of an original fictional character named ${name}: ${description}.`,
|
|
62862
|
+
"Show three full-body views side by side on one clean canvas \u2014 front view, side profile, and back view \u2014",
|
|
62863
|
+
"with identical outfit, hairstyle, hair color, and body proportions across all three views.",
|
|
62864
|
+
"Plain light-grey studio background, even neutral lighting, no props.",
|
|
62865
|
+
"Photorealistic digital character, professional concept-art character sheet layout."
|
|
62866
|
+
].join(" ");
|
|
62867
|
+
}
|
|
62868
|
+
async function buildCharacters(characters, ctx) {
|
|
62869
|
+
const paths = /* @__PURE__ */ new Map();
|
|
62870
|
+
const failures = [];
|
|
62871
|
+
let costUsd = 0;
|
|
62872
|
+
const size = ctx.imageSize ?? "1536x1024";
|
|
62873
|
+
for (const character of characters) {
|
|
62874
|
+
if (character.imagePath) {
|
|
62875
|
+
if (existsSync49(join25(ctx.projectDir, character.imagePath))) {
|
|
62876
|
+
paths.set(character.name, character.imagePath);
|
|
62877
|
+
ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
|
|
62878
|
+
} else {
|
|
62879
|
+
const error = `character "${character.name}" image not found: ${character.imagePath}`;
|
|
62880
|
+
failures.push(error);
|
|
62881
|
+
ctx.onProgress({ type: "character-failed", name: character.name, error });
|
|
62882
|
+
}
|
|
62883
|
+
continue;
|
|
62884
|
+
}
|
|
62885
|
+
if (!character.prompt) continue;
|
|
62886
|
+
const prompt3 = characterSheetPrompt(character.name, character.prompt);
|
|
62887
|
+
const rel = `assets/character-${character.name}.png`;
|
|
62888
|
+
const abs = join25(ctx.projectDir, rel);
|
|
62889
|
+
const metadataOptions = { quality: ctx.imageQuality, size };
|
|
62890
|
+
const cache = characterCacheDescriptor({
|
|
62891
|
+
name: character.name,
|
|
62892
|
+
cue: prompt3,
|
|
62893
|
+
provider: ctx.imageProvider,
|
|
62894
|
+
quality: ctx.imageQuality,
|
|
62895
|
+
size
|
|
62896
|
+
});
|
|
62897
|
+
if (existsSync49(abs) && !ctx.force) {
|
|
62898
|
+
const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
|
|
62899
|
+
if (!metadata || isFreshCanonicalAsset({
|
|
62900
|
+
projectDir: ctx.projectDir,
|
|
62901
|
+
kind: "character",
|
|
62902
|
+
beatId: character.name,
|
|
62903
|
+
cue: prompt3,
|
|
62904
|
+
provider: ctx.imageProvider,
|
|
62905
|
+
options: metadataOptions,
|
|
62906
|
+
cacheKey: cache.key
|
|
62907
|
+
})) {
|
|
62908
|
+
paths.set(character.name, rel);
|
|
62909
|
+
ctx.onProgress({ type: "character-cached", name: character.name, path: rel });
|
|
62910
|
+
continue;
|
|
62911
|
+
}
|
|
62912
|
+
}
|
|
62913
|
+
const cacheAbs = join25(ctx.projectDir, cache.path);
|
|
62914
|
+
if (existsSync49(cacheAbs) && !ctx.force) {
|
|
62915
|
+
await mkdir26(dirname24(abs), { recursive: true });
|
|
62916
|
+
await copyFile5(cacheAbs, abs);
|
|
62917
|
+
await writeAssetMetadata({
|
|
62918
|
+
projectDir: ctx.projectDir,
|
|
62919
|
+
kind: "character",
|
|
62920
|
+
beatId: character.name,
|
|
62921
|
+
cue: prompt3,
|
|
62922
|
+
provider: ctx.imageProvider,
|
|
62923
|
+
options: metadataOptions,
|
|
62924
|
+
cacheKey: cache.key,
|
|
62925
|
+
canonicalPath: rel,
|
|
62926
|
+
cachePath: cache.path
|
|
62927
|
+
});
|
|
62928
|
+
paths.set(character.name, rel);
|
|
62929
|
+
ctx.onProgress({ type: "character-cached", name: character.name, path: rel });
|
|
62930
|
+
continue;
|
|
62931
|
+
}
|
|
62932
|
+
const generated = await generateBackdropImage(
|
|
62933
|
+
prompt3,
|
|
62934
|
+
{
|
|
62935
|
+
projectDir: ctx.projectDir,
|
|
62936
|
+
imageProvider: ctx.imageProvider,
|
|
62937
|
+
imageSize: size,
|
|
62938
|
+
imageQuality: ctx.imageQuality
|
|
62939
|
+
},
|
|
62940
|
+
imageRatioForSize(size)
|
|
62941
|
+
);
|
|
62942
|
+
if (!generated.success) {
|
|
62943
|
+
failures.push(`character "${character.name}": ${generated.error}`);
|
|
62944
|
+
ctx.onProgress({ type: "character-failed", name: character.name, error: generated.error });
|
|
62945
|
+
continue;
|
|
62946
|
+
}
|
|
62947
|
+
await mkdir26(dirname24(abs), { recursive: true });
|
|
62948
|
+
await writeFile50(abs, generated.buffer);
|
|
62949
|
+
await mkdir26(dirname24(cacheAbs), { recursive: true });
|
|
62950
|
+
await writeFile50(cacheAbs, generated.buffer);
|
|
62951
|
+
await writeAssetMetadata({
|
|
62952
|
+
projectDir: ctx.projectDir,
|
|
62953
|
+
kind: "character",
|
|
62954
|
+
beatId: character.name,
|
|
62955
|
+
cue: prompt3,
|
|
62956
|
+
provider: ctx.imageProvider,
|
|
62957
|
+
options: metadataOptions,
|
|
62958
|
+
cacheKey: cache.key,
|
|
62959
|
+
canonicalPath: rel,
|
|
62960
|
+
cachePath: cache.path
|
|
62961
|
+
});
|
|
62962
|
+
costUsd += backdropCostUsd(ctx.imageQuality);
|
|
62963
|
+
paths.set(character.name, rel);
|
|
62964
|
+
ctx.onProgress({
|
|
62965
|
+
type: "character-generated",
|
|
62966
|
+
name: character.name,
|
|
62967
|
+
path: rel,
|
|
62968
|
+
provider: ctx.imageProvider
|
|
62969
|
+
});
|
|
62970
|
+
}
|
|
62971
|
+
return { paths, costUsd: Number(costUsd.toFixed(2)), failures };
|
|
62972
|
+
}
|
|
61770
62973
|
async function dispatchBackdrop(beat, ctx) {
|
|
61771
62974
|
const reference = assetReferenceForBeat(ctx.projectDir, "backdrop", beat);
|
|
61772
62975
|
if (reference) return referencePrimitiveOutcome("backdrop", beat, ctx, reference);
|
|
@@ -61936,13 +63139,16 @@ async function dispatchVideo(beat, ctx) {
|
|
|
61936
63139
|
if (reference) return referencePrimitiveOutcome("video", beat, ctx, reference);
|
|
61937
63140
|
const prompt3 = stringOrUndefined4(beat.cues?.video);
|
|
61938
63141
|
if (!prompt3) return { status: "no-cue" };
|
|
63142
|
+
const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
|
|
63143
|
+
const characterRefsAbs = characterRefsRel.map((p) => join25(ctx.projectDir, p));
|
|
61939
63144
|
const rel = `assets/video-${beat.id}.mp4`;
|
|
61940
63145
|
const abs = join25(ctx.projectDir, rel);
|
|
61941
63146
|
const cache = videoCacheDescriptor({
|
|
61942
63147
|
beatId: beat.id,
|
|
61943
63148
|
cue: prompt3,
|
|
61944
63149
|
provider: ctx.videoProvider,
|
|
61945
|
-
duration: normalizeVideoDuration(beat.duration)
|
|
63150
|
+
duration: normalizeVideoDuration(beat.duration),
|
|
63151
|
+
characters: characterRefsRel
|
|
61946
63152
|
});
|
|
61947
63153
|
const metadataPath = assetMetadataPath("video", beat.id);
|
|
61948
63154
|
if (existsSync49(abs) && !ctx.force) {
|
|
@@ -62002,6 +63208,7 @@ async function dispatchVideo(beat, ctx) {
|
|
|
62002
63208
|
ratio: "16:9",
|
|
62003
63209
|
output: abs,
|
|
62004
63210
|
wait: false,
|
|
63211
|
+
refImages: characterRefsAbs.length > 0 ? characterRefsAbs : void 0,
|
|
62005
63212
|
apiKey: await apiKeyForVideoProvider(ctx.videoProvider, ctx.projectDir)
|
|
62006
63213
|
});
|
|
62007
63214
|
if (!result.success || !result.taskId) {
|
|
@@ -62370,7 +63577,13 @@ function estimateActualAssetCost(outcomes, imageQuality) {
|
|
|
62370
63577
|
for (const outcome of outcomes) {
|
|
62371
63578
|
if (outcome.narrationStatus === "generated") cost += 0.05;
|
|
62372
63579
|
if (outcome.backdropStatus === "generated") cost += backdropCostUsd(imageQuality);
|
|
62373
|
-
if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending")
|
|
63580
|
+
if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") {
|
|
63581
|
+
cost += outcome.videoProvider === "seedance" || outcome.videoProvider === "fal" ? estimateSeedanceVideoCostUsd({
|
|
63582
|
+
durationSec: normalizeVideoDuration(outcome.sceneDurationSec),
|
|
63583
|
+
resolution: "720p",
|
|
63584
|
+
aspectRatio: "16:9"
|
|
63585
|
+
}) : 5;
|
|
63586
|
+
}
|
|
62374
63587
|
if (outcome.musicStatus === "generated" || outcome.musicStatus === "pending") cost += 0.5;
|
|
62375
63588
|
}
|
|
62376
63589
|
return Number(cost.toFixed(2));
|
|
@@ -67930,8 +69143,9 @@ function planBuildElicitation(args) {
|
|
|
67930
69143
|
description: "Which TTS engine narrates the video?",
|
|
67931
69144
|
enum: [...NARRATION_CHOICES],
|
|
67932
69145
|
enumNames: [
|
|
67933
|
-
"Kokoro \u2014 free, runs
|
|
67934
|
-
"
|
|
69146
|
+
"Kokoro \u2014 free, runs on this machine (first use downloads a ~90 MB voice model; slower)",
|
|
69147
|
+
"OpenAI \u2014 fast cloud voice, uses your OpenAI API key (~$0.02 per video)",
|
|
69148
|
+
"ElevenLabs \u2014 premium cloud voices, uses your ElevenLabs credits"
|
|
67935
69149
|
]
|
|
67936
69150
|
};
|
|
67937
69151
|
required.push("narration");
|
|
@@ -67987,7 +69201,7 @@ var NARRATION_CHOICES, BACKDROP_CHOICES;
|
|
|
67987
69201
|
var init_elicit = __esm({
|
|
67988
69202
|
"src/tools/_shared/elicit.ts"() {
|
|
67989
69203
|
"use strict";
|
|
67990
|
-
NARRATION_CHOICES = ["kokoro", "elevenlabs"];
|
|
69204
|
+
NARRATION_CHOICES = ["kokoro", "openai", "elevenlabs"];
|
|
67991
69205
|
BACKDROP_CHOICES = ["skip", "openai"];
|
|
67992
69206
|
}
|
|
67993
69207
|
});
|
|
@@ -68266,6 +69480,9 @@ var init_scene2 = __esm({
|
|
|
68266
69480
|
kicker: external_exports.string().optional().describe("Small label above the headline (used by 'explainer' and 'product-shot' presets)."),
|
|
68267
69481
|
projectDir: external_exports.string().optional().describe(PROJECT_DIR_DESCRIPTION),
|
|
68268
69482
|
insertInto: external_exports.string().optional().describe("Root composition file (relative to projectDir). Default 'index.html'."),
|
|
69483
|
+
syncStoryboard: external_exports.boolean().optional().describe(
|
|
69484
|
+
"Sync STORYBOARD.md when present, then rebuild root refs from it. Default true; set false for root-only insertion."
|
|
69485
|
+
),
|
|
68269
69486
|
imageProvider: external_exports.enum(["gemini", "openai"]).optional().describe("Image provider for visuals. Default 'gemini'."),
|
|
68270
69487
|
voice: external_exports.string().optional().describe("ElevenLabs voice id or name."),
|
|
68271
69488
|
skipAudio: external_exports.boolean().optional().describe("Skip TTS even if narration is provided."),
|
|
@@ -68278,7 +69495,7 @@ var init_scene2 = __esm({
|
|
|
68278
69495
|
cost: "low",
|
|
68279
69496
|
title: "Add Scene from Preset",
|
|
68280
69497
|
annotations: { readOnly: false, openWorld: true },
|
|
68281
|
-
description: "Add a single scene to an existing scene project. Optionally generates narration audio (ElevenLabs) and/or a backdrop image (Gemini/OpenAI), then emits compositions/scene-<id>.html with a paused GSAP timeline and
|
|
69498
|
+
description: "Add a single scene to an existing scene project. Optionally generates narration audio (ElevenLabs) and/or a backdrop image (Gemini/OpenAI), then emits compositions/scene-<id>.html with a paused GSAP timeline. By default, syncs STORYBOARD.md and rebuilds root refs from it; set syncStoryboard:false for root-only insertion. Use skipAudio:true and skipImage:true for text-only scenes that need no API calls.",
|
|
68282
69499
|
schema: sceneAddSchema,
|
|
68283
69500
|
async execute(args, ctx) {
|
|
68284
69501
|
const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
|
|
@@ -68292,6 +69509,7 @@ var init_scene2 = __esm({
|
|
|
68292
69509
|
kicker: args.kicker,
|
|
68293
69510
|
projectDir,
|
|
68294
69511
|
insertInto: args.insertInto,
|
|
69512
|
+
syncStoryboard: args.syncStoryboard,
|
|
68295
69513
|
imageProvider: args.imageProvider,
|
|
68296
69514
|
voice: args.voice,
|
|
68297
69515
|
skipAudio: args.skipAudio,
|
|
@@ -68310,6 +69528,9 @@ var init_scene2 = __esm({
|
|
|
68310
69528
|
];
|
|
68311
69529
|
if (result.audioPath) lines.push(` audio: ${result.audioPath}`);
|
|
68312
69530
|
if (result.imagePath) lines.push(` image: ${result.imagePath}`);
|
|
69531
|
+
if (result.storyboardPath && result.storyboardSynced) {
|
|
69532
|
+
lines.push(` story: ${result.storyboardPath} (${result.storyboardAction})`);
|
|
69533
|
+
}
|
|
68313
69534
|
return {
|
|
68314
69535
|
success: true,
|
|
68315
69536
|
data: {
|
|
@@ -68320,7 +69541,10 @@ var init_scene2 = __esm({
|
|
|
68320
69541
|
scenePath: result.scenePath,
|
|
68321
69542
|
rootPath: result.rootPath,
|
|
68322
69543
|
audioPath: result.audioPath,
|
|
68323
|
-
imagePath: result.imagePath
|
|
69544
|
+
imagePath: result.imagePath,
|
|
69545
|
+
storyboardPath: result.storyboardPath,
|
|
69546
|
+
storyboardSynced: result.storyboardSynced,
|
|
69547
|
+
storyboardAction: result.storyboardAction
|
|
68324
69548
|
},
|
|
68325
69549
|
humanLines: lines
|
|
68326
69550
|
};
|
|
@@ -68465,7 +69689,9 @@ var init_scene2 = __esm({
|
|
|
68465
69689
|
skipVideo: external_exports.boolean().optional().describe("Skip video generation for every beat (use existing MP4 assets if present)."),
|
|
68466
69690
|
skipMusic: external_exports.boolean().optional().describe("Skip music generation for every beat (use existing audio assets if present)."),
|
|
68467
69691
|
skipRender: external_exports.boolean().optional().describe("Stop after compose \u2014 produces compositions/*.html but no final MP4."),
|
|
68468
|
-
ttsProvider: external_exports.enum(["auto", "elevenlabs", "kokoro"]).optional().describe(
|
|
69692
|
+
ttsProvider: external_exports.enum(["auto", "elevenlabs", "openai", "kokoro"]).optional().describe(
|
|
69693
|
+
"TTS provider override. Default 'auto' (elevenlabs key > openai key > local kokoro)."
|
|
69694
|
+
),
|
|
68469
69695
|
voice: external_exports.string().optional().describe("TTS voice id (provider-specific)."),
|
|
68470
69696
|
imageProvider: external_exports.enum(["openai"]).optional().describe("Image provider for backdrops. Default 'openai' (gpt-image-2)."),
|
|
68471
69697
|
videoProvider: external_exports.enum(["seedance", "grok", "kling", "runway", "veo"]).optional().describe("Video provider for per-beat video cues. Default seedance."),
|
|
@@ -70320,7 +71546,7 @@ var init_storyboard2 = __esm({
|
|
|
70320
71546
|
skipBackdrop: external_exports.boolean().optional().describe("Don't include backdrop image generation in the plan."),
|
|
70321
71547
|
skipVideo: external_exports.boolean().optional().describe("Don't include video generation in the plan."),
|
|
70322
71548
|
skipMusic: external_exports.boolean().optional().describe("Don't include music generation in the plan."),
|
|
70323
|
-
ttsProvider: external_exports.enum(["auto", "elevenlabs", "kokoro"]).optional().describe("TTS provider override."),
|
|
71549
|
+
ttsProvider: external_exports.enum(["auto", "elevenlabs", "openai", "kokoro"]).optional().describe("TTS provider override."),
|
|
70324
71550
|
voice: external_exports.string().optional().describe("Voice id."),
|
|
70325
71551
|
imageProvider: external_exports.string().optional().describe("Image provider override."),
|
|
70326
71552
|
imageQuality: external_exports.enum(["standard", "hd"]).optional().describe("Image quality override."),
|
|
@@ -73436,7 +74662,7 @@ init_source();
|
|
|
73436
74662
|
// package.json
|
|
73437
74663
|
var package_default = {
|
|
73438
74664
|
name: "@vibeframe/cli",
|
|
73439
|
-
version: "0.
|
|
74665
|
+
version: "0.113.2",
|
|
73440
74666
|
description: "VibeFrame CLI - agentic video workflow runtime",
|
|
73441
74667
|
private: false,
|
|
73442
74668
|
license: "MIT",
|
|
@@ -73501,7 +74727,7 @@ var package_default = {
|
|
|
73501
74727
|
lint: "eslint --ext .ts,.tsx src/",
|
|
73502
74728
|
test: "vitest run",
|
|
73503
74729
|
"test:watch": "vitest",
|
|
73504
|
-
pretest: "node build.js",
|
|
74730
|
+
pretest: "pnpm --dir ../core build && pnpm --dir ../ai-providers build && node build.js",
|
|
73505
74731
|
prepublishOnly: "node build.js"
|
|
73506
74732
|
},
|
|
73507
74733
|
dependencies: {
|
|
@@ -82019,13 +83245,13 @@ function titleizeProjectName(name) {
|
|
|
82019
83245
|
return name.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ") || "Video";
|
|
82020
83246
|
}
|
|
82021
83247
|
function describeTheme(brief, product) {
|
|
82022
|
-
const cleaned = brief.replace(/\b\d+[- ]?second\b/gi, "").replace(new RegExp(`\\bfor\\s+${
|
|
83248
|
+
const cleaned = brief.replace(/\b\d+[- ]?second\b/gi, "").replace(new RegExp(`\\bfor\\s+${escapeRegExp5(product)}\\b`, "i"), "").replace(/\blaunch video\b/gi, "launch story").replace(/\s+/g, " ").trim();
|
|
82023
83249
|
return cleaned || "focused product story";
|
|
82024
83250
|
}
|
|
82025
83251
|
function isMountainSunriseBrief(brief) {
|
|
82026
83252
|
return /\b(mountain|ridge|sunrise|fog|peak)\b/i.test(brief);
|
|
82027
83253
|
}
|
|
82028
|
-
function
|
|
83254
|
+
function escapeRegExp5(value) {
|
|
82029
83255
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
82030
83256
|
}
|
|
82031
83257
|
|
|
@@ -82068,6 +83294,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
|
|
|
82068
83294
|
configKind: "codex-toml",
|
|
82069
83295
|
notes: [
|
|
82070
83296
|
"Codex reads AGENTS.md and can also load project-scoped .codex/config.toml after the project is trusted.",
|
|
83297
|
+
"Use Codex /goal as the outer loop for long-running VibeFrame projects; vibe supplies JSON reports, nextActions classified by cost/safety (prefer these), retryWith as a compatibility fallback, and repair commands.",
|
|
82071
83298
|
"Keep provider/auth keys in VibeFrame config or environment, not in project-local Codex config."
|
|
82072
83299
|
]
|
|
82073
83300
|
},
|
|
@@ -82080,6 +83307,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
|
|
|
82080
83307
|
configKind: "mcp-json",
|
|
82081
83308
|
notes: [
|
|
82082
83309
|
"Claude Code can drive vibe directly through shell plus AGENTS.md/CLAUDE.md.",
|
|
83310
|
+
"Use Claude Code /goal as the outer loop for long-running VibeFrame projects; vibe supplies JSON reports, nextActions classified by cost/safety (prefer these), retryWith as a compatibility fallback, and repair commands.",
|
|
82083
83311
|
"The MCP entry is optional and gives Claude Code a typed tool surface."
|
|
82084
83312
|
]
|
|
82085
83313
|
},
|
|
@@ -82154,7 +83382,15 @@ async function planHostSetup(host, projectDir, opts) {
|
|
|
82154
83382
|
status: opts.write ? "would-merge" : "would-write",
|
|
82155
83383
|
reason: opts.write ? "dry run" : "snippet only; pass --write to modify"
|
|
82156
83384
|
});
|
|
82157
|
-
return {
|
|
83385
|
+
return {
|
|
83386
|
+
host,
|
|
83387
|
+
snippet,
|
|
83388
|
+
command: host.id === "claude-code" ? claudeCodeAddCommand() : void 0,
|
|
83389
|
+
configPath,
|
|
83390
|
+
files,
|
|
83391
|
+
warnings,
|
|
83392
|
+
nextSteps
|
|
83393
|
+
};
|
|
82158
83394
|
}
|
|
82159
83395
|
try {
|
|
82160
83396
|
const action = host.configKind === "codex-toml" ? await mergeCodexToml(configPath, { force: opts.force === true }) : await mergeMcpJson(configPath, {
|
|
@@ -82166,7 +83402,15 @@ async function planHostSetup(host, projectDir, opts) {
|
|
|
82166
83402
|
} catch (error) {
|
|
82167
83403
|
warnings.push(error instanceof Error ? error.message : String(error));
|
|
82168
83404
|
}
|
|
82169
|
-
return {
|
|
83405
|
+
return {
|
|
83406
|
+
host,
|
|
83407
|
+
snippet,
|
|
83408
|
+
command: host.id === "claude-code" ? claudeCodeAddCommand() : void 0,
|
|
83409
|
+
configPath,
|
|
83410
|
+
files,
|
|
83411
|
+
warnings,
|
|
83412
|
+
nextSteps
|
|
83413
|
+
};
|
|
82170
83414
|
}
|
|
82171
83415
|
async function inspectHost(host, projectDir) {
|
|
82172
83416
|
const configPath = host.configPath(projectDir);
|
|
@@ -82210,7 +83454,9 @@ async function mergeMcpJson(configPath, opts) {
|
|
|
82210
83454
|
try {
|
|
82211
83455
|
root = raw.trim() ? JSON.parse(raw) : {};
|
|
82212
83456
|
} catch (error) {
|
|
82213
|
-
throw new Error(
|
|
83457
|
+
throw new Error(
|
|
83458
|
+
`Invalid JSON in ${configPath}: ${error instanceof Error ? error.message : String(error)}`
|
|
83459
|
+
);
|
|
82214
83460
|
}
|
|
82215
83461
|
}
|
|
82216
83462
|
const mcpServers = typeof root.mcpServers === "object" && root.mcpServers !== null && !Array.isArray(root.mcpServers) ? root.mcpServers : {};
|
|
@@ -82223,10 +83469,18 @@ async function mergeMcpJson(configPath, opts) {
|
|
|
82223
83469
|
};
|
|
82224
83470
|
await mkdir23(dirname19(configPath), { recursive: true });
|
|
82225
83471
|
if (exists && opts.backup) {
|
|
82226
|
-
await writeFile45(
|
|
83472
|
+
await writeFile45(
|
|
83473
|
+
`${configPath}.bak-${timestamp()}`,
|
|
83474
|
+
await readFile39(configPath, "utf-8"),
|
|
83475
|
+
"utf-8"
|
|
83476
|
+
);
|
|
82227
83477
|
}
|
|
82228
83478
|
await writeFile45(configPath, JSON.stringify(root, null, 2) + "\n", "utf-8");
|
|
82229
|
-
return {
|
|
83479
|
+
return {
|
|
83480
|
+
path: configPath,
|
|
83481
|
+
status: "merged",
|
|
83482
|
+
reason: "anchored existing default vibeframe MCP server"
|
|
83483
|
+
};
|
|
82230
83484
|
}
|
|
82231
83485
|
return {
|
|
82232
83486
|
path: configPath,
|
|
@@ -82237,7 +83491,11 @@ async function mergeMcpJson(configPath, opts) {
|
|
|
82237
83491
|
root.mcpServers = { ...mcpServers, vibeframe: opts.server };
|
|
82238
83492
|
await mkdir23(dirname19(configPath), { recursive: true });
|
|
82239
83493
|
if (exists && opts.backup) {
|
|
82240
|
-
await writeFile45(
|
|
83494
|
+
await writeFile45(
|
|
83495
|
+
`${configPath}.bak-${timestamp()}`,
|
|
83496
|
+
await readFile39(configPath, "utf-8"),
|
|
83497
|
+
"utf-8"
|
|
83498
|
+
);
|
|
82241
83499
|
}
|
|
82242
83500
|
await writeFile45(configPath, JSON.stringify(root, null, 2) + "\n", "utf-8");
|
|
82243
83501
|
return { path: configPath, status: exists ? "merged" : "wrote" };
|
|
@@ -82248,16 +83506,22 @@ async function mergeCodexToml(configPath, opts) {
|
|
|
82248
83506
|
${CODEX_MCP_BLOCK}${CODEX_MANAGED_END}`;
|
|
82249
83507
|
if (!exists) {
|
|
82250
83508
|
await mkdir23(dirname19(configPath), { recursive: true });
|
|
82251
|
-
await writeFile45(
|
|
83509
|
+
await writeFile45(
|
|
83510
|
+
configPath,
|
|
83511
|
+
`#:schema https://developers.openai.com/codex/config-schema.json
|
|
82252
83512
|
|
|
82253
83513
|
${block}
|
|
82254
|
-
`,
|
|
83514
|
+
`,
|
|
83515
|
+
"utf-8"
|
|
83516
|
+
);
|
|
82255
83517
|
return { path: configPath, status: "wrote" };
|
|
82256
83518
|
}
|
|
82257
83519
|
const raw = await readFile39(configPath, "utf-8");
|
|
82258
83520
|
if (raw.includes(CODEX_MANAGED_START)) {
|
|
82259
83521
|
const updated = raw.replace(
|
|
82260
|
-
new RegExp(
|
|
83522
|
+
new RegExp(
|
|
83523
|
+
`${escapeRegExp6(CODEX_MANAGED_START)}[\\s\\S]*?${escapeRegExp6(CODEX_MANAGED_END)}`
|
|
83524
|
+
),
|
|
82261
83525
|
block
|
|
82262
83526
|
);
|
|
82263
83527
|
await writeFile45(configPath, ensureTrailingNewline(updated), "utf-8");
|
|
@@ -82291,7 +83555,8 @@ function hasVibeframeMcpServer(value) {
|
|
|
82291
83555
|
function readVibeframeMcpServer(value) {
|
|
82292
83556
|
if (!value || typeof value !== "object") return null;
|
|
82293
83557
|
const root = value;
|
|
82294
|
-
if (!root.mcpServers || typeof root.mcpServers !== "object" || Array.isArray(root.mcpServers))
|
|
83558
|
+
if (!root.mcpServers || typeof root.mcpServers !== "object" || Array.isArray(root.mcpServers))
|
|
83559
|
+
return null;
|
|
82295
83560
|
const server = root.mcpServers.vibeframe;
|
|
82296
83561
|
if (!server || typeof server !== "object" || Array.isArray(server)) return null;
|
|
82297
83562
|
return server;
|
|
@@ -82302,7 +83567,9 @@ function isAnchoredClaudeDesktopServer(server) {
|
|
|
82302
83567
|
return server.args[0] === "-lc" && typeof server.args[1] === "string" && server.args[1].includes("exec npx -y @vibeframe/mcp-server");
|
|
82303
83568
|
}
|
|
82304
83569
|
if (server.command === "cmd.exe" && Array.isArray(server.args)) {
|
|
82305
|
-
return server.args.some(
|
|
83570
|
+
return server.args.some(
|
|
83571
|
+
(arg) => typeof arg === "string" && arg.includes("npx -y @vibeframe/mcp-server")
|
|
83572
|
+
);
|
|
82306
83573
|
}
|
|
82307
83574
|
return false;
|
|
82308
83575
|
}
|
|
@@ -82319,13 +83586,15 @@ function hostNextSteps(host) {
|
|
|
82319
83586
|
if (host.id === "codex") {
|
|
82320
83587
|
return [
|
|
82321
83588
|
"Trust the project in Codex so .codex/config.toml is loaded.",
|
|
82322
|
-
"Run /mcp in Codex to confirm the vibeframe server is connected."
|
|
83589
|
+
"Run /mcp in Codex to confirm the vibeframe server is connected.",
|
|
83590
|
+
"Use /goal with the project AGENTS.md stop rules for long-running video builds."
|
|
82323
83591
|
];
|
|
82324
83592
|
}
|
|
82325
83593
|
if (host.id === "claude-code") {
|
|
82326
83594
|
return [
|
|
82327
83595
|
"Restart Claude Code or run /mcp to approve the project-scoped server.",
|
|
82328
|
-
"Use AGENTS.md/CLAUDE.md for shell-driven vibe workflows."
|
|
83596
|
+
"Use AGENTS.md/CLAUDE.md for shell-driven vibe workflows.",
|
|
83597
|
+
"Use /goal with the project AGENTS.md stop rules for long-running video builds."
|
|
82329
83598
|
];
|
|
82330
83599
|
}
|
|
82331
83600
|
if (host.id === "claude-desktop") {
|
|
@@ -82339,7 +83608,11 @@ function claudeDesktopConfigPath() {
|
|
|
82339
83608
|
return join21(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
82340
83609
|
}
|
|
82341
83610
|
if (process.platform === "win32") {
|
|
82342
|
-
return join21(
|
|
83611
|
+
return join21(
|
|
83612
|
+
process.env.APPDATA ?? join21(home, "AppData", "Roaming"),
|
|
83613
|
+
"Claude",
|
|
83614
|
+
"claude_desktop_config.json"
|
|
83615
|
+
);
|
|
82343
83616
|
}
|
|
82344
83617
|
return join21(home, ".config", "Claude", "claude_desktop_config.json");
|
|
82345
83618
|
}
|
|
@@ -82350,7 +83623,7 @@ function ensureTrailingNewline(value) {
|
|
|
82350
83623
|
return value.endsWith("\n") ? value : `${value}
|
|
82351
83624
|
`;
|
|
82352
83625
|
}
|
|
82353
|
-
function
|
|
83626
|
+
function escapeRegExp6(value) {
|
|
82354
83627
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
82355
83628
|
}
|
|
82356
83629
|
function shellQuote(value) {
|
|
@@ -82542,6 +83815,45 @@ Default setup is snippet-only; use \`--write\` when you want VibeFrame to merge
|
|
|
82542
83815
|
the generated config. Keep provider keys in \`.vibeframe/config.yaml\`, user
|
|
82543
83816
|
config, or env files \u2014 not in app host config.
|
|
82544
83817
|
|
|
83818
|
+
## Native host goal loop
|
|
83819
|
+
|
|
83820
|
+
For long-running work, use Codex Goal mode, Claude Code \`/goal\`, Cursor, or
|
|
83821
|
+
another host-native goal feature as the outer loop. VibeFrame should not own a
|
|
83822
|
+
competing project-level goal runner. It provides \`--json\` commands, dry runs,
|
|
83823
|
+
budget caps, \`build-report.json\`, \`review-report.json\`, \`nextActions\`,
|
|
83824
|
+
\`safeToAutoRun\`, \`requiresConfirmation\`, \`fixOwner\`, deterministic repair,
|
|
83825
|
+
and render inspection for the host agent to decide what to do next.
|
|
83826
|
+
|
|
83827
|
+
Copy-paste Codex:
|
|
83828
|
+
|
|
83829
|
+
\`\`\`text
|
|
83830
|
+
/goal Build this VibeFrame project into renders/final.mp4. Use --json for every
|
|
83831
|
+
vibe command, run --dry-run before paid operations, use --max-cost 5 for builds
|
|
83832
|
+
unless the user gives another budget, read build-report.json and
|
|
83833
|
+
review-report.json before deciding the next action, prefer nextActions before
|
|
83834
|
+
guessing, run only safeToAutoRun:true actions automatically, and ask before
|
|
83835
|
+
requiresConfirmation:true actions. Treat retryWith as the compatibility
|
|
83836
|
+
fallback, fixOwner:"vibe" as CLI repair work, and fixOwner:"host-agent" as
|
|
83837
|
+
STORYBOARD.md, DESIGN.md, or composition edits.
|
|
83838
|
+
Stop only when renders/final.mp4 exists, target duration and aspect ratio are
|
|
83839
|
+
met, inspect render --cheap has no errors, any AI review score is >= 90 when AI review is requested, and every
|
|
83840
|
+
remaining host-agent issue is fixed, accepted with rationale, or reported as
|
|
83841
|
+
blocked.
|
|
83842
|
+
\`\`\`
|
|
83843
|
+
|
|
83844
|
+
Copy-paste Claude Code:
|
|
83845
|
+
|
|
83846
|
+
\`\`\`text
|
|
83847
|
+
/goal Finish this VibeFrame render using Claude Code goal mode as the outer
|
|
83848
|
+
loop. Use vibe context/schema when unsure, --json everywhere, dry-run before
|
|
83849
|
+
paid operations, budget cap via --max-cost 5, nextActions before guessing,
|
|
83850
|
+
build-report.json/review-report.json as loop state, and safeToAutoRun,
|
|
83851
|
+
requiresConfirmation, and fixOwner to decide between vibe scene repair and
|
|
83852
|
+
host-agent edits. Stop only after final MP4,
|
|
83853
|
+
target duration/aspect ratio, clean render inspection, any AI review score >= 90 when AI review is requested, and
|
|
83854
|
+
no unresolved unacknowledged host-agent issues.
|
|
83855
|
+
\`\`\`
|
|
83856
|
+
|
|
82545
83857
|
### Composition rules (scene HTML)
|
|
82546
83858
|
|
|
82547
83859
|
\`vibe init\` installs local composition rules into your project. The
|
|
@@ -83439,13 +84751,13 @@ init_output();
|
|
|
83439
84751
|
import { resolve as resolve61 } from "node:path";
|
|
83440
84752
|
var VALID_STAGES = ["assets", "compose", "sync", "render", "all"];
|
|
83441
84753
|
var VALID_MODES = ["agent", "batch", "auto"];
|
|
83442
|
-
var VALID_TTS_PROVIDERS = ["auto", "elevenlabs", "kokoro"];
|
|
84754
|
+
var VALID_TTS_PROVIDERS = ["auto", "elevenlabs", "openai", "kokoro"];
|
|
83443
84755
|
var VALID_IMAGE_PROVIDERS = ["openai", "gemini", "grok"];
|
|
83444
84756
|
var VALID_VIDEO_PROVIDERS = ["seedance", "grok", "kling", "runway", "veo"];
|
|
83445
84757
|
var VALID_MUSIC_PROVIDERS = ["elevenlabs", "replicate"];
|
|
83446
84758
|
var VALID_COMPOSERS = ["claude", "openai", "gemini"];
|
|
83447
84759
|
var VALID_IMAGE_QUALITIES = ["standard", "hd"];
|
|
83448
|
-
var planCommand = new Command("plan").description("Read STORYBOARD.md and show build plan, costs, missing cues, and provider needs").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Stage to plan: ${VALID_STAGES.join("|")}`, "all").option("--beat <id>", "Restrict the plan to one beat").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--skip-narration", "Don't include narration generation in the plan").option("--skip-backdrop", "Don't include backdrop image generation in the plan").option("--skip-video", "Don't include video generation in the plan").option("--skip-music", "Don't include music generation in the plan").option("--tts <provider>", "TTS provider: auto|elevenlabs|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS.join("|")}`).option("--quality <q>", "Image quality: standard|hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--force", "Plan regeneration even when outputs already exist").option("--max-cost <usd>", "Fail if estimated cost exceeds this USD cap").action(async (projectDirArg, options) => {
|
|
84760
|
+
var planCommand = new Command("plan").description("Read STORYBOARD.md and show build plan, costs, missing cues, and provider needs").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Stage to plan: ${VALID_STAGES.join("|")}`, "all").option("--beat <id>", "Restrict the plan to one beat").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--skip-narration", "Don't include narration generation in the plan").option("--skip-backdrop", "Don't include backdrop image generation in the plan").option("--skip-video", "Don't include video generation in the plan").option("--skip-music", "Don't include music generation in the plan").option("--tts <provider>", "TTS provider: auto|elevenlabs|openai|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS.join("|")}`).option("--quality <q>", "Image quality: standard|hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--force", "Plan regeneration even when outputs already exist").option("--max-cost <usd>", "Fail if estimated cost exceeds this USD cap").action(async (projectDirArg, options) => {
|
|
83449
84761
|
const startedAt = Date.now();
|
|
83450
84762
|
const stage = String(options.stage ?? "all");
|
|
83451
84763
|
if (!VALID_STAGES.includes(stage)) {
|
|
@@ -83630,13 +84942,13 @@ init_build_plan();
|
|
|
83630
84942
|
init_storyboard_parse();
|
|
83631
84943
|
init_output();
|
|
83632
84944
|
import { resolve as resolve66, join as join26 } from "node:path";
|
|
83633
|
-
import { readFileSync as
|
|
84945
|
+
import { readFileSync as readFileSync4, existsSync as existsSync50 } from "node:fs";
|
|
83634
84946
|
var VALID_MODES2 = ["agent", "batch", "auto"];
|
|
83635
84947
|
var VALID_STAGES2 = ["assets", "compose", "sync", "render", "all"];
|
|
83636
84948
|
var VALID_IMAGE_PROVIDERS2 = ["openai", "gemini", "grok"];
|
|
83637
84949
|
var VALID_VIDEO_PROVIDERS2 = ["seedance", "grok", "kling", "runway", "veo"];
|
|
83638
84950
|
var VALID_MUSIC_PROVIDERS2 = ["elevenlabs", "replicate"];
|
|
83639
|
-
var buildCommand = new Command("build").description("Build a VibeFrame video project from STORYBOARD.md").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Build stage: ${VALID_STAGES2.join("|")}`, "all").option("--beat <id>", "Restrict asset/compose work to one beat id").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--max-cost <usd>", "Fail before provider spend when estimated cost exceeds this USD cap").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-video", "Don't dispatch video generation even when beats declare video cues").option("--skip-music", "Don't dispatch music generation even when beats declare music cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS2.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS2.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS2.join("|")}`).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").addHelpText(
|
|
84951
|
+
var buildCommand = new Command("build").description("Build a VibeFrame video project from STORYBOARD.md").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Build stage: ${VALID_STAGES2.join("|")}`, "all").option("--beat <id>", "Restrict asset/compose work to one beat id").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--max-cost <usd>", "Fail before provider spend when estimated cost exceeds this USD cap").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-video", "Don't dispatch video generation even when beats declare video cues").option("--skip-music", "Don't dispatch music generation even when beats declare music cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|openai|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS2.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS2.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS2.join("|")}`).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").addHelpText(
|
|
83640
84952
|
"after",
|
|
83641
84953
|
`
|
|
83642
84954
|
Examples:
|
|
@@ -83997,7 +85309,7 @@ function estimateBuildCost(params) {
|
|
|
83997
85309
|
if (!existsSync50(storyboardPath2)) return null;
|
|
83998
85310
|
let beats = 0;
|
|
83999
85311
|
try {
|
|
84000
|
-
const md =
|
|
85312
|
+
const md = readFileSync4(storyboardPath2, "utf-8");
|
|
84001
85313
|
beats = parseStoryboard(md).beats.length;
|
|
84002
85314
|
} catch {
|
|
84003
85315
|
return null;
|
|
@@ -84381,6 +85693,18 @@ function printProjectStatus(result) {
|
|
|
84381
85693
|
console.log(` Build: ${result.build ? formatBuild(result.build) : source_default.dim("no build-report.json")}`);
|
|
84382
85694
|
console.log(` Review: ${result.review ? formatReview(result.review) : source_default.dim("no review-report.json")}`);
|
|
84383
85695
|
console.log(` Jobs: ${result.jobs.total} total, ${result.jobs.active} active, ${result.jobs.failed} failed`);
|
|
85696
|
+
if (result.nextActions.length > 0) {
|
|
85697
|
+
console.log();
|
|
85698
|
+
console.log(source_default.bold("Next actions"));
|
|
85699
|
+
for (const action of result.nextActions.slice(0, 5)) {
|
|
85700
|
+
console.log(formatNextAction(action));
|
|
85701
|
+
if (action.command) console.log(source_default.dim(` ${action.command}`));
|
|
85702
|
+
if (action.agentPrompt) console.log(source_default.dim(` ${action.agentPrompt}`));
|
|
85703
|
+
}
|
|
85704
|
+
if (result.nextActions.length > 5) {
|
|
85705
|
+
console.log(source_default.dim(` ... ${result.nextActions.length - 5} more`));
|
|
85706
|
+
}
|
|
85707
|
+
}
|
|
84384
85708
|
if (result.jobs.latest.length > 0) {
|
|
84385
85709
|
console.log();
|
|
84386
85710
|
for (const job of result.jobs.latest.slice(0, 5)) {
|
|
@@ -84401,6 +85725,10 @@ function formatReview(review) {
|
|
|
84401
85725
|
const status = review.status ?? "unknown";
|
|
84402
85726
|
return `${formatStatus(status)} score ${review.score ?? "-"} (${review.issueCount} issue${review.issueCount === 1 ? "" : "s"})`;
|
|
84403
85727
|
}
|
|
85728
|
+
function formatNextAction(action) {
|
|
85729
|
+
const badge = action.safeToAutoRun ? "auto" : action.requiresConfirmation ? "confirm" : action.kind;
|
|
85730
|
+
return ` ${source_default.dim(`[${badge}]`)} ${action.label}`;
|
|
85731
|
+
}
|
|
84404
85732
|
function formatStatus(status) {
|
|
84405
85733
|
if (status === "completed" || status === "pass") return source_default.green(status);
|
|
84406
85734
|
if (status === "failed" || status === "fail") return source_default.red(status);
|
|
@@ -85407,6 +86735,8 @@ var contextCommand = new Command("context").description("Print CLI context/guide
|
|
|
85407
86735
|
product: "vibeframe",
|
|
85408
86736
|
sourceOfTruth: ["STORYBOARD.md", "DESIGN.md", "vibe.config.json"],
|
|
85409
86737
|
preferredFlow: [
|
|
86738
|
+
"native Codex Goal mode or Claude Code /goal owns persistence, iteration, and stop decisions",
|
|
86739
|
+
"vibe context/schema for command discovery",
|
|
85410
86740
|
"storyboard revise after init when the brief changes",
|
|
85411
86741
|
"storyboard validate",
|
|
85412
86742
|
"plan",
|
|
@@ -85421,6 +86751,19 @@ var contextCommand = new Command("context").description("Print CLI context/guide
|
|
|
85421
86751
|
"inspect render --ai when needed",
|
|
85422
86752
|
"status project when async jobs are involved"
|
|
85423
86753
|
],
|
|
86754
|
+
nativeGoalContract: {
|
|
86755
|
+
owner: "host-native goal mode (Codex /goal, Claude Code /goal, Cursor or other host equivalent)",
|
|
86756
|
+
vibeRole: "video-specific commands, JSON reports, cost gates, deterministic repair, render inspection, nextActions, and retryWith compatibility hints",
|
|
86757
|
+
canonicalLoop: "native host goal -> vibe context/schema -> plan dry-run -> build with budget -> status polling -> inspect project -> render -> inspect render -> repair/edit using nextActions/fixOwner -> repeat",
|
|
86758
|
+
stopRules: [
|
|
86759
|
+
"final MP4 exists at the requested output path",
|
|
86760
|
+
"duration and aspect ratio match the brief or explicitly accepted constraints",
|
|
86761
|
+
"inspect render --cheap reports no errors",
|
|
86762
|
+
"any AI review score meets the goal threshold, commonly >= 90, when AI review is requested",
|
|
86763
|
+
"all fixOwner:vibe issues have been repaired or are blocked with evidence",
|
|
86764
|
+
"all fixOwner:host-agent issues are fixed, intentionally accepted with a written reason, or reported as blocked"
|
|
86765
|
+
]
|
|
86766
|
+
},
|
|
85424
86767
|
mentalModel: {
|
|
85425
86768
|
storyboard: "intent layer; edit or mutate beat cues here",
|
|
85426
86769
|
scene: "generated artifact layer; lint/repair composition HTML here"
|
|
@@ -85454,7 +86797,8 @@ var contextCommand = new Command("context").description("Print CLI context/guide
|
|
|
85454
86797
|
reviewReportContract: {
|
|
85455
86798
|
kind: "review",
|
|
85456
86799
|
modes: ["project", "render"],
|
|
85457
|
-
fields: ["status", "score", "issues", "summary", "sourceReports", "retryWith"],
|
|
86800
|
+
fields: ["status", "score", "issues", "summary", "sourceReports", "nextActions", "retryWith"],
|
|
86801
|
+
recovery: "prefer nextActions (classified by costTier/safeToAutoRun/requiresConfirmation/fixOwner); retryWith is the compatibility fallback",
|
|
85458
86802
|
issueFixOwner: {
|
|
85459
86803
|
vibe: "deterministic CLI recovery",
|
|
85460
86804
|
"host-agent": "storyboard/design/composition edits the host agent should make"
|
|
@@ -85462,7 +86806,7 @@ var contextCommand = new Command("context").description("Print CLI context/guide
|
|
|
85462
86806
|
},
|
|
85463
86807
|
machineStatusContract: {
|
|
85464
86808
|
buildReport: "kind/status/currentStage/beatSummary/jobs/sceneRepair/stageReports/warnings/retryWith plus beat timing and nested per-beat asset metadata",
|
|
85465
|
-
projectStatus: "kind/status/currentStage/beats/jobs.latest/build/review/warnings/retryWith",
|
|
86809
|
+
projectStatus: "kind/status/currentStage/beats/jobs.latest/build/review/warnings/nextActions/retryWith (nextActions is populated for every workflow state, not only when a review report exists)",
|
|
85466
86810
|
reviewSummary: "review.mode/issueCount/errorCount/warningCount/infoCount/fixOwners/sourceReports/retryWith"
|
|
85467
86811
|
},
|
|
85468
86812
|
productSurfaceContract: {
|
|
@@ -85545,12 +86889,13 @@ render. Repair failures return \`code:"SCENE_REPAIR_FAILED"\` with
|
|
|
85545
86889
|
render with \`currentStage:"assets"\` and \`code:"ASSET_REFERENCE_INVALID"\`,
|
|
85546
86890
|
\`code:"MISSING_API_KEY"\`, or \`code:"ASSET_GENERATION_FAILED"\`.
|
|
85547
86891
|
\`build-report.json\` includes \`sceneRepair\`, and \`status project\` carries
|
|
85548
|
-
review issue counts, \`fixOwners\`, \`sourceReports\`,
|
|
85549
|
-
\`
|
|
86892
|
+
review issue counts, \`fixOwners\`, \`sourceReports\`, top-level
|
|
86893
|
+
\`nextActions\` (populated for every workflow state), and \`retryWith\`.
|
|
86894
|
+
Prefer \`nextActions\` and treat \`retryWith\` as the compatibility fallback.
|
|
85550
86895
|
|
|
85551
86896
|
\`review-report.json\` is written by \`inspect project\` and \`inspect render\`
|
|
85552
86897
|
as \`kind:"review"\` with \`mode:"project"|"render"\`, \`summary\`,
|
|
85553
|
-
\`sourceReports\`, \`retryWith\`, and issue-level
|
|
86898
|
+
\`sourceReports\`, \`nextActions\`, \`retryWith\`, and issue-level
|
|
85554
86899
|
\`fixOwner:"vibe"|"host-agent"\`.
|
|
85555
86900
|
\`inspect render --cheap\` checks duration drift, audio presence, black frames,
|
|
85556
86901
|
long silence, and static-frame holds. Static or semantic beat issues are
|
|
@@ -85572,6 +86917,8 @@ Use 'vibe schema --list --surface public' for the small product surface.
|
|
|
85572
86917
|
Use 'vibe schema <command> --json' to get parameter schemas.
|
|
85573
86918
|
Use 'vibe doctor --json' to check configured API keys.
|
|
85574
86919
|
Use '--dry-run --json' before any mutating/costly operation.
|
|
86920
|
+
Use native Codex Goal mode or Claude Code /goal as the outer loop; VibeFrame provides JSON reports, retryWith, fixOwner, cost gates, and render inspection for that host loop.
|
|
86921
|
+
Stop only when the final MP4 exists, target duration/aspect ratio are met, inspect render has no errors, any AI review score meets the goal threshold when AI review is requested, and every host-agent issue is fixed, accepted with rationale, or reported blocked.
|
|
85575
86922
|
|
|
85576
86923
|
Cost tiers: Free (schema/context/doctor/detect/status/plan/storyboard validate/inspect project/render --cheap, deterministic edits) | Low (generate narration/sound-effect/music, audio transcribe, inspect media, optional AI review) | High (generate image/motion, edit image/reframe/grade/speed-ramp) | Very High (generate video, edit fill-gaps, remix highlights/auto-shorts, build with generated assets)
|
|
85577
86924
|
|