/** * vclaw schema --json — the v3 introspection bundle. * * Returns the full CLI contract in one call: commands, flags, artifact * schemas, error codes, exit codes. Agents call this once to learn the * surface, then drive the CLI without further introspection. * * Stateless function — no fs writes, no env reads, no network. Pure * read of bundled JSON + reflection of the dispatch table. */ import { readFileSync, readdirSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { ALL_ERROR_CODES } from './errors.js'; import { ExitCode } from './cli-output.js'; import { listMultiShotPresets, type MultiShotPreset } from './multi-shot-prompt.js'; import { listMultiShotIssueExplanations, type PromptQualityIssueExplanation } from './prompt-quality.js'; export interface CommandFlag { name: string; /** "value" if it takes an argument, "boolean" if it's a switch. */ kind: 'value' | 'boolean'; description?: string; } export interface CommandSpec { name: string; usage: string; description?: string; flags?: CommandFlag[]; /** Backwards-compat aliases that dispatch to this command. */ aliases?: string[]; } export interface SchemaDump { version: string; generatedAt: string; exitCodes: Record; errorCodes: ReadonlyArray; commands: CommandSpec[]; artifactSchemas: Record; multiShot: { presets: readonly MultiShotPreset[]; issueExplanations: readonly PromptQualityIssueExplanation[]; }; } /** * Hand-curated list of subcommands covering the FULL VIDEO_DISPATCH table in * src/cli/vclaw.ts (back-compat aliases — execution-plan/execute/preflight — * are listed via their canonical entries only). cli-schema.test.ts asserts the * exact count, cli-dispatch-table.test.ts asserts every advertised command + * alias resolves to a dispatch entry, and cli-schema-dispatch-sync.test.ts * asserts the REVERSE: every dispatch key is either listed here or an alias of * a listed command — so `vclaw schema --json` can never silently drift behind * the dispatch again (it drifted to 40 missing public commands once). */ export const COMMANDS: CommandSpec[] = [ // --- core lifecycle --- { name: 'video providers', usage: 'vclaw video providers [--workspace-root ]' }, { name: 'video verify-env', usage: 'vclaw video verify-env [--root ] [--workspace-root ]' }, { name: 'video init', usage: 'vclaw video init [--root ] [--mode storyboard|director]' }, { name: 'video brand-extract', usage: 'vclaw video brand-extract --project --url [--root ] [--gemini-endpoint ]' }, { name: 'video brief', usage: 'vclaw video brief --project --title --intent <intent> [--root <path>] [--mode storyboard|director] [--platform <name>] [--aspect-ratio 16:9|9:16|1:1] [--quality fast|quality] [--resolution 720p|1080p] [--audio on|off] [--outputs 1-4] [--from-brand-dna]', flags: [{ name: '--veo-model', kind: 'value' }] }, { name: 'video storyboard', usage: 'vclaw video storyboard --project <slug> (--scene <text> [--scene <text> ...] | --template <template-id>) [--root <path>] [--mode storyboard|director]', flags: [{ name: '--environment', kind: 'value' }, { name: '--character-a', kind: 'value' }, { name: '--character-b', kind: 'value' }, { name: '--scene-character', kind: 'value' }, { name: '--scene-color', kind: 'value' }, { name: '--scene-voice', kind: 'value' }, { name: '--scene-ref-video', kind: 'value' }, { name: '--scene-first-frame', kind: 'value' }] }, { name: 'video assets', usage: 'vclaw video assets --project <slug> --asset <kind:path[:sceneIndex][:backend]> [--asset ...] [--root <path>]' }, { name: 'video review', usage: 'vclaw video review --project <slug> --verdict pass|retry|fail [--finding <text> ...] [--root <path>]' }, { name: 'video publish', usage: 'vclaw video publish --project <slug> --status ready|published|blocked [--final-output <path>] [--note <text> ...] [--root <path>]' }, // --- creator-mode pipeline drivers --- { name: 'video create', usage: 'vclaw video create "<intent>" [--project <slug>] [...]', flags: [{ name: '--slug', kind: 'value' }, { name: '--production-mode', kind: 'value' }, { name: '--runtime', kind: 'value' }, { name: '--color-grading', kind: 'value' }, { name: '--import-library-characters', kind: 'boolean' }, { name: '--auto-create-characters', kind: 'value' }, { name: '--veo-model', kind: 'value' }] }, { name: 'video auto', usage: 'vclaw video auto "<intent>" [--project <slug>] [...]', flags: [{ name: '--slug', kind: 'value' }, { name: '--production-mode', kind: 'value' }, { name: '--runtime', kind: 'value' }, { name: '--color-grading', kind: 'value' }, { name: '--import-library-characters', kind: 'boolean' }, { name: '--auto-create-characters', kind: 'value' }, { name: '--veo-model', kind: 'value' }] }, { name: 'video iterate', usage: 'vclaw video iterate "<intent>" [--project <slug>] [...]', flags: [{ name: '--slug', kind: 'value' }, { name: '--production-mode', kind: 'value' }, { name: '--runtime', kind: 'value' }, { name: '--color-grading', kind: 'value' }, { name: '--import-library-characters', kind: 'boolean' }, { name: '--auto-create-characters', kind: 'value' }, { name: '--veo-model', kind: 'value' }] }, { name: 'video run-pipeline', usage: 'vclaw video run-pipeline "<intent>" [--project <slug>] [...]', flags: [{ name: '--slug', kind: 'value' }, { name: '--production-mode', kind: 'value' }, { name: '--runtime', kind: 'value' }, { name: '--color-grading', kind: 'value' }, { name: '--import-library-characters', kind: 'boolean' }, { name: '--auto-create-characters', kind: 'value' }, { name: '--veo-model', kind: 'value' }] }, { name: 'video approve', usage: 'vclaw video approve --project <slug> [--root <path>] [--mode storyboard|director] [--dry-run]' }, // --- readiness + execution --- { name: 'video readiness', usage: 'vclaw video readiness --project <slug> [--root <path>] [--mode storyboard|director]' }, { name: 'video plan', usage: 'vclaw video plan --project <slug> [--root <path>] [--mode storyboard|director]', aliases: ['video execution-plan'] }, { name: 'video produce', usage: 'vclaw video produce --project <slug> [--root <path>] [--mode storyboard|director] [--dry-run] [--scene <sceneIndex> ...]', aliases: ['video execute'], flags: [{ name: '--auto-chain', kind: 'boolean' }, { name: '--chain-fallback', kind: 'boolean' }, { name: '--continuity-feedback', kind: 'boolean' }] }, { name: 'video execute-status', usage: 'vclaw video execute-status --project <slug> [--root <path>] [--mode storyboard|director]' }, { name: 'video execute-cancel', usage: 'vclaw video execute-cancel --project <slug> [--root <path>] [--mode storyboard|director]' }, { name: 'video pool', usage: 'vclaw video pool --project <slug> [--root <path>] [--mode storyboard|director] [--max-concurrent <N>] [--scenes <csv>] [--dry-run] [--confirm-spend]', description: 'Render a project\'s pending scenes with a concurrency cap, auto-refilling as each completes (the PARALLEL, no-chain counterpart to produce --auto-chain). Keeps at most --max-concurrent (default 2) scene renders in-flight at once. Resumable: scenes that already have a selected candidate are skipped. SPEND path: refuses without --dry-run unless --confirm-spend is passed; --dry-run prints the plan (which scenes, the cap) without rendering.', flags: [{ name: '--project', kind: 'value' }, { name: '--root', kind: 'value' }, { name: '--mode', kind: 'value' }, { name: '--max-concurrent', kind: 'value' }, { name: '--scenes', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, { name: '--confirm-spend', kind: 'boolean' }] }, { name: 'video render-scenes', usage: 'vclaw video render-scenes --project <slug> [--root <path>] [--mode storyboard|director] [--method <route>] [--fallback-chain] [--continue-from <i>] [--scenes <csv>] [--dry-run] [--confirm-spend]', description: 'Render a project\'s pending scenes SEQUENTIALLY, walking a FALLBACK ROUTE LADDER per scene: try the primary route; if the provider rejects it, escalate to the next route, and so on. The distinguishing driver vs. produce --auto-chain (sequential + chained) and pool (parallel + independent) — it productizes the hand-written voice-render.mjs / chain.mjs loops (render one scene, on rejection escalate to a fallback method, resumable across crashes). --method picks the primary route; --fallback-chain appends the project routePreference / mode defaults as fallback rungs (without it the ladder is just the primary, i.e. a plain sequential render). Resumable: --continue-from <i> skips scenes below i, and scenes that already have a selected candidate are skipped. SPEND path: refuses without --dry-run unless --confirm-spend; --dry-run prints the plan (scenes, the route ladder, continueFrom) without rendering.', flags: [{ name: '--project', kind: 'value' }, { name: '--root', kind: 'value' }, { name: '--mode', kind: 'value' }, { name: '--method', kind: 'value' }, { name: '--fallback-chain', kind: 'boolean' }, { name: '--continue-from', kind: 'value' }, { name: '--scenes', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, { name: '--confirm-spend', kind: 'boolean' }] }, { name: 'video assemble', usage: 'vclaw video assemble --project <slug> [--root <path>] [--brand-profile <path>] [--dry-run]', description: 'Assemble the final video: PDF slides -> title card -> per-slide animation -> TTS narration -> music bed -> stitched MP4 (dry-run plans the whole pipeline without ffmpeg/keys).', flags: [{ name: '--project', kind: 'value' }, { name: '--root', kind: 'value' }, { name: '--brand-profile', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, { name: '--from-clips', kind: 'boolean' }] }, { name: 'video diagnose', usage: 'vclaw video diagnose [--symptom <text>] [--retry-pattern]', description: 'Diagnose a Seedance output-quality failure (generic/morphing/jittery/blocked/off-prompt): match a symptom to its root cause, the first repair, and the vclaw tool that fixes it. Read-only/free. Without --symptom lists the whole tree; --retry-pattern also emits the conservative retry template. Continuation/sequence failures live in the continuation-handoff failure-atlas. Harvested from the MIT Emily2040/seedance-2.0 troubleshoot tree.', flags: [{ name: '--symptom', kind: 'value' }] }, { name: 'video animation-styles', usage: 'vclaw video animation-styles [--style <id>]', description: 'List the slide-animation styles from the shared registry — 11 subtle (broadcast/tabloid/minimal/comic/indian-tv/neon-esports/cinematic-film/gold-luxe/retro-vhs/stadium-live/chalkboard) + 4 BOLD with camera/element motion (slow-push/hero-pop/light-sweep/energetic) — or show one style\'s full Veo motion prompt with --style <id>. Read-only/free. These styles drive bunty_animate_slides.py for animated decks; bold styles suit hero/title slides, subtle styles suit text-dense scorecards.', flags: [{ name: '--style', kind: 'value' }] }, { name: 'video soundtrack', usage: 'vclaw video soundtrack --project <slug> (--prompt "<text>" [--duration <seconds>] [--backends suno,lyria] [--dry-run] [--confirm-spend] | --select <backendId>) [--root <path>]', description: 'Generate A/B soundtrack candidates from every available music backend (Suno/Lyria) and persist soundtrack.json; --select <backendId> marks the human-chosen candidate and writes its path into the project manifest soundtrack field for the preview portal. The generate path is paid: it refuses without --dry-run unless --confirm-spend is passed.', flags: [{ name: '--project', kind: 'value' }, { name: '--prompt', kind: 'value' }, { name: '--duration', kind: 'value' }, { name: '--backends', kind: 'value' }, { name: '--select', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, { name: '--confirm-spend', kind: 'boolean' }, { name: '--root', kind: 'value' }, { name: '--lyrics', kind: 'value' }, { name: '--instrumental', kind: 'boolean' }] }, { name: 'video narrate', usage: 'vclaw video narrate --project <slug> (--text "<script>" | --text-file <path>) [--voice <name>] [--backend gemini-tts] [--video-duration-ms <ms>] [--dry-run] [--confirm-spend] [--root <path>]', description: 'Synthesize a narration clip via a TTS backend (Gemini TTS) to artifacts/audio/narration.wav and persist narration.json; --video-duration-ms embeds a planNarrationFit() timing plan (atempo / loop-video) for the assemble step. Paid: refuses without --dry-run unless --confirm-spend is passed.', flags: [{ name: '--project', kind: 'value' }, { name: '--text', kind: 'value' }, { name: '--text-file', kind: 'value' }, { name: '--voice', kind: 'value' }, { name: '--backend', kind: 'value' }, { name: '--video-duration-ms', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, { name: '--confirm-spend', kind: 'boolean' }, { name: '--root', kind: 'value' }] }, { name: 'video dialogue', usage: 'vclaw video dialogue --project <slug> --turns "Name: line || Name2: line2" [--voice <name>] [--backend gemini-tts] [--dry-run] [--confirm-spend] [--root <path>]', description: 'Synthesize one TTS clip per dialogue turn (each "Name: line" split on "||") to artifacts/audio/dialogue-<i>-<name>.wav and persist dialogue.json; --voice applies to every turn. Paid: refuses without --dry-run unless --confirm-spend is passed.', flags: [{ name: '--project', kind: 'value' }, { name: '--root', kind: 'value' }, { name: '--turns', kind: 'value' }, { name: '--voice', kind: 'value' }, { name: '--backend', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, { name: '--confirm-spend', kind: 'boolean' }] }, { name: 'video sfx', usage: 'vclaw video sfx --project <slug> --prompt "<text>" [--duration <seconds>] [--prompt-influence <0..1>] [--backend elevenlabs-sfx] [--dry-run] [--confirm-spend] [--root <path>]', description: 'Generate one sound-effect clip from a text prompt via an SFX backend (ElevenLabs Sound Effects) to artifacts/audio/sfx-<n>.mp3 and append it to sfx.json. Paid: refuses without --dry-run unless --confirm-spend is passed.', flags: [{ name: '--project', kind: 'value' }, { name: '--root', kind: 'value' }, { name: '--prompt', kind: 'value' }, { name: '--duration', kind: 'value' }, { name: '--prompt-influence', kind: 'value' }, { name: '--backend', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, { name: '--confirm-spend', kind: 'boolean' }] }, { name: 'video voice-clone', usage: 'vclaw video voice-clone --project <slug> --name <name> --audio <sample> [--character <name>] [--description <text>] [--slice-seconds <n>] [--width <px>] [--height <px>] [--execute | --dry-run] [--root <path>]', description: 'Build the "blank video with audio" voice reference (the cartoon-show voice-cloning trick) and persist it as a reusable voice clone in artifacts/voice-clones.json. Given an --audio sample, renders a black-frame MP4 carrying that voice (MP3 references do not lock a voice in Seedance/Veo; a black-frame video carrying the same audio does). --character binds the clone to a character so any scene featuring that character locks the cloned voice (routed into Seedance reference_videos on the seedance-direct route); an explicit @<voice-name> tag also resolves a voice. --slice-seconds chops the recording into N-second clips (drift fix). Local ffmpeg only — no provider spend. Default-safe: plans only unless --execute is passed.', flags: [{ name: '--project', kind: 'value' }, { name: '--name', kind: 'value' }, { name: '--audio', kind: 'value' }, { name: '--character', kind: 'value' }, { name: '--description', kind: 'value' }, { name: '--slice-seconds', kind: 'value' }, { name: '--width', kind: 'value' }, { name: '--height', kind: 'value' }, { name: '--execute', kind: 'boolean' }, { name: '--dry-run', kind: 'boolean' }, { name: '--root', kind: 'value' }] }, { name: 'video gen-image', usage: 'vclaw video gen-image --project <slug> --prompt "<text>" --kind prop|screen|overlay [--backend gobananas|openai|flow] [--scene <i>] [--out <path>] [--aspect <ratio>] [--model <id>] [--ref <path|mediaGenerationId>]... [--character <name|ref>]... [--count <1-4>] [--seed <n>] [--character-id <id>] [--style-preset-id <id>] [--dry-run] [--confirm-spend] [--root <path>]', description: 'Generate a diegetic still (prop / on-screen UI dashboard / overlay graphic) and write it under assets/props/. PAID: a real run requires --confirm-spend (exit-3 spend_confirmation_required otherwise); --dry-run plans without spending. Backends: gobananas (default, Go Bananas image API), openai (gpt-image), flow (Google Flow via useapi.net POST /google-flow/images: imagen-4 / nano-banana / nano-banana-pro, auto-selected by reference count unless --model pins one). Flow-only flags (rejected on other backends): repeated --ref (reference_1..10 slots; local paths are uploaded first), repeated --character (character_1..7 slots; names resolve via flow-characters.json), --count (images per generation, default 1), --seed. Inline @reference_N/@character_N prompt markers are validated against the provided slots before any upload or spend. Weaves a per-kind render directive into the prompt; screens and overlays keep text (they are UIs/alerts), props suppress it. --dry-run prints the composed request without spending. Register the result into a scene with vclaw video assets.', flags: [{ name: '--project', kind: 'value' }, { name: '--prompt', kind: 'value' }, { name: '--kind', kind: 'value' }, { name: '--backend', kind: 'value' }, { name: '--scene', kind: 'value' }, { name: '--out', kind: 'value' }, { name: '--aspect', kind: 'value' }, { name: '--model', kind: 'value' }, { name: '--ref', kind: 'value' }, { name: '--character', kind: 'value' }, { name: '--count', kind: 'value' }, { name: '--seed', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, { name: '--confirm-spend', kind: 'boolean' }, { name: '--root', kind: 'value' }] }, { name: 'video overlay', usage: 'vclaw video overlay --input <video> --output <path> (--graphic <png> | --alert "<text>" | --lower-third "<text>") [--position top-left|top-center|top-right|center|bottom-left|bottom-center|bottom-right|full] [--start <s>] [--end <s>] [--fade-in <s>] [--fade-out <s>] [--opacity <0..1>] [--pulse-hz <n>] [--font-size <n>] [--color <c>] [--dry-run]', description: 'Composite a motion-graphics overlay onto a video via FFmpeg. --graphic overlays a PNG/alpha image (time-gated, faded, positioned) — pairs with gen-image to drop a generated SYSTEM COMPROMISED/dashboard screen onto a clip. --alert burns a pulsing alert and --lower-third a boxed caption (both drawtext, require an ffmpeg built with libfreetype). --dry-run prints the planned ffmpeg command without running it.', flags: [{ name: '--input', kind: 'value' }, { name: '--output', kind: 'value' }, { name: '--graphic', kind: 'value' }, { name: '--alert', kind: 'value' }, { name: '--lower-third', kind: 'value' }, { name: '--position', kind: 'value' }, { name: '--start', kind: 'value' }, { name: '--end', kind: 'value' }, { name: '--fade-in', kind: 'value' }, { name: '--fade-out', kind: 'value' }, { name: '--opacity', kind: 'value' }, { name: '--pulse-hz', kind: 'value' }, { name: '--font-size', kind: 'value' }, { name: '--color', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }] }, { name: 'video motion-overlay', usage: 'vclaw video motion-overlay --input <video-path> (--project <slug> | --output-dir <path>) [--layout split|overlay|motion-only|avatar-host] [--style apple-clean|editorial-dark|knowledge-tool] [--accent <hex>] [--lang <code>] [--max-take-seconds 10] [--transcript <path>] [--render local|v2v] [--delivery local|flow-web|flow-api] [--kicker <label>] [--animate] [--headlines] [--icons] [--emit-flow-pack] [--restitch <dir>] [--gb-character <Name:ID>] [--gb-character-image <path>] [--gb-voice <name>] [--host-engine omni-r2v|veo-i2v] [--host-retries <n>] [--host-look <id>] [--no-host-chain] [--preview] [--root <path>] [--execute] [--dry-run] [--confirm-spend]', description: 'Turn an existing talking-head video into a motion-graphics reel synced to the speech. Plan/dry by default: ingest (ffmpeg) -> Gemini STT (or --transcript) -> sentence-boundary slice into <=10s takes -> per-take Omni V2V prompt composition -> writes a work folder (source/ takes/ frames/ prompts/), README, and motion-overlay-plan.json manifest. No provider spend in plan mode. --execute (Omni Flash V2V render) is gated behind --confirm-spend (exit-3 spend_confirmation_required otherwise). Render transports: Omni Flash V2V (default), local per-frame render (--render local with --animate/--headlines/--icons/--kicker), and avatar-host (--layout avatar-host with --gb-character / --host-engine). --delivery / --emit-flow-pack drive the Flow hand-off loop; --restitch re-stitches an existing work folder.', flags: [ { name: '--input', kind: 'value' }, { name: '--project', kind: 'value' }, { name: '--output-dir', kind: 'value' }, { name: '--layout', kind: 'value' }, { name: '--style', kind: 'value' }, { name: '--accent', kind: 'value' }, { name: '--lang', kind: 'value' }, { name: '--max-take-seconds', kind: 'value' }, { name: '--transcript', kind: 'value' }, { name: '--render', kind: 'value', description: 'Render transport: local (per-frame motion graphics) or v2v (Omni Flash). Default v2v.' }, { name: '--delivery', kind: 'value', description: 'Delivery target: local | flow-web | flow-api.' }, { name: '--kicker', kind: 'value', description: 'Brand label burned into local-render overlays.' }, { name: '--animate', kind: 'boolean', description: 'Local render: per-frame motion graphics (word reveal, count-up, gauge fill).' }, { name: '--headlines', kind: 'boolean', description: 'Local render: kinetic-typography headlines.' }, { name: '--icons', kind: 'boolean', description: 'Local render: stat-bug icons.' }, { name: '--emit-flow-pack', kind: 'boolean', description: 'Emit a Flow hand-off pack instead of rendering.' }, { name: '--restitch', kind: 'value', description: 'Re-stitch an existing work folder at the given path.' }, { name: '--gb-character', kind: 'value' }, { name: '--gb-character-image', kind: 'value', description: 'avatar-host: explicit go-bananas character still path.' }, { name: '--gb-voice', kind: 'value', description: 'avatar-host: voice preset (default Puck).' }, { name: '--host-engine', kind: 'value', description: 'avatar-host engine: omni-r2v (default) or veo-i2v.' }, { name: '--host-retries', kind: 'value', description: 'avatar-host: max retries on probabilistic Flow moderation.' }, { name: '--host-look', kind: 'value', description: 'avatar-host: look/style preset id.' }, { name: '--no-host-chain', kind: 'boolean', description: 'avatar-host: disable pin+chain identity carry across takes.' }, { name: '--preview', kind: 'boolean', description: 'Emit a preview artifact without spending.' }, { name: '--root', kind: 'value' }, { name: '--execute', kind: 'boolean' }, { name: '--dry-run', kind: 'boolean' }, { name: '--confirm-spend', kind: 'boolean' }, ], }, { name: 'video music-video', usage: 'vclaw video music-video --config <music-video-config.json> [--work-dir <path>] [--output <path>] [--execute] [--dry-run] [--ffmpeg-bin <path>] [--ffprobe-bin <path>]', description: 'Vocal-synced, beat-exact music-video assembler. Reads an operator-authored config (song + clip registry + B-roll pools + an explicit vocal map or a transcript to auto-classify), pins each performer to their own vocal range time-aligned across B-roll cutaways, cuts every segment frame-exact (-frames:v, no drift), then concats + grades + muxes the song. Fully local (ffmpeg only, no provider/spend). PLAN/DRY by default; --execute renders and asserts zero drift.', flags: [ { name: '--config', kind: 'value' }, { name: '--work-dir', kind: 'value' }, { name: '--output', kind: 'value' }, { name: '--execute', kind: 'boolean' }, { name: '--dry-run', kind: 'boolean' }, { name: '--ffmpeg-bin', kind: 'value' }, { name: '--ffprobe-bin', kind: 'value' }, ], }, { name: 'video stitch-ad', usage: 'vclaw video stitch-ad --clip <path> [--clip <path> ...] --out <path> [--dissolve <sec>] [--width <px>] [--height <px>] [--fps <n>] [--bed <track>] [--bed-level <0-1>] [--sfx <track>] [--sfx-at <sec>] [--sfx-level <0-1>] [--dry-run]', description: 'Assemble ordered scene clips into a short ad: cross-dissolves (xfade video + acrossfade native audio) between shots, with an optional instrumental music bed laid UNDER the native voice (--bed, low --bed-level, fades in/out) and an optional one-shot --sfx at --sfx-at. The native character-ad finishing recipe — pair it after flow-r2v scenes and before title-card / make-vertical. Clips are normalized to a common WxH/fps. Fully local FFmpeg, no spend. --dry-run prints the planned ffmpeg command.', flags: [ { name: '--clip', kind: 'value' }, { name: '--out', kind: 'value' }, { name: '--dissolve', kind: 'value' }, { name: '--width', kind: 'value' }, { name: '--height', kind: 'value' }, { name: '--fps', kind: 'value' }, { name: '--bed', kind: 'value' }, { name: '--bed-level', kind: 'value' }, { name: '--sfx', kind: 'value' }, { name: '--sfx-at', kind: 'value' }, { name: '--sfx-level', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, ], }, { name: 'video title-card', usage: 'vclaw video title-card --input <video> --output <path> (--lower-third "<a||b>" | --end-card "<a||b>" | both) [--lt-start <s>] [--lt-end <s>] [--end-hold <s>] [--title-font <alias|path>] [--body-font <alias|path>] [--title-color <#hex>] [--sub-color <#hex>] [--accent-color <#hex>] [--no-accent] [--python <bin>] [--dry-run]', description: 'Burn music-video TITLE overlays — a faded lower-third early in the cut and/or a centred end card that holds to EOF — onto a finished video. Text is rasterized via Pillow + RAQM (transparent PNG, measure-ink-and-stack), so it works on ANY ffmpeg build (no libfreetype) and ANY script (Devanagari/Arabic). Each PNG is a looped input so delayed alpha fades animate. Fully local, no spend. PLAN/DRY by default; omit --dry-run to render.', flags: [ { name: '--input', kind: 'value' }, { name: '--output', kind: 'value' }, { name: '--lower-third', kind: 'value' }, { name: '--end-card', kind: 'value' }, { name: '--lt-start', kind: 'value' }, { name: '--lt-end', kind: 'value' }, { name: '--end-hold', kind: 'value' }, { name: '--title-font', kind: 'value' }, { name: '--body-font', kind: 'value' }, { name: '--title-color', kind: 'value' }, { name: '--sub-color', kind: 'value' }, { name: '--accent-color', kind: 'value' }, { name: '--no-accent', kind: 'boolean' }, { name: '--python', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, ], }, { name: 'video finish', usage: 'vclaw video finish --input <video> --output <path> [--backend topaz-proteus|topaz-gaia|topaz-starlight|magnific-precision|runway-topaz-free|topaz-local] [--scale 1..4] [--target-resolution 720p|1k|2k|4k] [--grain 0..0.1] [--noise 0..1] [--recover-detail 0..1] [--sharpen] [--normalize] [--topaz-cli <path>] [--dry-run] [--confirm-spend]', description: 'Upscale/finish a rendered video to a clean HD master. Hosted Topaz (Proteus default / Gaia / Starlight) runs via the apiz/xskill aggregator; magnific-precision runs Magnific Video Upscaler Precision via the direct Magnific REST API (needs MAGNIFIC_API_KEY) with --target-resolution 1k|2k|4k — it accepts MP4/MOV/AVI/WebM/MKV and enforces Magnific limits (<=15s, <=450 frames, <=150MB, <=4K input), failing fast unless --normalize re-encodes the clip to fit; topaz-local shells a local Topaz CLI. The anti-plastic detail-not-sharp recipe (noise/sharpen off, grain kept) is on by default; --sharpen disables it. --grain accepts 0..0.1 — Topaz\'s REAL ceiling (its published 0..1 schema lies); out-of-range values are rejected, not silently clamped. Hosted backends are PAID -> refuses without --confirm-spend (exit-3 spend_confirmation_required); --dry-run plans for free. topaz-local is free. Needs APIZ_API_KEY (or XSKILL_API_KEY) for Topaz hosted backends, MAGNIFIC_API_KEY for magnific-precision.', flags: [ { name: '--input', kind: 'value' }, { name: '--output', kind: 'value' }, { name: '--backend', kind: 'value' }, { name: '--scale', kind: 'value' }, { name: '--target-resolution', kind: 'value' }, { name: '--grain', kind: 'value' }, { name: '--noise', kind: 'value' }, { name: '--recover-detail', kind: 'value' }, { name: '--sharpen', kind: 'boolean' }, { name: '--normalize', kind: 'boolean' }, { name: '--topaz-cli', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, { name: '--confirm-spend', kind: 'boolean' }, ], }, { name: 'video image-ops', usage: 'vclaw video image-ops --op upscale --input <image> --output <path> [--backend magnific] [--scale 2..16] [--flavor sublime|photo|photo_denoiser] [--sharpen 0..100] [--smart-grain 0..100] [--ultra-detail 0..100] [--logo-safe] [--dry-run] [--confirm-spend]', description: 'Still-image post-processing via Magnific. --op upscale runs the Magnific image upscaler (/v1/ai/image-upscaler-precision-v2, verified live and distinct from the video upscaler) — a local image is base64-encoded inline, an http(s) URL is passed through. --scale 2..16 (default 4), --flavor sublime (default) | photo | photo_denoiser, plus --sharpen/--smart-grain/--ultra-detail (0..100). --logo-safe is the flat-graphics/text preset (sharpen 10, smart-grain 0, ultra-detail 0) so logos do not get hallucinated texture. PAID -> refuses without --confirm-spend (exit-3 spend_confirmation_required); --dry-run plans for free. Needs MAGNIFIC_API_KEY.', flags: [ { name: '--op', kind: 'value' }, { name: '--input', kind: 'value' }, { name: '--output', kind: 'value' }, { name: '--backend', kind: 'value' }, { name: '--scale', kind: 'value' }, { name: '--flavor', kind: 'value' }, { name: '--sharpen', kind: 'value' }, { name: '--smart-grain', kind: 'value' }, { name: '--ultra-detail', kind: 'value' }, { name: '--logo-safe', kind: 'boolean' }, { name: '--dry-run', kind: 'boolean' }, { name: '--confirm-spend', kind: 'boolean' }, ], }, { name: 'video lipsync', usage: 'vclaw video lipsync --image <path> --audio <path> --output <path> [--resolution 720p|1080p] [--prompt "<text>"] [--turbo] [--no-normalize] [--fps <n>] [--dry-run] [--confirm-spend]', description: 'Audio-driven lip-sync: a still/keyframe + a vocal track -> a lip-synced talking-head clip via OmniHuman v1.5 (apiz/xskill). Uploads image+audio -> submits -> awaits -> downloads -> NORMALIZES to CFR fps (default 24) + even dimensions (OmniHuman returns 25fps/odd dims that break frame-accurate seeking in the assembler). Audio length is checked against the model cap (1080p<=30s, 720p<=60s) up front. Drives an EXTERNAL vocal (unlike motion-overlay avatar-host, which speaks in its own generated voice). PAID -> refuses without --confirm-spend (exit-3); --dry-run plans free. Needs APIZ_API_KEY (or XSKILL_API_KEY).', flags: [ { name: '--image', kind: 'value' }, { name: '--audio', kind: 'value' }, { name: '--output', kind: 'value' }, { name: '--resolution', kind: 'value' }, { name: '--prompt', kind: 'value' }, { name: '--turbo', kind: 'boolean' }, { name: '--no-normalize', kind: 'boolean' }, { name: '--fps', kind: 'value' }, { name: '--dry-run', kind: 'boolean' }, { name: '--confirm-spend', kind: 'boolean' }, ], }, // --- director gate --- { name: 'video director-preflight', usage: 'vclaw video director-preflight --project <slug> [--root <path>] [--apply-content-fixes]' }, { name: 'video storyboard-review', usage: 'vclaw video storyboard-review --project <slug> [--root <path>] [--mode storyboard|director] [--apply-content-fixes]' }, // --- review UI --- { name: 'video review-ui', usage: 'vclaw video review-ui --project <slug> [--root <path>] [--host <host>] [--port <port>] [--ui-path <path>] [--dry-run]' }, { name: 'video review-autopilot', usage: 'vclaw video review-autopilot --project <slug> [--root <path>] [--template <template-id>] [--character <name>] [--run-id <id>]' }, { name: 'video portal', usage: 'vclaw video portal --project <slug> [--root <path>] [--client <name>] [--run <id>] [--surface edit|review|client-review|preview|compare|index|run]' }, { name: 'video portal-index', usage: 'vclaw video portal-index [--root <path>] [--client <name>] [--output <path>]' }, { name: 'video publish-preview', usage: 'vclaw video publish-preview --project <slug> --client <name> --bucket <bucket> [--root <path>] [--run <id>] [--surface review|preview|run|index] [--public-base-url <url>] [--wrangler-bin <path>] [--dry-run]' }, { name: 'video publish-portal-index', usage: 'vclaw video publish-portal-index --bucket <bucket> [--root <path>] [--client <name>] [--public-base-url <url>] [--wrangler-bin <path>] [--dry-run]' }, // --- character management --- { name: 'video character-add', usage: 'vclaw video character-add --project <slug> --name <name> [--gb-id <id>] [--description <text>] [--costume <text>] [...] [--root <path>]', flags: [{ name: '--description', kind: 'value' }, { name: '--costume', kind: 'value' }] }, { name: 'video character-auto-create', usage: 'vclaw video character-auto-create --project <slug> --input <json-path> [--root <path>] [--api-url <url>] [--no-sheet] [--sheet-preset <id>] [--dry-run]' }, { name: 'video environment-auto-create', usage: 'vclaw video environment-auto-create --project <slug> --input <json-path> [--root <path>] [--api-url <url>] [--dry-run]' }, { name: 'video show-bible', usage: 'vclaw video show-bible --project <slug> [--title <t>] [--premise <p>] [--style <s>] [--add-episode "id|title|logline"]... [--from-json <path>] [--show] [--root <path>]', description: 'Derive or persist the cartoon-SHOW asset-library index (artifacts/show-bible.json): the repeatable production system that ties the project’s characters + locations + voice clones into one reusable world and tracks the episode list. By default the bible is DERIVED from the project’s existing artifacts and written; --show prints it without writing; --from-json validates+persists an operator-authored bible; --add-episode (repeatable) merges episodes by id. Deterministic — no provider calls. Distinct from story-bible (per-project continuity) and director-blueprint (visual direction).', flags: [{ name: '--project', kind: 'value' }, { name: '--title', kind: 'value' }, { name: '--premise', kind: 'value' }, { name: '--style', kind: 'value' }, { name: '--add-episode', kind: 'value' }, { name: '--from-json', kind: 'value' }, { name: '--show', kind: 'boolean' }, { name: '--root', kind: 'value' }] }, { name: 'video show-preflight', usage: 'vclaw video show-preflight --project <slug> [--root <path>] [--mode storyboard|director]', description: 'Fail-fast gate that ENFORCES the cartoon-show method per route. Given the project’s show-bible + storyboard, it confirms every cast/speaking subject in every scene has the references the resolved provider route actually needs — on the Seedance family (seedance-direct / runway-useapi / dreamina-useapi) each cast member’s character sheet + the scene’s location plate + each speaking character’s bound voice clip, plus a specific visual descriptor (not "the man"); on Flow (veo-useapi) a registered Flow Character. Read-only: prints a JSON blocker report and exits 3 (GATE) when any piece is missing so an && chain or the studio runner halts before a render. A project with no show-bible.json is inert (ready, exit 0). produce/execute apply the same gate when a show-bible exists.', flags: [{ name: '--project', kind: 'value' }, { name: '--root', kind: 'value' }, { name: '--mode', kind: 'value' }] }, { name: 'video character-import-library', usage: 'vclaw video character-import-library --project <slug> --intent "<text>" [--root <path>] [--api-url <url>]' }, { name: 'video character-list', usage: 'vclaw video character-list --project <slug> [--root <path>]' }, { name: 'video character-show', usage: 'vclaw video character-show --project <slug> --name <name> [--root <path>]' }, { name: 'video character-consistency', usage: 'vclaw video character-consistency --project <slug> [--root <path>]' }, { name: 'video consistency-audit', usage: 'vclaw video consistency-audit --project <slug> [--root <path>] [--json]', description: 'Automated character identity/costume vision audit of the project’s rendered scenes (artifacts/consistency-audit.json). Extracts a representative mid-frame per rendered scene and asks a Gemini-Vision client whether each registered scene character still matches its locked reference face/hair AND costume/colours, plus a deterministic dark-border check and an extra-figure flag. Catches identity/wardrobe drift (e.g. a dhoti rendering crimson in some scenes and tan in others) BEFORE a render is presented as done. Requires a Gemini vision key (GEMINI_API_KEYS / GOOGLE_API_KEY).', flags: [{ name: '--project', kind: 'value' }, { name: '--root', kind: 'value' }, { name: '--json', kind: 'boolean' }] }, { name: 'video motion-qc', usage: 'vclaw video motion-qc --project <slug> [--root <path>] [--samples <2-24>]', description: 'Dense-frame motion-artifact vision QC of the project’s rendered clips (artifacts/motion-artifact-qc.json) — the defect classes a still-frame + whisper QC pass is BLIND to. Samples K frames per clip (default 9, denser than consistency-audit) and runs two lanes: a per-frame lane for breath-vapour condensation puffs and film-grain rendering as drifting fog over dark regions, and an adjacent-pair lane (anchored on the scene keyframe when bound) for objects/faces MORPHING between moments, solid props VANISHING mid-clip, and TEMPORAL vapour wisps that only read as a difference between frames. Complements consistency-audit (identity/costume drift), whose prompt deliberately excludes vapour/fog and which never compares adjacent samples. Conservative prompts; transport failures degrade to advisory. Requires a Gemini vision key (GEMINI_API_KEYS / GOOGLE_API_KEY).', flags: [{ name: '--project', kind: 'value' }, { name: '--root', kind: 'value' }, { name: '--samples', kind: 'value' }] }, // --- reference sheets --- { name: 'video reference-sheet-add', usage: 'vclaw video reference-sheet-add --project <slug> --type <type> --name <name> [...]', flags: [{ name: '--description', kind: 'value' }, { name: '--character-name', kind: 'value' }, { name: '--gb-ref', kind: 'value' }, { name: '--binding', kind: 'value' }] }, { name: 'video reference-sheet-list', usage: 'vclaw video reference-sheet-list --project <slug> [--type <sheet-type>] [--root <path>]' }, { name: 'video reference-sheet-show', usage: 'vclaw video reference-sheet-show --project <slug> --id <sheet-id> [--root <path>]' }, { name: 'video reference-sheet-bind', usage: 'vclaw video reference-sheet-bind --project <slug> --id <sheet-id> --scene <sceneIndex> [...]' }, { name: 'video reference-sheet-validate', usage: 'vclaw video reference-sheet-validate --project <slug> [--root <path>]' }, // --- candidates --- { name: 'video candidates-list', usage: 'vclaw video candidates-list --project <slug> [--scene <sceneIndex>] [--root <path>]' }, { name: 'video candidates-show', usage: 'vclaw video candidates-show --project <slug> --candidate-id <id> [--root <path>]' }, { name: 'video select-candidate', usage: 'vclaw video select-candidate --project <slug> (--scene <sceneIndex> --candidate-id <id> [--notes <text>] | --auto-select [--ref <imagePath> ...]) [--root <path>]' }, { name: 'video reject-candidate', usage: 'vclaw video reject-candidate --project <slug> --scene <sceneIndex> --candidate-id <id> [--notes <text>] [--root <path>]' }, { name: 'video reroll-scene', usage: 'vclaw video reroll-scene --project <slug> --scene <sceneIndex> [...]', flags: [{ name: '--chain-from-prev', kind: 'value' }, { name: '--verdict', kind: 'value' }, { name: '--flaw', kind: 'value' }, { name: '--changed', kind: 'value' }, { name: '--seed', kind: 'value' }, { name: '--evidence', kind: 'value' }, { name: '--attempt-budget', kind: 'value' }] }, { name: 'video chain-from', usage: 'vclaw video chain-from --project <slug> --scene <sceneIndex> --from <sourceSceneIndex> [...]' }, { name: 'video unchain', usage: 'vclaw video unchain --project <slug> --scene <sceneIndex> [...]' }, // --- templates + clone --- { name: 'video template-list', usage: 'vclaw video template-list [--root <path>]' }, { name: 'video template-show', usage: 'vclaw video template-show --name <template-name> [--root <path>]' }, { name: 'video clone-plan', usage: 'vclaw video clone-plan --template <template-name> --project <slug> --intent <text> [--root <path>]' }, // --- authoring aids --- { name: 'video multi-shot', usage: 'vclaw video multi-shot (--presets | --plan [--shots N] [--seed N] [--genre <id>] [--format default|seedance-paragraph|per-shot] [--lang en|zh|en+zh] [--category <id>] [--hook <patternId>] [--dialogue "<speaker>: <line> [emotion] [|| <speaker>: <line> [emotion]]"] [--emotion-cues] [--vfx <id>] | --validate [--file <path>] [--explain-issues] | --fix [--file <path>] | --auto --image <path> [--dry-run] [--retry-invalid N] [...]) [--preset <name>] [--provider <name>] [--route <name>] [--from-storyboard --project <slug> --scene <sceneIndex>] [--total-seconds N] [--max-chars N] [--root <path>] [--raw]', description: 'Scaffold, validate, and Gemini-author timecoded multi-shot cinematic prompts, including project storyboard scene hydration.', flags: [ { name: '--style-line', kind: 'value' }, { name: '--audio-line', kind: 'value' }, { name: '--location', kind: 'value' }, { name: '--time', kind: 'value' }, { name: '--action', kind: 'value' }, { name: '--character', kind: 'value' }, ], }, { name: 'video filmmaking-prompts', usage: 'vclaw video filmmaking-prompts --project <slug> [--root <path>] [--duration <seconds>] [--storyboard-grid <path>] [--category <id>] [--genre <id>] [--phase storyboard|video] [--detail terse|standard|rich] [--register prose|numeric] [--sheet 8-shot|6-panel] [--realism] [--no-realism] [--wet] [--haze thin|light|heavy] [--background mid-gray|white|black] [--lighting <id>] [--grade <id>] [--dialogue "<speaker>: <line> [emotion] [|| <speaker>: <line> [emotion]]"] [--dialogue-scene "<sceneIndex>:<speaker>: <line>"] [--emotion-cues] [--no-faces] [--write]', description: 'Generate ai-filmmaking prompt packets: character sheet prompts, optional attached 9-panel storyboard grid, reference map, and Seedance Variant A/B/C packets. Default is the full photoreal treatment (rich + realism + prose); dial down with --detail/--register/--no-realism or a project cinema-profile.', flags: [{ name: '--checklist', kind: 'boolean' }], }, { name: 'video director-blueprint', usage: 'vclaw video director-blueprint --project <slug> (--from-json <path> [--write] | --show) [--root <path>]', description: 'Validate + persist an AI-Director Project Blueprint (the ai-director skill authors the JSON): visual identity, master color system, lighting grammar, per-character blueprint (silhouette/palette/voice/power-vulnerability-signature framing), environment blueprint (5 sensory words), project camera bible (dominant + forbidden movements + the one rule), and performance rules. The filmmaking-prompts composer reads it back to enrich every scene packet.', }, { name: 'video brand-definition', usage: 'vclaw video brand-definition --project <slug> (--from-json <path> [--write] | --show) [--root <path>]', description: 'Validate + persist the locked brand system (the brand-agency skill authors the JSON): brand name, positioning, taglines, voice rules, 6-color hex palette, typography hierarchy, 12-week theme map, and the vision-verified master asset (go-bananas product ref + Flow character name). The filmmaking-prompts composer reads it back to append a prose BRAND line to every scene packet.', }, { name: 'video prompt-lint', usage: 'vclaw video prompt-lint (--project <slug> | --file <path>) [--root <path>] [--register prose|numeric] [--cast <Name:descriptor> ...] [--brand <token> ...]', description: 'Pure validator over a filmmaking-prompts artifact: per-packet 10-block order, word-count window, brand/proper-name scrub, grid-guard presence, required SUBJECT LOCK/CAPTURE REALISM/CAMERA CAPTURE blocks, and no Kelvin/hue tokens in a prose-register packet. Storyboard-grid panels get advisory annotation-slug-format (2-6 word uppercase strips) and framing-progression (wide -> medium -> close, climax in the final third) checks; in --project mode each stored character description is checked against the 30-60 word identity budget (>100 errors). Emits {packets:[{sceneIndex,issues}],grid?,characters?,ok} and exits non-zero when not ok.', }, { name: 'video cinema-profile', usage: 'vclaw video cinema-profile --project <slug> [--detail terse|standard|rich] [--register prose|numeric] [--realism on|off] [--no-realism] [--haze thin|light|heavy] [--capture cinema|phone] [--root <path>]', description: 'Set the project-level cinematic look profile (persisted on project.json). Photorealism is the universal default; this dials it down by exception across every subsequent filmmaking-prompts run.', }, { name: 'video storyboard-grid', usage: 'vclaw video storyboard-grid --project <slug> [--root <path>] [--output <path>] [--width <px>] [--height <px>] [--image <path> ...] [--images <a,b,c>] [--dry-run]', description: 'Render a deterministic storyboard story-sheet PNG from the filmmaking-prompt panels — each cell shows the shot beat plus a CAM/MOVE/MOOD slug strip. Pass --image (repeatable, panel order) or a comma-separated --images list to embed the actual keyframe stills into the cells; omitted cells stay empty placeholders. Marks the grid reference ready for execution.', }, { name: 'video seedance-register-assets', usage: 'vclaw video seedance-register-assets --project <slug> --character <name>:<imageUrl> [--character ...] [--group <name>] [--root <path>]', description: 'Register character reference images as xskill Asset Library avatars (Asset:// URIs) for ark/seedance-2.0 character consistency; writes artifacts/seedance-assets.json.', }, { name: 'video flow-register-characters', usage: 'vclaw video flow-register-characters --project <slug> --input <json-path> [--root <path>]', description: 'Register reusable Google Flow characters (locked identity + optional bundled voice) from a JSON array of {name,images,voice?}; writes artifacts/flow-characters.json. Scene characters then resolve to character_1..7 on veo-useapi. R2V-routed; the Veo 3.1 R2V character lane now clears photoreal human faces (live-proven), so register real people directly and render scenes with vclaw video flow-r2v.', }, { name: 'video flow-r2v', usage: 'vclaw video flow-r2v --prompt "<text>" --character <name|ref> [--character ...] --out <path> [--project <slug>] [--root <path>] [--duration 4|6|8|10] [--aspect landscape|portrait] [--keep-music] [--allow-reverb] [--retries <n>] [--cooldown <sec>] [--dry-run]', description: 'Render ONE Google Flow Reference-to-Video (R2V) scene directly from saved Flow characters — the native character-ad workflow. Each --character is a friendly name (resolved via artifacts/flow-characters.json when --project is set) or a raw character ref → character_1..7 (lock the person AND the product in one scene). Dialogue lives inline in --prompt; Veo generates the voice + lip-sync natively. Uses veo-3.1-fast (R2V; no startImage). By default applies the dry-voice (close-mic, no reverb) + no-baked-music hygiene so a post music bed sits cleanly under the native voice — opt out with --keep-music / --allow-reverb. A 403 reCAPTCHA throttle is cooled-down-and-retried (--retries / --cooldown). --dry-run prints the composed request without spending.', }, { name: 'video flow-register-voices', usage: 'vclaw video flow-register-voices --project <slug> --input <json-path> [--root <path>]', description: 'Register reusable Google Flow custom voices from a JSON array of {name,basePreset,dialog,voicePerformance}; writes artifacts/flow-voices.json.', }, // --- overnight batch video queue --- { name: 'video batch-submit', usage: 'vclaw video batch-submit --manifest <path> --out <dir> [--route runway-useapi|dreamina-useapi|seedance-direct] [--confirm-spend] [--root <path>]', description: 'Submit a batch-queue manifest (many jobs) through a native route; persists <dir>/batch-queue.json. Defaults to the free runway-useapi explore mode (no gate). PAID routes (dreamina-useapi / seedance-direct) submit every task immediately and require --confirm-spend (exit-3 spend_confirmation_required otherwise).', }, { name: 'video batch-monitor', usage: 'vclaw video batch-monitor --out <dir> [--once] [--interval <sec>] [--max-minutes <n>] [--stall-minutes <n>] [--fail-wedged] [--auto-resubmit] [--max-resubmits <n>]', description: 'Poll a submitted batch, download completed clips to <dir>/clips/<jobId>.mp4, and refresh <dir>/batch-status.json. --once does a single pass (schedule it via launchd); without --once it loops every --interval seconds until terminal or --max-minutes elapses, backing off automatically when the explore queue is throttled. --stall-minutes <n> (0=off) flags scenes the provider has left "submitted" longer than n minutes as wedged (surfaced in status/output); add --fail-wedged to mark those wedged scenes failed so the queue reaches terminal. Or add --auto-resubmit (with --stall-minutes <n>) to re-submit wedged scenes (up to --max-resubmits <n>, default 2) — ONLY allowed on the free runway-useapi route (refused on paid routes so it never spends credits).', }, { name: 'video batch-status', usage: 'vclaw video batch-status --out <dir>', description: 'Print the current batch queue-state rollup (done/pending/failed) without polling (read-only).', }, // --- portfolio + status --- { name: 'video list', usage: 'vclaw video list [--root <path>]' }, { name: 'video index', usage: 'vclaw video index [--root <path>] [--output <path>]' }, { name: 'video monitor', usage: 'vclaw video monitor [--port <n>] [--root <path> ...]' }, { name: 'video migrate-home', usage: 'vclaw video migrate-home [--confirm] [--root <home>]', description: 'Consolidate scattered projects (~/.videoclaw-* roots) into the one canonical workspace home (~/videoclaw): mv each project in, leave a symlink at the old path so old absolute paths still resolve. Dry-run by default (prints the move plan); --confirm performs the moves. Collision-safe (slug clashes get a -2/-3 suffix) and idempotent (already-symlinked dirs are skipped).' }, { name: 'video metrics', usage: 'vclaw video metrics [--root <path>] [--mode storyboard|director]' }, { name: 'video next-actions', usage: 'vclaw video next-actions [--root <path>] [--mode storyboard|director]' }, { name: 'video report', usage: 'vclaw video report [--root <path>] [--mode storyboard|director]' }, { name: 'video status', usage: 'vclaw video status --project <slug> [--root <path>] [--mode storyboard|director]' }, { name: 'video doctor-project', usage: 'vclaw video doctor-project --project <slug> [--root <path>] [--mode storyboard|director]' }, { name: 'video doctor-portfolio', usage: 'vclaw video doctor-portfolio [--root <path>] [--mode storyboard|director]' }, // --- export + obsidian --- { name: 'video export-csv', usage: 'vclaw video export-csv [--root <path>] [--output-dir <path>] [--mode storyboard|director]' }, { name: 'video export-obsidian', usage: 'vclaw video export-obsidian --project <slug> [--root <path>] [--output-dir <path>] [--mode storyboard|director]' }, { name: 'video sync-obsidian', usage: 'vclaw video sync-obsidian [--root <path>] [--output-dir <path>] [--mode storyboard|director]' }, // --- veo (Bun bridge for Google Flow) --- { name: 'veo status', usage: 'vclaw veo status [batchId]', description: 'Show status of current or specific Veo batch.' }, { name: 'veo list', usage: 'vclaw veo list', description: 'List all Veo batches.' }, { name: 'veo history', usage: 'vclaw veo history [--limit <n>]', description: 'Show recent Veo job history.' }, { name: 'veo resume', usage: 'vclaw veo resume [batchId]', description: 'Resume a paused Veo batch.' }, { name: 'veo reset', usage: 'vclaw veo reset', description: 'Reset failed Veo jobs to pending.' }, { name: 'veo cancel', usage: 'vclaw veo cancel', description: 'Cancel current Veo batch.' }, { name: 'veo useapi:accounts', usage: 'vclaw veo useapi:accounts list|add [--cookies <path>]', description: 'Manage useapi.net accounts (via Bun bridge).' }, { name: 'veo useapi:captcha', usage: 'vclaw veo useapi:captcha list | --provider <name> --key <key>', description: 'Manage useapi.net CAPTCHA providers.' }, { name: 'veo useapi:health', usage: 'vclaw veo useapi:health', description: 'useapi.net account health + history.' }, { name: 'veo useapi:image', usage: 'vclaw veo useapi:image --image-prompt "<text>" [--image-model imagen-4|nano-banana|nano-banana-pro] [--ref <url> ...] [--yes]', description: 'Generate images via useapi.net (Imagen-4 / nano-banana family).' }, { name: 'veo useapi:image:upscale', usage: 'vclaw veo useapi:image:upscale --media-id <id> --resolution 2k|4k', description: 'Upscale a nano-banana-pro image.' }, { name: 'veo useapi:gif', usage: 'vclaw veo useapi:gif --media-id <id> --output-file <path>', description: 'Convert a Veo video to GIF (free, no CAPTCHA).' }, { name: 'veo useapi:upscale', usage: 'vclaw veo useapi:upscale --media-id <id> --resolution 1080p|4k', description: 'Upscale a Veo video.' }, // --- mcp server --- { name: 'mcp serve', usage: 'vclaw mcp serve', description: 'Start the videoclaw MCP server (stdio) exposing read-only project introspection to MCP-aware agent hosts.' }, // --- human-friendly studio planner --- { name: 'studio', usage: 'vclaw studio [--dry-run] [--goal <goal>] [--project <slug>] [--title <title>] [--intent <text>] [--input <path-or-url>] [--client <name>] [--duration <seconds>] [--write-session] [--execute] [--confirm-spend] [--auto-approve-storyboard] [--from-step <id>] [--root <path>]', description: 'Generate a guided Studio production plan from high-level goals. Plan-only by default. With --execute it RUNS the emitted plan by shelling out to the same vclaw video commands (not a second orchestrator). Dry-by-default: spend steps run with --dry-run and a spend step lacking --dry-run is refused; the human checkpoint is the director storyboard gate, surfaced as a blocked step. --confirm-spend promotes the dry spend steps to REAL renders (storyboard approval still required out-of-band); add --auto-approve-storyboard for an unattended render (sets VIDEOCLAW_APPROVE_STORYBOARD in the child). --from-step <id> resumes after an approval. Reports an execution block with per-step status + mode (dry|confirm-spend|auto-render).', }, // --- keyframe tools --- { name: 'video outpaint-keyframe', usage: 'vclaw video outpaint-keyframe --input <path> --output <path> [--width <px>] [--height <px>] [--mask-dilation <frac>] [--fill gobananas|none] [--prompt <text>] [--size <WxH>] [--project <slug>] [--root <path>]', description: 'Pad a keyframe image onto a target canvas (default 1920×1080, centred letterbox) and build an RGBA alpha inpainting mask (transparent border = fill, opaque centre = preserve) for the border region. --fill none (the default) writes the padded letterbox only (deterministic, offline). --fill gobananas uploads the padded source + mask to go-bananas and runs a masked gpt-image-2 edit (requires GO_BANANAS_API_KEY).', flags: [ { name: '--input', kind: 'value', description: 'Source keyframe image path.' }, { name: '--output', kind: 'value', description: 'Output PNG path.' }, { name: '--width', kind: 'value', description: 'Target canvas width in pixels (default 1920).' }, { name: '--height', kind: 'value', description: 'Target canvas height in pixels (default 1080).' }, { name: '--mask-dilation', kind: 'value', description: 'Mask dilation as a fraction of the smaller canvas dimension (default 0.03).' }, { name: '--fill', kind: 'value', description: 'Fill backend: gobananas (uploads source + mask, runs a masked gpt-image-2 edit, requires GO_BANANAS_API_KEY) or none (default; writes the padded letterbox only).' }, { name: '--prompt', kind: 'value', description: 'Outpaint instruction for --fill gobananas (overrides the default extend-the-scene prompt).' }, { name: '--size', kind: 'value', description: 'Optional gpt-image-2 output size for --fill gobananas (e.g. 1536x1024).' }, { name: '--project', kind: 'value', description: 'Optional project slug for output-path resolution context.' }, { name: '--root', kind: 'value', description: 'Workspace root (default cwd).' }, ], }, // --- project metadata + estimation --- { name: 'video set-meta', usage: 'vclaw video set-meta --project <slug> [--root <path>] [--owner <name>] [--priority low|medium|high|critical] [--due YYYY-MM-DD] [--tag <value> ...]', flags: [{ name: '--blocked-by', kind: 'value' }, { name: '--blocked-reason', kind: 'value' }] }, { name: 'video set-execution-profile', usage: 'vclaw video set-execution-profile --project <slug> [--root <path>] [--aspect-ratio 16:9|9:16|1:1] [--quality fast|quality] [--resolution 720p|1080p]', flags: [{ name: '--veo-model', kind: 'value' }] }, { name: 'video cost-estimate', usage: 'vclaw video cost-estimate [--project <slug>] [--root <path>] [--scenes <count>] [--clip-duration <seconds>] [--new-characters <count>]', flags: [{ name: '--narration', kind: 'value' }] }, { name: 'video archive-project', usage: 'vclaw video archive-project --project <slug> [--root <path>] [--archive-dir <path>] [--cleanup]' }, { name: 'video artifact-history', usage: 'vclaw video artifact-history --project <slug> --artifact <name> [--root <path>]' }, // --- character / prompt library --- { name: 'video list-library', usage: 'vclaw video list-library [--name-regex <pattern>] [--root <path>]' }, { name: 'video find-library', usage: 'vclaw video find-library --intent "<text>" [--api-url <url>]' }, { name: 'video library', usage: 'vclaw video library find --intent "<text>" [--api-url <url>]' }, { name: 'video prompt-lib-list', usage: 'vclaw video prompt-lib-list' }, { name: 'video prompt-lib-show', usage: 'vclaw video prompt-lib-show --name <reference-name> [--root <path>]' }, { name: 'video playbook-list', usage: 'vclaw video playbook-list [--root <path>]' }, { name: 'video playbook-show', usage: 'vclaw video playbook-show --name <playbook-name> [--root <path>]' }, // --- templates + clone-from-template --- { name: 'video template-save', usage: 'vclaw video template-save --project <slug> --name <template-name> [--root <path>]' }, { name: 'video template-create', usage: 'vclaw video template-create --project <slug> --name <template-name> [--root <path>]' }, { name: 'video template-validate', usage: 'vclaw video template-validate --name <template-name> [--root <path>]' }, { name: 'video storyboard-template-list', usage: 'vclaw video storyboard-template-list' }, { name: 'video storyboard-template-show', usage: 'vclaw video storyboard-template-show --name <template-id>' }, { name: 'video clone-init', usage: 'vclaw video clone-init --template <template-name> --project <slug> --intent <text> [--root <path>] [--mode storyboard|director] [--platform <name>]', flags: [{ name: '--veo-model', kind: 'value' }] }, { name: 'video clone-execute', usage: 'vclaw video clone-execute --template <template-name> --project <slug> --intent <text> [--root <path>] [--mode storyboard|director] [--platform <name>]', flags: [{ name: '--veo-model', kind: 'value' }] }, { name: 'video clone-ad', usage: 'vclaw video clone-ad --template <template-name> --project <slug> --intent <text> [--root <path>] [--mode storyboard|director] [--platform <name>]', flags: [{ name: '--veo-model', kind: 'value' }] }, { name: 'video storyboard-from-clone', usage: 'vclaw video storyboard-from-clone --project <slug> [--root <path>] [--mode storyboard|director]' }, { name: 'video import-legacy', usage: 'vclaw video import-legacy --source <path> [--root <path>]' }, // --- scene-candidate maintenance --- { name: 'video storyboard-still-add', usage: 'vclaw video storyboard-still-add --project <slug> --scene <sceneIndex> --image-url <url> [--image-id <id>] [--prompt <text>] [--notes <text>] [--root <path>]', flags: [{ name: '--image-id', kind: 'value' }, { name: '--prompt', kind: 'value' }, { name: '--notes', kind: 'value' }] }, { name: 'video candidates-migrate-from-assets', usage: 'vclaw video candidates-migrate-from-assets --project <slug> [--root <path>]' }, // --- portfolio reporting + analysis --- { name: 'video workload', usage: 'vclaw video workload [--root <path>] [--mode storyboard|director]' }, { name: 'video dependencies', usage: 'vclaw video dependencies [--root <path>] [--mode storyboard|director]' }, { name: 'video report-snapshot', usage: 'vclaw video report-snapshot [--root <path>] [--mode storyboard|director]' }, { name: 'video report-history', usage: 'vclaw video report-history [--root <path>]' }, { name: 'video report-diff', usage: 'vclaw video report-diff [--root <path>] [--from <snapshot-path>] [--to <snapshot-path>]' }, { name: 'video trends', usage: 'vclaw video trends [--root <path>]' }, { name: 'video analyze', usage: 'vclaw video analyze --project <slug> --source <path-or-url> [--auto] [--root <path>]', flags: [{ name: '--pacing', kind: 'value' }, { name: '--motion', kind: 'value' }, { name: '--beat', kind: 'value' }, { name: '--keep', kind: 'value' }, { name: '--change', kind: 'value' }, { name: '--var', kind: 'value' }] }, { name: 'video analyze-template', usage: 'vclaw video analyze-template --project <slug> --source <path-or-url> [--auto] [--root <path>]', flags: [{ name: '--pacing', kind: 'value' }, { name: '--motion', kind: 'value' }, { name: '--beat', kind: 'value' }, { name: '--keep', kind: 'value' }, { name: '--change', kind: 'value' }, { name: '--var', kind: 'value' }] }, // --- local media post-production --- { name: 'video remix-narrated', usage: 'vclaw video remix-narrated --project <slug> [--root <path>] [--output <path>]' }, { name: 'video verify-final', usage: 'vclaw video verify-final (--project <slug> | --file <path>) [--root <path>] [--output-dir <path>]' }, { name: 'video qc', usage: 'vclaw video qc --project <slug> [--root <path>]' }, { name: 'video make-vertical', usage: 'vclaw video make-vertical (--project <slug> | --file <path>) [--root <path>] [--output <path>]' }, { name: 'video make-square', usage: 'vclaw video make-square (--project <slug> | --file <path>) [--root <path>] [--output <path>]' }, { name: 'video make-loop', usage: 'vclaw video make-loop (--project <slug> | --file <path>) [--root <path>] [--output <path>]' }, { name: 'video thumbnail', usage: 'vclaw video thumbnail (--project <slug> | --file <path>) [--root <path>] [--output <path>] [--text <title>]' }, { name: 'video burn-subtitles', usage: 'vclaw video burn-subtitles (--project <slug> | --file <path>) --subtitle <path> [--root <path>] [--output <path>]' }, // --- exports --- { name: 'video scaffold-obsidian-vault', usage: 'vclaw video scaffold-obsidian-vault [--output-dir <path>]' }, // --- introspection --- { name: 'schema', usage: 'vclaw schema [--json]', description: 'Dump the full v3 contract (commands, flags, artifact schemas, error codes, exit codes) for agent introspection.' }, ]; function loadArtifactSchemas(): Record<string, unknown> { const here = dirname(fileURLToPath(import.meta.url)); const schemasDir = join(here, '..', '..', 'schemas', 'video', 'artifacts'); const out: Record<string, unknown> = {}; let entries: string[]; try { entries = readdirSync(schemasDir); } catch (err: unknown) { // Directory missing is expected in some test envs. Anything else // (permission denied, etc.) is genuinely unexpected. if ((err as NodeJS.ErrnoException).code === 'ENOENT') { return out; } throw err; } for (const file of entries) { if (!file.endsWith('.schema.json')) continue; const name = file.replace(/\.schema\.json$/, ''); const raw = readFileSync(join(schemasDir, file), 'utf-8'); // JSON.parse errors propagate — a corrupt schema should fail loudly. out[name] = JSON.parse(raw); } return out; } export function buildSchemaDump(): SchemaDump { return { version: '3.0.0-alpha.1', generatedAt: new Date().toISOString(), exitCodes: { ...ExitCode }, errorCodes: ALL_ERROR_CODES, commands: COMMANDS, artifactSchemas: loadArtifactSchemas(), multiShot: { presets: listMultiShotPresets(), issueExplanations: listMultiShotIssueExplanations(), }, }; }