@vibeframe/cli 0.108.0 → 0.109.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -85,6 +85,25 @@ export declare function isEpsilonOverlapFinding(finding: LintFinding): boolean;
|
|
|
85
85
|
* findings pass through unchanged.
|
|
86
86
|
*/
|
|
87
87
|
export declare function filterSubCompFalsePositives(findings: LintFinding[], isSubComposition: boolean): LintFinding[];
|
|
88
|
+
/**
|
|
89
|
+
* VibeFrame-side finding: inner phase clips inside a sub-composition.
|
|
90
|
+
*
|
|
91
|
+
* The producer's screenshot render mode does NOT toggle the visibility of
|
|
92
|
+
* `.clip` elements INSIDE a sub-composition by their `data-start` window —
|
|
93
|
+
* a scene split into clip phases (0-10s, 10-26s) renders BOTH phases
|
|
94
|
+
* simultaneously and text stacks on top of itself. Root index.html clips
|
|
95
|
+
* are unaffected (their windows work); this rule only applies to
|
|
96
|
+
* sub-composition internals. Phase changes must instead be driven by GSAP
|
|
97
|
+
* (animate the old phase out with autoAlpha, the new one in) inside
|
|
98
|
+
* full-window clips.
|
|
99
|
+
*/
|
|
100
|
+
export declare function findInternalPhaseClipFindings(html: string): LintFinding[];
|
|
101
|
+
/**
|
|
102
|
+
* Append VibeFrame-side findings for sub-composition HTML. Call at every
|
|
103
|
+
* lint site (batch composer, scene_submit, scene repair, project lint) so
|
|
104
|
+
* all authoring paths share the exact same contract.
|
|
105
|
+
*/
|
|
106
|
+
export declare function withVibeframeSubCompFindings(findings: LintFinding[], html: string, isSubComposition: boolean): LintFinding[];
|
|
88
107
|
/**
|
|
89
108
|
* Apply mechanical `--fix` rewrites for known-safe finding codes. Returns the
|
|
90
109
|
* (possibly unchanged) HTML and the list of codes that were fixed at least
|
|
@@ -12,6 +12,8 @@ export interface StoryboardValidationResult {
|
|
|
12
12
|
beats: Beat[];
|
|
13
13
|
issues: StoryboardValidationIssue[];
|
|
14
14
|
}
|
|
15
|
+
/** Beats beyond this render as static, overstuffed scenes. */
|
|
16
|
+
export declare const MAX_RECOMMENDED_BEAT_SEC = 15;
|
|
15
17
|
export declare function validateStoryboardMarkdown(markdown: string): StoryboardValidationResult;
|
|
16
18
|
export declare function getStoryboardBeat(markdown: string, beatId: string): Beat | null;
|
|
17
19
|
export declare function setStoryboardCue(markdown: string, opts: {
|
package/dist/index.js
CHANGED
|
@@ -11604,13 +11604,32 @@ async function loadKokoroFactory() {
|
|
|
11604
11604
|
const mod = await import("kokoro-js");
|
|
11605
11605
|
return mod.KokoroTTS;
|
|
11606
11606
|
} catch (err) {
|
|
11607
|
+
const fallback2 = await loadKokoroFromWorkspace();
|
|
11608
|
+
if (fallback2)
|
|
11609
|
+
return fallback2;
|
|
11607
11610
|
throw mapKokoroImportError(err);
|
|
11608
11611
|
}
|
|
11609
11612
|
}
|
|
11613
|
+
async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
|
|
11614
|
+
try {
|
|
11615
|
+
const { readFile: readFile55 } = await import("node:fs/promises");
|
|
11616
|
+
const { join: join29, resolve: resolve85 } = await import("node:path");
|
|
11617
|
+
const { pathToFileURL } = await import("node:url");
|
|
11618
|
+
const pkgDir = resolve85(baseDir, "node_modules", "kokoro-js");
|
|
11619
|
+
const pkg = JSON.parse(await readFile55(join29(pkgDir, "package.json"), "utf-8"));
|
|
11620
|
+
const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
|
|
11621
|
+
if (!entry)
|
|
11622
|
+
return null;
|
|
11623
|
+
const mod = await import(pathToFileURL(join29(pkgDir, entry)).href);
|
|
11624
|
+
return mod.KokoroTTS ?? null;
|
|
11625
|
+
} catch {
|
|
11626
|
+
return null;
|
|
11627
|
+
}
|
|
11628
|
+
}
|
|
11610
11629
|
function mapKokoroImportError(err) {
|
|
11611
11630
|
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
11612
11631
|
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
|
|
11613
|
-
return new Error("Local Kokoro TTS is unavailable: optional dependency kokoro-js is not installed (its native dependencies may have failed to build on this platform). Install it with `npm i kokoro-js
|
|
11632
|
+
return new Error("Local Kokoro TTS is unavailable: optional dependency kokoro-js is not installed (its native dependencies may have failed to build on this platform). Install it with `npm i kokoro-js` \u2014 run it inside your workspace folder when using the Claude Desktop extension \u2014 or set ELEVENLABS_API_KEY to use the elevenlabs TTS provider instead.");
|
|
11614
11633
|
}
|
|
11615
11634
|
return err instanceof Error ? err : new Error(String(err));
|
|
11616
11635
|
}
|
|
@@ -26662,6 +26681,8 @@ imageProvider: openai
|
|
|
26662
26681
|
|
|
26663
26682
|
Edit these beats before running \`vibe build\`. Each beat starts with
|
|
26664
26683
|
YAML cues that drive narration, backdrop generation, and timing.
|
|
26684
|
+
Pacing: keep beats 6-15 seconds (split longer ones); a 90s video should
|
|
26685
|
+
have 6-8 beats, not 3 long ones.
|
|
26665
26686
|
|
|
26666
26687
|
## Beat hook \u2014 Hook
|
|
26667
26688
|
|
|
@@ -27778,6 +27799,29 @@ function filterSubCompFalsePositives(findings, isSubComposition) {
|
|
|
27778
27799
|
if (!isSubComposition) return noEpsilon;
|
|
27779
27800
|
return noEpsilon.filter((f) => !SUB_COMP_FALSE_POSITIVES.has(f.code));
|
|
27780
27801
|
}
|
|
27802
|
+
function findInternalPhaseClipFindings(html) {
|
|
27803
|
+
const findings = [];
|
|
27804
|
+
for (const tagMatch of html.matchAll(/<[a-z][a-z0-9-]*\s[^>]*>/gi)) {
|
|
27805
|
+
const tag = tagMatch[0];
|
|
27806
|
+
if (!/class="[^"]*\bclip\b[^"]*"/i.test(tag)) continue;
|
|
27807
|
+
const startAttr = tag.match(/data-start="([^"]+)"/i);
|
|
27808
|
+
if (!startAttr) continue;
|
|
27809
|
+
const start = Number(startAttr[1]);
|
|
27810
|
+
if (!Number.isFinite(start) || start === 0) continue;
|
|
27811
|
+
findings.push({
|
|
27812
|
+
code: "internal_phase_clip_unsupported",
|
|
27813
|
+
severity: "error",
|
|
27814
|
+
message: `Inner .clip has data-start="${startAttr[1]}". The renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render ALL phases at once (overlapping text).`,
|
|
27815
|
+
fixHint: 'Give every inner .clip data-start="0" spanning the full beat, and drive phase changes with GSAP autoAlpha instead (animate the previous phase out, the next one in).',
|
|
27816
|
+
snippet: tag.slice(0, 120)
|
|
27817
|
+
});
|
|
27818
|
+
}
|
|
27819
|
+
return findings;
|
|
27820
|
+
}
|
|
27821
|
+
function withVibeframeSubCompFindings(findings, html, isSubComposition) {
|
|
27822
|
+
if (!isSubComposition) return findings;
|
|
27823
|
+
return [...findings, ...findInternalPhaseClipFindings(html)];
|
|
27824
|
+
}
|
|
27781
27825
|
function applyMechanicalFixes(html, findings) {
|
|
27782
27826
|
const fixedCodes = /* @__PURE__ */ new Set();
|
|
27783
27827
|
let updated = html;
|
|
@@ -27823,7 +27867,11 @@ async function runProjectLint(opts) {
|
|
|
27823
27867
|
source: "projectDir"
|
|
27824
27868
|
};
|
|
27825
27869
|
const raw = runHyperframeLint(prepared);
|
|
27826
|
-
const findings =
|
|
27870
|
+
const findings = withVibeframeSubCompFindings(
|
|
27871
|
+
filterSubCompFalsePositives(raw.findings, isSub),
|
|
27872
|
+
html,
|
|
27873
|
+
isSub
|
|
27874
|
+
);
|
|
27827
27875
|
if (opts.fix && findings.length > 0) {
|
|
27828
27876
|
const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
|
|
27829
27877
|
if (fixedCodes.length > 0) {
|
|
@@ -27833,7 +27881,11 @@ async function runProjectLint(opts) {
|
|
|
27833
27881
|
files.push({
|
|
27834
27882
|
file: rel,
|
|
27835
27883
|
isSubComposition: isSub,
|
|
27836
|
-
findings:
|
|
27884
|
+
findings: withVibeframeSubCompFindings(
|
|
27885
|
+
filterSubCompFalsePositives(reLinted.findings, isSub),
|
|
27886
|
+
nextHtml,
|
|
27887
|
+
isSub
|
|
27888
|
+
)
|
|
27837
27889
|
});
|
|
27838
27890
|
continue;
|
|
27839
27891
|
}
|
|
@@ -29713,8 +29765,14 @@ ${finalDurationBullet}
|
|
|
29713
29765
|
Animate the backdrop/media plane instead; let text enter briefly, then hold
|
|
29714
29766
|
still at its final CSS position. Continuous transforms on text ancestors can
|
|
29715
29767
|
create subpixel shimmer in screenshot-captured renders.
|
|
29716
|
-
-
|
|
29717
|
-
\`data-
|
|
29768
|
+
- Inner \`.clip\` elements carry \`data-start\`, \`data-duration\`,
|
|
29769
|
+
\`data-track-index\` \u2014 but **every inner clip MUST be full-window:
|
|
29770
|
+
\`data-start="0"\` spanning the whole beat.** The renderer does NOT toggle
|
|
29771
|
+
internal clip visibility inside sub-compositions, so "phase clips" with a
|
|
29772
|
+
non-zero \`data-start\` render ALL phases at once and text stacks on top of
|
|
29773
|
+
itself. For multi-phase beats, drive phase changes with GSAP \`autoAlpha\`
|
|
29774
|
+
inside full-window clips: animate phase A out (\`autoAlpha: 0\`), then
|
|
29775
|
+
phase B in, on the same timeline.
|
|
29718
29776
|
- If \`assets/backdrop-${ctx.beat.id}.png\` exists, use that local file as the
|
|
29719
29777
|
full-frame visual backdrop. The exact path string is
|
|
29720
29778
|
\`assets/backdrop-${ctx.beat.id}.png\`; do NOT prefix it with \`../\` or \`./\`
|
|
@@ -29728,7 +29786,9 @@ ${finalDurationBullet}
|
|
|
29728
29786
|
narration.
|
|
29729
29787
|
- Keep a meaningful visual progression across the full beat duration: line
|
|
29730
29788
|
tracing, gentle camera drift, staged labels, value cards, or parallax. Avoid
|
|
29731
|
-
a completed static frame holding silently for most of the beat.
|
|
29789
|
+
a completed static frame holding silently for most of the beat. For longer
|
|
29790
|
+
beats, progress through content phases with GSAP autoAlpha (old phase out,
|
|
29791
|
+
new phase in) \u2014 never with inner clip windows.
|
|
29732
29792
|
- Do not import external font or image URLs. Use the project DESIGN.md font
|
|
29733
29793
|
choice when explicit; otherwise use \`Inter\`, which is available in the
|
|
29734
29794
|
deterministic render font map.
|
|
@@ -29903,7 +29963,11 @@ function lintBeatHtml(html, beatId) {
|
|
|
29903
29963
|
source: "projectDir"
|
|
29904
29964
|
};
|
|
29905
29965
|
const raw = runHyperframeLint2(prepared);
|
|
29906
|
-
const findings =
|
|
29966
|
+
const findings = withVibeframeSubCompFindings(
|
|
29967
|
+
filterSubCompFalsePositives(raw.findings, true),
|
|
29968
|
+
html,
|
|
29969
|
+
true
|
|
29970
|
+
);
|
|
29907
29971
|
return {
|
|
29908
29972
|
errorCount: findings.filter((f) => f.severity === "error").length,
|
|
29909
29973
|
warningCount: findings.filter((f) => f.severity === "warning").length,
|
|
@@ -30338,7 +30402,8 @@ function buildInstructions(args) {
|
|
|
30338
30402
|
lines.push(`2. Read \`DESIGN.md\` for project-specific palette, typography, motion signature.`);
|
|
30339
30403
|
lines.push(`3. For each beat in the \`beats\` array below, author HTML at \`outputPath\` matching the \`userPrompt\`. The beat \`body\` carries the narrative + visual + animation intent; \`cues\` carries machine-readable per-beat overrides (narration, duration, backdrop, voice).`);
|
|
30340
30404
|
lines.push(`3b. Use each beat's \`finalDurationSec\` (narration-synced) for \`data-duration\` and timeline anchors when present \u2014 NOT the storyboard \`duration\`, which is only the minimum. Scenes composed at the storyboard duration end early and render black tails.`);
|
|
30341
|
-
lines.push(`3c.
|
|
30405
|
+
lines.push(`3c. Never give inner \`.clip\` elements a non-zero \`data-start\` \u2014 the renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render all phases at once (overlapping text). Use full-window clips and GSAP autoAlpha phase transitions instead. Also keep beats 6-15s; split anything longer in the storyboard first.`);
|
|
30406
|
+
lines.push(`3d. If your environment cannot write files (e.g. Claude Desktop / MCP-only hosts), author each beat's HTML and submit it with the \`scene_submit\` tool (beat id + html). It validates with the same Hyperframes lint and writes the file for you; on lint errors it returns the findings without writing \u2014 fix and resubmit.`);
|
|
30342
30407
|
if (args.beatCount > 1) {
|
|
30343
30408
|
lines.push(`4. After authoring all ${args.beatCount} beat(s), run \`vibe scene lint --fix\` to validate. Fix any remaining errors by editing the HTML directly.`);
|
|
30344
30409
|
} else if (args.filtered) {
|
|
@@ -30780,7 +30845,11 @@ function lintHtml(html, rel, isSub) {
|
|
|
30780
30845
|
source: "projectDir"
|
|
30781
30846
|
};
|
|
30782
30847
|
const raw = runHyperframeLint3(prepared);
|
|
30783
|
-
return
|
|
30848
|
+
return withVibeframeSubCompFindings(
|
|
30849
|
+
filterSubCompFalsePositives(raw.findings, isSub),
|
|
30850
|
+
html,
|
|
30851
|
+
isSub
|
|
30852
|
+
);
|
|
30784
30853
|
}
|
|
30785
30854
|
function lintFilesToIssues(files) {
|
|
30786
30855
|
const issues = [];
|
|
@@ -44130,6 +44199,16 @@ function validateStoryboardMarkdown(markdown) {
|
|
|
44130
44199
|
}
|
|
44131
44200
|
}
|
|
44132
44201
|
}
|
|
44202
|
+
for (const beat of parsed.beats) {
|
|
44203
|
+
if (beat.duration !== void 0 && beat.duration > MAX_RECOMMENDED_BEAT_SEC) {
|
|
44204
|
+
issues.push({
|
|
44205
|
+
severity: "warning",
|
|
44206
|
+
code: "BEAT_DURATION_TOO_LONG",
|
|
44207
|
+
beatId: beat.id,
|
|
44208
|
+
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).`
|
|
44209
|
+
});
|
|
44210
|
+
}
|
|
44211
|
+
}
|
|
44133
44212
|
return {
|
|
44134
44213
|
ok: !issues.some((i) => i.severity === "error"),
|
|
44135
44214
|
beats: parsed.beats,
|
|
@@ -44228,7 +44307,7 @@ function readLeadingCueBlock(body) {
|
|
|
44228
44307
|
return { full: match[0], error: error instanceof Error ? error.message : String(error) };
|
|
44229
44308
|
}
|
|
44230
44309
|
}
|
|
44231
|
-
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS;
|
|
44310
|
+
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS, MAX_RECOMMENDED_BEAT_SEC;
|
|
44232
44311
|
var init_storyboard_edit = __esm({
|
|
44233
44312
|
"src/commands/_shared/storyboard-edit.ts"() {
|
|
44234
44313
|
"use strict";
|
|
@@ -44248,6 +44327,7 @@ var init_storyboard_edit = __esm({
|
|
|
44248
44327
|
LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
|
|
44249
44328
|
ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
|
|
44250
44329
|
STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
|
|
44330
|
+
MAX_RECOMMENDED_BEAT_SEC = 15;
|
|
44251
44331
|
}
|
|
44252
44332
|
});
|
|
44253
44333
|
|
|
@@ -59092,7 +59172,8 @@ function revisionMessages(opts) {
|
|
|
59092
59172
|
"The JSON object must have: storyboardMd string, summary string, changedBeats string[], warnings string[].",
|
|
59093
59173
|
"Preserve existing frontmatter, beat ids, cue YAML keys, and useful prose unless the user asks to change structure.",
|
|
59094
59174
|
"Keep cue YAML valid. Allowed cue keys are duration, narration, backdrop, video, motion, voice, music, asset.",
|
|
59095
|
-
"If a target duration is supplied, every beat must have a positive duration cue and durations must sum to that target."
|
|
59175
|
+
"If a target duration is supplied, every beat must have a positive duration cue and durations must sum to that target.",
|
|
59176
|
+
"Aim for 6-15 seconds per beat and never exceed ~15s \u2014 long beats render as static, overstuffed scenes. A 90-second video should have 6-8 beats."
|
|
59096
59177
|
].join("\n");
|
|
59097
59178
|
const repairBlock = opts.invalidStoryboard ? [
|
|
59098
59179
|
"The previous revision failed validation. Repair it.",
|
|
@@ -72828,7 +72909,7 @@ init_source();
|
|
|
72828
72909
|
// package.json
|
|
72829
72910
|
var package_default = {
|
|
72830
72911
|
name: "@vibeframe/cli",
|
|
72831
|
-
version: "0.
|
|
72912
|
+
version: "0.109.0",
|
|
72832
72913
|
description: "VibeFrame CLI - agentic video workflow runtime",
|
|
72833
72914
|
private: false,
|
|
72834
72915
|
license: "MIT",
|
|
@@ -81287,6 +81368,9 @@ providers:
|
|
|
81287
81368
|
|
|
81288
81369
|
# ${product} - Storyboard
|
|
81289
81370
|
|
|
81371
|
+
Pacing: keep beats 6-15 seconds. Longer beats render as static, overstuffed
|
|
81372
|
+
scenes \u2014 split them instead.
|
|
81373
|
+
|
|
81290
81374
|
Brief: ${brief}
|
|
81291
81375
|
|
|
81292
81376
|
${beats.map(renderBeatDraft).join("\n\n")}
|
|
@@ -4729,13 +4729,32 @@ async function loadKokoroFactory() {
|
|
|
4729
4729
|
const mod = await import("kokoro-js");
|
|
4730
4730
|
return mod.KokoroTTS;
|
|
4731
4731
|
} catch (err) {
|
|
4732
|
+
const fallback2 = await loadKokoroFromWorkspace();
|
|
4733
|
+
if (fallback2)
|
|
4734
|
+
return fallback2;
|
|
4732
4735
|
throw mapKokoroImportError(err);
|
|
4733
4736
|
}
|
|
4734
4737
|
}
|
|
4738
|
+
async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
|
|
4739
|
+
try {
|
|
4740
|
+
const { readFile: readFile44 } = await import("node:fs/promises");
|
|
4741
|
+
const { join: join26, resolve: resolve65 } = await import("node:path");
|
|
4742
|
+
const { pathToFileURL } = await import("node:url");
|
|
4743
|
+
const pkgDir = resolve65(baseDir, "node_modules", "kokoro-js");
|
|
4744
|
+
const pkg = JSON.parse(await readFile44(join26(pkgDir, "package.json"), "utf-8"));
|
|
4745
|
+
const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
|
|
4746
|
+
if (!entry)
|
|
4747
|
+
return null;
|
|
4748
|
+
const mod = await import(pathToFileURL(join26(pkgDir, entry)).href);
|
|
4749
|
+
return mod.KokoroTTS ?? null;
|
|
4750
|
+
} catch {
|
|
4751
|
+
return null;
|
|
4752
|
+
}
|
|
4753
|
+
}
|
|
4735
4754
|
function mapKokoroImportError(err) {
|
|
4736
4755
|
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
4737
4756
|
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
|
|
4738
|
-
return new Error("Local Kokoro TTS is unavailable: optional dependency kokoro-js is not installed (its native dependencies may have failed to build on this platform). Install it with `npm i kokoro-js
|
|
4757
|
+
return new Error("Local Kokoro TTS is unavailable: optional dependency kokoro-js is not installed (its native dependencies may have failed to build on this platform). Install it with `npm i kokoro-js` \u2014 run it inside your workspace folder when using the Claude Desktop extension \u2014 or set ELEVENLABS_API_KEY to use the elevenlabs TTS provider instead.");
|
|
4739
4758
|
}
|
|
4740
4759
|
return err instanceof Error ? err : new Error(String(err));
|
|
4741
4760
|
}
|
|
@@ -25266,6 +25285,8 @@ imageProvider: openai
|
|
|
25266
25285
|
|
|
25267
25286
|
Edit these beats before running \`vibe build\`. Each beat starts with
|
|
25268
25287
|
YAML cues that drive narration, backdrop generation, and timing.
|
|
25288
|
+
Pacing: keep beats 6-15 seconds (split longer ones); a 90s video should
|
|
25289
|
+
have 6-8 beats, not 3 long ones.
|
|
25269
25290
|
|
|
25270
25291
|
## Beat hook \u2014 Hook
|
|
25271
25292
|
|
|
@@ -28829,6 +28850,29 @@ function filterSubCompFalsePositives(findings, isSubComposition) {
|
|
|
28829
28850
|
if (!isSubComposition) return noEpsilon;
|
|
28830
28851
|
return noEpsilon.filter((f) => !SUB_COMP_FALSE_POSITIVES.has(f.code));
|
|
28831
28852
|
}
|
|
28853
|
+
function findInternalPhaseClipFindings(html) {
|
|
28854
|
+
const findings = [];
|
|
28855
|
+
for (const tagMatch of html.matchAll(/<[a-z][a-z0-9-]*\s[^>]*>/gi)) {
|
|
28856
|
+
const tag = tagMatch[0];
|
|
28857
|
+
if (!/class="[^"]*\bclip\b[^"]*"/i.test(tag)) continue;
|
|
28858
|
+
const startAttr = tag.match(/data-start="([^"]+)"/i);
|
|
28859
|
+
if (!startAttr) continue;
|
|
28860
|
+
const start = Number(startAttr[1]);
|
|
28861
|
+
if (!Number.isFinite(start) || start === 0) continue;
|
|
28862
|
+
findings.push({
|
|
28863
|
+
code: "internal_phase_clip_unsupported",
|
|
28864
|
+
severity: "error",
|
|
28865
|
+
message: `Inner .clip has data-start="${startAttr[1]}". The renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render ALL phases at once (overlapping text).`,
|
|
28866
|
+
fixHint: 'Give every inner .clip data-start="0" spanning the full beat, and drive phase changes with GSAP autoAlpha instead (animate the previous phase out, the next one in).',
|
|
28867
|
+
snippet: tag.slice(0, 120)
|
|
28868
|
+
});
|
|
28869
|
+
}
|
|
28870
|
+
return findings;
|
|
28871
|
+
}
|
|
28872
|
+
function withVibeframeSubCompFindings(findings, html, isSubComposition) {
|
|
28873
|
+
if (!isSubComposition) return findings;
|
|
28874
|
+
return [...findings, ...findInternalPhaseClipFindings(html)];
|
|
28875
|
+
}
|
|
28832
28876
|
function applyMechanicalFixes(html, findings) {
|
|
28833
28877
|
const fixedCodes = /* @__PURE__ */ new Set();
|
|
28834
28878
|
let updated = html;
|
|
@@ -28874,7 +28918,11 @@ async function runProjectLint(opts) {
|
|
|
28874
28918
|
source: "projectDir"
|
|
28875
28919
|
};
|
|
28876
28920
|
const raw = runHyperframeLint(prepared);
|
|
28877
|
-
const findings =
|
|
28921
|
+
const findings = withVibeframeSubCompFindings(
|
|
28922
|
+
filterSubCompFalsePositives(raw.findings, isSub),
|
|
28923
|
+
html,
|
|
28924
|
+
isSub
|
|
28925
|
+
);
|
|
28878
28926
|
if (opts.fix && findings.length > 0) {
|
|
28879
28927
|
const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
|
|
28880
28928
|
if (fixedCodes.length > 0) {
|
|
@@ -28884,7 +28932,11 @@ async function runProjectLint(opts) {
|
|
|
28884
28932
|
files.push({
|
|
28885
28933
|
file: rel,
|
|
28886
28934
|
isSubComposition: isSub,
|
|
28887
|
-
findings:
|
|
28935
|
+
findings: withVibeframeSubCompFindings(
|
|
28936
|
+
filterSubCompFalsePositives(reLinted.findings, isSub),
|
|
28937
|
+
nextHtml,
|
|
28938
|
+
isSub
|
|
28939
|
+
)
|
|
28888
28940
|
});
|
|
28889
28941
|
continue;
|
|
28890
28942
|
}
|
|
@@ -30419,8 +30471,14 @@ ${finalDurationBullet}
|
|
|
30419
30471
|
Animate the backdrop/media plane instead; let text enter briefly, then hold
|
|
30420
30472
|
still at its final CSS position. Continuous transforms on text ancestors can
|
|
30421
30473
|
create subpixel shimmer in screenshot-captured renders.
|
|
30422
|
-
-
|
|
30423
|
-
\`data-
|
|
30474
|
+
- Inner \`.clip\` elements carry \`data-start\`, \`data-duration\`,
|
|
30475
|
+
\`data-track-index\` \u2014 but **every inner clip MUST be full-window:
|
|
30476
|
+
\`data-start="0"\` spanning the whole beat.** The renderer does NOT toggle
|
|
30477
|
+
internal clip visibility inside sub-compositions, so "phase clips" with a
|
|
30478
|
+
non-zero \`data-start\` render ALL phases at once and text stacks on top of
|
|
30479
|
+
itself. For multi-phase beats, drive phase changes with GSAP \`autoAlpha\`
|
|
30480
|
+
inside full-window clips: animate phase A out (\`autoAlpha: 0\`), then
|
|
30481
|
+
phase B in, on the same timeline.
|
|
30424
30482
|
- If \`assets/backdrop-${ctx.beat.id}.png\` exists, use that local file as the
|
|
30425
30483
|
full-frame visual backdrop. The exact path string is
|
|
30426
30484
|
\`assets/backdrop-${ctx.beat.id}.png\`; do NOT prefix it with \`../\` or \`./\`
|
|
@@ -30434,7 +30492,9 @@ ${finalDurationBullet}
|
|
|
30434
30492
|
narration.
|
|
30435
30493
|
- Keep a meaningful visual progression across the full beat duration: line
|
|
30436
30494
|
tracing, gentle camera drift, staged labels, value cards, or parallax. Avoid
|
|
30437
|
-
a completed static frame holding silently for most of the beat.
|
|
30495
|
+
a completed static frame holding silently for most of the beat. For longer
|
|
30496
|
+
beats, progress through content phases with GSAP autoAlpha (old phase out,
|
|
30497
|
+
new phase in) \u2014 never with inner clip windows.
|
|
30438
30498
|
- Do not import external font or image URLs. Use the project DESIGN.md font
|
|
30439
30499
|
choice when explicit; otherwise use \`Inter\`, which is available in the
|
|
30440
30500
|
deterministic render font map.
|
|
@@ -30609,7 +30669,11 @@ function lintBeatHtml(html, beatId) {
|
|
|
30609
30669
|
source: "projectDir"
|
|
30610
30670
|
};
|
|
30611
30671
|
const raw = runHyperframeLint2(prepared);
|
|
30612
|
-
const findings =
|
|
30672
|
+
const findings = withVibeframeSubCompFindings(
|
|
30673
|
+
filterSubCompFalsePositives(raw.findings, true),
|
|
30674
|
+
html,
|
|
30675
|
+
true
|
|
30676
|
+
);
|
|
30613
30677
|
return {
|
|
30614
30678
|
errorCount: findings.filter((f) => f.severity === "error").length,
|
|
30615
30679
|
warningCount: findings.filter((f) => f.severity === "warning").length,
|
|
@@ -31044,7 +31108,8 @@ function buildInstructions(args) {
|
|
|
31044
31108
|
lines.push(`2. Read \`DESIGN.md\` for project-specific palette, typography, motion signature.`);
|
|
31045
31109
|
lines.push(`3. For each beat in the \`beats\` array below, author HTML at \`outputPath\` matching the \`userPrompt\`. The beat \`body\` carries the narrative + visual + animation intent; \`cues\` carries machine-readable per-beat overrides (narration, duration, backdrop, voice).`);
|
|
31046
31110
|
lines.push(`3b. Use each beat's \`finalDurationSec\` (narration-synced) for \`data-duration\` and timeline anchors when present \u2014 NOT the storyboard \`duration\`, which is only the minimum. Scenes composed at the storyboard duration end early and render black tails.`);
|
|
31047
|
-
lines.push(`3c.
|
|
31111
|
+
lines.push(`3c. Never give inner \`.clip\` elements a non-zero \`data-start\` \u2014 the renderer does not toggle internal clip visibility inside sub-compositions, so phased clips render all phases at once (overlapping text). Use full-window clips and GSAP autoAlpha phase transitions instead. Also keep beats 6-15s; split anything longer in the storyboard first.`);
|
|
31112
|
+
lines.push(`3d. If your environment cannot write files (e.g. Claude Desktop / MCP-only hosts), author each beat's HTML and submit it with the \`scene_submit\` tool (beat id + html). It validates with the same Hyperframes lint and writes the file for you; on lint errors it returns the findings without writing \u2014 fix and resubmit.`);
|
|
31048
31113
|
if (args.beatCount > 1) {
|
|
31049
31114
|
lines.push(`4. After authoring all ${args.beatCount} beat(s), run \`vibe scene lint --fix\` to validate. Fix any remaining errors by editing the HTML directly.`);
|
|
31050
31115
|
} else if (args.filtered) {
|
|
@@ -31367,7 +31432,11 @@ function lintHtml(html, rel, isSub) {
|
|
|
31367
31432
|
source: "projectDir"
|
|
31368
31433
|
};
|
|
31369
31434
|
const raw = runHyperframeLint3(prepared);
|
|
31370
|
-
return
|
|
31435
|
+
return withVibeframeSubCompFindings(
|
|
31436
|
+
filterSubCompFalsePositives(raw.findings, isSub),
|
|
31437
|
+
html,
|
|
31438
|
+
isSub
|
|
31439
|
+
);
|
|
31371
31440
|
}
|
|
31372
31441
|
function lintFilesToIssues(files) {
|
|
31373
31442
|
const issues = [];
|
|
@@ -32440,6 +32509,16 @@ function validateStoryboardMarkdown(markdown) {
|
|
|
32440
32509
|
}
|
|
32441
32510
|
}
|
|
32442
32511
|
}
|
|
32512
|
+
for (const beat of parsed.beats) {
|
|
32513
|
+
if (beat.duration !== void 0 && beat.duration > MAX_RECOMMENDED_BEAT_SEC) {
|
|
32514
|
+
issues.push({
|
|
32515
|
+
severity: "warning",
|
|
32516
|
+
code: "BEAT_DURATION_TOO_LONG",
|
|
32517
|
+
beatId: beat.id,
|
|
32518
|
+
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).`
|
|
32519
|
+
});
|
|
32520
|
+
}
|
|
32521
|
+
}
|
|
32443
32522
|
return {
|
|
32444
32523
|
ok: !issues.some((i) => i.severity === "error"),
|
|
32445
32524
|
beats: parsed.beats,
|
|
@@ -32538,7 +32617,7 @@ function readLeadingCueBlock(body) {
|
|
|
32538
32617
|
return { full: match[0], error: error instanceof Error ? error.message : String(error) };
|
|
32539
32618
|
}
|
|
32540
32619
|
}
|
|
32541
|
-
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS;
|
|
32620
|
+
var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS, MAX_RECOMMENDED_BEAT_SEC;
|
|
32542
32621
|
var init_storyboard_edit = __esm({
|
|
32543
32622
|
"src/commands/_shared/storyboard-edit.ts"() {
|
|
32544
32623
|
"use strict";
|
|
@@ -32558,6 +32637,7 @@ var init_storyboard_edit = __esm({
|
|
|
32558
32637
|
LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
|
|
32559
32638
|
ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
|
|
32560
32639
|
STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
|
|
32640
|
+
MAX_RECOMMENDED_BEAT_SEC = 15;
|
|
32561
32641
|
}
|
|
32562
32642
|
});
|
|
32563
32643
|
|
|
@@ -56751,7 +56831,8 @@ function revisionMessages(opts) {
|
|
|
56751
56831
|
"The JSON object must have: storyboardMd string, summary string, changedBeats string[], warnings string[].",
|
|
56752
56832
|
"Preserve existing frontmatter, beat ids, cue YAML keys, and useful prose unless the user asks to change structure.",
|
|
56753
56833
|
"Keep cue YAML valid. Allowed cue keys are duration, narration, backdrop, video, motion, voice, music, asset.",
|
|
56754
|
-
"If a target duration is supplied, every beat must have a positive duration cue and durations must sum to that target."
|
|
56834
|
+
"If a target duration is supplied, every beat must have a positive duration cue and durations must sum to that target.",
|
|
56835
|
+
"Aim for 6-15 seconds per beat and never exceed ~15s \u2014 long beats render as static, overstuffed scenes. A 90-second video should have 6-8 beats."
|
|
56755
56836
|
].join("\n");
|
|
56756
56837
|
const repairBlock = opts.invalidStoryboard ? [
|
|
56757
56838
|
"The previous revision failed validation. Repair it.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibeframe/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.109.0",
|
|
4
4
|
"description": "VibeFrame CLI - agentic video workflow runtime",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"tsx": "^4.21.0",
|
|
84
84
|
"typescript": "^5.3.3",
|
|
85
85
|
"vitest": "^1.2.2",
|
|
86
|
-
"@vibeframe/
|
|
87
|
-
"@vibeframe/
|
|
86
|
+
"@vibeframe/core": "0.109.0",
|
|
87
|
+
"@vibeframe/ai-providers": "0.109.0"
|
|
88
88
|
},
|
|
89
89
|
"engines": {
|
|
90
90
|
"node": ">=20"
|