@vibeframe/cli 0.113.0 → 0.113.3

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 CHANGED
@@ -5,12 +5,57 @@
5
5
  VibeFrame CLI (`vibe`) is a CLI-first video toolkit. Every operation is
6
6
  a shell command. The same surface is exposed as MCP tools through
7
7
  [`@vibeframe/mcp-server`](https://www.npmjs.com/package/@vibeframe/mcp-server).
8
+ For long-running video work, native Codex Goal mode and Claude Code `/goal`
9
+ should own the outer loop: persistence, iteration, and stop decisions.
10
+ VibeFrame owns the video-specific command surface, JSON reports, cost gates,
11
+ deterministic repair, render inspection, and `retryWith` recovery hints.
8
12
 
9
13
  For the full reference (every flag, default, enum), read
10
14
  [`docs/cli-reference.md`](../../docs/cli-reference.md) — auto-generated
11
15
  from `vibe schema --list`. This file is the _agent quickstart_: rules,
12
16
  conventions, and discovery hooks.
13
17
 
18
+ ## Native host goal loop
19
+
20
+ Use the host's native goal feature rather than inventing a VibeFrame-owned
21
+ goal runner. The canonical loop is:
22
+
23
+ ```text
24
+ native host goal -> vibe context/schema -> plan dry-run -> build with budget
25
+ -> status polling -> inspect project -> render -> inspect render
26
+ -> repair/edit using nextActions/fixOwner -> repeat until stop rules pass
27
+ ```
28
+
29
+ Copy-paste Codex goal:
30
+
31
+ ```text
32
+ /goal Build my-video/ into a reviewed VibeFrame MP4. Use --json for every vibe
33
+ command, run --dry-run before paid operations, cap generated-asset spend with
34
+ --max-cost 5 where supported, and read build-report.json plus
35
+ review-report.json before deciding the next action. Prefer nextActions: run
36
+ only safeToAutoRun:true actions automatically, ask before
37
+ requiresConfirmation:true actions, and use retryWith only as the compatibility
38
+ fallback. Treat fixOwner:"vibe" as deterministic CLI repair work and
39
+ fixOwner:"host-agent" as storyboard, DESIGN.md, or composition edits.
40
+ Stop only when my-video/renders/final.mp4 exists, duration is within the target,
41
+ aspect ratio matches the brief, inspect render --cheap has no errors, review
42
+ AI review score is >= 90 when AI review is requested, and unresolved host-agent issues are fixed, intentionally
43
+ accepted with a written reason, or reported as blocked.
44
+ ```
45
+
46
+ Copy-paste Claude Code goal:
47
+
48
+ ```text
49
+ /goal Finish the VibeFrame render for my-video/ using Claude Code goal mode as
50
+ the outer loop. Use vibe context/schema when unsure, --json everywhere,
51
+ dry-run before paid operations, --max-cost 5 for builds, nextActions before
52
+ custom recovery, and build-report.json/review-report.json as loop state.
53
+ Stop only when renders/final.mp4 exists, target duration and aspect ratio are
54
+ met, render inspection has no errors, any AI review score is at least 90 when AI review is requested, and every
55
+ remaining host-agent issue is fixed, accepted with rationale, or reported as
56
+ blocked.
57
+ ```
58
+
14
59
  ## Discovery (use these first)
15
60
 
16
61
  ```bash
@@ -38,12 +83,12 @@ vibe guide <topic> --json # Step-by-step guides (motion | scene | p
38
83
 
39
84
  ## Cost tiers
40
85
 
41
- | Tier | Commands | Per-call cost |
42
- | --------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------- |
86
+ | Tier | Commands | Per-call cost |
87
+ | --------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
43
88
  | Free | `schema/context/doctor`, `detect *`, `status *`, `plan`, `storyboard validate`, `inspect project/render --cheap`, deterministic edits | $0 |
44
- | Low | `generate narration/sound-effect/music`, `audio transcribe/list-voices`, `inspect media`, optional AI review | $0.01–0.10 |
45
- | High | `generate image/motion`, `edit image/reframe/grade/speed-ramp` | $1–5 |
46
- | Very High | `generate video`, `edit fill-gaps`, `remix highlights/auto-shorts`, `build` with generated assets | $5–50+ |
89
+ | Low | `generate narration/sound-effect/music`, `audio transcribe/list-voices`, `inspect media`, optional AI review | $0.01–0.10 |
90
+ | High | `generate image/motion`, `edit image/reframe/grade/speed-ramp` | $1–5 |
91
+ | Very High | `generate video`, `edit fill-gaps`, `remix highlights/auto-shorts`, `build` with generated assets | $5–50+ |
47
92
 
48
93
  > Rule: **confirm with the user before any High / Very-High call**.
49
94
 
@@ -173,10 +218,14 @@ Review report contract:
173
218
  `review-report.json` by default. The file uses `kind:"review"`,
174
219
  `mode:"project"|"render"`, `status`, `score`, `issues[]`,
175
220
  `summary:{issueCount,errorCount,warningCount,infoCount,fixOwners}`,
176
- `sourceReports`, and `retryWith`. Each issue has `fixOwner:"vibe"` for
177
- deterministic CLI recovery or `fixOwner:"host-agent"` for storyboard/design/
178
- composition edits the host agent should make. Use `retryWith` first, then
179
- hand remaining `host-agent` issues to the agent.
221
+ `sourceReports`, `nextActions`, and `retryWith`. Each issue has
222
+ `fixOwner:"vibe"` for deterministic CLI recovery or `fixOwner:"host-agent"`
223
+ for storyboard/design/composition edits the host agent should make. Prefer
224
+ `nextActions` first: run only `safeToAutoRun:true` actions automatically, ask
225
+ before `requiresConfirmation:true`, and use `retryWith` only as the fallback.
226
+ A host-native goal should not stop while `host-agent` issues remain unless
227
+ they are fixed, intentionally accepted with a written reason, or reported as
228
+ blocked.
180
229
 
181
230
  Product surface contract:
182
231
 
@@ -242,7 +291,7 @@ vibe scene repair --project my-video --json
242
291
 
243
292
  `vibe status job --json` emits the normal success envelope with `data.kind:"job"`, flat job fields (`id`, `jobType`, `provider`, `status`, timestamps), `progress`, `result`, `retryWith`, and the raw `job` record for compatibility.
244
293
 
245
- `vibe status project --json` emits `data.kind:"project"`, `status`, `currentStage`, `beats:{total,assetsReady,compositionsReady,needsAuthor}`, `jobs.latest`, `build`, `review`, `warnings`, and `retryWith`. `review` includes `mode`, `issueCount`, `errorCount`, `warningCount`, `infoCount`, `fixOwners`, `sourceReports`, and its own `retryWith`; top-level `retryWith` carries the next resume command. Use `retryWith` rather than guessing the next command.
294
+ `vibe status project --json` emits `data.kind:"project"`, `status`, `currentStage`, `beats:{total,assetsReady,compositionsReady,needsAuthor}`, `jobs.latest`, `build`, `review`, `warnings`, `nextActions`, and `retryWith`. `review` includes `mode`, `issueCount`, `errorCount`, `warningCount`, `infoCount`, `fixOwners`, `sourceReports`, `nextActions`, and its own `retryWith`; top-level `nextActions` carries the preferred resume contract while `retryWith` remains the compatibility fallback.
246
295
 
247
296
  ### Storyboard revision contract
248
297
 
@@ -1,4 +1,4 @@
1
- export type BuildAssetKind = "narration" | "backdrop" | "video" | "music";
1
+ export type BuildAssetKind = "narration" | "backdrop" | "character" | "keyframe" | "video" | "music";
2
2
  export interface CacheAssetDescriptor {
3
3
  key: string;
4
4
  path: string;
@@ -20,12 +20,36 @@ export declare function backdropCacheDescriptor(opts: {
20
20
  size: string;
21
21
  ratio?: string;
22
22
  }): CacheAssetDescriptor;
23
+ export declare function characterCacheDescriptor(opts: {
24
+ name: string;
25
+ cue: string;
26
+ provider: string;
27
+ quality: "standard" | "hd";
28
+ size: string;
29
+ }): CacheAssetDescriptor;
30
+ export declare function keyframeCacheDescriptor(opts: {
31
+ beatId: string;
32
+ cue: string;
33
+ provider: string;
34
+ quality: "standard" | "hd";
35
+ size: string;
36
+ ratio?: string;
37
+ /** Character sheet paths used as edit references — changing them re-keys the keyframe. */
38
+ characters?: string[];
39
+ }): CacheAssetDescriptor;
23
40
  export declare function imageRatioForSize(size: string | undefined): string;
24
41
  export declare function videoCacheDescriptor(opts: {
25
42
  beatId: string;
26
43
  cue: string;
27
44
  provider: string;
28
45
  duration: number | undefined;
46
+ /** Character reference image paths — changing them must invalidate the clip. */
47
+ characters?: string[];
48
+ /**
49
+ * Keyframe still path for image-to-video mode — changing the keyframe must
50
+ * invalidate the clip. Omitted for text/reference-to-video clips.
51
+ */
52
+ keyframe?: string;
29
53
  }): CacheAssetDescriptor;
30
54
  export declare function musicCacheDescriptor(opts: {
31
55
  beatId: string;
@@ -0,0 +1 @@
1
+ export {};
@@ -18,7 +18,7 @@
18
18
  * file. Keep this as the canonical source of truth for agent guidance —
19
19
  * \`CLAUDE.md\` and \`GEMINI.md\` just import it.
20
20
  */
21
- export declare const AGENTS_MD = "# AGENTS.md\n\nThis project uses [VibeFrame](https://github.com/vericontext/vibeframe) \u2014\na CLI for AI-powered video editing. The `vibe` command provides 80+\nsubcommands for video generation, editing, analysis, and pipeline\norchestration. Cost tiers below \u2014 **always confirm with the user before\nrunning High / Very High tier commands**.\n\n## Agent host coverage\n\nVibeFrame's CLI works with any bash-capable AI coding agent. Six host\nfamilies have first-class auto-detection and scaffolds today:\n\n- **Claude Code** \u2014 `CLAUDE.md` (this file's import wrapper) +\n `AGENTS.md` + `/vibe-*` slash commands.\n- **OpenAI Codex** \u2014 reads `AGENTS.md` natively (agents.md spec).\n- **Cursor** \u2014 reads `AGENTS.md` plus `.cursor/rules/` for project\n rules; supports MCP via `.cursor/mcp.json`.\n- **Aider** \u2014 reads `AGENTS.md` (binary-only detection by VibeFrame).\n- **Gemini CLI** \u2014 primary context file is `GEMINI.md`; also reads\n `AGENTS.md` if present.\n- **OpenCode** \u2014 reads `AGENTS.md` (officially supported per\n `opencode.ai/docs/rules`); supports MCP via `.opencode/mcp.json`.\n\nRun `vibe doctor` to see which host(s) the CLI detects in your\nenvironment. Anyone running another bash-capable agent (Continue,\nSourcegraph Cody, etc.) still gets this `AGENTS.md` as the universal\nfallback.\n\n## Self-discovery\n\nDon't memorise the command surface \u2014 read it from the CLI:\n\n```bash\nvibe --help # command groups overview\nvibe schema --list # full machine-readable catalog (80+)\nvibe schema --list --surface public # small first-run/product command surface\nvibe schema generate.video # JSON Schema for any single command\nvibe doctor # available providers + system health\nvibe host setup all # print Codex/Claude/Cursor app integration snippets\nvibe host doctor all --json # verify project guidance + MCP config\nvibe guide motion # choose text-overlay vs motion-overlay vs generate motion\nvibe guide scene # step-by-step authoring guide (scene)\nvibe guide pipeline # step-by-step authoring guide (pipeline)\n```\n\n## Route by the user's actual request\n\nRouting the user's request correctly is the most important judgement call\nyou'll make. Do not force everything into a scene project.\n\n**ASSET \u2014 default for any single-asset request OR ambiguous prompt.**\nIf the user asks for a single image, single video clip, sound effect, music\nbed, or narration file \u2014 **OR pastes a visual/audio brief without explicit\nstoryboard / scene / multi-scene language** \u2014 treat it as ASSET. Use\n`vibe generate ...` directly. Do **not** edit `DESIGN.md`,\n`STORYBOARD.md`, run `vibe scene ...`, or auto-load the hyperframes\nskill until BUILD intent is explicit. The visual-identity hard-gate\napplies to BUILD only.\n\nExamples:\n- \"make this image\" \u2192 `vibe generate image \"...\" -o assets/name.png`\n- \"use this image to make a video\" \u2192 `vibe generate video \"...\" -i image.png -o renders/name.mp4`\n- \"add a lower-third/title/animated overlay/grain/vignette to this clip\" \u2192 prefer `vibe edit motion-overlay clip.mp4 \"...\" --understand auto -o out.mp4` when the request asks for designed or animated motion graphics.\n- \"please add visuals using OpenAI image gen\" \u2192 `vibe generate image \"...\" -p openai ...`\n- *(verb-less paste)* \"aerial view of a misty mountain peak at sunrise...\" \u2192 `vibe generate image \"<paste>\" -o assets/mountain-peak.png`. **Don't** read it as a brief for DESIGN.md.\n\nIf genuinely uncertain, ask one short question: *\"single asset or\nmulti-scene project?\"* before authoring DESIGN.md or loading the\nhyperframes skill.\n\n**BUILD \u2014 create new video from text intent.**\nUse `vibe build` with a STORYBOARD.md + DESIGN.md. The\nskills-driven pipeline (v0.60+) dispatches narration TTS + backdrop\nimage-gen per beat, composes scene HTML via the bundled composition rules\nbundle, then renders to MP4. Idempotent re-runs reuse cached assets.\n\n**REMIX \u2014 transform existing video / audio.**\nUse `vibe remix`, `vibe edit`, or `vibe audio`. One-shot,\nbatch-oriented operations on a file the user already has on disk.\n`vibe edit text-overlay` is the free deterministic path for simple static\ntext burn-in. If the user asks for motion design, animated lower-thirds,\ndesigned titles, grain/vignette as part of a graphic treatment, or says\n\"motion graphics\", use `vibe edit motion-overlay ... --understand auto` instead.\n\nDecision rule: if the user asks for one asset, it's ASSET. If the user asks\nfor a multi-scene/storyboard/composed video, it's BUILD. If the user is\nstarting from a media file and wants it transformed, it's REMIX.\n\n## Common workflows\n\n### BUILD (new video from text)\n\n| Task | Command |\n|---|---|\n| One-shot storyboard \u2192 video | `vibe build [project-dir]` |\n| Render an existing scene project | `vibe render [project-dir] -o out.mp4` |\n| Lint scene HTML | `vibe scene lint --json` |\n| Generate a single image | `vibe generate image \"prompt\" -o img.png --quality hd` |\n| Generate a single video | `vibe generate video \"prompt\" -i image.png -o clip.mp4` |\n| Generate narration | `vibe generate narration \"text\" -o voice.mp3` |\n\n### REMIX (transform existing media)\n\n| Task | Command |\n|---|---|\n| Extract highlights from a long video | `vibe remix highlights file.mp4 -d 60` |\n| Long video \u2192 vertical shorts | `vibe remix auto-shorts file.mp4 -n 3 --add-captions` |\n| Add animated word-by-word captions | `vibe remix animated-caption file.mp4 -s karaoke-sweep` |\n| Add designed motion graphics overlay | `vibe edit motion-overlay file.mp4 \"lower-third...\" --understand auto -o out.mp4` |\n| Remove silence | `vibe edit silence-cut in.mp4 -o out.mp4` |\n| Add static captions | `vibe edit caption in.mp4 -o out.mp4` |\n| Translate audio (transcribe \u2192 TTS) | `vibe audio dub file.mp4 -t ko` |\n| Transcribe (Whisper, word-level) | `vibe audio transcribe file.mp4 --granularity word` |\n| Inspect media with Gemini | `vibe inspect media file.mp4 \"summarise\" --json` |\n\n### Compose pipelines (Video as Code)\n\n| Task | Command |\n|---|---|\n| Run a YAML pipeline | `vibe run pipeline.yaml --dry-run` |\n| Resume from last checkpoint | `vibe run pipeline.yaml --resume` |\n\n## Cost tiers\n\n| Tier | Examples | Approx. per call |\n|---|---|---|\n| **Free** | `schema/context/doctor`, `detect *`, `status *`, `plan`, `storyboard validate`, `inspect project/render --cheap`, deterministic edits | $0 |\n| **Low** | `generate narration/sound-effect/music`, `audio transcribe`, `inspect media`, optional AI review | $0.01\u2013$0.10 |\n| **High** | `generate image/motion`, `edit image/reframe/grade/speed-ramp` | $1\u2013$5 |\n| **Very High** | `generate video`, `edit fill-gaps`, `remix highlights/auto-shorts`, `build` with generated assets | $5\u2013$50+ |\n\n## Agent invariants\n\nWhen you run vibe commands programmatically:\n\n1. **Always `--json`** for structured output you can parse without regex\n2. **Always `--dry-run` first** for any High/Very-High tier command \u2014 it returns\n the cost estimate without spending API budget\n3. **Use `vibe schema <command>`** to discover parameters; do not guess flags\n4. **Pass complex options via `--stdin`**:\n ```bash\n echo '{\"video\":\"long.mp4\",\"count\":3,\"aspect\":\"9:16\"}' | vibe remix auto-shorts --stdin --json\n ```\n\n## Provider keys and project scope\n\nPrefer `vibe setup --scope project` for project-local keys. It writes\n`.vibeframe/config.yaml` in the setup directory; when you work from a\nnested scene directory, the `vibe` CLI automatically searches parent\ndirectories and uses the nearest project config. Do not assume the config\nfile must exist in the current subdirectory.\n\nRun `vibe doctor --json` from the directory where you will run commands\nand check `data.scope.project.configPath` / `data.scope.activeScope` before\ndeciding keys are missing. Never print or copy config file contents.\n\nFor generated project assets, use VibeFrame commands such as\n`vibe generate image|video|speech ...` so these project-scope keys are\nused. Do not switch to a host agent's built-in image/audio generation tool\nunless the user explicitly asks for a non-VibeFrame asset.\n\n`.env` is still supported as a gitignored fallback. Copy `.env.example`\nto start if you prefer environment variables.\n\n## App host setup\n\nVibeFrame is CLI-first, not terminal-only. Codex, Claude Code, and Cursor can\ndrive the same project through shell commands, and app hosts can also use\nVibeFrame's MCP server as a typed tool surface.\n\n```bash\nvibe host setup all # print snippets only\nvibe host setup all --write # write project/app config\nvibe host doctor all --json # verify readiness\n```\n\nDefault setup is snippet-only; use `--write` when you want VibeFrame to merge\nthe generated config. Keep provider keys in `.vibeframe/config.yaml`, user\nconfig, or env files \u2014 not in app host config.\n\n### Composition rules (scene HTML)\n\n`vibe init` installs local composition rules into your project. The\nuniversal copy lives at `SKILL.md` (with `references/*.md`), and\nhost-specific copies are placed where each agent expects them\n(`.claude/skills/hyperframes/` for Claude Code, `.cursor/rules/hyperframes.mdc`\nfor Cursor). **Read `SKILL.md` before authoring any scene composition HTML\nunder `compositions/`** \u2014 it defines the framework rules, motion\nprinciples, type system, and visual-identity hard-gate. The same skill\ngoverns `vibe scene lint` so your authored HTML and the linter stay in\nagreement.\n\nFor render-stable text, do not apply continuous `scale`, `x`, `y`,\n`filter`, or other transform tweens to `.scene-content` or any ancestor\ncontaining live text/cards. Put ambient zoom/parallax on background or media\nlayers only; text should enter briefly and then hold still at its final CSS\nposition.\n\nTo retro-install on a project scaffolded before this command existed, run\n`vibe scene install-skill [--host all]`.\n\n### Scene composer (batch / non-agent fallback)\n\nWhen you don't want to author HTML yourself, `vibe build --mode batch` runs an\nLLM internally with the same skill bundle. It auto-picks a provider based\non available keys (`claude > gemini > openai`) \u2014 pass `--composer <name>`\nto force one:\n\n| Provider | Env var | Spike notes (v0.70) |\n|---|---|---|\n| Claude (default) | `ANTHROPIC_API_KEY` | ~9 s/beat. Fastest, validated baseline. |\n| Gemini | `GOOGLE_API_KEY` | ~20 s/beat. ~2.6\u00D7 cheaper than Claude. |\n| OpenAI | `OPENAI_API_KEY` | ~70 s/beat. gpt-5 reasoning latency \u2014 opt-in only. |\n\nAll three pass first-shot lint at every effort tier on the `vibeframe-promo`\nfixture. Quality on more complex storyboards may differ \u2014 fall back to\n`--composer claude` if a non-default provider repeatedly fails lint.\n\n## Project conventions\n\n<!-- Edit this section with YOUR project's specifics. Examples below. -->\n\n- Default aspect ratio: `16:9`\n- Default scene duration: `3 seconds` (per beat)\n- Brand voice: <describe>\n- Output naming: `renders/<name>-<timestamp>.mp4`\n";
21
+ export declare const AGENTS_MD = "# AGENTS.md\n\nThis project uses [VibeFrame](https://github.com/vericontext/vibeframe) \u2014\na CLI for AI-powered video editing. The `vibe` command provides 80+\nsubcommands for video generation, editing, analysis, and pipeline\norchestration. Cost tiers below \u2014 **always confirm with the user before\nrunning High / Very High tier commands**.\n\n## Agent host coverage\n\nVibeFrame's CLI works with any bash-capable AI coding agent. Six host\nfamilies have first-class auto-detection and scaffolds today:\n\n- **Claude Code** \u2014 `CLAUDE.md` (this file's import wrapper) +\n `AGENTS.md` + `/vibe-*` slash commands.\n- **OpenAI Codex** \u2014 reads `AGENTS.md` natively (agents.md spec).\n- **Cursor** \u2014 reads `AGENTS.md` plus `.cursor/rules/` for project\n rules; supports MCP via `.cursor/mcp.json`.\n- **Aider** \u2014 reads `AGENTS.md` (binary-only detection by VibeFrame).\n- **Gemini CLI** \u2014 primary context file is `GEMINI.md`; also reads\n `AGENTS.md` if present.\n- **OpenCode** \u2014 reads `AGENTS.md` (officially supported per\n `opencode.ai/docs/rules`); supports MCP via `.opencode/mcp.json`.\n\nRun `vibe doctor` to see which host(s) the CLI detects in your\nenvironment. Anyone running another bash-capable agent (Continue,\nSourcegraph Cody, etc.) still gets this `AGENTS.md` as the universal\nfallback.\n\n## Self-discovery\n\nDon't memorise the command surface \u2014 read it from the CLI:\n\n```bash\nvibe --help # command groups overview\nvibe schema --list # full machine-readable catalog (80+)\nvibe schema --list --surface public # small first-run/product command surface\nvibe schema generate.video # JSON Schema for any single command\nvibe doctor # available providers + system health\nvibe host setup all # print Codex/Claude/Cursor app integration snippets\nvibe host doctor all --json # verify project guidance + MCP config\nvibe guide motion # choose text-overlay vs motion-overlay vs generate motion\nvibe guide scene # step-by-step authoring guide (scene)\nvibe guide pipeline # step-by-step authoring guide (pipeline)\n```\n\n## Route by the user's actual request\n\nRouting the user's request correctly is the most important judgement call\nyou'll make. Do not force everything into a scene project.\n\n**ASSET \u2014 default for any single-asset request OR ambiguous prompt.**\nIf the user asks for a single image, single video clip, sound effect, music\nbed, or narration file \u2014 **OR pastes a visual/audio brief without explicit\nstoryboard / scene / multi-scene language** \u2014 treat it as ASSET. Use\n`vibe generate ...` directly. Do **not** edit `DESIGN.md`,\n`STORYBOARD.md`, run `vibe scene ...`, or auto-load the hyperframes\nskill until BUILD intent is explicit. The visual-identity hard-gate\napplies to BUILD only.\n\nExamples:\n- \"make this image\" \u2192 `vibe generate image \"...\" -o assets/name.png`\n- \"use this image to make a video\" \u2192 `vibe generate video \"...\" -i image.png -o renders/name.mp4`\n- \"add a lower-third/title/animated overlay/grain/vignette to this clip\" \u2192 prefer `vibe edit motion-overlay clip.mp4 \"...\" --understand auto -o out.mp4` when the request asks for designed or animated motion graphics.\n- \"please add visuals using OpenAI image gen\" \u2192 `vibe generate image \"...\" -p openai ...`\n- *(verb-less paste)* \"aerial view of a misty mountain peak at sunrise...\" \u2192 `vibe generate image \"<paste>\" -o assets/mountain-peak.png`. **Don't** read it as a brief for DESIGN.md.\n\nIf genuinely uncertain, ask one short question: *\"single asset or\nmulti-scene project?\"* before authoring DESIGN.md or loading the\nhyperframes skill.\n\n**BUILD \u2014 create new video from text intent.**\nUse `vibe build` with a STORYBOARD.md + DESIGN.md. The\nskills-driven pipeline (v0.60+) dispatches narration TTS + backdrop\nimage-gen per beat, composes scene HTML via the bundled composition rules\nbundle, then renders to MP4. Idempotent re-runs reuse cached assets.\n\n**REMIX \u2014 transform existing video / audio.**\nUse `vibe remix`, `vibe edit`, or `vibe audio`. One-shot,\nbatch-oriented operations on a file the user already has on disk.\n`vibe edit text-overlay` is the free deterministic path for simple static\ntext burn-in. If the user asks for motion design, animated lower-thirds,\ndesigned titles, grain/vignette as part of a graphic treatment, or says\n\"motion graphics\", use `vibe edit motion-overlay ... --understand auto` instead.\n\nDecision rule: if the user asks for one asset, it's ASSET. If the user asks\nfor a multi-scene/storyboard/composed video, it's BUILD. If the user is\nstarting from a media file and wants it transformed, it's REMIX.\n\n## Common workflows\n\n### BUILD (new video from text)\n\n| Task | Command |\n|---|---|\n| One-shot storyboard \u2192 video | `vibe build [project-dir]` |\n| Render an existing scene project | `vibe render [project-dir] -o out.mp4` |\n| Lint scene HTML | `vibe scene lint --json` |\n| Generate a single image | `vibe generate image \"prompt\" -o img.png --quality hd` |\n| Generate a single video | `vibe generate video \"prompt\" -i image.png -o clip.mp4` |\n| Generate narration | `vibe generate narration \"text\" -o voice.mp3` |\n\n### REMIX (transform existing media)\n\n| Task | Command |\n|---|---|\n| Extract highlights from a long video | `vibe remix highlights file.mp4 -d 60` |\n| Long video \u2192 vertical shorts | `vibe remix auto-shorts file.mp4 -n 3 --add-captions` |\n| Add animated word-by-word captions | `vibe remix animated-caption file.mp4 -s karaoke-sweep` |\n| Add designed motion graphics overlay | `vibe edit motion-overlay file.mp4 \"lower-third...\" --understand auto -o out.mp4` |\n| Remove silence | `vibe edit silence-cut in.mp4 -o out.mp4` |\n| Add static captions | `vibe edit caption in.mp4 -o out.mp4` |\n| Translate audio (transcribe \u2192 TTS) | `vibe audio dub file.mp4 -t ko` |\n| Transcribe (Whisper, word-level) | `vibe audio transcribe file.mp4 --granularity word` |\n| Inspect media with Gemini | `vibe inspect media file.mp4 \"summarise\" --json` |\n\n### Compose pipelines (Video as Code)\n\n| Task | Command |\n|---|---|\n| Run a YAML pipeline | `vibe run pipeline.yaml --dry-run` |\n| Resume from last checkpoint | `vibe run pipeline.yaml --resume` |\n\n## Cost tiers\n\n| Tier | Examples | Approx. per call |\n|---|---|---|\n| **Free** | `schema/context/doctor`, `detect *`, `status *`, `plan`, `storyboard validate`, `inspect project/render --cheap`, deterministic edits | $0 |\n| **Low** | `generate narration/sound-effect/music`, `audio transcribe`, `inspect media`, optional AI review | $0.01\u2013$0.10 |\n| **High** | `generate image/motion`, `edit image/reframe/grade/speed-ramp` | $1\u2013$5 |\n| **Very High** | `generate video`, `edit fill-gaps`, `remix highlights/auto-shorts`, `build` with generated assets | $5\u2013$50+ |\n\n## Agent invariants\n\nWhen you run vibe commands programmatically:\n\n1. **Always `--json`** for structured output you can parse without regex\n2. **Always `--dry-run` first** for any High/Very-High tier command \u2014 it returns\n the cost estimate without spending API budget\n3. **Use `vibe schema <command>`** to discover parameters; do not guess flags\n4. **Pass complex options via `--stdin`**:\n ```bash\n echo '{\"video\":\"long.mp4\",\"count\":3,\"aspect\":\"9:16\"}' | vibe remix auto-shorts --stdin --json\n ```\n\n## Provider keys and project scope\n\nPrefer `vibe setup --scope project` for project-local keys. It writes\n`.vibeframe/config.yaml` in the setup directory; when you work from a\nnested scene directory, the `vibe` CLI automatically searches parent\ndirectories and uses the nearest project config. Do not assume the config\nfile must exist in the current subdirectory.\n\nRun `vibe doctor --json` from the directory where you will run commands\nand check `data.scope.project.configPath` / `data.scope.activeScope` before\ndeciding keys are missing. Never print or copy config file contents.\n\nFor generated project assets, use VibeFrame commands such as\n`vibe generate image|video|speech ...` so these project-scope keys are\nused. Do not switch to a host agent's built-in image/audio generation tool\nunless the user explicitly asks for a non-VibeFrame asset.\n\n`.env` is still supported as a gitignored fallback. Copy `.env.example`\nto start if you prefer environment variables.\n\n## App host setup\n\nVibeFrame is CLI-first, not terminal-only. Codex, Claude Code, and Cursor can\ndrive the same project through shell commands, and app hosts can also use\nVibeFrame's MCP server as a typed tool surface.\n\n```bash\nvibe host setup all # print snippets only\nvibe host setup all --write # write project/app config\nvibe host doctor all --json # verify readiness\n```\n\nDefault setup is snippet-only; use `--write` when you want VibeFrame to merge\nthe generated config. Keep provider keys in `.vibeframe/config.yaml`, user\nconfig, or env files \u2014 not in app host config.\n\n## Native host goal loop\n\nFor long-running work, use Codex Goal mode, Claude Code `/goal`, Cursor, or\nanother host-native goal feature as the outer loop. VibeFrame should not own a\ncompeting project-level goal runner. It provides `--json` commands, dry runs,\nbudget caps, `build-report.json`, `review-report.json`, `nextActions`,\n`safeToAutoRun`, `requiresConfirmation`, `fixOwner`, deterministic repair,\nand render inspection for the host agent to decide what to do next.\n\nCopy-paste Codex:\n\n```text\n/goal Build this VibeFrame project into renders/final.mp4. Use --json for every\nvibe command, run --dry-run before paid operations, use --max-cost 5 for builds\nunless the user gives another budget, read build-report.json and\nreview-report.json before deciding the next action, prefer nextActions before\nguessing, run only safeToAutoRun:true actions automatically, and ask before\nrequiresConfirmation:true actions. Treat retryWith as the compatibility\nfallback, fixOwner:\"vibe\" as CLI repair work, and fixOwner:\"host-agent\" as\nSTORYBOARD.md, DESIGN.md, or composition edits.\nStop only when renders/final.mp4 exists, target duration and aspect ratio are\nmet, inspect render --cheap has no errors, any AI review score is >= 90 when AI review is requested, and every\nremaining host-agent issue is fixed, accepted with rationale, or reported as\nblocked.\n```\n\nCopy-paste Claude Code:\n\n```text\n/goal Finish this VibeFrame render using Claude Code goal mode as the outer\nloop. Use vibe context/schema when unsure, --json everywhere, dry-run before\npaid operations, budget cap via --max-cost 5, nextActions before guessing,\nbuild-report.json/review-report.json as loop state, and safeToAutoRun,\nrequiresConfirmation, and fixOwner to decide between vibe scene repair and\nhost-agent edits. Stop only after final MP4,\ntarget duration/aspect ratio, clean render inspection, any AI review score >= 90 when AI review is requested, and\nno unresolved unacknowledged host-agent issues.\n```\n\n### Composition rules (scene HTML)\n\n`vibe init` installs local composition rules into your project. The\nuniversal copy lives at `SKILL.md` (with `references/*.md`), and\nhost-specific copies are placed where each agent expects them\n(`.claude/skills/hyperframes/` for Claude Code, `.cursor/rules/hyperframes.mdc`\nfor Cursor). **Read `SKILL.md` before authoring any scene composition HTML\nunder `compositions/`** \u2014 it defines the framework rules, motion\nprinciples, type system, and visual-identity hard-gate. The same skill\ngoverns `vibe scene lint` so your authored HTML and the linter stay in\nagreement.\n\nFor render-stable text, do not apply continuous `scale`, `x`, `y`,\n`filter`, or other transform tweens to `.scene-content` or any ancestor\ncontaining live text/cards. Put ambient zoom/parallax on background or media\nlayers only; text should enter briefly and then hold still at its final CSS\nposition.\n\nTo retro-install on a project scaffolded before this command existed, run\n`vibe scene install-skill [--host all]`.\n\n### Scene composer (batch / non-agent fallback)\n\nWhen you don't want to author HTML yourself, `vibe build --mode batch` runs an\nLLM internally with the same skill bundle. It auto-picks a provider based\non available keys (`claude > gemini > openai`) \u2014 pass `--composer <name>`\nto force one:\n\n| Provider | Env var | Spike notes (v0.70) |\n|---|---|---|\n| Claude (default) | `ANTHROPIC_API_KEY` | ~9 s/beat. Fastest, validated baseline. |\n| Gemini | `GOOGLE_API_KEY` | ~20 s/beat. ~2.6\u00D7 cheaper than Claude. |\n| OpenAI | `OPENAI_API_KEY` | ~70 s/beat. gpt-5 reasoning latency \u2014 opt-in only. |\n\nAll three pass first-shot lint at every effort tier on the `vibeframe-promo`\nfixture. Quality on more complex storyboards may differ \u2014 fall back to\n`--composer claude` if a non-default provider repeatedly fails lint.\n\n## Project conventions\n\n<!-- Edit this section with YOUR project's specifics. Examples below. -->\n\n- Default aspect ratio: `16:9`\n- Default scene duration: `3 seconds` (per beat)\n- Brand voice: <describe>\n- Output naming: `renders/<name>-<timestamp>.mp4`\n";
22
22
  /**
23
23
  * Claude Code project file. Imports AGENTS.md so guidance stays
24
24
  * single-sourced; adds Claude-specific overrides (slash commands, etc.).
@@ -1,5 +1,5 @@
1
1
  import type { VideoReviewFeedback } from "../ai-edit.js";
2
- import { type ReviewIssue, type ReviewSeverity, type ReviewSummary, type ReviewStatus } from "./review-report.js";
2
+ import { type ReviewAction, type ReviewIssue, type ReviewSeverity, type ReviewSummary, type ReviewStatus } from "./review-report.js";
3
3
  export interface TimeRange {
4
4
  start: number;
5
5
  end: number;
@@ -64,6 +64,7 @@ export interface RenderInspectResult {
64
64
  score: number;
65
65
  issues: ReviewIssue[];
66
66
  summary: ReviewSummary;
67
+ nextActions: ReviewAction[];
67
68
  sourceReports: string[];
68
69
  checks: {
69
70
  renderFound: boolean;
@@ -75,6 +76,7 @@ export interface RenderInspectResult {
75
76
  height?: number;
76
77
  expectedAspect?: string;
77
78
  hasAudio?: boolean;
79
+ expectedAudio?: boolean;
78
80
  blackFrames: TimeRange[];
79
81
  staticFrames: TimeRange[];
80
82
  silences: TimeRange[];
@@ -101,4 +103,5 @@ export declare function aiReviewSeverity(score: number): ReviewSeverity;
101
103
  export declare function mapAiReviewFeedbackToIssues(feedback: VideoReviewFeedback, videoPath?: string): ReviewIssue[];
102
104
  export declare function scoreRenderReview(issues: ReviewIssue[], aiOverallScore?: number): number;
103
105
  export declare function inspectRender(opts: RenderInspectOptions): Promise<RenderInspectResult>;
106
+ export declare function renderExpectsAudio(projectDir: string, beatId?: string): Promise<boolean>;
104
107
  export declare function resolveRenderVideoPath(projectDir: string, explicit?: string, beatId?: string): Promise<string | null>;
@@ -2,6 +2,21 @@ export type ReviewSeverity = "error" | "warning" | "info";
2
2
  export type ReviewStatus = "pass" | "warn" | "fail";
3
3
  export type ReviewMode = "project" | "render";
4
4
  export type ReviewFixOwner = "vibe" | "host-agent";
5
+ export type ReviewActionKind = "command" | "agent" | "manual";
6
+ export type ReviewActionCostTier = "free" | "low" | "high" | "very-high" | "unknown";
7
+ export interface ReviewAction {
8
+ id: string;
9
+ kind: ReviewActionKind;
10
+ label: string;
11
+ command?: string;
12
+ agentPrompt?: string;
13
+ fixOwner: ReviewFixOwner;
14
+ costTier: ReviewActionCostTier;
15
+ safeToAutoRun: boolean;
16
+ requiresConfirmation: boolean;
17
+ reason: string;
18
+ sourceIssueCodes?: string[];
19
+ }
5
20
  export interface ReviewIssue {
6
21
  severity: ReviewSeverity;
7
22
  code: string;
@@ -19,6 +34,7 @@ export interface ReviewIssue {
19
34
  audioCoverageRatio?: number;
20
35
  fixOwner?: ReviewFixOwner;
21
36
  suggestedFix?: string;
37
+ actions?: ReviewAction[];
22
38
  }
23
39
  export interface ReviewSummary {
24
40
  issueCount: number;
@@ -40,6 +56,7 @@ export interface ReviewReport {
40
56
  score: number;
41
57
  issues: ReviewIssue[];
42
58
  summary: ReviewSummary;
59
+ nextActions: ReviewAction[];
43
60
  retryWith: string[];
44
61
  sourceReports: string[];
45
62
  reportPath?: string;
@@ -47,8 +64,40 @@ export interface ReviewReport {
47
64
  export declare function statusFromIssues(issues: ReviewIssue[]): ReviewStatus;
48
65
  export declare function scoreIssues(issues: ReviewIssue[]): number;
49
66
  export declare function uniqueRetryWith(items: Array<string | undefined | null>): string[];
67
+ export declare function commandReviewAction(command: string, opts: {
68
+ label?: string;
69
+ fixOwner?: ReviewFixOwner;
70
+ reason: string;
71
+ sourceIssueCodes?: string[];
72
+ costTier?: ReviewActionCostTier;
73
+ safeToAutoRun?: boolean;
74
+ requiresConfirmation?: boolean;
75
+ }): ReviewAction;
76
+ export declare function agentReviewAction(opts: {
77
+ label: string;
78
+ agentPrompt: string;
79
+ reason: string;
80
+ sourceIssueCodes?: string[];
81
+ }): ReviewAction;
82
+ export declare function manualReviewAction(opts: {
83
+ label: string;
84
+ reason: string;
85
+ fixOwner?: ReviewFixOwner;
86
+ sourceIssueCodes?: string[];
87
+ }): ReviewAction;
88
+ export declare function reviewActionsFromRetryWith(retryWith: string[], sourceIssueCodes?: string[]): ReviewAction[];
89
+ export declare function normalizeReviewActions(actions: ReviewAction[]): ReviewAction[];
90
+ export declare function deriveNextReviewActions(opts: {
91
+ issues: ReviewIssue[];
92
+ retryWith: string[];
93
+ projectDir?: string;
94
+ }): ReviewAction[];
50
95
  export declare function fixOwnerForIssue(issue: Pick<ReviewIssue, "code" | "fixOwner">): ReviewFixOwner;
51
- export declare function normalizeReviewIssues(issues: ReviewIssue[], fallbackOwner?: ReviewFixOwner): ReviewIssue[];
96
+ export declare function normalizeReviewIssues(issues: ReviewIssue[], opts?: ReviewFixOwner | {
97
+ fallbackOwner?: ReviewFixOwner;
98
+ projectDir?: string;
99
+ retryWith?: string[];
100
+ }): ReviewIssue[];
52
101
  export declare function summarizeReviewIssues(issues: ReviewIssue[]): ReviewSummary;
53
102
  export declare function buildReviewReport(opts: {
54
103
  project: string;
@@ -0,0 +1 @@
1
+ export {};
@@ -34,6 +34,19 @@ export type BuildImageProvider = "openai" | "gemini" | "grok";
34
34
  export type SceneBuildProgressEvent = {
35
35
  type: "phase-start";
36
36
  phase: "primitives" | "compose" | "render";
37
+ } | {
38
+ type: "character-cached";
39
+ name: string;
40
+ path: string;
41
+ } | {
42
+ type: "character-generated";
43
+ name: string;
44
+ path: string;
45
+ provider: string;
46
+ } | {
47
+ type: "character-failed";
48
+ name: string;
49
+ error: string;
37
50
  } | {
38
51
  type: "narration-cached";
39
52
  beatId: string;
@@ -155,6 +168,9 @@ export interface BeatBuildOutcome {
155
168
  videoMetadataPath?: string;
156
169
  videoFreshness?: AssetFreshness;
157
170
  videoSourcePath?: string;
171
+ /** Keyframe still status when the beat uses keyframe → image-to-video mode. */
172
+ keyframeStatus?: PrimitiveStatus;
173
+ keyframePath?: string;
158
174
  musicStatus: PrimitiveStatus;
159
175
  musicPath?: string;
160
176
  musicJobId?: string;
@@ -46,7 +46,7 @@ export interface LottieOverlayInput {
46
46
  loop?: boolean;
47
47
  }
48
48
  export interface EmitSceneInput {
49
- /** Kebab-case scene id; appears in `data-composition-id` and template id. */
49
+ /** Kebab-case beat id; emitted composition id is `scene-<id>` to match root-sync. */
50
50
  id: string;
51
51
  preset: ScenePreset;
52
52
  /** Canvas width in px (must match the root composition). */
@@ -80,6 +80,8 @@ export interface EmitSceneInput {
80
80
  */
81
81
  lottie?: LottieOverlayInput;
82
82
  }
83
+ /** Root-sync's canonical composition id for a storyboard beat id. */
84
+ export declare function sceneCompositionId(beatId: string): string;
83
85
  /**
84
86
  * Slugify a free-form scene name to a kebab-case id usable in HTML attributes
85
87
  * and filenames. Strips diacritics, collapses whitespace, lowercases.
@@ -133,7 +135,7 @@ export declare function emitSceneHtml(input: EmitSceneInput): string;
133
135
  */
134
136
  export declare function nextSceneStart(rootHtml: string, overlap?: number): number;
135
137
  export interface ClipReferenceInput {
136
- /** Scene id — used to derive the composition src path. */
138
+ /** Beat id — used to derive `scene-<id>` composition id and src path. */
137
139
  id: string;
138
140
  start: number;
139
141
  duration: number;
@@ -1,4 +1,4 @@
1
- import { type ReviewIssue, type ReviewSummary, type ReviewStatus } from "./review-report.js";
1
+ import { type ReviewAction, type ReviewIssue, type ReviewSummary, type ReviewStatus } from "./review-report.js";
2
2
  export interface ProjectInspectOptions {
3
3
  projectDir: string;
4
4
  beatId?: string;
@@ -15,6 +15,7 @@ export interface ProjectInspectResult {
15
15
  score: number;
16
16
  issues: ReviewIssue[];
17
17
  summary: ReviewSummary;
18
+ nextActions: ReviewAction[];
18
19
  sourceReports: string[];
19
20
  checks: {
20
21
  files: Record<string, boolean>;
@@ -1,4 +1,5 @@
1
1
  import type { BuildAssetKind } from "./build-cache.js";
2
+ import type { ReviewAction } from "./review-report.js";
2
3
  export type JobStatus = "queued" | "running" | "completed" | "failed" | "cancelled" | "unknown";
3
4
  /**
4
5
  * `generate-video` / `generate-music` are provider-backed jobs refreshed by
@@ -127,6 +128,7 @@ export interface ProjectStatusResult {
127
128
  latest: JobSummary[];
128
129
  };
129
130
  warnings: string[];
131
+ nextActions: ReviewAction[];
130
132
  retryWith: string[];
131
133
  }
132
134
  export interface BuildSummary {
@@ -161,6 +163,7 @@ export interface ReviewSummary {
161
163
  };
162
164
  sourceReports: string[];
163
165
  updatedAt?: string;
166
+ nextActions: ReviewAction[];
164
167
  retryWith: string[];
165
168
  }
166
169
  export interface JobSummary {
@@ -1,5 +1,5 @@
1
1
  import { type Beat } from "./storyboard-parse.js";
2
- export declare const STORYBOARD_CUE_KEYS: readonly ["duration", "narration", "backdrop", "video", "motion", "voice", "music", "asset"];
2
+ export declare const STORYBOARD_CUE_KEYS: readonly ["duration", "narration", "backdrop", "video", "keyframe", "motion", "voice", "music", "asset", "characters"];
3
3
  export type StoryboardCueKey = (typeof STORYBOARD_CUE_KEYS)[number];
4
4
  export interface StoryboardValidationIssue {
5
5
  severity: "error" | "warning";
@@ -12,10 +12,24 @@ export interface StoryboardValidationResult {
12
12
  beats: Beat[];
13
13
  issues: StoryboardValidationIssue[];
14
14
  }
15
+ export type StoryboardBeatUpsertAction = "replaced-starter" | "appended" | "updated";
16
+ export interface StoryboardBeatUpsertInput {
17
+ beatId: string;
18
+ title?: string;
19
+ duration?: number;
20
+ narration?: string;
21
+ backdrop?: string;
22
+ body?: string;
23
+ }
24
+ export interface StoryboardBeatUpsertResult {
25
+ markdown: string;
26
+ action: StoryboardBeatUpsertAction;
27
+ }
15
28
  /** Beats beyond this render as static, overstuffed scenes. */
16
29
  export declare const MAX_RECOMMENDED_BEAT_SEC = 15;
17
30
  export declare function validateStoryboardMarkdown(markdown: string): StoryboardValidationResult;
18
31
  export declare function getStoryboardBeat(markdown: string, beatId: string): Beat | null;
32
+ export declare function upsertStoryboardBeat(markdown: string, opts: StoryboardBeatUpsertInput): StoryboardBeatUpsertResult;
19
33
  export declare function setStoryboardCue(markdown: string, opts: {
20
34
  beatId: string;
21
35
  key: string;
@@ -65,8 +65,35 @@ export interface ProjectFrontmatter {
65
65
  defaultDuration?: number;
66
66
  /** Visual identity preset hint (informational; e.g. "Swiss Pulse"). */
67
67
  style?: string;
68
+ /**
69
+ * Reusable character pool. Each entry is either a generation prompt
70
+ * (string → build renders a turnaround sheet at `assets/character-<name>.png`)
71
+ * or an object with `description` (same, explicit) and/or `image` (a
72
+ * bring-your-own reference path, which skips generation). Beats reference
73
+ * these by name via the `characters:` cue to drive reference-to-video.
74
+ */
75
+ characters?: Record<string, CharacterSpec>;
68
76
  [key: string]: unknown;
69
77
  }
78
+ /** A character pool entry — a prompt string or a structured spec. */
79
+ export type CharacterSpec = string | {
80
+ description?: string;
81
+ image?: string;
82
+ };
83
+ /** Normalized character ready for build: generation prompt and/or supplied image. */
84
+ export interface ResolvedCharacter {
85
+ name: string;
86
+ /** Generation prompt when the sheet should be rendered (absent if `image` given). */
87
+ prompt?: string;
88
+ /** Bring-your-own reference image path, relative to the project dir. */
89
+ imagePath?: string;
90
+ }
91
+ /**
92
+ * Flatten the frontmatter `characters` map into build-ready specs. A string or
93
+ * `{ description }` becomes a generation prompt; `{ image }` is used as-is
94
+ * (generation skipped). Entries with neither are dropped.
95
+ */
96
+ export declare function resolveCharacters(frontmatter: ProjectFrontmatter | undefined): ResolvedCharacter[];
70
97
  /**
71
98
  * Per-beat cues extracted from the first ```yaml fenced block inside a beat
72
99
  * body. The driver passes these to TTS / image / compose calls so the
@@ -81,8 +108,22 @@ export interface BeatCues {
81
108
  duration?: number;
82
109
  /** Voice override for this beat (overrides project frontmatter `voice`). */
83
110
  voice?: string;
111
+ /**
112
+ * Character pool names whose reference sheets drive this beat's
113
+ * reference-to-video generation. A single name or a list.
114
+ */
115
+ characters?: string | string[];
116
+ /**
117
+ * Keyframe still prompt. When set, the build generates a keyframe image for
118
+ * this beat (using the character sheet as an edit reference when `characters`
119
+ * is also present) and runs Seedance image-to-video on it. The `video` cue,
120
+ * if present, supplies the motion prompt; otherwise this text is used.
121
+ */
122
+ keyframe?: string;
84
123
  [key: string]: unknown;
85
124
  }
125
+ /** Normalize a beat's `characters` cue to a clean string[] (single or list). */
126
+ export declare function beatCharacterNames(cues: BeatCues | undefined): string[];
86
127
  export interface Beat {
87
128
  /**
88
129
  * Stable id for the beat. Derivation order:
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @file error-envelopes.test.ts
3
+ *
4
+ * Contract/drift detection for the CLI's STRUCTURED ERROR envelope — the JSON
5
+ * that `exitWithError()` (commands/output.ts) writes to stderr when a command
6
+ * fails. Companion to `envelope-snapshots.test.ts`, which only covers the
7
+ * SUCCESS envelope (`outputSuccess`) and `--describe` schemas; the error
8
+ * envelope was previously untested.
9
+ *
10
+ * Why:
11
+ * - Agents and MCP hosts parse the error envelope (`code`, `exitCode`,
12
+ * `retryWith`, `recoverable`) to decide recovery. A regression that renames
13
+ * or drops a field, or changes a code/exit-code, silently breaks that
14
+ * contract. Snapshots make it visible in PR review.
15
+ * - This test also guards against fabricated contracts: the real envelope has
16
+ * no `hint` field and uses SCREAMING_SNAKE codes with no `E_` prefix.
17
+ *
18
+ * The asserted codes/exit-codes were verified by running the built CLI:
19
+ * NOT_FOUND=3, USAGE_ERROR=2, API_ERROR=5, ERROR=1 (the ExitCode enum in
20
+ * commands/output.ts).
21
+ *
22
+ * Maintenance:
23
+ * - Intentional envelope change → update with `pnpm -F @vibeframe/cli exec
24
+ * vitest run -u` and review the diff.
25
+ */
26
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -19,6 +19,7 @@
19
19
  import { Command } from "commander";
20
20
  import { type TtsProviderName } from "./_shared/tts-resolve.js";
21
21
  import { type ScenePreset, type LottieOverlayInput } from "./_shared/scene-html-emit.js";
22
+ import { type StoryboardBeatUpsertAction } from "./_shared/storyboard-edit.js";
22
23
  export declare const sceneCommand: Command;
23
24
  export interface SceneAddOptions {
24
25
  name: string;
@@ -43,6 +44,11 @@ export interface SceneAddOptions {
43
44
  projectDir?: string;
44
45
  /** Filename of the root composition (relative to projectDir). */
45
46
  insertInto?: string;
47
+ /**
48
+ * Sync STORYBOARD.md when present, then rebuild root refs via root-sync.
49
+ * Defaults to true; pass false for the legacy root-only quick-draft path.
50
+ */
51
+ syncStoryboard?: boolean;
46
52
  /** "gemini" | "openai". */
47
53
  imageProvider?: string;
48
54
  /** TTS provider preference. Defaults to `"auto"` (ElevenLabs if key set, else Kokoro). */
@@ -86,6 +92,12 @@ export interface SceneAddResult {
86
92
  /** Number of word entries emitted into the transcript JSON. */
87
93
  transcriptWordCount?: number;
88
94
  lottiePath?: string;
95
+ /** Project-relative path to STORYBOARD.md when the command synced it. */
96
+ storyboardPath?: string;
97
+ /** Whether STORYBOARD.md was used as the root-sync source of truth. */
98
+ storyboardSynced?: boolean;
99
+ /** How STORYBOARD.md was changed, or "skipped" when root-only behavior was used. */
100
+ storyboardAction?: "skipped" | StoryboardBeatUpsertAction;
89
101
  error?: string;
90
102
  }
91
103
  export declare function executeSceneAdd(opts: SceneAddOptions): Promise<SceneAddResult>;