@vibeframe/cli 0.115.4 → 0.115.5

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
@@ -3,7 +3,7 @@
3
3
  ## Overview
4
4
 
5
5
  VibeFrame CLI (`vibe`) generates AI video on the user's own provider keys
6
- (Seedance, Runway, Veo, Kling), behind dry runs and a hard `--max-cost`
6
+ (Seedance, Runway, Kling, Gemini Omni), behind dry runs and a hard `--max-cost`
7
7
  ceiling, and edits the results. Every operation is
8
8
  a shell command. The same surface is exposed as MCP tools through
9
9
  [`@vibeframe/mcp-server`](https://www.npmjs.com/package/@vibeframe/mcp-server).
@@ -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) -\na CLI for AI video generation on the user's own provider keys (Seedance,\nRunway, Veo, Kling), behind dry runs and a hard `--max-cost` ceiling.\nThe `vibe` command provides 80+ subcommands for video generation,\nediting, and analysis. Cost tiers below - **always confirm with the user\nbefore running 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** - `CLAUDE.md` (this file's import wrapper) +\n `AGENTS.md` + `/vibe-*` slash commands.\n- **OpenAI Codex** - reads `AGENTS.md` natively (agents.md spec).\n- **Cursor** - reads `AGENTS.md` plus `.cursor/rules/` for project\n rules; supports MCP via `.cursor/mcp.json`.\n- **Aider** - reads `AGENTS.md` (binary-only detection by VibeFrame).\n- **Gemini CLI** - primary context file is `GEMINI.md`; also reads\n `AGENTS.md` if present.\n- **OpenCode** - 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 - 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 - 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 - **OR pastes a visual/audio brief without explicit\nstoryboard / scene / multi-scene language** - 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 - 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 - 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 - 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 - not in app host config.\n\n## Host agent loop\n\nFor long-running work, use your host's agent loop (Claude Code, Codex, Cursor,\nor another coding-agent host) as the outer loop. VibeFrame should not own a\ncompeting project-level agent loop. 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 agent prompt (Codex) - a plain prompt, not a built-in command:\n\n```text\nBuild 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 agent prompt (Claude Code) - a plain prompt, not a built-in command:\n\n```text\nFinish this VibeFrame render using your host's agent loop 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\nComposition rules live in the **Hyperframes skill** - framework rules, motion\nprinciples, type system, and visual-identity hard-gate. **Load that skill\nbefore authoring any scene composition HTML under `compositions/`.** The same\nrules govern `vibe scene lint`, so your authored HTML and the linter stay in\nagreement.\n\nWhere the skill comes from depends on your setup:\n\n- If the `hyperframes` skill is installed globally (e.g. Claude Code), your\n agent already has it and nothing else is needed.\n- Otherwise install it into this project with `vibe scene install-skill`,\n which runs upstream's own installer and writes\n `.agents/skills/hyperframes/`.\n\n`vibe init` never copies the rules in. They come from upstream so they stay\ncurrent, and fetching them is a network call the scaffold does not make on\nyour behalf. `vibe doctor` reports whether your agent can read them.\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\n### Scene composer (non-agent fallback)\n\nScene HTML is authored by you, the host agent - the CLI makes no LLM call\nduring `vibe build`. When no agent is available to author scenes, pass\n`--composer template` for the deterministic no-model composer\n(concat background + lower-thirds).\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) -\na CLI for AI video generation on the user's own provider keys (Seedance,\nRunway, Kling, Gemini Omni), behind dry runs and a hard `--max-cost` ceiling.\nThe `vibe` command provides 80+ subcommands for video generation,\nediting, and analysis. Cost tiers below - **always confirm with the user\nbefore running 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** - `CLAUDE.md` (this file's import wrapper) +\n `AGENTS.md` + `/vibe-*` slash commands.\n- **OpenAI Codex** - reads `AGENTS.md` natively (agents.md spec).\n- **Cursor** - reads `AGENTS.md` plus `.cursor/rules/` for project\n rules; supports MCP via `.cursor/mcp.json`.\n- **Aider** - reads `AGENTS.md` (binary-only detection by VibeFrame).\n- **Gemini CLI** - primary context file is `GEMINI.md`; also reads\n `AGENTS.md` if present.\n- **OpenCode** - 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 - 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 - 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 - **OR pastes a visual/audio brief without explicit\nstoryboard / scene / multi-scene language** - 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 - 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 - 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 - 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 - not in app host config.\n\n## Host agent loop\n\nFor long-running work, use your host's agent loop (Claude Code, Codex, Cursor,\nor another coding-agent host) as the outer loop. VibeFrame should not own a\ncompeting project-level agent loop. 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 agent prompt (Codex) - a plain prompt, not a built-in command:\n\n```text\nBuild 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 agent prompt (Claude Code) - a plain prompt, not a built-in command:\n\n```text\nFinish this VibeFrame render using your host's agent loop 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\nComposition rules live in the **Hyperframes skill** - framework rules, motion\nprinciples, type system, and visual-identity hard-gate. **Load that skill\nbefore authoring any scene composition HTML under `compositions/`.** The same\nrules govern `vibe scene lint`, so your authored HTML and the linter stay in\nagreement.\n\nWhere the skill comes from depends on your setup:\n\n- If the `hyperframes` skill is installed globally (e.g. Claude Code), your\n agent already has it and nothing else is needed.\n- Otherwise install it into this project with `vibe scene install-skill`,\n which runs upstream's own installer and writes\n `.agents/skills/hyperframes/`.\n\n`vibe init` never copies the rules in. They come from upstream so they stay\ncurrent, and fetching them is a network call the scaffold does not make on\nyour behalf. `vibe doctor` reports whether your agent can read them.\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\n### Scene composer (non-agent fallback)\n\nScene HTML is authored by you, the host agent - the CLI makes no LLM call\nduring `vibe build`. When no agent is available to author scenes, pass\n`--composer template` for the deterministic no-model composer\n(concat background + lower-thirds).\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.).
@@ -7,6 +7,15 @@
7
7
  */
8
8
  import type { Command } from "commander";
9
9
  export declare function registerVideoCommand(parent: Command): void;
10
+ /**
11
+ * Cost figure for a REAL (non-dry-run) generation envelope.
12
+ *
13
+ * Seedance has a token-accurate estimator; every other provider reports the
14
+ * command's tier upper bound with a warning naming it an estimate. Without
15
+ * this the success envelope defaulted to costUsd 0, which an agent budget
16
+ * loop reads as "free" - the hybrid-brew dogfood run billed Runway twice
17
+ * while reporting $0.
18
+ */
10
19
  export declare function realRunCost(provider: string, options: {
11
20
  duration?: string;
12
21
  resolution?: string;
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Commands:
7
7
  * generate image - Generate image (Gemini, OpenAI, Grok, Runway)
8
- * generate video - Generate video (Seedance, Grok, Kling, Runway, Veo)
8
+ * generate video - Generate video (Seedance, Grok, Kling, Runway, Omni, Veo)
9
9
  * generate sound-effect - Sound effects (ElevenLabs)
10
10
  * generate music - Music generation (ElevenLabs default, Replicate MusicGen)
11
11
  * generate motion - Standalone motion assets (Claude/Gemini + Remotion)
@@ -75,6 +75,11 @@ export interface SuccessEnvelopeOptions {
75
75
  warnings?: string[];
76
76
  /** Pass-through of `--dry-run` flag — surfaces as a top-level `dryRun: true` in the envelope. */
77
77
  dryRun?: boolean;
78
+ /**
79
+ * The caller already printed its own human-readable output (a build plan,
80
+ * a render summary), so human mode adds nothing for this dry run.
81
+ */
82
+ humanRendered?: boolean;
78
83
  }
79
84
  /** Look up the upper-bound cost estimate for a command (used by dry-run). */
80
85
  /**
@@ -87,12 +92,15 @@ export declare function lookupCostEstimateUpperBound(command: string): number;
87
92
  * Emit a canonical success envelope (#33 — 2c). JSON mode prints the
88
93
  * envelope; quiet mode prints a single primary value extracted from
89
94
  * `data.outputPath ?? data.output ?? data.path ?? data.url ?? data.id`;
90
- * human mode prints nothing — the caller renders human output itself.
95
+ * human mode prints a summary for dry runs (unless the caller rendered its
96
+ * own) and otherwise nothing — the caller renders human output itself.
91
97
  *
92
98
  * Canary scope: `generate image` only in 2c-canary. 2c-sweep migrates
93
99
  * the rest. Old `outputResult()` remains for the sweep transition.
94
100
  */
95
101
  export declare function outputSuccess(opts: SuccessEnvelopeOptions): void;
102
+ /** Print warnings to stderr in human mode; JSON envelopes carry them instead. */
103
+ export declare function printWarnings(warnings: readonly string[]): void;
96
104
  /** Output result - JSON mode outputs JSON, quiet mode outputs primary value only */
97
105
  export declare function outputResult(result: Record<string, unknown>): void;
98
106
  /** Wrap console output - suppressed in JSON/quiet mode */
package/dist/index.js CHANGED
@@ -6629,7 +6629,42 @@ function outputSuccess(opts) {
6629
6629
  const data = opts.data;
6630
6630
  const primary = data.outputPath ?? data.output ?? data.path ?? data.url ?? data.id;
6631
6631
  if (primary !== void 0) console.log(String(primary));
6632
+ return;
6633
+ }
6634
+ if (opts.dryRun && !opts.humanRendered) {
6635
+ printDryRunSummary(opts.command, costUsd, opts.data, opts.warnings ?? []);
6636
+ }
6637
+ }
6638
+ function printWarnings(warnings) {
6639
+ if (isJsonMode()) return;
6640
+ for (const warning of warnings) {
6641
+ process.stderr.write(`${source_default.yellow(`Warning: ${warning}`)}
6642
+ `);
6643
+ }
6644
+ }
6645
+ function formatDryRunValue(value) {
6646
+ const text = Array.isArray(value) ? value.map((v) => formatDryRunValue(v)).join(", ") : typeof value === "object" ? JSON.stringify(value) : String(value);
6647
+ return text.length > 80 ? `${text.slice(0, 77)}...` : text;
6648
+ }
6649
+ function printDryRunSummary(command, costUsd, data, warnings) {
6650
+ const params = data.params && typeof data.params === "object" ? data.params : data;
6651
+ const rows = [
6652
+ ["cost", costUsd > 0 ? `up to $${costUsd.toFixed(2)} (estimate)` : "free"]
6653
+ ];
6654
+ for (const [key, value] of Object.entries(params)) {
6655
+ if (value === void 0 || value === null || value === "") continue;
6656
+ if (Array.isArray(value) && value.length === 0) continue;
6657
+ rows.push([key, formatDryRunValue(value)]);
6632
6658
  }
6659
+ const width = Math.max(...rows.map(([key]) => key.length));
6660
+ console.log();
6661
+ console.log(`${source_default.bold.cyan(`Dry run: vibe ${command}`)} ${source_default.dim("(nothing was sent)")}`);
6662
+ for (const [key, value] of rows) {
6663
+ console.log(` ${source_default.dim(key.padEnd(width))} ${value}`);
6664
+ }
6665
+ printWarnings(warnings);
6666
+ console.log(source_default.dim(" Run again without --dry-run to execute."));
6667
+ console.log();
6633
6668
  }
6634
6669
  function log(...args) {
6635
6670
  if (!isJsonMode() && !isQuietMode()) {
@@ -6984,8 +7019,8 @@ var init_api_keys = __esm({
6984
7019
  envVar: "GOOGLE_API_KEY",
6985
7020
  label: "Google",
6986
7021
  showInSetup: true,
6987
- setupDescription: "Gemini \u2014 image gen (free tier), video analysis ($), Veo ($$)",
6988
- envExampleComment: "Google API Key (Gemini auto-edit suggestions, image gen, Veo video)",
7022
+ setupDescription: "Gemini \u2014 image gen (free tier), video analysis ($), Omni video ($$)",
7023
+ envExampleComment: "Google API Key (Gemini auto-edit suggestions, image gen, Omni video)",
6989
7024
  envExampleUrl: "https://aistudio.google.com/apikey",
6990
7025
  keyFormat: { prefix: /^AIza/, example: "AIza..." }
6991
7026
  });
@@ -7239,10 +7274,35 @@ function findModel(provider, kind, aliasOrId) {
7239
7274
  const lower = key.toLowerCase();
7240
7275
  return listModels({ provider, kind }).find((m) => m.id === key || m.aliases?.includes(lower));
7241
7276
  }
7277
+ function modelLifecycle(spec, now = /* @__PURE__ */ new Date()) {
7278
+ if (!spec.shutdown)
7279
+ return { state: "served" };
7280
+ const remainingMs = Date.parse(`${spec.shutdown}T00:00:00Z`) - now.getTime();
7281
+ const instead = spec.replacement ? ` Use ${spec.replacement} instead.` : "";
7282
+ if (remainingMs <= 0) {
7283
+ return {
7284
+ state: "retired",
7285
+ shutdown: spec.shutdown,
7286
+ message: `${spec.label} (${spec.id}) was shut down by the provider on ${spec.shutdown}.${instead}`
7287
+ };
7288
+ }
7289
+ const daysLeft = Math.ceil(remainingMs / 864e5);
7290
+ return {
7291
+ state: "retiring",
7292
+ shutdown: spec.shutdown,
7293
+ daysLeft,
7294
+ message: `${spec.label} (${spec.id}) shuts down on ${spec.shutdown} (${daysLeft} day${daysLeft === 1 ? "" : "s"} left).${instead}`
7295
+ };
7296
+ }
7297
+ function assertModelServed(spec, now = /* @__PURE__ */ new Date()) {
7298
+ const lifecycle = modelLifecycle(spec, now);
7299
+ if (lifecycle.state === "retired")
7300
+ throw new ModelRetiredError(lifecycle.message, spec);
7301
+ }
7242
7302
  function modelAliases(provider, kind) {
7243
7303
  return listModels({ provider, kind }).flatMap((m) => m.aliases ?? []);
7244
7304
  }
7245
- var MODEL_CATALOG;
7305
+ var MODEL_CATALOG, ModelRetiredError;
7246
7306
  var init_catalog = __esm({
7247
7307
  "../ai-providers/dist/catalog/catalog.js"() {
7248
7308
  "use strict";
@@ -7278,8 +7338,8 @@ var init_catalog = __esm({
7278
7338
  { id: "seedance-2.0-fast", provider: "seedance", kind: "video", label: "Seedance 2.0 Fast", aliases: ["fast"], status: "ga", price: { usd: 0.2419, per: "second", basis: "720p 16:9" }, note: "4-15 s, up to 720p, native audio" },
7279
7339
  { id: "seedance-2.5", provider: "seedance", kind: "video", label: "Seedance 2.5", aliases: ["2.5"], status: "ga", price: { usd: 0.473, per: "second", basis: "720p 16:9" }, note: "Opt-in; 4-30 s, native audio" },
7280
7340
  { id: "gemini-omni-1.1-flash", provider: "omni", kind: "video", label: "Gemini Omni 1.1 Flash", default: true, status: "ga", price: { usd: 0.1, per: "second", basis: "720p" }, note: "Google video default; the model picks 3-10 s; native audio" },
7281
- { id: "veo-3.1-generate-preview", provider: "veo", kind: "video", label: "Veo 3.1", aliases: ["3.1"], status: "deprecated", shutdown: "2026-10-22", price: { usd: 0.4, per: "second", basis: "720p/1080p" }, note: "4-8 s, native audio; explicit `-p veo` only" },
7282
- { id: "veo-3.1-fast-generate-preview", provider: "veo", kind: "video", label: "Veo 3.1 Fast", aliases: ["3.1-fast"], default: true, status: "deprecated", shutdown: "2026-10-22", price: { usd: 0.1, per: "second", basis: "720p" }, note: "4-8 s, native audio; explicit `-p veo` only" },
7341
+ { id: "veo-3.1-generate-preview", provider: "veo", kind: "video", label: "Veo 3.1", aliases: ["3.1"], status: "deprecated", shutdown: "2026-10-22", replacement: "`-p omni` (Gemini Omni 1.1 Flash)", price: { usd: 0.4, per: "second", basis: "720p/1080p" }, note: "4-8 s, native audio; explicit `-p veo` only" },
7342
+ { id: "veo-3.1-fast-generate-preview", provider: "veo", kind: "video", label: "Veo 3.1 Fast", aliases: ["3.1-fast"], default: true, status: "deprecated", shutdown: "2026-10-22", replacement: "`-p omni` (Gemini Omni 1.1 Flash)", price: { usd: 0.1, per: "second", basis: "720p" }, note: "4-8 s, native audio; explicit `-p veo` only" },
7283
7343
  { id: "grok-imagine-video-1.5", provider: "grok", kind: "video", label: "Grok Imagine Video 1.5", aliases: ["1.5"], default: true, status: "ga", price: { usd: 0.14, per: "second", basis: "720p" }, note: "1-15 s, native audio; 720p unless `--resolution` says otherwise" },
7284
7344
  { id: "grok-imagine-video-1.5-lite", provider: "grok", kind: "video", label: "Grok Imagine Video 1.5 Lite", aliases: ["lite", "1.5-lite"], status: "ga", price: { usd: 0.03, per: "second", basis: "720p" }, note: "1-15 s, native audio" },
7285
7345
  { id: "grok-imagine-video", provider: "grok", kind: "video", label: "Grok Imagine Video", aliases: ["classic"], status: "legacy", price: { usd: 0.05, per: "second", basis: "480p" }, note: "Previous generation" },
@@ -7295,6 +7355,13 @@ var init_catalog = __esm({
7295
7355
  { id: "eleven_text_to_sound_v2", provider: "elevenlabs", kind: "sound-effect", label: "ElevenLabs Sound Effects v2", default: true, status: "ga", price: { usd: 0.12, per: "minute" }, note: "0.5-30 s" },
7296
7356
  { id: "whisper-1", provider: "openai", kind: "transcription", label: "Whisper", default: true, status: "deprecated", shutdown: "2027-02-26", note: "Only OpenAI model with word timestamps" }
7297
7357
  ];
7358
+ ModelRetiredError = class extends Error {
7359
+ constructor(message, model) {
7360
+ super(message);
7361
+ this.model = model;
7362
+ this.name = "ModelRetiredError";
7363
+ }
7364
+ };
7298
7365
  }
7299
7366
  });
7300
7367
 
@@ -7842,12 +7909,18 @@ var init_http = __esm({
7842
7909
  // ../ai-providers/dist/gemini/GeminiProvider.js
7843
7910
  function resolveVeoModel(alias) {
7844
7911
  if (Object.hasOwn(VEO_MODEL_ALIASES, alias)) {
7845
- return VEO_MODEL_ALIASES[alias];
7912
+ return servedVeoModel(VEO_MODEL_ALIASES[alias]);
7846
7913
  }
7847
7914
  const valid = Object.keys(VEO_MODEL_ALIASES).join(", ");
7848
7915
  const hint = alias === "3.0" ? " Veo 3.0 was shut down by Google on 2025-11-12." : "";
7849
7916
  throw new Error(`Unknown Veo model "${alias}". Valid: ${valid}.${hint}`);
7850
7917
  }
7918
+ function servedVeoModel(id) {
7919
+ const spec = id ? findModel("veo", "video", id) : defaultModel("veo", "video");
7920
+ if (spec)
7921
+ assertModelServed(spec);
7922
+ return spec?.id ?? id;
7923
+ }
7851
7924
  var VEO_MODEL_ALIASES, GeminiProvider, geminiProvider;
7852
7925
  var init_GeminiProvider = __esm({
7853
7926
  "../ai-providers/dist/gemini/GeminiProvider.js"() {
@@ -7891,7 +7964,7 @@ var init_GeminiProvider = __esm({
7891
7964
  }
7892
7965
  try {
7893
7966
  const veoOpts = options ?? {};
7894
- const model = veoOpts.model || "veo-3.1-fast-generate-preview";
7967
+ const model = servedVeoModel(veoOpts.model);
7895
7968
  const aspectRatioMap = {
7896
7969
  "16:9": "16:9",
7897
7970
  "9:16": "9:16",
@@ -8068,7 +8141,7 @@ var init_GeminiProvider = __esm({
8068
8141
  };
8069
8142
  }
8070
8143
  try {
8071
- const model = options?.model || "veo-3.1-generate-preview";
8144
+ const model = servedVeoModel(options?.model ?? findModel("veo", "video", "3.1")?.id);
8072
8145
  const instance = {
8073
8146
  video: { previousOperationName }
8074
8147
  };
@@ -18510,6 +18583,7 @@ __export(dist_exports2, {
18510
18583
  KlingProvider: () => KlingProvider,
18511
18584
  KokoroProvider: () => KokoroProvider,
18512
18585
  MODEL_CATALOG: () => MODEL_CATALOG,
18586
+ ModelRetiredError: () => ModelRetiredError,
18513
18587
  OPENAI_IMAGE_DEFAULT_MODEL: () => OPENAI_IMAGE_DEFAULT_MODEL,
18514
18588
  OPENAI_TTS_VOICES: () => OPENAI_TTS_VOICES,
18515
18589
  OllamaProvider: () => OllamaProvider,
@@ -18521,6 +18595,7 @@ __export(dist_exports2, {
18521
18595
  RunwayProvider: () => RunwayProvider,
18522
18596
  VEO_MODEL_ALIASES: () => VEO_MODEL_ALIASES,
18523
18597
  WhisperProvider: () => WhisperProvider,
18598
+ assertModelServed: () => assertModelServed,
18524
18599
  claudeProvider: () => claudeProvider,
18525
18600
  defaultModel: () => defaultModel,
18526
18601
  defineApiKey: () => defineApiKey,
@@ -18549,6 +18624,7 @@ __export(dist_exports2, {
18549
18624
  loadBundledKokoroRuntime: () => loadBundledKokoroRuntime,
18550
18625
  loadKokoroFromWorkspace: () => loadKokoroFromWorkspace,
18551
18626
  modelAliases: () => modelAliases,
18627
+ modelLifecycle: () => modelLifecycle,
18552
18628
  ollamaProvider: () => ollamaProvider,
18553
18629
  omniProvider: () => omniProvider,
18554
18630
  openaiImageProvider: () => openaiImageProvider,
@@ -36602,6 +36678,34 @@ var init_thumbnail = __esm({
36602
36678
  }
36603
36679
  });
36604
36680
 
36681
+ // src/utils/model-lifecycle.ts
36682
+ function videoModelSpec(provider, flags) {
36683
+ const catalogProvider = provider === "fal" ? "seedance" : provider;
36684
+ const flag = VIDEO_MODEL_FLAG[catalogProvider];
36685
+ return findModel(catalogProvider, "video", flag ? flags[flag] : void 0);
36686
+ }
36687
+ function checkModelLifecycle(spec, now = /* @__PURE__ */ new Date()) {
36688
+ if (!spec) return [];
36689
+ const lifecycle = modelLifecycle(spec, now);
36690
+ if (lifecycle.state === "retired") exitWithError(usageError(lifecycle.message));
36691
+ return lifecycle.state === "retiring" ? [lifecycle.message] : [];
36692
+ }
36693
+ var VIDEO_MODEL_FLAG;
36694
+ var init_model_lifecycle = __esm({
36695
+ "src/utils/model-lifecycle.ts"() {
36696
+ "use strict";
36697
+ init_dist2();
36698
+ init_output();
36699
+ VIDEO_MODEL_FLAG = {
36700
+ seedance: "seedanceModel",
36701
+ grok: "grokModel",
36702
+ kling: "klingModel",
36703
+ veo: "veoModel",
36704
+ runway: "runwayModel"
36705
+ };
36706
+ }
36707
+ });
36708
+
36605
36709
  // src/commands/generate/video-extend.ts
36606
36710
  import { resolve as resolve26 } from "node:path";
36607
36711
  import { writeFile as writeFile23 } from "node:fs/promises";
@@ -36613,11 +36717,22 @@ function registerVideoExtendCommand(parent) {
36613
36717
  if (options.output) {
36614
36718
  validateOutputPath(options.output);
36615
36719
  }
36720
+ let lifecycleWarnings = [];
36721
+ if (provider === "veo") {
36722
+ try {
36723
+ resolveVeoModel(options.veoModel);
36724
+ } catch (err) {
36725
+ exitWithError(usageError(err instanceof Error ? err.message : String(err)));
36726
+ }
36727
+ lifecycleWarnings = checkModelLifecycle(findModel("veo", "video", options.veoModel));
36728
+ if (!options.dryRun) printWarnings(lifecycleWarnings);
36729
+ }
36616
36730
  if (options.dryRun) {
36617
36731
  outputSuccess({
36618
36732
  command: "generate video-extend",
36619
36733
  startedAt,
36620
36734
  dryRun: true,
36735
+ warnings: lifecycleWarnings,
36621
36736
  data: {
36622
36737
  params: {
36623
36738
  id,
@@ -36770,6 +36885,7 @@ function registerVideoExtendCommand(parent) {
36770
36885
  outputSuccess({
36771
36886
  command: "generate video-extend",
36772
36887
  startedAt,
36888
+ warnings: lifecycleWarnings,
36773
36889
  data: {
36774
36890
  provider: "veo",
36775
36891
  taskId: result.id,
@@ -36818,6 +36934,7 @@ var init_video_extend = __esm({
36818
36934
  init_ora();
36819
36935
  init_dist2();
36820
36936
  init_api_key();
36937
+ init_model_lifecycle();
36821
36938
  init_output();
36822
36939
  init_validate();
36823
36940
  init_ai_helpers();
@@ -38528,6 +38645,8 @@ Examples:
38528
38645
  } catch (err) {
38529
38646
  exitWithError(usageError(err instanceof Error ? err.message : String(err)));
38530
38647
  }
38648
+ const lifecycleWarnings = checkModelLifecycle(videoModelSpec(provider, options));
38649
+ if (!options.dryRun) printWarnings(lifecycleWarnings);
38531
38650
  if (options.dryRun) {
38532
38651
  const costUsd = provider === "seedance" || provider === "fal" ? realRunCost(provider, options).costUsd : void 0;
38533
38652
  outputSuccess({
@@ -38535,6 +38654,7 @@ Examples:
38535
38654
  startedAt,
38536
38655
  dryRun: true,
38537
38656
  costUsd,
38657
+ warnings: lifecycleWarnings,
38538
38658
  data: {
38539
38659
  params: {
38540
38660
  prompt: prompt3,
@@ -38542,13 +38662,10 @@ Examples:
38542
38662
  duration: options.duration,
38543
38663
  ratio: options.ratio,
38544
38664
  image: options.image,
38545
- mode: options.mode,
38665
+ mode: provider === "kling" ? options.mode : void 0,
38546
38666
  negative: options.negative,
38547
38667
  resolution: options.resolution,
38548
- veoModel: options.veoModel,
38549
- seedanceModel: options.seedanceModel,
38550
- grokModel: options.grokModel,
38551
- klingModel: options.klingModel,
38668
+ model: videoModelSpec(provider, options)?.id,
38552
38669
  refImages: options.refImages,
38553
38670
  refVideos: options.refVideos,
38554
38671
  refAudio: options.refAudio,
@@ -38940,7 +39057,7 @@ Examples:
38940
39057
  command: "generate video",
38941
39058
  startedAt,
38942
39059
  costUsd: cost.costUsd,
38943
- warnings: provider === "veo" ? [VEO_SHUTDOWN_WARNING, ...cost.warnings] : cost.warnings,
39060
+ warnings: [...lifecycleWarnings, ...cost.warnings],
38944
39061
  data: {
38945
39062
  provider,
38946
39063
  taskId: result?.id,
@@ -38952,9 +39069,6 @@ Examples:
38952
39069
  return;
38953
39070
  }
38954
39071
  console.log();
38955
- if (provider === "veo") {
38956
- console.log(source_default.yellow(VEO_SHUTDOWN_WARNING));
38957
- }
38958
39072
  if (finalResult.videoUrl) {
38959
39073
  console.log(`Video URL: ${finalResult.videoUrl}`);
38960
39074
  }
@@ -39079,7 +39193,6 @@ function printVideoStarted(providerLabel, taskId, taskType) {
39079
39193
  console.log(`Task ID: ${source_default.bold(taskId)}`);
39080
39194
  if (taskType) console.log(`Type: ${taskType}`);
39081
39195
  }
39082
- var VEO_SHUTDOWN_WARNING;
39083
39196
  var init_video = __esm({
39084
39197
  "src/commands/generate/video.ts"() {
39085
39198
  "use strict";
@@ -39088,6 +39201,7 @@ var init_video = __esm({
39088
39201
  init_dist3();
39089
39202
  init_dist2();
39090
39203
  init_api_key();
39204
+ init_model_lifecycle();
39091
39205
  init_tty();
39092
39206
  init_output();
39093
39207
  init_validate();
@@ -39095,7 +39209,6 @@ var init_video = __esm({
39095
39209
  init_upload_host();
39096
39210
  init_ai_helpers();
39097
39211
  init_status_jobs();
39098
- VEO_SHUTDOWN_WARNING = "Veo 3.1 preview models shut down on 2026-10-22. Use `-p omni` (Gemini Omni 1.1 Flash) instead.";
39099
39212
  }
39100
39213
  });
39101
39214
 
@@ -39205,7 +39318,7 @@ Advanced:
39205
39318
  generate video-cancel Provider lifecycle control; poll with 'vibe status job <job-id> --json'
39206
39319
 
39207
39320
  API Keys (per provider):
39208
- GOOGLE_API_KEY Image (default), Veo video
39321
+ GOOGLE_API_KEY Image (default), Omni video
39209
39322
  OPENAI_API_KEY Image (-p openai)
39210
39323
  FAL_API_KEY Seedance video (-p seedance, default video)
39211
39324
  XAI_API_KEY Grok image/video
@@ -56227,6 +56340,7 @@ var init_detect = __esm({
56227
56340
  validateOutputPath(options.output);
56228
56341
  }
56229
56342
  if (options.dryRun) {
56343
+ spinner2.stop();
56230
56344
  outputSuccess({
56231
56345
  command: "detect scenes",
56232
56346
  startedAt,
@@ -56346,6 +56460,7 @@ var init_detect = __esm({
56346
56460
  validateOutputPath(options.output);
56347
56461
  }
56348
56462
  if (options.dryRun) {
56463
+ spinner2.stop();
56349
56464
  outputSuccess({
56350
56465
  command: "detect silence",
56351
56466
  startedAt,
@@ -56432,6 +56547,7 @@ var init_detect = __esm({
56432
56547
  validateOutputPath(options.output);
56433
56548
  }
56434
56549
  if (options.dryRun) {
56550
+ spinner2.stop();
56435
56551
  outputSuccess({
56436
56552
  command: "detect beats",
56437
56553
  startedAt,
@@ -70389,7 +70505,7 @@ var init_generate2 = __esm({
70389
70505
  cost: "high",
70390
70506
  title: "Generate Video",
70391
70507
  annotations: { readOnly: false, openWorld: true },
70392
- description: "Generate video using AI. Supports Seedance 2.0 via fal.ai, Grok, Kling, Runway, and Veo. Requires FAL_API_KEY, XAI_API_KEY, KLING_API_KEY, RUNWAY_API_SECRET, or GOOGLE_API_KEY.",
70508
+ description: "Generate video using AI. Supports Seedance 2.0 via fal.ai, Grok, Kling, Runway, Gemini Omni, and Veo (shuts down 2026-10-22). Requires FAL_API_KEY, XAI_API_KEY, KLING_API_KEY, RUNWAY_API_SECRET, or GOOGLE_API_KEY.",
70393
70509
  schema: external_exports.object({
70394
70510
  prompt: external_exports.string().describe("Text prompt describing the video"),
70395
70511
  provider: external_exports.enum(["seedance", "grok", "kling", "runway", "veo", "omni"]).optional().describe(
@@ -73672,7 +73788,7 @@ init_source();
73672
73788
  // package.json
73673
73789
  var package_default = {
73674
73790
  name: "@vibeframe/cli",
73675
- version: "0.115.4",
73791
+ version: "0.115.5",
73676
73792
  description: "Frontier AI video generation for coding agents - Seedance, Runway, Veo, and Kling on your own keys, behind a hard cost cap",
73677
73793
  private: false,
73678
73794
  license: "MIT",
@@ -74058,6 +74174,7 @@ timelineCommand.command("add-source").description("Add a media source to the tim
74058
74174
  const spinner2 = ora("Adding source...").start();
74059
74175
  try {
74060
74176
  if (options.dryRun) {
74177
+ spinner2.stop();
74061
74178
  outputSuccess({
74062
74179
  command: "timeline add-source",
74063
74180
  startedAt,
@@ -74122,6 +74239,7 @@ timelineCommand.command("add-clip").description("Add a clip to the timeline").ar
74122
74239
  validateResourceId(sourceId);
74123
74240
  if (options.track) validateResourceId(options.track);
74124
74241
  if (options.dryRun) {
74242
+ spinner2.stop();
74125
74243
  outputSuccess({
74126
74244
  command: "timeline add-clip",
74127
74245
  startedAt,
@@ -74202,6 +74320,7 @@ timelineCommand.command("add-track").description("Add a new track").argument("<p
74202
74320
  const spinner2 = ora("Adding track...").start();
74203
74321
  try {
74204
74322
  if (options.dryRun) {
74323
+ spinner2.stop();
74205
74324
  outputSuccess({
74206
74325
  command: "timeline add-track",
74207
74326
  startedAt,
@@ -74263,6 +74382,7 @@ timelineCommand.command("add-effect").description("Add an effect to a clip").arg
74263
74382
  try {
74264
74383
  validateResourceId(clipId);
74265
74384
  if (options.dryRun) {
74385
+ spinner2.stop();
74266
74386
  outputSuccess({
74267
74387
  command: "timeline add-effect",
74268
74388
  startedAt,
@@ -74335,6 +74455,7 @@ timelineCommand.command("trim-clip").description("Trim a clip").argument("<proje
74335
74455
  try {
74336
74456
  validateResourceId(clipId);
74337
74457
  if (options.dryRun) {
74458
+ spinner2.stop();
74338
74459
  outputSuccess({
74339
74460
  command: "timeline trim-clip",
74340
74461
  startedAt,
@@ -74502,6 +74623,7 @@ timelineCommand.command("split-clip").description("Split a clip at a specific ti
74502
74623
  try {
74503
74624
  validateResourceId(clipId);
74504
74625
  if (options.dryRun) {
74626
+ spinner2.stop();
74505
74627
  outputSuccess({
74506
74628
  command: "timeline split-clip",
74507
74629
  startedAt,
@@ -74564,6 +74686,7 @@ timelineCommand.command("duplicate-clip").description("Duplicate a clip").argume
74564
74686
  try {
74565
74687
  validateResourceId(clipId);
74566
74688
  if (options.dryRun) {
74689
+ spinner2.stop();
74567
74690
  outputSuccess({
74568
74691
  command: "timeline duplicate-clip",
74569
74692
  startedAt,
@@ -74623,6 +74746,7 @@ timelineCommand.command("delete-clip").description("Delete a clip from the timel
74623
74746
  try {
74624
74747
  validateResourceId(clipId);
74625
74748
  if (options.dryRun) {
74749
+ spinner2.stop();
74626
74750
  outputSuccess({
74627
74751
  command: "timeline delete-clip",
74628
74752
  startedAt,
@@ -74676,6 +74800,7 @@ timelineCommand.command("move-clip").description("Move a clip to a new position"
74676
74800
  validateResourceId(clipId);
74677
74801
  if (options.track) validateResourceId(options.track);
74678
74802
  if (options.dryRun) {
74803
+ spinner2.stop();
74679
74804
  outputSuccess({
74680
74805
  command: "timeline move-clip",
74681
74806
  startedAt,
@@ -80171,6 +80296,7 @@ batchCommand.command("import").description("Import multiple media files from a d
80171
80296
  const spinner2 = ora("Scanning directory...").start();
80172
80297
  try {
80173
80298
  if (options.dryRun) {
80299
+ spinner2.stop();
80174
80300
  outputSuccess({
80175
80301
  command: "batch import",
80176
80302
  startedAt,
@@ -80248,6 +80374,7 @@ batchCommand.command("concat").description("Concatenate multiple sources into se
80248
80374
  const spinner2 = ora("Creating clips...").start();
80249
80375
  try {
80250
80376
  if (options.dryRun) {
80377
+ spinner2.stop();
80251
80378
  outputSuccess({
80252
80379
  command: "batch concat",
80253
80380
  startedAt,
@@ -80326,6 +80453,7 @@ batchCommand.command("apply-effect").description("Apply an effect to multiple cl
80326
80453
  const spinner2 = ora("Applying effects...").start();
80327
80454
  try {
80328
80455
  if (options.dryRun) {
80456
+ spinner2.stop();
80329
80457
  outputSuccess({
80330
80458
  command: "batch apply-effect",
80331
80459
  startedAt,
@@ -80397,6 +80525,7 @@ batchCommand.command("remove-clips").description("Remove multiple clips from the
80397
80525
  const spinner2 = ora("Removing clips...").start();
80398
80526
  try {
80399
80527
  if (options.dryRun) {
80528
+ spinner2.stop();
80400
80529
  outputSuccess({
80401
80530
  command: "batch remove-clips",
80402
80531
  startedAt,
@@ -80459,7 +80588,9 @@ batchCommand.command("info").description("Show batch processing statistics").arg
80459
80588
  const tracks = project.getTracks();
80460
80589
  const summary = project.getSummary();
80461
80590
  spinner2.stop();
80462
- console.log(source_default.bold("\nProject Statistics\n"));
80591
+ console.log(`
80592
+ ${source_default.bold("Project Statistics")}
80593
+ `);
80463
80594
  const videoSources = sources.filter((s) => s.type === "video").length;
80464
80595
  const audioSources = sources.filter((s) => s.type === "audio").length;
80465
80596
  const imageSources = sources.filter((s) => s.type === "image").length;
@@ -80853,7 +80984,7 @@ var AI_FEATURES = [
80853
80984
  label: "Videos",
80854
80985
  desc: "generate + extend",
80855
80986
  defaultProvider: "Seedance 2.0 via fal.ai (Artificial Analysis #2 t2v + i2v, since v0.57)",
80856
- alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Google Veo",
80987
+ alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Gemini Omni",
80857
80988
  keyHint: "1-2 keys",
80858
80989
  keys: [
80859
80990
  {
@@ -80932,16 +81063,16 @@ var AI_FEATURES = [
80932
81063
  defaultFor: { kind: "video", value: "runway" }
80933
81064
  },
80934
81065
  {
80935
- label: "Veo",
80936
- desc: "Google Veo video generation",
81066
+ label: "Gemini Omni",
81067
+ desc: "Google Gemini Omni video generation",
80937
81068
  key: {
80938
81069
  configKey: "google",
80939
81070
  envVar: "GOOGLE_API_KEY",
80940
81071
  name: "Google",
80941
81072
  url: "https://aistudio.google.com/apikey",
80942
- what: "Google Veo video generation"
81073
+ what: "Google Gemini Omni video generation"
80943
81074
  },
80944
- defaultFor: { kind: "video", value: "veo" }
81075
+ defaultFor: { kind: "video", value: "omni" }
80945
81076
  }
80946
81077
  ],
80947
81078
  tryCommand: 'vibe generate video "ocean waves" -o waves.mp4'
@@ -82475,7 +82606,7 @@ var AGENTS_MD = `# AGENTS.md
82475
82606
 
82476
82607
  This project uses [VibeFrame](https://github.com/vericontext/vibeframe) -
82477
82608
  a CLI for AI video generation on the user's own provider keys (Seedance,
82478
- Runway, Veo, Kling), behind dry runs and a hard \`--max-cost\` ceiling.
82609
+ Runway, Kling, Gemini Omni), behind dry runs and a hard \`--max-cost\` ceiling.
82479
82610
  The \`vibe\` command provides 80+ subcommands for video generation,
82480
82611
  editing, and analysis. Cost tiers below - **always confirm with the user
82481
82612
  before running High / Very High tier commands**.
@@ -82800,7 +82931,7 @@ function renderEnvExample(opts = {}) {
82800
82931
  ${fallback2}# \u2500\u2500 LLM provider / optional \`vibe agent\` fallback (pick one) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
82801
82932
  ANTHROPIC_API_KEY= # Claude - recommended default
82802
82933
  OPENAI_API_KEY= # GPT-5.4 mini \xB7 Whisper \xB7 gpt-image-2.5
82803
- GOOGLE_API_KEY= # Gemini \xB7 Veo
82934
+ GOOGLE_API_KEY= # Gemini \xB7 Omni video
82804
82935
  XAI_API_KEY= # Grok image+video
82805
82936
  OPENROUTER_API_KEY= # multiplexes any provider above
82806
82937
  # OLLAMA_HOST=http://localhost:11434 # offline, no key needed
@@ -87654,7 +87785,8 @@ program2.name("vibe").showSuggestionAfterError(true).description("VibeFrame CLI
87654
87785
  process.stderr.write(JSON.stringify(err, null, 2) + "\n");
87655
87786
  } else {
87656
87787
  write2(source_default.red(str.trim()) + "\n");
87657
- write2(source_default.dim("Run with --help for full options.\n"));
87788
+ write2(`${source_default.dim("Run with --help for full options.")}
87789
+ `);
87658
87790
  }
87659
87791
  }
87660
87792
  }).configureHelp({
@@ -87807,7 +87939,8 @@ function propagateErrorHandling(cmd) {
87807
87939
  process.stderr.write(JSON.stringify(err, null, 2) + "\n");
87808
87940
  } else {
87809
87941
  write2(source_default.red(str.trim()) + "\n");
87810
- write2(source_default.dim("Run with --help for full options.\n"));
87942
+ write2(`${source_default.dim("Run with --help for full options.")}
87943
+ `);
87811
87944
  }
87812
87945
  }
87813
87946
  });
@@ -201,8 +201,8 @@ var init_api_keys = __esm({
201
201
  envVar: "GOOGLE_API_KEY",
202
202
  label: "Google",
203
203
  showInSetup: true,
204
- setupDescription: "Gemini \u2014 image gen (free tier), video analysis ($), Veo ($$)",
205
- envExampleComment: "Google API Key (Gemini auto-edit suggestions, image gen, Veo video)",
204
+ setupDescription: "Gemini \u2014 image gen (free tier), video analysis ($), Omni video ($$)",
205
+ envExampleComment: "Google API Key (Gemini auto-edit suggestions, image gen, Omni video)",
206
206
  envExampleUrl: "https://aistudio.google.com/apikey",
207
207
  keyFormat: { prefix: /^AIza/, example: "AIza..." }
208
208
  });
@@ -456,10 +456,35 @@ function findModel(provider, kind, aliasOrId) {
456
456
  const lower = key.toLowerCase();
457
457
  return listModels({ provider, kind }).find((m) => m.id === key || m.aliases?.includes(lower));
458
458
  }
459
+ function modelLifecycle(spec, now = /* @__PURE__ */ new Date()) {
460
+ if (!spec.shutdown)
461
+ return { state: "served" };
462
+ const remainingMs = Date.parse(`${spec.shutdown}T00:00:00Z`) - now.getTime();
463
+ const instead = spec.replacement ? ` Use ${spec.replacement} instead.` : "";
464
+ if (remainingMs <= 0) {
465
+ return {
466
+ state: "retired",
467
+ shutdown: spec.shutdown,
468
+ message: `${spec.label} (${spec.id}) was shut down by the provider on ${spec.shutdown}.${instead}`
469
+ };
470
+ }
471
+ const daysLeft = Math.ceil(remainingMs / 864e5);
472
+ return {
473
+ state: "retiring",
474
+ shutdown: spec.shutdown,
475
+ daysLeft,
476
+ message: `${spec.label} (${spec.id}) shuts down on ${spec.shutdown} (${daysLeft} day${daysLeft === 1 ? "" : "s"} left).${instead}`
477
+ };
478
+ }
479
+ function assertModelServed(spec, now = /* @__PURE__ */ new Date()) {
480
+ const lifecycle = modelLifecycle(spec, now);
481
+ if (lifecycle.state === "retired")
482
+ throw new ModelRetiredError(lifecycle.message, spec);
483
+ }
459
484
  function modelAliases(provider, kind) {
460
485
  return listModels({ provider, kind }).flatMap((m) => m.aliases ?? []);
461
486
  }
462
- var MODEL_CATALOG;
487
+ var MODEL_CATALOG, ModelRetiredError;
463
488
  var init_catalog = __esm({
464
489
  "../ai-providers/dist/catalog/catalog.js"() {
465
490
  "use strict";
@@ -495,8 +520,8 @@ var init_catalog = __esm({
495
520
  { id: "seedance-2.0-fast", provider: "seedance", kind: "video", label: "Seedance 2.0 Fast", aliases: ["fast"], status: "ga", price: { usd: 0.2419, per: "second", basis: "720p 16:9" }, note: "4-15 s, up to 720p, native audio" },
496
521
  { id: "seedance-2.5", provider: "seedance", kind: "video", label: "Seedance 2.5", aliases: ["2.5"], status: "ga", price: { usd: 0.473, per: "second", basis: "720p 16:9" }, note: "Opt-in; 4-30 s, native audio" },
497
522
  { id: "gemini-omni-1.1-flash", provider: "omni", kind: "video", label: "Gemini Omni 1.1 Flash", default: true, status: "ga", price: { usd: 0.1, per: "second", basis: "720p" }, note: "Google video default; the model picks 3-10 s; native audio" },
498
- { id: "veo-3.1-generate-preview", provider: "veo", kind: "video", label: "Veo 3.1", aliases: ["3.1"], status: "deprecated", shutdown: "2026-10-22", price: { usd: 0.4, per: "second", basis: "720p/1080p" }, note: "4-8 s, native audio; explicit `-p veo` only" },
499
- { id: "veo-3.1-fast-generate-preview", provider: "veo", kind: "video", label: "Veo 3.1 Fast", aliases: ["3.1-fast"], default: true, status: "deprecated", shutdown: "2026-10-22", price: { usd: 0.1, per: "second", basis: "720p" }, note: "4-8 s, native audio; explicit `-p veo` only" },
523
+ { id: "veo-3.1-generate-preview", provider: "veo", kind: "video", label: "Veo 3.1", aliases: ["3.1"], status: "deprecated", shutdown: "2026-10-22", replacement: "`-p omni` (Gemini Omni 1.1 Flash)", price: { usd: 0.4, per: "second", basis: "720p/1080p" }, note: "4-8 s, native audio; explicit `-p veo` only" },
524
+ { id: "veo-3.1-fast-generate-preview", provider: "veo", kind: "video", label: "Veo 3.1 Fast", aliases: ["3.1-fast"], default: true, status: "deprecated", shutdown: "2026-10-22", replacement: "`-p omni` (Gemini Omni 1.1 Flash)", price: { usd: 0.1, per: "second", basis: "720p" }, note: "4-8 s, native audio; explicit `-p veo` only" },
500
525
  { id: "grok-imagine-video-1.5", provider: "grok", kind: "video", label: "Grok Imagine Video 1.5", aliases: ["1.5"], default: true, status: "ga", price: { usd: 0.14, per: "second", basis: "720p" }, note: "1-15 s, native audio; 720p unless `--resolution` says otherwise" },
501
526
  { id: "grok-imagine-video-1.5-lite", provider: "grok", kind: "video", label: "Grok Imagine Video 1.5 Lite", aliases: ["lite", "1.5-lite"], status: "ga", price: { usd: 0.03, per: "second", basis: "720p" }, note: "1-15 s, native audio" },
502
527
  { id: "grok-imagine-video", provider: "grok", kind: "video", label: "Grok Imagine Video", aliases: ["classic"], status: "legacy", price: { usd: 0.05, per: "second", basis: "480p" }, note: "Previous generation" },
@@ -512,6 +537,13 @@ var init_catalog = __esm({
512
537
  { id: "eleven_text_to_sound_v2", provider: "elevenlabs", kind: "sound-effect", label: "ElevenLabs Sound Effects v2", default: true, status: "ga", price: { usd: 0.12, per: "minute" }, note: "0.5-30 s" },
513
538
  { id: "whisper-1", provider: "openai", kind: "transcription", label: "Whisper", default: true, status: "deprecated", shutdown: "2027-02-26", note: "Only OpenAI model with word timestamps" }
514
539
  ];
540
+ ModelRetiredError = class extends Error {
541
+ constructor(message, model) {
542
+ super(message);
543
+ this.model = model;
544
+ this.name = "ModelRetiredError";
545
+ }
546
+ };
515
547
  }
516
548
  });
517
549
 
@@ -1059,12 +1091,18 @@ var init_http = __esm({
1059
1091
  // ../ai-providers/dist/gemini/GeminiProvider.js
1060
1092
  function resolveVeoModel(alias) {
1061
1093
  if (Object.hasOwn(VEO_MODEL_ALIASES, alias)) {
1062
- return VEO_MODEL_ALIASES[alias];
1094
+ return servedVeoModel(VEO_MODEL_ALIASES[alias]);
1063
1095
  }
1064
1096
  const valid = Object.keys(VEO_MODEL_ALIASES).join(", ");
1065
1097
  const hint = alias === "3.0" ? " Veo 3.0 was shut down by Google on 2025-11-12." : "";
1066
1098
  throw new Error(`Unknown Veo model "${alias}". Valid: ${valid}.${hint}`);
1067
1099
  }
1100
+ function servedVeoModel(id) {
1101
+ const spec = id ? findModel("veo", "video", id) : defaultModel("veo", "video");
1102
+ if (spec)
1103
+ assertModelServed(spec);
1104
+ return spec?.id ?? id;
1105
+ }
1068
1106
  var VEO_MODEL_ALIASES, GeminiProvider, geminiProvider;
1069
1107
  var init_GeminiProvider = __esm({
1070
1108
  "../ai-providers/dist/gemini/GeminiProvider.js"() {
@@ -1108,7 +1146,7 @@ var init_GeminiProvider = __esm({
1108
1146
  }
1109
1147
  try {
1110
1148
  const veoOpts = options ?? {};
1111
- const model = veoOpts.model || "veo-3.1-fast-generate-preview";
1149
+ const model = servedVeoModel(veoOpts.model);
1112
1150
  const aspectRatioMap = {
1113
1151
  "16:9": "16:9",
1114
1152
  "9:16": "9:16",
@@ -1285,7 +1323,7 @@ var init_GeminiProvider = __esm({
1285
1323
  };
1286
1324
  }
1287
1325
  try {
1288
- const model = options?.model || "veo-3.1-generate-preview";
1326
+ const model = servedVeoModel(options?.model ?? findModel("veo", "video", "3.1")?.id);
1289
1327
  const instance = {
1290
1328
  video: { previousOperationName }
1291
1329
  };
@@ -11727,6 +11765,7 @@ __export(dist_exports2, {
11727
11765
  KlingProvider: () => KlingProvider,
11728
11766
  KokoroProvider: () => KokoroProvider,
11729
11767
  MODEL_CATALOG: () => MODEL_CATALOG,
11768
+ ModelRetiredError: () => ModelRetiredError,
11730
11769
  OPENAI_IMAGE_DEFAULT_MODEL: () => OPENAI_IMAGE_DEFAULT_MODEL,
11731
11770
  OPENAI_TTS_VOICES: () => OPENAI_TTS_VOICES,
11732
11771
  OllamaProvider: () => OllamaProvider,
@@ -11738,6 +11777,7 @@ __export(dist_exports2, {
11738
11777
  RunwayProvider: () => RunwayProvider,
11739
11778
  VEO_MODEL_ALIASES: () => VEO_MODEL_ALIASES,
11740
11779
  WhisperProvider: () => WhisperProvider,
11780
+ assertModelServed: () => assertModelServed,
11741
11781
  claudeProvider: () => claudeProvider,
11742
11782
  defaultModel: () => defaultModel,
11743
11783
  defineApiKey: () => defineApiKey,
@@ -11766,6 +11806,7 @@ __export(dist_exports2, {
11766
11806
  loadBundledKokoroRuntime: () => loadBundledKokoroRuntime,
11767
11807
  loadKokoroFromWorkspace: () => loadKokoroFromWorkspace,
11768
11808
  modelAliases: () => modelAliases,
11809
+ modelLifecycle: () => modelLifecycle,
11769
11810
  ollamaProvider: () => ollamaProvider,
11770
11811
  omniProvider: () => omniProvider,
11771
11812
  openaiImageProvider: () => openaiImageProvider,
@@ -30473,8 +30514,43 @@ function outputSuccess(opts) {
30473
30514
  const data = opts.data;
30474
30515
  const primary = data.outputPath ?? data.output ?? data.path ?? data.url ?? data.id;
30475
30516
  if (primary !== void 0) console.log(String(primary));
30517
+ return;
30518
+ }
30519
+ if (opts.dryRun && !opts.humanRendered) {
30520
+ printDryRunSummary(opts.command, costUsd, opts.data, opts.warnings ?? []);
30521
+ }
30522
+ }
30523
+ function printWarnings(warnings) {
30524
+ if (isJsonMode()) return;
30525
+ for (const warning of warnings) {
30526
+ process.stderr.write(`${source_default.yellow(`Warning: ${warning}`)}
30527
+ `);
30476
30528
  }
30477
30529
  }
30530
+ function formatDryRunValue(value) {
30531
+ const text = Array.isArray(value) ? value.map((v) => formatDryRunValue(v)).join(", ") : typeof value === "object" ? JSON.stringify(value) : String(value);
30532
+ return text.length > 80 ? `${text.slice(0, 77)}...` : text;
30533
+ }
30534
+ function printDryRunSummary(command, costUsd, data, warnings) {
30535
+ const params = data.params && typeof data.params === "object" ? data.params : data;
30536
+ const rows = [
30537
+ ["cost", costUsd > 0 ? `up to $${costUsd.toFixed(2)} (estimate)` : "free"]
30538
+ ];
30539
+ for (const [key, value] of Object.entries(params)) {
30540
+ if (value === void 0 || value === null || value === "") continue;
30541
+ if (Array.isArray(value) && value.length === 0) continue;
30542
+ rows.push([key, formatDryRunValue(value)]);
30543
+ }
30544
+ const width = Math.max(...rows.map(([key]) => key.length));
30545
+ console.log();
30546
+ console.log(`${source_default.bold.cyan(`Dry run: vibe ${command}`)} ${source_default.dim("(nothing was sent)")}`);
30547
+ for (const [key, value] of rows) {
30548
+ console.log(` ${source_default.dim(key.padEnd(width))} ${value}`);
30549
+ }
30550
+ printWarnings(warnings);
30551
+ console.log(source_default.dim(" Run again without --dry-run to execute."));
30552
+ console.log();
30553
+ }
30478
30554
  function log(...args) {
30479
30555
  if (!isJsonMode() && !isQuietMode()) {
30480
30556
  console.log(...args);
@@ -44079,6 +44155,34 @@ var init_thumbnail = __esm({
44079
44155
  }
44080
44156
  });
44081
44157
 
44158
+ // src/utils/model-lifecycle.ts
44159
+ function videoModelSpec(provider, flags) {
44160
+ const catalogProvider = provider === "fal" ? "seedance" : provider;
44161
+ const flag = VIDEO_MODEL_FLAG[catalogProvider];
44162
+ return findModel(catalogProvider, "video", flag ? flags[flag] : void 0);
44163
+ }
44164
+ function checkModelLifecycle(spec, now = /* @__PURE__ */ new Date()) {
44165
+ if (!spec) return [];
44166
+ const lifecycle = modelLifecycle(spec, now);
44167
+ if (lifecycle.state === "retired") exitWithError(usageError(lifecycle.message));
44168
+ return lifecycle.state === "retiring" ? [lifecycle.message] : [];
44169
+ }
44170
+ var VIDEO_MODEL_FLAG;
44171
+ var init_model_lifecycle = __esm({
44172
+ "src/utils/model-lifecycle.ts"() {
44173
+ "use strict";
44174
+ init_dist2();
44175
+ init_output();
44176
+ VIDEO_MODEL_FLAG = {
44177
+ seedance: "seedanceModel",
44178
+ grok: "grokModel",
44179
+ kling: "klingModel",
44180
+ veo: "veoModel",
44181
+ runway: "runwayModel"
44182
+ };
44183
+ }
44184
+ });
44185
+
44082
44186
  // src/commands/generate/video-extend.ts
44083
44187
  import { resolve as resolve48 } from "node:path";
44084
44188
  import { writeFile as writeFile34 } from "node:fs/promises";
@@ -44090,11 +44194,22 @@ function registerVideoExtendCommand(parent) {
44090
44194
  if (options.output) {
44091
44195
  validateOutputPath(options.output);
44092
44196
  }
44197
+ let lifecycleWarnings = [];
44198
+ if (provider === "veo") {
44199
+ try {
44200
+ resolveVeoModel(options.veoModel);
44201
+ } catch (err) {
44202
+ exitWithError(usageError(err instanceof Error ? err.message : String(err)));
44203
+ }
44204
+ lifecycleWarnings = checkModelLifecycle(findModel("veo", "video", options.veoModel));
44205
+ if (!options.dryRun) printWarnings(lifecycleWarnings);
44206
+ }
44093
44207
  if (options.dryRun) {
44094
44208
  outputSuccess({
44095
44209
  command: "generate video-extend",
44096
44210
  startedAt,
44097
44211
  dryRun: true,
44212
+ warnings: lifecycleWarnings,
44098
44213
  data: {
44099
44214
  params: {
44100
44215
  id,
@@ -44247,6 +44362,7 @@ function registerVideoExtendCommand(parent) {
44247
44362
  outputSuccess({
44248
44363
  command: "generate video-extend",
44249
44364
  startedAt,
44365
+ warnings: lifecycleWarnings,
44250
44366
  data: {
44251
44367
  provider: "veo",
44252
44368
  taskId: result.id,
@@ -44295,6 +44411,7 @@ var init_video_extend = __esm({
44295
44411
  init_ora();
44296
44412
  init_dist2();
44297
44413
  init_api_key();
44414
+ init_model_lifecycle();
44298
44415
  init_output();
44299
44416
  init_validate();
44300
44417
  init_ai_helpers();
@@ -46005,6 +46122,8 @@ Examples:
46005
46122
  } catch (err) {
46006
46123
  exitWithError(usageError(err instanceof Error ? err.message : String(err)));
46007
46124
  }
46125
+ const lifecycleWarnings = checkModelLifecycle(videoModelSpec(provider, options));
46126
+ if (!options.dryRun) printWarnings(lifecycleWarnings);
46008
46127
  if (options.dryRun) {
46009
46128
  const costUsd = provider === "seedance" || provider === "fal" ? realRunCost(provider, options).costUsd : void 0;
46010
46129
  outputSuccess({
@@ -46012,6 +46131,7 @@ Examples:
46012
46131
  startedAt,
46013
46132
  dryRun: true,
46014
46133
  costUsd,
46134
+ warnings: lifecycleWarnings,
46015
46135
  data: {
46016
46136
  params: {
46017
46137
  prompt: prompt3,
@@ -46019,13 +46139,10 @@ Examples:
46019
46139
  duration: options.duration,
46020
46140
  ratio: options.ratio,
46021
46141
  image: options.image,
46022
- mode: options.mode,
46142
+ mode: provider === "kling" ? options.mode : void 0,
46023
46143
  negative: options.negative,
46024
46144
  resolution: options.resolution,
46025
- veoModel: options.veoModel,
46026
- seedanceModel: options.seedanceModel,
46027
- grokModel: options.grokModel,
46028
- klingModel: options.klingModel,
46145
+ model: videoModelSpec(provider, options)?.id,
46029
46146
  refImages: options.refImages,
46030
46147
  refVideos: options.refVideos,
46031
46148
  refAudio: options.refAudio,
@@ -46417,7 +46534,7 @@ Examples:
46417
46534
  command: "generate video",
46418
46535
  startedAt,
46419
46536
  costUsd: cost.costUsd,
46420
- warnings: provider === "veo" ? [VEO_SHUTDOWN_WARNING, ...cost.warnings] : cost.warnings,
46537
+ warnings: [...lifecycleWarnings, ...cost.warnings],
46421
46538
  data: {
46422
46539
  provider,
46423
46540
  taskId: result?.id,
@@ -46429,9 +46546,6 @@ Examples:
46429
46546
  return;
46430
46547
  }
46431
46548
  console.log();
46432
- if (provider === "veo") {
46433
- console.log(source_default.yellow(VEO_SHUTDOWN_WARNING));
46434
- }
46435
46549
  if (finalResult.videoUrl) {
46436
46550
  console.log(`Video URL: ${finalResult.videoUrl}`);
46437
46551
  }
@@ -46556,7 +46670,6 @@ function printVideoStarted(providerLabel, taskId, taskType) {
46556
46670
  console.log(`Task ID: ${source_default.bold(taskId)}`);
46557
46671
  if (taskType) console.log(`Type: ${taskType}`);
46558
46672
  }
46559
- var VEO_SHUTDOWN_WARNING;
46560
46673
  var init_video = __esm({
46561
46674
  "src/commands/generate/video.ts"() {
46562
46675
  "use strict";
@@ -46565,6 +46678,7 @@ var init_video = __esm({
46565
46678
  init_dist3();
46566
46679
  init_dist2();
46567
46680
  init_api_key();
46681
+ init_model_lifecycle();
46568
46682
  init_tty();
46569
46683
  init_output();
46570
46684
  init_validate();
@@ -46572,7 +46686,6 @@ var init_video = __esm({
46572
46686
  init_upload_host();
46573
46687
  init_ai_helpers();
46574
46688
  init_status_jobs();
46575
- VEO_SHUTDOWN_WARNING = "Veo 3.1 preview models shut down on 2026-10-22. Use `-p omni` (Gemini Omni 1.1 Flash) instead.";
46576
46689
  }
46577
46690
  });
46578
46691
 
@@ -46682,7 +46795,7 @@ Advanced:
46682
46795
  generate video-cancel Provider lifecycle control; poll with 'vibe status job <job-id> --json'
46683
46796
 
46684
46797
  API Keys (per provider):
46685
- GOOGLE_API_KEY Image (default), Veo video
46798
+ GOOGLE_API_KEY Image (default), Omni video
46686
46799
  OPENAI_API_KEY Image (-p openai)
46687
46800
  FAL_API_KEY Seedance video (-p seedance, default video)
46688
46801
  XAI_API_KEY Grok image/video
@@ -47780,6 +47893,7 @@ var init_detect = __esm({
47780
47893
  validateOutputPath(options.output);
47781
47894
  }
47782
47895
  if (options.dryRun) {
47896
+ spinner2.stop();
47783
47897
  outputSuccess({
47784
47898
  command: "detect scenes",
47785
47899
  startedAt,
@@ -47899,6 +48013,7 @@ var init_detect = __esm({
47899
48013
  validateOutputPath(options.output);
47900
48014
  }
47901
48015
  if (options.dryRun) {
48016
+ spinner2.stop();
47902
48017
  outputSuccess({
47903
48018
  command: "detect silence",
47904
48019
  startedAt,
@@ -47985,6 +48100,7 @@ var init_detect = __esm({
47985
48100
  validateOutputPath(options.output);
47986
48101
  }
47987
48102
  if (options.dryRun) {
48103
+ spinner2.stop();
47988
48104
  outputSuccess({
47989
48105
  command: "detect beats",
47990
48106
  startedAt,
@@ -57720,7 +57836,7 @@ var generateVideoTool = defineTool({
57720
57836
  cost: "high",
57721
57837
  title: "Generate Video",
57722
57838
  annotations: { readOnly: false, openWorld: true },
57723
- description: "Generate video using AI. Supports Seedance 2.0 via fal.ai, Grok, Kling, Runway, and Veo. Requires FAL_API_KEY, XAI_API_KEY, KLING_API_KEY, RUNWAY_API_SECRET, or GOOGLE_API_KEY.",
57839
+ description: "Generate video using AI. Supports Seedance 2.0 via fal.ai, Grok, Kling, Runway, Gemini Omni, and Veo (shuts down 2026-10-22). Requires FAL_API_KEY, XAI_API_KEY, KLING_API_KEY, RUNWAY_API_SECRET, or GOOGLE_API_KEY.",
57724
57840
  schema: external_exports.object({
57725
57841
  prompt: external_exports.string().describe("Text prompt describing the video"),
57726
57842
  provider: external_exports.enum(["seedance", "grok", "kling", "runway", "veo", "omni"]).optional().describe(
@@ -32,15 +32,15 @@ export declare const storyboardSetTool: import("../define-tool.js").ToolDefiniti
32
32
  jsonValue: z.ZodOptional<z.ZodBoolean>;
33
33
  unset: z.ZodOptional<z.ZodBoolean>;
34
34
  }, "strip", z.ZodTypeAny, {
35
- beat: string;
36
35
  key: string;
36
+ beat: string;
37
37
  value?: string | undefined;
38
38
  projectDir?: string | undefined;
39
39
  unset?: boolean | undefined;
40
40
  jsonValue?: boolean | undefined;
41
41
  }, {
42
- beat: string;
43
42
  key: string;
43
+ beat: string;
44
44
  value?: string | undefined;
45
45
  projectDir?: string | undefined;
46
46
  unset?: boolean | undefined;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Model shutdown checks for commands that call a dated model.
3
+ *
4
+ * The catalog records each model's announced shutdown date. Commands check
5
+ * it before any dry run or paid request: a retiring model adds a warning to
6
+ * the dry run, the result, and stderr; a retired model is a usage error that
7
+ * names the replacement. Providers enforce the same rule on their own, so
8
+ * executors that skip this check still never call a retired model.
9
+ */
10
+ import { type ModelSpec } from "@vibeframe/ai-providers";
11
+ /** Model flags `generate video` reads, keyed by provider. */
12
+ export interface VideoModelFlags {
13
+ seedanceModel?: string;
14
+ grokModel?: string;
15
+ klingModel?: string;
16
+ veoModel?: string;
17
+ runwayModel?: string;
18
+ }
19
+ /** The catalog model a video provider will call, given the command's model flags. */
20
+ export declare function videoModelSpec(provider: string, flags: VideoModelFlags): ModelSpec | undefined;
21
+ /**
22
+ * Exit with a usage error for a retired model; return the shutdown warning
23
+ * for a retiring one, for the caller's envelope and `printWarnings()`.
24
+ */
25
+ export declare function checkModelLifecycle(spec: ModelSpec | undefined, now?: Date): string[];
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.115.4",
3
+ "version": "0.115.5",
4
4
  "description": "Frontier AI video generation for coding agents - Seedance, Runway, Veo, and Kling on your own keys, behind a hard cost cap",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -91,8 +91,8 @@
91
91
  "tsx": "^4.21.0",
92
92
  "typescript": "^5.3.3",
93
93
  "vitest": "^1.2.2",
94
- "@vibeframe/ai-providers": "0.115.4",
95
- "@vibeframe/core": "0.115.4"
94
+ "@vibeframe/ai-providers": "0.115.5",
95
+ "@vibeframe/core": "0.115.5"
96
96
  },
97
97
  "engines": {
98
98
  "node": ">=20"