videoclaw 3.0.0-alpha.1
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/AGENTS.md +165 -0
- package/CLAUDE.md +232 -0
- package/LICENSE +36 -0
- package/README.md +737 -0
- package/dist/cli/args.d.ts +59 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +279 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/handlers/analysis.d.ts +3 -0
- package/dist/cli/handlers/analysis.d.ts.map +1 -0
- package/dist/cli/handlers/analysis.js +111 -0
- package/dist/cli/handlers/analysis.js.map +1 -0
- package/dist/cli/handlers/audio.d.ts +25 -0
- package/dist/cli/handlers/audio.d.ts.map +1 -0
- package/dist/cli/handlers/audio.js +321 -0
- package/dist/cli/handlers/audio.js.map +1 -0
- package/dist/cli/handlers/batch.d.ts +4 -0
- package/dist/cli/handlers/batch.d.ts.map +1 -0
- package/dist/cli/handlers/batch.js +424 -0
- package/dist/cli/handlers/batch.js.map +1 -0
- package/dist/cli/handlers/candidates.d.ts +10 -0
- package/dist/cli/handlers/candidates.d.ts.map +1 -0
- package/dist/cli/handlers/candidates.js +389 -0
- package/dist/cli/handlers/candidates.js.map +1 -0
- package/dist/cli/handlers/character.d.ts +8 -0
- package/dist/cli/handlers/character.d.ts.map +1 -0
- package/dist/cli/handlers/character.js +202 -0
- package/dist/cli/handlers/character.js.map +1 -0
- package/dist/cli/handlers/clone.d.ts +5 -0
- package/dist/cli/handlers/clone.d.ts.map +1 -0
- package/dist/cli/handlers/clone.js +303 -0
- package/dist/cli/handlers/clone.js.map +1 -0
- package/dist/cli/handlers/create.d.ts +5 -0
- package/dist/cli/handlers/create.d.ts.map +1 -0
- package/dist/cli/handlers/create.js +635 -0
- package/dist/cli/handlers/create.js.map +1 -0
- package/dist/cli/handlers/execution.d.ts +5 -0
- package/dist/cli/handlers/execution.d.ts.map +1 -0
- package/dist/cli/handlers/execution.js +132 -0
- package/dist/cli/handlers/execution.js.map +1 -0
- package/dist/cli/handlers/library.d.ts +8 -0
- package/dist/cli/handlers/library.d.ts.map +1 -0
- package/dist/cli/handlers/library.js +77 -0
- package/dist/cli/handlers/library.js.map +1 -0
- package/dist/cli/handlers/media-ops.d.ts +7 -0
- package/dist/cli/handlers/media-ops.d.ts.map +1 -0
- package/dist/cli/handlers/media-ops.js +120 -0
- package/dist/cli/handlers/media-ops.js.map +1 -0
- package/dist/cli/handlers/media-production.d.ts +56 -0
- package/dist/cli/handlers/media-production.d.ts.map +1 -0
- package/dist/cli/handlers/media-production.js +673 -0
- package/dist/cli/handlers/media-production.js.map +1 -0
- package/dist/cli/handlers/motion-overlay.d.ts +10 -0
- package/dist/cli/handlers/motion-overlay.d.ts.map +1 -0
- package/dist/cli/handlers/motion-overlay.js +651 -0
- package/dist/cli/handlers/motion-overlay.js.map +1 -0
- package/dist/cli/handlers/multi-shot.d.ts +2 -0
- package/dist/cli/handlers/multi-shot.d.ts.map +1 -0
- package/dist/cli/handlers/multi-shot.js +424 -0
- package/dist/cli/handlers/multi-shot.js.map +1 -0
- package/dist/cli/handlers/project-ops.d.ts +14 -0
- package/dist/cli/handlers/project-ops.d.ts.map +1 -0
- package/dist/cli/handlers/project-ops.js +241 -0
- package/dist/cli/handlers/project-ops.js.map +1 -0
- package/dist/cli/handlers/prompt-craft.d.ts +8 -0
- package/dist/cli/handlers/prompt-craft.d.ts.map +1 -0
- package/dist/cli/handlers/prompt-craft.js +397 -0
- package/dist/cli/handlers/prompt-craft.js.map +1 -0
- package/dist/cli/handlers/provider-registration.d.ts +29 -0
- package/dist/cli/handlers/provider-registration.d.ts.map +1 -0
- package/dist/cli/handlers/provider-registration.js +225 -0
- package/dist/cli/handlers/provider-registration.js.map +1 -0
- package/dist/cli/handlers/reference-sheets.d.ts +6 -0
- package/dist/cli/handlers/reference-sheets.d.ts.map +1 -0
- package/dist/cli/handlers/reference-sheets.js +181 -0
- package/dist/cli/handlers/reference-sheets.js.map +1 -0
- package/dist/cli/handlers/reporting.d.ts +18 -0
- package/dist/cli/handlers/reporting.d.ts.map +1 -0
- package/dist/cli/handlers/reporting.js +155 -0
- package/dist/cli/handlers/reporting.js.map +1 -0
- package/dist/cli/handlers/review-portal.d.ts +8 -0
- package/dist/cli/handlers/review-portal.d.ts.map +1 -0
- package/dist/cli/handlers/review-portal.js +276 -0
- package/dist/cli/handlers/review-portal.js.map +1 -0
- package/dist/cli/handlers/show.d.ts +11 -0
- package/dist/cli/handlers/show.d.ts.map +1 -0
- package/dist/cli/handlers/show.js +136 -0
- package/dist/cli/handlers/show.js.map +1 -0
- package/dist/cli/handlers/stages.d.ts +6 -0
- package/dist/cli/handlers/stages.d.ts.map +1 -0
- package/dist/cli/handlers/stages.js +333 -0
- package/dist/cli/handlers/stages.js.map +1 -0
- package/dist/cli/handlers/studio.d.ts +2 -0
- package/dist/cli/handlers/studio.d.ts.map +1 -0
- package/dist/cli/handlers/studio.js +159 -0
- package/dist/cli/handlers/studio.js.map +1 -0
- package/dist/cli/handlers/templates.d.ts +7 -0
- package/dist/cli/handlers/templates.d.ts.map +1 -0
- package/dist/cli/handlers/templates.js +61 -0
- package/dist/cli/handlers/templates.js.map +1 -0
- package/dist/cli/provider-adapter.d.ts +3 -0
- package/dist/cli/provider-adapter.d.ts.map +1 -0
- package/dist/cli/provider-adapter.js +96 -0
- package/dist/cli/provider-adapter.js.map +1 -0
- package/dist/cli/vclaw.d.ts +15 -0
- package/dist/cli/vclaw.d.ts.map +1 -0
- package/dist/cli/vclaw.js +318 -0
- package/dist/cli/vclaw.js.map +1 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +27 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +168 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +55 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +80 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/video/analyze-output.d.ts +5 -0
- package/dist/video/analyze-output.d.ts.map +1 -0
- package/dist/video/analyze-output.js +7 -0
- package/dist/video/analyze-output.js.map +1 -0
- package/dist/video/archive-project.d.ts +14 -0
- package/dist/video/archive-project.d.ts.map +1 -0
- package/dist/video/archive-project.js +41 -0
- package/dist/video/archive-project.js.map +1 -0
- package/dist/video/artifact-history.d.ts +8 -0
- package/dist/video/artifact-history.d.ts.map +1 -0
- package/dist/video/artifact-history.js +12 -0
- package/dist/video/artifact-history.js.map +1 -0
- package/dist/video/artifact-store.d.ts +8 -0
- package/dist/video/artifact-store.d.ts.map +1 -0
- package/dist/video/artifact-store.js +37 -0
- package/dist/video/artifact-store.js.map +1 -0
- package/dist/video/artifact-validation.d.ts +15 -0
- package/dist/video/artifact-validation.d.ts.map +1 -0
- package/dist/video/artifact-validation.js +287 -0
- package/dist/video/artifact-validation.js.map +1 -0
- package/dist/video/artifacts.d.ts +112 -0
- package/dist/video/artifacts.d.ts.map +1 -0
- package/dist/video/artifacts.js +31 -0
- package/dist/video/artifacts.js.map +1 -0
- package/dist/video/assemble/animate-slides.d.ts +108 -0
- package/dist/video/assemble/animate-slides.d.ts.map +1 -0
- package/dist/video/assemble/animate-slides.js +152 -0
- package/dist/video/assemble/animate-slides.js.map +1 -0
- package/dist/video/assemble/animation-styles.d.ts +21 -0
- package/dist/video/assemble/animation-styles.d.ts.map +1 -0
- package/dist/video/assemble/animation-styles.js +32 -0
- package/dist/video/assemble/animation-styles.js.map +1 -0
- package/dist/video/assemble/animation-styles.json +97 -0
- package/dist/video/assemble/assemble.d.ts +24 -0
- package/dist/video/assemble/assemble.d.ts.map +1 -0
- package/dist/video/assemble/assemble.js +457 -0
- package/dist/video/assemble/assemble.js.map +1 -0
- package/dist/video/assemble/audio-concat.d.ts +61 -0
- package/dist/video/assemble/audio-concat.d.ts.map +1 -0
- package/dist/video/assemble/audio-concat.js +108 -0
- package/dist/video/assemble/audio-concat.js.map +1 -0
- package/dist/video/assemble/audio-mix-plan.d.ts +84 -0
- package/dist/video/assemble/audio-mix-plan.d.ts.map +1 -0
- package/dist/video/assemble/audio-mix-plan.js +74 -0
- package/dist/video/assemble/audio-mix-plan.js.map +1 -0
- package/dist/video/assemble/audio-utils.d.ts +32 -0
- package/dist/video/assemble/audio-utils.d.ts.map +1 -0
- package/dist/video/assemble/audio-utils.js +91 -0
- package/dist/video/assemble/audio-utils.js.map +1 -0
- package/dist/video/assemble/cut-segment.d.ts +40 -0
- package/dist/video/assemble/cut-segment.d.ts.map +1 -0
- package/dist/video/assemble/cut-segment.js +77 -0
- package/dist/video/assemble/cut-segment.js.map +1 -0
- package/dist/video/assemble/ffmpeg.d.ts +91 -0
- package/dist/video/assemble/ffmpeg.d.ts.map +1 -0
- package/dist/video/assemble/ffmpeg.js +251 -0
- package/dist/video/assemble/ffmpeg.js.map +1 -0
- package/dist/video/assemble/gemini-vision-classify.d.ts +37 -0
- package/dist/video/assemble/gemini-vision-classify.d.ts.map +1 -0
- package/dist/video/assemble/gemini-vision-classify.js +123 -0
- package/dist/video/assemble/gemini-vision-classify.js.map +1 -0
- package/dist/video/assemble/index.d.ts +47 -0
- package/dist/video/assemble/index.d.ts.map +1 -0
- package/dist/video/assemble/index.js +40 -0
- package/dist/video/assemble/index.js.map +1 -0
- package/dist/video/assemble/media-qc.d.ts +44 -0
- package/dist/video/assemble/media-qc.d.ts.map +1 -0
- package/dist/video/assemble/media-qc.js +181 -0
- package/dist/video/assemble/media-qc.js.map +1 -0
- package/dist/video/assemble/music.d.ts +51 -0
- package/dist/video/assemble/music.d.ts.map +1 -0
- package/dist/video/assemble/music.js +171 -0
- package/dist/video/assemble/music.js.map +1 -0
- package/dist/video/assemble/narration-fit.d.ts +32 -0
- package/dist/video/assemble/narration-fit.d.ts.map +1 -0
- package/dist/video/assemble/narration-fit.js +59 -0
- package/dist/video/assemble/narration-fit.js.map +1 -0
- package/dist/video/assemble/overlay.d.ts +81 -0
- package/dist/video/assemble/overlay.d.ts.map +1 -0
- package/dist/video/assemble/overlay.js +141 -0
- package/dist/video/assemble/overlay.js.map +1 -0
- package/dist/video/assemble/pdf.d.ts +46 -0
- package/dist/video/assemble/pdf.d.ts.map +1 -0
- package/dist/video/assemble/pdf.js +111 -0
- package/dist/video/assemble/pdf.js.map +1 -0
- package/dist/video/assemble/qa-dialogue-lint.d.ts +56 -0
- package/dist/video/assemble/qa-dialogue-lint.d.ts.map +1 -0
- package/dist/video/assemble/qa-dialogue-lint.js +85 -0
- package/dist/video/assemble/qa-dialogue-lint.js.map +1 -0
- package/dist/video/assemble/qa-image-filter.d.ts +52 -0
- package/dist/video/assemble/qa-image-filter.d.ts.map +1 -0
- package/dist/video/assemble/qa-image-filter.js +102 -0
- package/dist/video/assemble/qa-image-filter.js.map +1 -0
- package/dist/video/assemble/qa-image-vision.d.ts +57 -0
- package/dist/video/assemble/qa-image-vision.d.ts.map +1 -0
- package/dist/video/assemble/qa-image-vision.js +96 -0
- package/dist/video/assemble/qa-image-vision.js.map +1 -0
- package/dist/video/assemble/qa-narration-vision.d.ts +53 -0
- package/dist/video/assemble/qa-narration-vision.d.ts.map +1 -0
- package/dist/video/assemble/qa-narration-vision.js +87 -0
- package/dist/video/assemble/qa-narration-vision.js.map +1 -0
- package/dist/video/assemble/qa-narration.d.ts +53 -0
- package/dist/video/assemble/qa-narration.d.ts.map +1 -0
- package/dist/video/assemble/qa-narration.js +85 -0
- package/dist/video/assemble/qa-narration.js.map +1 -0
- package/dist/video/assemble/stitch-ad.d.ts +88 -0
- package/dist/video/assemble/stitch-ad.d.ts.map +1 -0
- package/dist/video/assemble/stitch-ad.js +161 -0
- package/dist/video/assemble/stitch-ad.js.map +1 -0
- package/dist/video/assemble/stitch.d.ts +341 -0
- package/dist/video/assemble/stitch.d.ts.map +1 -0
- package/dist/video/assemble/stitch.js +607 -0
- package/dist/video/assemble/stitch.js.map +1 -0
- package/dist/video/assemble/text-card.d.ts +78 -0
- package/dist/video/assemble/text-card.d.ts.map +1 -0
- package/dist/video/assemble/text-card.js +210 -0
- package/dist/video/assemble/text-card.js.map +1 -0
- package/dist/video/assemble/title-card.d.ts +39 -0
- package/dist/video/assemble/title-card.d.ts.map +1 -0
- package/dist/video/assemble/title-card.js +127 -0
- package/dist/video/assemble/title-card.js.map +1 -0
- package/dist/video/assemble/transcript.d.ts +66 -0
- package/dist/video/assemble/transcript.d.ts.map +1 -0
- package/dist/video/assemble/transcript.js +200 -0
- package/dist/video/assemble/transcript.js.map +1 -0
- package/dist/video/assemble/tts-elevenlabs.d.ts +52 -0
- package/dist/video/assemble/tts-elevenlabs.d.ts.map +1 -0
- package/dist/video/assemble/tts-elevenlabs.js +118 -0
- package/dist/video/assemble/tts-elevenlabs.js.map +1 -0
- package/dist/video/assemble/tts.d.ts +79 -0
- package/dist/video/assemble/tts.d.ts.map +1 -0
- package/dist/video/assemble/tts.js +131 -0
- package/dist/video/assemble/tts.js.map +1 -0
- package/dist/video/assemble/types.d.ts +43 -0
- package/dist/video/assemble/types.d.ts.map +1 -0
- package/dist/video/assemble/types.js +2 -0
- package/dist/video/assemble/types.js.map +1 -0
- package/dist/video/assemble/upscale.d.ts +43 -0
- package/dist/video/assemble/upscale.d.ts.map +1 -0
- package/dist/video/assemble/upscale.js +52 -0
- package/dist/video/assemble/upscale.js.map +1 -0
- package/dist/video/asset-spec.d.ts +15 -0
- package/dist/video/asset-spec.d.ts.map +1 -0
- package/dist/video/asset-spec.js +43 -0
- package/dist/video/asset-spec.js.map +1 -0
- package/dist/video/asset-tag-lookup.d.ts +31 -0
- package/dist/video/asset-tag-lookup.d.ts.map +1 -0
- package/dist/video/asset-tag-lookup.js +45 -0
- package/dist/video/asset-tag-lookup.js.map +1 -0
- package/dist/video/atomic-write.d.ts +2 -0
- package/dist/video/atomic-write.d.ts.map +1 -0
- package/dist/video/atomic-write.js +7 -0
- package/dist/video/atomic-write.js.map +1 -0
- package/dist/video/audio-platform/native-elevenlabs-sfx.d.ts +29 -0
- package/dist/video/audio-platform/native-elevenlabs-sfx.d.ts.map +1 -0
- package/dist/video/audio-platform/native-elevenlabs-sfx.js +95 -0
- package/dist/video/audio-platform/native-elevenlabs-sfx.js.map +1 -0
- package/dist/video/audio-platform/native-elevenlabs-tts.d.ts +30 -0
- package/dist/video/audio-platform/native-elevenlabs-tts.d.ts.map +1 -0
- package/dist/video/audio-platform/native-elevenlabs-tts.js +95 -0
- package/dist/video/audio-platform/native-elevenlabs-tts.js.map +1 -0
- package/dist/video/audio-platform/native-flowmusic.d.ts +21 -0
- package/dist/video/audio-platform/native-flowmusic.d.ts.map +1 -0
- package/dist/video/audio-platform/native-flowmusic.js +137 -0
- package/dist/video/audio-platform/native-flowmusic.js.map +1 -0
- package/dist/video/audio-platform/native-gemini-tts.d.ts +26 -0
- package/dist/video/audio-platform/native-gemini-tts.d.ts.map +1 -0
- package/dist/video/audio-platform/native-gemini-tts.js +202 -0
- package/dist/video/audio-platform/native-gemini-tts.js.map +1 -0
- package/dist/video/audio-platform/native-lyria.d.ts +35 -0
- package/dist/video/audio-platform/native-lyria.d.ts.map +1 -0
- package/dist/video/audio-platform/native-lyria.js +189 -0
- package/dist/video/audio-platform/native-lyria.js.map +1 -0
- package/dist/video/audio-platform/native-lyria3.d.ts +36 -0
- package/dist/video/audio-platform/native-lyria3.d.ts.map +1 -0
- package/dist/video/audio-platform/native-lyria3.js +210 -0
- package/dist/video/audio-platform/native-lyria3.js.map +1 -0
- package/dist/video/audio-platform/registry.d.ts +82 -0
- package/dist/video/audio-platform/registry.d.ts.map +1 -0
- package/dist/video/audio-platform/registry.js +189 -0
- package/dist/video/audio-platform/registry.js.map +1 -0
- package/dist/video/audio-platform/suno-backend.d.ts +11 -0
- package/dist/video/audio-platform/suno-backend.d.ts.map +1 -0
- package/dist/video/audio-platform/suno-backend.js +33 -0
- package/dist/video/audio-platform/suno-backend.js.map +1 -0
- package/dist/video/audio-platform/types.d.ts +128 -0
- package/dist/video/audio-platform/types.d.ts.map +1 -0
- package/dist/video/audio-platform/types.js +9 -0
- package/dist/video/audio-platform/types.js.map +1 -0
- package/dist/video/batch-queue.d.ts +276 -0
- package/dist/video/batch-queue.d.ts.map +1 -0
- package/dist/video/batch-queue.js +519 -0
- package/dist/video/batch-queue.js.map +1 -0
- package/dist/video/blueprint-prompt.d.ts +37 -0
- package/dist/video/blueprint-prompt.d.ts.map +1 -0
- package/dist/video/blueprint-prompt.js +107 -0
- package/dist/video/blueprint-prompt.js.map +1 -0
- package/dist/video/brand-definition.d.ts +97 -0
- package/dist/video/brand-definition.d.ts.map +1 -0
- package/dist/video/brand-definition.js +206 -0
- package/dist/video/brand-definition.js.map +1 -0
- package/dist/video/brand-dna.d.ts +101 -0
- package/dist/video/brand-dna.d.ts.map +1 -0
- package/dist/video/brand-dna.js +370 -0
- package/dist/video/brand-dna.js.map +1 -0
- package/dist/video/brand-prompt.d.ts +13 -0
- package/dist/video/brand-prompt.d.ts.map +1 -0
- package/dist/video/brand-prompt.js +26 -0
- package/dist/video/brand-prompt.js.map +1 -0
- package/dist/video/candidate-migrate.d.ts +27 -0
- package/dist/video/candidate-migrate.d.ts.map +1 -0
- package/dist/video/candidate-migrate.js +119 -0
- package/dist/video/candidate-migrate.js.map +1 -0
- package/dist/video/category-registry.d.ts +38 -0
- package/dist/video/category-registry.d.ts.map +1 -0
- package/dist/video/category-registry.js +188 -0
- package/dist/video/category-registry.js.map +1 -0
- package/dist/video/chain-fallback.d.ts +39 -0
- package/dist/video/chain-fallback.d.ts.map +1 -0
- package/dist/video/chain-fallback.js +43 -0
- package/dist/video/chain-fallback.js.map +1 -0
- package/dist/video/character-auto-create.d.ts +72 -0
- package/dist/video/character-auto-create.d.ts.map +1 -0
- package/dist/video/character-auto-create.js +392 -0
- package/dist/video/character-auto-create.js.map +1 -0
- package/dist/video/character-consistency.d.ts +3 -0
- package/dist/video/character-consistency.d.ts.map +1 -0
- package/dist/video/character-consistency.js +69 -0
- package/dist/video/character-consistency.js.map +1 -0
- package/dist/video/characters.d.ts +44 -0
- package/dist/video/characters.d.ts.map +1 -0
- package/dist/video/characters.js +96 -0
- package/dist/video/characters.js.map +1 -0
- package/dist/video/checkpoints.d.ts +16 -0
- package/dist/video/checkpoints.d.ts.map +1 -0
- package/dist/video/checkpoints.js +29 -0
- package/dist/video/checkpoints.js.map +1 -0
- package/dist/video/cinema-profile.d.ts +57 -0
- package/dist/video/cinema-profile.d.ts.map +1 -0
- package/dist/video/cinema-profile.js +89 -0
- package/dist/video/cinema-profile.js.map +1 -0
- package/dist/video/cinematography.d.ts +299 -0
- package/dist/video/cinematography.d.ts.map +1 -0
- package/dist/video/cinematography.js +914 -0
- package/dist/video/cinematography.js.map +1 -0
- package/dist/video/cli-output.d.ts +52 -0
- package/dist/video/cli-output.d.ts.map +1 -0
- package/dist/video/cli-output.js +81 -0
- package/dist/video/cli-output.js.map +1 -0
- package/dist/video/cli-schema.d.ts +40 -0
- package/dist/video/cli-schema.d.ts.map +1 -0
- package/dist/video/cli-schema.js +447 -0
- package/dist/video/cli-schema.js.map +1 -0
- package/dist/video/cost-estimate.d.ts +37 -0
- package/dist/video/cost-estimate.d.ts.map +1 -0
- package/dist/video/cost-estimate.js +103 -0
- package/dist/video/cost-estimate.js.map +1 -0
- package/dist/video/csv-export.d.ts +8 -0
- package/dist/video/csv-export.d.ts.map +1 -0
- package/dist/video/csv-export.js +133 -0
- package/dist/video/csv-export.js.map +1 -0
- package/dist/video/dependencies.d.ts +23 -0
- package/dist/video/dependencies.d.ts.map +1 -0
- package/dist/video/dependencies.js +36 -0
- package/dist/video/dependencies.js.map +1 -0
- package/dist/video/dialogue-fit.d.ts +23 -0
- package/dist/video/dialogue-fit.d.ts.map +1 -0
- package/dist/video/dialogue-fit.js +38 -0
- package/dist/video/dialogue-fit.js.map +1 -0
- package/dist/video/dialogue.d.ts +77 -0
- package/dist/video/dialogue.d.ts.map +1 -0
- package/dist/video/dialogue.js +143 -0
- package/dist/video/dialogue.js.map +1 -0
- package/dist/video/director-defaults.d.ts +37 -0
- package/dist/video/director-defaults.d.ts.map +1 -0
- package/dist/video/director-defaults.js +383 -0
- package/dist/video/director-defaults.js.map +1 -0
- package/dist/video/director-preflight.d.ts +53 -0
- package/dist/video/director-preflight.d.ts.map +1 -0
- package/dist/video/director-preflight.js +462 -0
- package/dist/video/director-preflight.js.map +1 -0
- package/dist/video/doctor-portfolio.d.ts +43 -0
- package/dist/video/doctor-portfolio.d.ts.map +1 -0
- package/dist/video/doctor-portfolio.js +137 -0
- package/dist/video/doctor-portfolio.js.map +1 -0
- package/dist/video/doctor.d.ts +13 -0
- package/dist/video/doctor.d.ts.map +1 -0
- package/dist/video/doctor.js +449 -0
- package/dist/video/doctor.js.map +1 -0
- package/dist/video/emotion-cues.d.ts +15 -0
- package/dist/video/emotion-cues.d.ts.map +1 -0
- package/dist/video/emotion-cues.js +37 -0
- package/dist/video/emotion-cues.js.map +1 -0
- package/dist/video/environment-assets.d.ts +28 -0
- package/dist/video/environment-assets.d.ts.map +1 -0
- package/dist/video/environment-assets.js +43 -0
- package/dist/video/environment-assets.js.map +1 -0
- package/dist/video/environment-auto-create.d.ts +20 -0
- package/dist/video/environment-auto-create.d.ts.map +1 -0
- package/dist/video/environment-auto-create.js +88 -0
- package/dist/video/environment-auto-create.js.map +1 -0
- package/dist/video/errors.d.ts +35 -0
- package/dist/video/errors.d.ts.map +1 -0
- package/dist/video/errors.js +117 -0
- package/dist/video/errors.js.map +1 -0
- package/dist/video/events.d.ts +11 -0
- package/dist/video/events.d.ts.map +1 -0
- package/dist/video/events.js +32 -0
- package/dist/video/events.js.map +1 -0
- package/dist/video/execute-autochain.d.ts +65 -0
- package/dist/video/execute-autochain.d.ts.map +1 -0
- package/dist/video/execute-autochain.js +165 -0
- package/dist/video/execute-autochain.js.map +1 -0
- package/dist/video/execute.d.ts +27 -0
- package/dist/video/execute.d.ts.map +1 -0
- package/dist/video/execute.js +477 -0
- package/dist/video/execute.js.map +1 -0
- package/dist/video/execution-cancel.d.ts +11 -0
- package/dist/video/execution-cancel.d.ts.map +1 -0
- package/dist/video/execution-cancel.js +85 -0
- package/dist/video/execution-cancel.js.map +1 -0
- package/dist/video/execution-plan.d.ts +5 -0
- package/dist/video/execution-plan.d.ts.map +1 -0
- package/dist/video/execution-plan.js +145 -0
- package/dist/video/execution-plan.js.map +1 -0
- package/dist/video/execution-profile.d.ts +25 -0
- package/dist/video/execution-profile.d.ts.map +1 -0
- package/dist/video/execution-profile.js +124 -0
- package/dist/video/execution-profile.js.map +1 -0
- package/dist/video/execution-runtime.d.ts +104 -0
- package/dist/video/execution-runtime.d.ts.map +1 -0
- package/dist/video/execution-runtime.js +750 -0
- package/dist/video/execution-runtime.js.map +1 -0
- package/dist/video/execution-seed.d.ts +14 -0
- package/dist/video/execution-seed.d.ts.map +1 -0
- package/dist/video/execution-seed.js +29 -0
- package/dist/video/execution-seed.js.map +1 -0
- package/dist/video/execution-status.d.ts +12 -0
- package/dist/video/execution-status.d.ts.map +1 -0
- package/dist/video/execution-status.js +666 -0
- package/dist/video/execution-status.js.map +1 -0
- package/dist/video/filmmaking-prompts.d.ts +356 -0
- package/dist/video/filmmaking-prompts.d.ts.map +1 -0
- package/dist/video/filmmaking-prompts.js +1212 -0
- package/dist/video/filmmaking-prompts.js.map +1 -0
- package/dist/video/final-media.d.ts +17 -0
- package/dist/video/final-media.d.ts.map +1 -0
- package/dist/video/final-media.js +78 -0
- package/dist/video/final-media.js.map +1 -0
- package/dist/video/finish.d.ts +99 -0
- package/dist/video/finish.d.ts.map +1 -0
- package/dist/video/finish.js +159 -0
- package/dist/video/finish.js.map +1 -0
- package/dist/video/flow-character-library.d.ts +143 -0
- package/dist/video/flow-character-library.d.ts.map +1 -0
- package/dist/video/flow-character-library.js +211 -0
- package/dist/video/flow-character-library.js.map +1 -0
- package/dist/video/flow-markers.d.ts +107 -0
- package/dist/video/flow-markers.d.ts.map +1 -0
- package/dist/video/flow-markers.js +278 -0
- package/dist/video/flow-markers.js.map +1 -0
- package/dist/video/gemini-analyze.d.ts +44 -0
- package/dist/video/gemini-analyze.d.ts.map +1 -0
- package/dist/video/gemini-analyze.js +323 -0
- package/dist/video/gemini-analyze.js.map +1 -0
- package/dist/video/gemini-continuity.d.ts +40 -0
- package/dist/video/gemini-continuity.d.ts.map +1 -0
- package/dist/video/gemini-continuity.js +145 -0
- package/dist/video/gemini-continuity.js.map +1 -0
- package/dist/video/gemini-judge.d.ts +33 -0
- package/dist/video/gemini-judge.d.ts.map +1 -0
- package/dist/video/gemini-judge.js +218 -0
- package/dist/video/gemini-judge.js.map +1 -0
- package/dist/video/gemini-key-pool.d.ts +23 -0
- package/dist/video/gemini-key-pool.d.ts.map +1 -0
- package/dist/video/gemini-key-pool.js +107 -0
- package/dist/video/gemini-key-pool.js.map +1 -0
- package/dist/video/gen-image-flow.d.ts +187 -0
- package/dist/video/gen-image-flow.d.ts.map +1 -0
- package/dist/video/gen-image-flow.js +331 -0
- package/dist/video/gen-image-flow.js.map +1 -0
- package/dist/video/gen-image.d.ts +153 -0
- package/dist/video/gen-image.d.ts.map +1 -0
- package/dist/video/gen-image.js +263 -0
- package/dist/video/gen-image.js.map +1 -0
- package/dist/video/generation-telemetry.d.ts +60 -0
- package/dist/video/generation-telemetry.d.ts.map +1 -0
- package/dist/video/generation-telemetry.js +214 -0
- package/dist/video/generation-telemetry.js.map +1 -0
- package/dist/video/http-error-safety.d.ts +9 -0
- package/dist/video/http-error-safety.d.ts.map +1 -0
- package/dist/video/http-error-safety.js +14 -0
- package/dist/video/http-error-safety.js.map +1 -0
- package/dist/video/image-dimensions.d.ts +7 -0
- package/dist/video/image-dimensions.d.ts.map +1 -0
- package/dist/video/image-dimensions.js +54 -0
- package/dist/video/image-dimensions.js.map +1 -0
- package/dist/video/legacy-import.d.ts +11 -0
- package/dist/video/legacy-import.d.ts.map +1 -0
- package/dist/video/legacy-import.js +181 -0
- package/dist/video/legacy-import.js.map +1 -0
- package/dist/video/library-clean.d.ts +34 -0
- package/dist/video/library-clean.d.ts.map +1 -0
- package/dist/video/library-clean.js +340 -0
- package/dist/video/library-clean.js.map +1 -0
- package/dist/video/lipsync.d.ts +72 -0
- package/dist/video/lipsync.d.ts.map +1 -0
- package/dist/video/lipsync.js +136 -0
- package/dist/video/lipsync.js.map +1 -0
- package/dist/video/media-host.d.ts +41 -0
- package/dist/video/media-host.d.ts.map +1 -0
- package/dist/video/media-host.js +106 -0
- package/dist/video/media-host.js.map +1 -0
- package/dist/video/metrics.d.ts +30 -0
- package/dist/video/metrics.d.ts.map +1 -0
- package/dist/video/metrics.js +81 -0
- package/dist/video/metrics.js.map +1 -0
- package/dist/video/motion-overlay/analyze-reel.d.ts +38 -0
- package/dist/video/motion-overlay/analyze-reel.d.ts.map +1 -0
- package/dist/video/motion-overlay/analyze-reel.js +185 -0
- package/dist/video/motion-overlay/analyze-reel.js.map +1 -0
- package/dist/video/motion-overlay/animate-render.d.ts +63 -0
- package/dist/video/motion-overlay/animate-render.d.ts.map +1 -0
- package/dist/video/motion-overlay/animate-render.js +128 -0
- package/dist/video/motion-overlay/animate-render.js.map +1 -0
- package/dist/video/motion-overlay/animate.d.ts +37 -0
- package/dist/video/motion-overlay/animate.d.ts.map +1 -0
- package/dist/video/motion-overlay/animate.js +97 -0
- package/dist/video/motion-overlay/animate.js.map +1 -0
- package/dist/video/motion-overlay/avatar-host-transport.d.ts +161 -0
- package/dist/video/motion-overlay/avatar-host-transport.d.ts.map +1 -0
- package/dist/video/motion-overlay/avatar-host-transport.js +325 -0
- package/dist/video/motion-overlay/avatar-host-transport.js.map +1 -0
- package/dist/video/motion-overlay/avatar-host.d.ts +131 -0
- package/dist/video/motion-overlay/avatar-host.d.ts.map +1 -0
- package/dist/video/motion-overlay/avatar-host.js +131 -0
- package/dist/video/motion-overlay/avatar-host.js.map +1 -0
- package/dist/video/motion-overlay/compose-prompt.d.ts +48 -0
- package/dist/video/motion-overlay/compose-prompt.d.ts.map +1 -0
- package/dist/video/motion-overlay/compose-prompt.js +217 -0
- package/dist/video/motion-overlay/compose-prompt.js.map +1 -0
- package/dist/video/motion-overlay/execute.d.ts +190 -0
- package/dist/video/motion-overlay/execute.d.ts.map +1 -0
- package/dist/video/motion-overlay/execute.js +205 -0
- package/dist/video/motion-overlay/execute.js.map +1 -0
- package/dist/video/motion-overlay/flow-pack.d.ts +65 -0
- package/dist/video/motion-overlay/flow-pack.d.ts.map +1 -0
- package/dist/video/motion-overlay/flow-pack.js +178 -0
- package/dist/video/motion-overlay/flow-pack.js.map +1 -0
- package/dist/video/motion-overlay/ingest.d.ts +74 -0
- package/dist/video/motion-overlay/ingest.d.ts.map +1 -0
- package/dist/video/motion-overlay/ingest.js +172 -0
- package/dist/video/motion-overlay/ingest.js.map +1 -0
- package/dist/video/motion-overlay/motifs.d.ts +63 -0
- package/dist/video/motion-overlay/motifs.d.ts.map +1 -0
- package/dist/video/motion-overlay/motifs.js +192 -0
- package/dist/video/motion-overlay/motifs.js.map +1 -0
- package/dist/video/motion-overlay/motion-style.d.ts +56 -0
- package/dist/video/motion-overlay/motion-style.d.ts.map +1 -0
- package/dist/video/motion-overlay/motion-style.js +130 -0
- package/dist/video/motion-overlay/motion-style.js.map +1 -0
- package/dist/video/motion-overlay/plan.d.ts +49 -0
- package/dist/video/motion-overlay/plan.d.ts.map +1 -0
- package/dist/video/motion-overlay/plan.js +79 -0
- package/dist/video/motion-overlay/plan.js.map +1 -0
- package/dist/video/motion-overlay/preview.d.ts +36 -0
- package/dist/video/motion-overlay/preview.d.ts.map +1 -0
- package/dist/video/motion-overlay/preview.js +169 -0
- package/dist/video/motion-overlay/preview.js.map +1 -0
- package/dist/video/motion-overlay/render-local.d.ts +118 -0
- package/dist/video/motion-overlay/render-local.d.ts.map +1 -0
- package/dist/video/motion-overlay/render-local.js +298 -0
- package/dist/video/motion-overlay/render-local.js.map +1 -0
- package/dist/video/motion-overlay/run.d.ts +58 -0
- package/dist/video/motion-overlay/run.d.ts.map +1 -0
- package/dist/video/motion-overlay/run.js +147 -0
- package/dist/video/motion-overlay/run.js.map +1 -0
- package/dist/video/motion-overlay/slice.d.ts +29 -0
- package/dist/video/motion-overlay/slice.d.ts.map +1 -0
- package/dist/video/motion-overlay/slice.js +88 -0
- package/dist/video/motion-overlay/slice.js.map +1 -0
- package/dist/video/motion-overlay/transcribe.d.ts +49 -0
- package/dist/video/motion-overlay/transcribe.d.ts.map +1 -0
- package/dist/video/motion-overlay/transcribe.js +180 -0
- package/dist/video/motion-overlay/transcribe.js.map +1 -0
- package/dist/video/motion-overlay/types.d.ts +82 -0
- package/dist/video/motion-overlay/types.d.ts.map +1 -0
- package/dist/video/motion-overlay/types.js +12 -0
- package/dist/video/motion-overlay/types.js.map +1 -0
- package/dist/video/motion-overlay/v2v-transport.d.ts +37 -0
- package/dist/video/motion-overlay/v2v-transport.d.ts.map +1 -0
- package/dist/video/motion-overlay/v2v-transport.js +178 -0
- package/dist/video/motion-overlay/v2v-transport.js.map +1 -0
- package/dist/video/motion-overlay/write.d.ts +40 -0
- package/dist/video/motion-overlay/write.d.ts.map +1 -0
- package/dist/video/motion-overlay/write.js +113 -0
- package/dist/video/motion-overlay/write.js.map +1 -0
- package/dist/video/multi-shot-artifact.d.ts +19 -0
- package/dist/video/multi-shot-artifact.d.ts.map +1 -0
- package/dist/video/multi-shot-artifact.js +44 -0
- package/dist/video/multi-shot-artifact.js.map +1 -0
- package/dist/video/multi-shot-prompt.d.ts +99 -0
- package/dist/video/multi-shot-prompt.d.ts.map +1 -0
- package/dist/video/multi-shot-prompt.js +466 -0
- package/dist/video/multi-shot-prompt.js.map +1 -0
- package/dist/video/music-video.d.ts +110 -0
- package/dist/video/music-video.d.ts.map +1 -0
- package/dist/video/music-video.js +190 -0
- package/dist/video/music-video.js.map +1 -0
- package/dist/video/narrate.d.ts +58 -0
- package/dist/video/narrate.d.ts.map +1 -0
- package/dist/video/narrate.js +145 -0
- package/dist/video/narrate.js.map +1 -0
- package/dist/video/native-dreamina.d.ts +35 -0
- package/dist/video/native-dreamina.d.ts.map +1 -0
- package/dist/video/native-dreamina.js +532 -0
- package/dist/video/native-dreamina.js.map +1 -0
- package/dist/video/native-flow-r2v.d.ts +113 -0
- package/dist/video/native-flow-r2v.d.ts.map +1 -0
- package/dist/video/native-flow-r2v.js +223 -0
- package/dist/video/native-flow-r2v.js.map +1 -0
- package/dist/video/native-runway.d.ts +41 -0
- package/dist/video/native-runway.d.ts.map +1 -0
- package/dist/video/native-runway.js +523 -0
- package/dist/video/native-runway.js.map +1 -0
- package/dist/video/native-seedance.d.ts +74 -0
- package/dist/video/native-seedance.d.ts.map +1 -0
- package/dist/video/native-seedance.js +461 -0
- package/dist/video/native-seedance.js.map +1 -0
- package/dist/video/native-veo.d.ts +20 -0
- package/dist/video/native-veo.d.ts.map +1 -0
- package/dist/video/native-veo.js +390 -0
- package/dist/video/native-veo.js.map +1 -0
- package/dist/video/next-actions.d.ts +27 -0
- package/dist/video/next-actions.d.ts.map +1 -0
- package/dist/video/next-actions.js +201 -0
- package/dist/video/next-actions.js.map +1 -0
- package/dist/video/obsidian-export.d.ts +12 -0
- package/dist/video/obsidian-export.d.ts.map +1 -0
- package/dist/video/obsidian-export.js +285 -0
- package/dist/video/obsidian-export.js.map +1 -0
- package/dist/video/obsidian-sync.d.ts +13 -0
- package/dist/video/obsidian-sync.d.ts.map +1 -0
- package/dist/video/obsidian-sync.js +479 -0
- package/dist/video/obsidian-sync.js.map +1 -0
- package/dist/video/obsidian-vault.d.ts +8 -0
- package/dist/video/obsidian-vault.d.ts.map +1 -0
- package/dist/video/obsidian-vault.js +81 -0
- package/dist/video/obsidian-vault.js.map +1 -0
- package/dist/video/outfit-prompts.d.ts +10 -0
- package/dist/video/outfit-prompts.d.ts.map +1 -0
- package/dist/video/outfit-prompts.js +14 -0
- package/dist/video/outfit-prompts.js.map +1 -0
- package/dist/video/outpaint-keyframe.d.ts +95 -0
- package/dist/video/outpaint-keyframe.d.ts.map +1 -0
- package/dist/video/outpaint-keyframe.js +244 -0
- package/dist/video/outpaint-keyframe.js.map +1 -0
- package/dist/video/pipeline-manifest.d.ts +4 -0
- package/dist/video/pipeline-manifest.d.ts.map +1 -0
- package/dist/video/pipeline-manifest.js +13 -0
- package/dist/video/pipeline-manifest.js.map +1 -0
- package/dist/video/pipeline-manifests/director.json +46 -0
- package/dist/video/pipeline-manifests/storyboard.json +46 -0
- package/dist/video/platform-specs.d.ts +26 -0
- package/dist/video/platform-specs.d.ts.map +1 -0
- package/dist/video/platform-specs.js +34 -0
- package/dist/video/platform-specs.js.map +1 -0
- package/dist/video/playbooks.d.ts +11 -0
- package/dist/video/playbooks.d.ts.map +1 -0
- package/dist/video/playbooks.js +22 -0
- package/dist/video/playbooks.js.map +1 -0
- package/dist/video/post-production.d.ts +57 -0
- package/dist/video/post-production.d.ts.map +1 -0
- package/dist/video/post-production.js +210 -0
- package/dist/video/post-production.js.map +1 -0
- package/dist/video/preview-portal/audit.d.ts +4 -0
- package/dist/video/preview-portal/audit.d.ts.map +1 -0
- package/dist/video/preview-portal/audit.js +16 -0
- package/dist/video/preview-portal/audit.js.map +1 -0
- package/dist/video/preview-portal/discovery.d.ts +23 -0
- package/dist/video/preview-portal/discovery.d.ts.map +1 -0
- package/dist/video/preview-portal/discovery.js +733 -0
- package/dist/video/preview-portal/discovery.js.map +1 -0
- package/dist/video/preview-portal/generate.d.ts +42 -0
- package/dist/video/preview-portal/generate.d.ts.map +1 -0
- package/dist/video/preview-portal/generate.js +101 -0
- package/dist/video/preview-portal/generate.js.map +1 -0
- package/dist/video/preview-portal/index.d.ts +12 -0
- package/dist/video/preview-portal/index.d.ts.map +1 -0
- package/dist/video/preview-portal/index.js +8 -0
- package/dist/video/preview-portal/index.js.map +1 -0
- package/dist/video/preview-portal/publish.d.ts +48 -0
- package/dist/video/preview-portal/publish.d.ts.map +1 -0
- package/dist/video/preview-portal/publish.js +169 -0
- package/dist/video/preview-portal/publish.js.map +1 -0
- package/dist/video/preview-portal/render.d.ts +4 -0
- package/dist/video/preview-portal/render.d.ts.map +1 -0
- package/dist/video/preview-portal/render.js +822 -0
- package/dist/video/preview-portal/render.js.map +1 -0
- package/dist/video/preview-portal/shared-assets.d.ts +6 -0
- package/dist/video/preview-portal/shared-assets.d.ts.map +1 -0
- package/dist/video/preview-portal/shared-assets.js +452 -0
- package/dist/video/preview-portal/shared-assets.js.map +1 -0
- package/dist/video/preview-portal/templates.d.ts +4 -0
- package/dist/video/preview-portal/templates.d.ts.map +1 -0
- package/dist/video/preview-portal/templates.js +109 -0
- package/dist/video/preview-portal/templates.js.map +1 -0
- package/dist/video/preview-portal/types.d.ts +246 -0
- package/dist/video/preview-portal/types.d.ts.map +1 -0
- package/dist/video/preview-portal/types.js +28 -0
- package/dist/video/preview-portal/types.js.map +1 -0
- package/dist/video/product-references.d.ts +21 -0
- package/dist/video/product-references.d.ts.map +1 -0
- package/dist/video/product-references.js +25 -0
- package/dist/video/product-references.js.map +1 -0
- package/dist/video/project-blueprint.d.ts +178 -0
- package/dist/video/project-blueprint.d.ts.map +1 -0
- package/dist/video/project-blueprint.js +286 -0
- package/dist/video/project-blueprint.js.map +1 -0
- package/dist/video/project-index.d.ts +82 -0
- package/dist/video/project-index.d.ts.map +1 -0
- package/dist/video/project-index.js +89 -0
- package/dist/video/project-index.js.map +1 -0
- package/dist/video/projects.d.ts +3 -0
- package/dist/video/projects.d.ts.map +1 -0
- package/dist/video/projects.js +18 -0
- package/dist/video/projects.js.map +1 -0
- package/dist/video/prompt-guidance.d.ts +14 -0
- package/dist/video/prompt-guidance.d.ts.map +1 -0
- package/dist/video/prompt-guidance.js +44 -0
- package/dist/video/prompt-guidance.js.map +1 -0
- package/dist/video/prompt-library.d.ts +11 -0
- package/dist/video/prompt-library.d.ts.map +1 -0
- package/dist/video/prompt-library.js +85 -0
- package/dist/video/prompt-library.js.map +1 -0
- package/dist/video/prompt-lint.d.ts +146 -0
- package/dist/video/prompt-lint.d.ts.map +1 -0
- package/dist/video/prompt-lint.js +433 -0
- package/dist/video/prompt-lint.js.map +1 -0
- package/dist/video/prompt-quality.d.ts +29 -0
- package/dist/video/prompt-quality.d.ts.map +1 -0
- package/dist/video/prompt-quality.js +532 -0
- package/dist/video/prompt-quality.js.map +1 -0
- package/dist/video/prompt-rules.d.ts +94 -0
- package/dist/video/prompt-rules.d.ts.map +1 -0
- package/dist/video/prompt-rules.js +169 -0
- package/dist/video/prompt-rules.js.map +1 -0
- package/dist/video/provider-adapter-runner.d.ts +5 -0
- package/dist/video/provider-adapter-runner.d.ts.map +1 -0
- package/dist/video/provider-adapter-runner.js +92 -0
- package/dist/video/provider-adapter-runner.js.map +1 -0
- package/dist/video/provider-platform/index.d.ts +6 -0
- package/dist/video/provider-platform/index.d.ts.map +1 -0
- package/dist/video/provider-platform/index.js +6 -0
- package/dist/video/provider-platform/index.js.map +1 -0
- package/dist/video/provider-platform/registry.d.ts +5 -0
- package/dist/video/provider-platform/registry.d.ts.map +1 -0
- package/dist/video/provider-platform/registry.js +279 -0
- package/dist/video/provider-platform/registry.js.map +1 -0
- package/dist/video/provider-platform/route-capabilities.d.ts +133 -0
- package/dist/video/provider-platform/route-capabilities.d.ts.map +1 -0
- package/dist/video/provider-platform/route-capabilities.js +192 -0
- package/dist/video/provider-platform/route-capabilities.js.map +1 -0
- package/dist/video/provider-platform/router.d.ts +7 -0
- package/dist/video/provider-platform/router.d.ts.map +1 -0
- package/dist/video/provider-platform/router.js +150 -0
- package/dist/video/provider-platform/router.js.map +1 -0
- package/dist/video/provider-platform/security.d.ts +8 -0
- package/dist/video/provider-platform/security.d.ts.map +1 -0
- package/dist/video/provider-platform/security.js +21 -0
- package/dist/video/provider-platform/security.js.map +1 -0
- package/dist/video/provider-platform/telemetry.d.ts +24 -0
- package/dist/video/provider-platform/telemetry.d.ts.map +1 -0
- package/dist/video/provider-platform/telemetry.js +88 -0
- package/dist/video/provider-platform/telemetry.js.map +1 -0
- package/dist/video/provider-platform/types.d.ts +139 -0
- package/dist/video/provider-platform/types.d.ts.map +1 -0
- package/dist/video/provider-platform/types.js +17 -0
- package/dist/video/provider-platform/types.js.map +1 -0
- package/dist/video/provider-status.d.ts +12 -0
- package/dist/video/provider-status.d.ts.map +1 -0
- package/dist/video/provider-status.js +169 -0
- package/dist/video/provider-status.js.map +1 -0
- package/dist/video/providers/dreamina-useapi.d.ts +170 -0
- package/dist/video/providers/dreamina-useapi.d.ts.map +1 -0
- package/dist/video/providers/dreamina-useapi.js +176 -0
- package/dist/video/providers/dreamina-useapi.js.map +1 -0
- package/dist/video/providers/flowmusic-useapi.d.ts +114 -0
- package/dist/video/providers/flowmusic-useapi.d.ts.map +1 -0
- package/dist/video/providers/flowmusic-useapi.js +159 -0
- package/dist/video/providers/flowmusic-useapi.js.map +1 -0
- package/dist/video/providers/google-flow.d.ts +57 -0
- package/dist/video/providers/google-flow.d.ts.map +1 -0
- package/dist/video/providers/google-flow.js +63 -0
- package/dist/video/providers/google-flow.js.map +1 -0
- package/dist/video/providers/public-host.d.ts +40 -0
- package/dist/video/providers/public-host.d.ts.map +1 -0
- package/dist/video/providers/public-host.js +91 -0
- package/dist/video/providers/public-host.js.map +1 -0
- package/dist/video/providers/runway-useapi.d.ts +159 -0
- package/dist/video/providers/runway-useapi.d.ts.map +1 -0
- package/dist/video/providers/runway-useapi.js +200 -0
- package/dist/video/providers/runway-useapi.js.map +1 -0
- package/dist/video/providers/xskill.d.ts +108 -0
- package/dist/video/providers/xskill.d.ts.map +1 -0
- package/dist/video/providers/xskill.js +233 -0
- package/dist/video/providers/xskill.js.map +1 -0
- package/dist/video/readiness.d.ts +18 -0
- package/dist/video/readiness.d.ts.map +1 -0
- package/dist/video/readiness.js +221 -0
- package/dist/video/readiness.js.map +1 -0
- package/dist/video/reference-sheet-store.d.ts +5 -0
- package/dist/video/reference-sheet-store.d.ts.map +1 -0
- package/dist/video/reference-sheet-store.js +28 -0
- package/dist/video/reference-sheet-store.js.map +1 -0
- package/dist/video/reference-sheets.d.ts +40 -0
- package/dist/video/reference-sheets.d.ts.map +1 -0
- package/dist/video/reference-sheets.js +160 -0
- package/dist/video/reference-sheets.js.map +1 -0
- package/dist/video/remix-narrated.d.ts +12 -0
- package/dist/video/remix-narrated.d.ts.map +1 -0
- package/dist/video/remix-narrated.js +73 -0
- package/dist/video/remix-narrated.js.map +1 -0
- package/dist/video/report-diff.d.ts +89 -0
- package/dist/video/report-diff.d.ts.map +1 -0
- package/dist/video/report-diff.js +176 -0
- package/dist/video/report-diff.js.map +1 -0
- package/dist/video/report-history.d.ts +40 -0
- package/dist/video/report-history.d.ts.map +1 -0
- package/dist/video/report-history.js +74 -0
- package/dist/video/report-history.js.map +1 -0
- package/dist/video/report.d.ts +15 -0
- package/dist/video/report.d.ts.map +1 -0
- package/dist/video/report.js +21 -0
- package/dist/video/report.js.map +1 -0
- package/dist/video/review-ui.d.ts +180 -0
- package/dist/video/review-ui.d.ts.map +1 -0
- package/dist/video/review-ui.js +2008 -0
- package/dist/video/review-ui.js.map +1 -0
- package/dist/video/scene-candidate-store.d.ts +12 -0
- package/dist/video/scene-candidate-store.d.ts.map +1 -0
- package/dist/video/scene-candidate-store.js +134 -0
- package/dist/video/scene-candidate-store.js.map +1 -0
- package/dist/video/scene-candidates.d.ts +57 -0
- package/dist/video/scene-candidates.d.ts.map +1 -0
- package/dist/video/scene-candidates.js +149 -0
- package/dist/video/scene-candidates.js.map +1 -0
- package/dist/video/scene-selection-store.d.ts +5 -0
- package/dist/video/scene-selection-store.d.ts.map +1 -0
- package/dist/video/scene-selection-store.js +22 -0
- package/dist/video/scene-selection-store.js.map +1 -0
- package/dist/video/scene-selection.d.ts +83 -0
- package/dist/video/scene-selection.d.ts.map +1 -0
- package/dist/video/scene-selection.js +225 -0
- package/dist/video/scene-selection.js.map +1 -0
- package/dist/video/scheduling.d.ts +3 -0
- package/dist/video/scheduling.d.ts.map +1 -0
- package/dist/video/scheduling.js +16 -0
- package/dist/video/scheduling.js.map +1 -0
- package/dist/video/scorecard.d.ts +12 -0
- package/dist/video/scorecard.d.ts.map +1 -0
- package/dist/video/scorecard.js +68 -0
- package/dist/video/scorecard.js.map +1 -0
- package/dist/video/seedance-asset-library.d.ts +82 -0
- package/dist/video/seedance-asset-library.d.ts.map +1 -0
- package/dist/video/seedance-asset-library.js +146 -0
- package/dist/video/seedance-asset-library.js.map +1 -0
- package/dist/video/seedance-blocks.d.ts +56 -0
- package/dist/video/seedance-blocks.d.ts.map +1 -0
- package/dist/video/seedance-blocks.js +97 -0
- package/dist/video/seedance-blocks.js.map +1 -0
- package/dist/video/seedance-chain-host.d.ts +42 -0
- package/dist/video/seedance-chain-host.d.ts.map +1 -0
- package/dist/video/seedance-chain-host.js +128 -0
- package/dist/video/seedance-chain-host.js.map +1 -0
- package/dist/video/seedance-content-filter.d.ts +80 -0
- package/dist/video/seedance-content-filter.d.ts.map +1 -0
- package/dist/video/seedance-content-filter.js +293 -0
- package/dist/video/seedance-content-filter.js.map +1 -0
- package/dist/video/seedance-skill-loader.d.ts +40 -0
- package/dist/video/seedance-skill-loader.d.ts.map +1 -0
- package/dist/video/seedance-skill-loader.js +53 -0
- package/dist/video/seedance-skill-loader.js.map +1 -0
- package/dist/video/sfx.d.ts +49 -0
- package/dist/video/sfx.d.ts.map +1 -0
- package/dist/video/sfx.js +104 -0
- package/dist/video/sfx.js.map +1 -0
- package/dist/video/shot-grammar.d.ts +103 -0
- package/dist/video/shot-grammar.d.ts.map +1 -0
- package/dist/video/shot-grammar.js +286 -0
- package/dist/video/shot-grammar.js.map +1 -0
- package/dist/video/show-bible.d.ts +113 -0
- package/dist/video/show-bible.d.ts.map +1 -0
- package/dist/video/show-bible.js +140 -0
- package/dist/video/show-bible.js.map +1 -0
- package/dist/video/soundtrack.d.ts +72 -0
- package/dist/video/soundtrack.d.ts.map +1 -0
- package/dist/video/soundtrack.js +171 -0
- package/dist/video/soundtrack.js.map +1 -0
- package/dist/video/stage-guards.d.ts +4 -0
- package/dist/video/stage-guards.d.ts.map +1 -0
- package/dist/video/stage-guards.js +94 -0
- package/dist/video/stage-guards.js.map +1 -0
- package/dist/video/status.d.ts +69 -0
- package/dist/video/status.d.ts.map +1 -0
- package/dist/video/status.js +245 -0
- package/dist/video/status.js.map +1 -0
- package/dist/video/story-bible.d.ts +70 -0
- package/dist/video/story-bible.d.ts.map +1 -0
- package/dist/video/story-bible.js +181 -0
- package/dist/video/story-bible.js.map +1 -0
- package/dist/video/storyboard-grid.d.ts +28 -0
- package/dist/video/storyboard-grid.d.ts.map +1 -0
- package/dist/video/storyboard-grid.js +231 -0
- package/dist/video/storyboard-grid.js.map +1 -0
- package/dist/video/storyboard-markdown.d.ts +62 -0
- package/dist/video/storyboard-markdown.d.ts.map +1 -0
- package/dist/video/storyboard-markdown.js +332 -0
- package/dist/video/storyboard-markdown.js.map +1 -0
- package/dist/video/storyboard-still-candidates.d.ts +31 -0
- package/dist/video/storyboard-still-candidates.d.ts.map +1 -0
- package/dist/video/storyboard-still-candidates.js +57 -0
- package/dist/video/storyboard-still-candidates.js.map +1 -0
- package/dist/video/storyboard-templates.d.ts +28 -0
- package/dist/video/storyboard-templates.d.ts.map +1 -0
- package/dist/video/storyboard-templates.js +215 -0
- package/dist/video/storyboard-templates.js.map +1 -0
- package/dist/video/studio/execute.d.ts +142 -0
- package/dist/video/studio/execute.d.ts.map +1 -0
- package/dist/video/studio/execute.js +270 -0
- package/dist/video/studio/execute.js.map +1 -0
- package/dist/video/studio/planner.d.ts +3 -0
- package/dist/video/studio/planner.d.ts.map +1 -0
- package/dist/video/studio/planner.js +110 -0
- package/dist/video/studio/planner.js.map +1 -0
- package/dist/video/studio/project-context.d.ts +4 -0
- package/dist/video/studio/project-context.d.ts.map +1 -0
- package/dist/video/studio/project-context.js +20 -0
- package/dist/video/studio/project-context.js.map +1 -0
- package/dist/video/studio/recipes.d.ts +4 -0
- package/dist/video/studio/recipes.d.ts.map +1 -0
- package/dist/video/studio/recipes.js +343 -0
- package/dist/video/studio/recipes.js.map +1 -0
- package/dist/video/studio/session.d.ts +8 -0
- package/dist/video/studio/session.d.ts.map +1 -0
- package/dist/video/studio/session.js +17 -0
- package/dist/video/studio/session.js.map +1 -0
- package/dist/video/studio/types.d.ts +59 -0
- package/dist/video/studio/types.d.ts.map +1 -0
- package/dist/video/studio/types.js +2 -0
- package/dist/video/studio/types.js.map +1 -0
- package/dist/video/template-store.d.ts +65 -0
- package/dist/video/template-store.d.ts.map +1 -0
- package/dist/video/template-store.js +168 -0
- package/dist/video/template-store.js.map +1 -0
- package/dist/video/timeline.d.ts +8 -0
- package/dist/video/timeline.d.ts.map +1 -0
- package/dist/video/timeline.js +21 -0
- package/dist/video/timeline.js.map +1 -0
- package/dist/video/title-overlay.d.ts +85 -0
- package/dist/video/title-overlay.d.ts.map +1 -0
- package/dist/video/title-overlay.js +163 -0
- package/dist/video/title-overlay.js.map +1 -0
- package/dist/video/types.d.ts +403 -0
- package/dist/video/types.d.ts.map +1 -0
- package/dist/video/types.js +2 -0
- package/dist/video/types.js.map +1 -0
- package/dist/video/veo-subprocess.d.ts +27 -0
- package/dist/video/veo-subprocess.d.ts.map +1 -0
- package/dist/video/veo-subprocess.js +52 -0
- package/dist/video/veo-subprocess.js.map +1 -0
- package/dist/video/verify-env.d.ts +43 -0
- package/dist/video/verify-env.d.ts.map +1 -0
- package/dist/video/verify-env.js +156 -0
- package/dist/video/verify-env.js.map +1 -0
- package/dist/video/verify-final.d.ts +20 -0
- package/dist/video/verify-final.d.ts.map +1 -0
- package/dist/video/verify-final.js +39 -0
- package/dist/video/verify-final.js.map +1 -0
- package/dist/video/video-context.d.ts +10 -0
- package/dist/video/video-context.d.ts.map +1 -0
- package/dist/video/video-context.js +74 -0
- package/dist/video/video-context.js.map +1 -0
- package/dist/video/vocal-map.d.ts +55 -0
- package/dist/video/vocal-map.d.ts.map +1 -0
- package/dist/video/vocal-map.js +107 -0
- package/dist/video/vocal-map.js.map +1 -0
- package/dist/video/vocal-sync-plan.d.ts +76 -0
- package/dist/video/vocal-sync-plan.d.ts.map +1 -0
- package/dist/video/vocal-sync-plan.js +166 -0
- package/dist/video/vocal-sync-plan.js.map +1 -0
- package/dist/video/voice-clone.d.ts +152 -0
- package/dist/video/voice-clone.d.ts.map +1 -0
- package/dist/video/voice-clone.js +267 -0
- package/dist/video/voice-clone.js.map +1 -0
- package/dist/video/with-retry.d.ts +67 -0
- package/dist/video/with-retry.d.ts.map +1 -0
- package/dist/video/with-retry.js +105 -0
- package/dist/video/with-retry.js.map +1 -0
- package/dist/video/workload.d.ts +20 -0
- package/dist/video/workload.d.ts.map +1 -0
- package/dist/video/workload.js +55 -0
- package/dist/video/workload.js.map +1 -0
- package/dist/video/workspace.d.ts +109 -0
- package/dist/video/workspace.d.ts.map +1 -0
- package/dist/video/workspace.js +132 -0
- package/dist/video/workspace.js.map +1 -0
- package/docs/AGENT_INTEGRATION_RESEARCH.md +123 -0
- package/docs/AI_FILMMAKING_PROMPTS.md +195 -0
- package/docs/ARCHITECTURE.md +257 -0
- package/docs/ASSEMBLE.md +376 -0
- package/docs/BRAND_AGENCY.md +63 -0
- package/docs/CLAWBOT_COVERAGE_PROMPT.md +78 -0
- package/docs/CLI_REFERENCE.md +2638 -0
- package/docs/CONCIERGE_PROMPT.md +139 -0
- package/docs/Claude Code + Higgsfield MCP = FULL Creative Agency.md +179 -0
- package/docs/DEMO_RECORDING.md +61 -0
- package/docs/DEPRECATION.md +79 -0
- package/docs/DIAGRAMS_SOURCE.md +347 -0
- package/docs/DIRECTOR_BLUEPRINT.md +94 -0
- package/docs/GENERATION_TELEMETRY.md +81 -0
- package/docs/IMPROVE_PROMPT.md +76 -0
- package/docs/INFOGRAPHICS_PROMPT.md +83 -0
- package/docs/MASTER_PLAN_ALIGNMENT.md +563 -0
- package/docs/MIGRATION.md +213 -0
- package/docs/MOTION_OVERLAY.md +355 -0
- package/docs/OBSIDIAN.md +300 -0
- package/docs/OPERATIONS.md +142 -0
- package/docs/OPERATOR_HANDOFF.md +108 -0
- package/docs/PRODUCTION_WORKFLOW.md +148 -0
- package/docs/PROJECT_LAYOUT.md +262 -0
- package/docs/PROMPT_QUALITY.md +214 -0
- package/docs/PROVIDER_PLATFORM.md +195 -0
- package/docs/PUBLISHING.md +175 -0
- package/docs/PYTHON_PIPELINE.md +139 -0
- package/docs/REFERENCE_SHEETS.md +469 -0
- package/docs/REFERENCE_VIDEO_SEEDANCE_MOTION_DESIGN_WORKFLOW.md +465 -0
- package/docs/RELEASE_READINESS.md +573 -0
- package/docs/REVIEW_UI_STORYBOARD_WORKFLOW.md +200 -0
- package/docs/SCENE_CANDIDATES.md +518 -0
- package/docs/SKILLS.md +477 -0
- package/docs/SKILL_COHESION_PROMPT.md +88 -0
- package/docs/STORY_BIBLE.md +288 -0
- package/docs/STUDIO.md +96 -0
- package/docs/TEMPLATES.md +143 -0
- package/docs/UNIFICATION_AUDIT.md +321 -0
- package/docs/UPDATE_DOCS_PROMPT.md +78 -0
- package/docs/assets/demo-quickstart.cast +13 -0
- package/docs/assets/demo-quickstart.gif +0 -0
- package/docs/assets/diagram-architecture.jpg +0 -0
- package/docs/assets/diagram-assemble.jpg +0 -0
- package/docs/assets/diagram-lifecycle.jpg +0 -0
- package/docs/assets/diagram-obsidian-loop.jpg +0 -0
- package/docs/assets/diagram-obsidian-vault.jpg +0 -0
- package/docs/assets/diagram-routing.jpg +0 -0
- package/docs/assets/diagram-skills-ecosystem.jpg +0 -0
- package/docs/assets/diagram-story-bible.jpg +0 -0
- package/docs/assets/diagram-studio-goals.jpg +0 -0
- package/docs/assets/docsite-preview.png +0 -0
- package/docs/assets/logo.jpg +0 -0
- package/docs/assets/logo.png +0 -0
- package/docs/preview-portal-audit.md +151 -0
- package/package.json +119 -0
- package/playbooks/seedance-ugc.json +32 -0
- package/playbooks/veo-generic.json +26 -0
- package/references/video/.fixtures/multi-shot-runway-10s.txt +9 -0
- package/references/video/.fixtures/multi-shot-seedance-10s.txt +9 -0
- package/references/video/.fixtures/multi-shot-valid.txt +13 -0
- package/references/video/.fixtures/multi-shot-veo-8s.txt +9 -0
- package/references/video/ai-director-blueprint.md +614 -0
- package/references/video/camera-bible.md +294 -0
- package/references/video/character-reference-sheet.md +14 -0
- package/references/video/checkpoint-protocol.md +26 -0
- package/references/video/clone-ad-template-workflow.md +14 -0
- package/references/video/dialogue-duration-preflight.md +17 -0
- package/references/video/generation-telemetry.md +18 -0
- package/references/video/multi-shot-framework.md +455 -0
- package/references/video/seedance-skills/01-cinematic.md +1329 -0
- package/references/video/seedance-skills/02-3d-cgi.md +884 -0
- package/references/video/seedance-skills/03-cartoon.md +1668 -0
- package/references/video/seedance-skills/04-comic-to-video.md +1810 -0
- package/references/video/seedance-skills/05-fight-scenes.md +741 -0
- package/references/video/seedance-skills/06-motion-design-ad.md +1145 -0
- package/references/video/seedance-skills/07-ecommerce-ad.md +928 -0
- package/references/video/seedance-skills/08-anime-action.md +1145 -0
- package/references/video/seedance-skills/09-product-360.md +988 -0
- package/references/video/seedance-skills/10-music-video.md +1662 -0
- package/references/video/seedance-skills/11-social-hook.md +1874 -0
- package/references/video/seedance-skills/12-brand-story.md +1151 -0
- package/references/video/seedance-skills/13-fashion-lookbook.md +1236 -0
- package/references/video/seedance-skills/14-food-beverage.md +1110 -0
- package/references/video/seedance-skills/15-real-estate.md +2344 -0
- package/references/video/seedance-transport-payloads.md +144 -0
- package/references/video/seedance-ugc-formulas.md +104 -0
- package/references/video/stage-directors.md +47 -0
- package/references/video/style-template-schema.md +40 -0
- package/references/video/veo-prompting-guide.md +42 -0
- package/schemas/video/artifacts/analyze-output.schema.json +60 -0
- package/schemas/video/artifacts/assemble-report.schema.json +116 -0
- package/schemas/video/artifacts/asset-manifest.schema.json +23 -0
- package/schemas/video/artifacts/batch-queue-manifest.schema.json +59 -0
- package/schemas/video/artifacts/brand-definition.schema.json +93 -0
- package/schemas/video/artifacts/brand-dna.schema.json +55 -0
- package/schemas/video/artifacts/brief.schema.json +13 -0
- package/schemas/video/artifacts/clone-plan.schema.json +44 -0
- package/schemas/video/artifacts/dialogue.schema.json +52 -0
- package/schemas/video/artifacts/environment-assets.schema.json +30 -0
- package/schemas/video/artifacts/execution-plan.schema.json +64 -0
- package/schemas/video/artifacts/execution-report.schema.json +37 -0
- package/schemas/video/artifacts/filmmaking-prompts.schema.json +129 -0
- package/schemas/video/artifacts/flow-characters.schema.json +31 -0
- package/schemas/video/artifacts/flow-voices.schema.json +30 -0
- package/schemas/video/artifacts/motion-overlay-plan.schema.json +50 -0
- package/schemas/video/artifacts/multi-shot-prompt.schema.json +90 -0
- package/schemas/video/artifacts/music-video-config.schema.json +108 -0
- package/schemas/video/artifacts/narration.schema.json +54 -0
- package/schemas/video/artifacts/product-references.schema.json +29 -0
- package/schemas/video/artifacts/project-blueprint.schema.json +236 -0
- package/schemas/video/artifacts/publish-report.schema.json +12 -0
- package/schemas/video/artifacts/reference-sheets.schema.json +85 -0
- package/schemas/video/artifacts/review-report.schema.json +12 -0
- package/schemas/video/artifacts/scene-candidates.schema.json +86 -0
- package/schemas/video/artifacts/scene-selection.schema.json +50 -0
- package/schemas/video/artifacts/seedance-assets.schema.json +31 -0
- package/schemas/video/artifacts/sfx.schema.json +31 -0
- package/schemas/video/artifacts/show-bible.schema.json +66 -0
- package/schemas/video/artifacts/soundtrack.schema.json +39 -0
- package/schemas/video/artifacts/story-bible.schema.json +131 -0
- package/schemas/video/artifacts/storyboard.schema.json +58 -0
- package/schemas/video/artifacts/voice-clones.schema.json +37 -0
- package/schemas/video/brand-profile.schema.json +20 -0
- package/schemas/video/errors.json +48 -0
- package/schemas/video/pipeline-manifest.schema.json +51 -0
- package/skills/README.md +113 -0
- package/skills/ai-director/SKILL.md +140 -0
- package/skills/ai-filmmaking/SKILL.md +239 -0
- package/skills/ai-slop-cleaner/SKILL.md +114 -0
- package/skills/brand-agency/SKILL.md +181 -0
- package/skills/brand-presenter/SKILL.md +114 -0
- package/skills/build-fix/SKILL.md +145 -0
- package/skills/bunty/SKILL.md +41 -0
- package/skills/catalog.json +326 -0
- package/skills/character-ad/SKILL.md +154 -0
- package/skills/character-creator/SKILL.md +303 -0
- package/skills/character-library/README.md +12 -0
- package/skills/character-library/SKILL.md +110 -0
- package/skills/clawbot/SKILL.md +26 -0
- package/skills/concierge/SKILL.md +183 -0
- package/skills/configure-notifications/SKILL.md +286 -0
- package/skills/davendra-presenter/SKILL.md +27 -0
- package/skills/davendra-presenter/assets/davendra_intro_1.jpg +0 -0
- package/skills/davendra-presenter/assets/davendra_intro_2.jpg +0 -0
- package/skills/davendra-presenter/assets/davendra_outro_1.jpg +0 -0
- package/skills/davendra-presenter/assets/davendra_outro_2.jpg +0 -0
- package/skills/deep-interview/SKILL.md +358 -0
- package/skills/deepsearch/SKILL.md +38 -0
- package/skills/doctor/SKILL.md +200 -0
- package/skills/graphify/SKILL.md +616 -0
- package/skills/help/SKILL.md +200 -0
- package/skills/higgsfield-generate/SKILL.md +171 -0
- package/skills/improvement-run/SKILL.md +47 -0
- package/skills/motion-reel/SKILL.md +102 -0
- package/skills/movie-director/SKILL.md +338 -0
- package/skills/multi-shot-prompt/SKILL.md +77 -0
- package/skills/nex-presenter/SKILL.md +25 -0
- package/skills/nex-presenter/assets/logo_manifest.json +26 -0
- package/skills/nex-presenter/assets/logo_source.jpg +0 -0
- package/skills/nex-presenter/assets/nex_brief_intro.mp4 +0 -0
- package/skills/nex-presenter/assets/nex_intro_1.jpg +0 -0
- package/skills/nex-presenter/assets/nex_intro_2.jpg +0 -0
- package/skills/nex-presenter/assets/nex_outro_1.jpg +0 -0
- package/skills/nex-presenter/assets/nex_outro_2.jpg +0 -0
- package/skills/nex-presenter/assets/text_overlay.png +0 -0
- package/skills/note/SKILL.md +62 -0
- package/skills/pipeline/SKILL.md +86 -0
- package/skills/review/SKILL.md +38 -0
- package/skills/runway/SKILL.md +131 -0
- package/skills/seedance-prompts/SKILL.md +194 -0
- package/skills/skill/SKILL.md +835 -0
- package/skills/skills-auditor/SKILL.md +73 -0
- package/skills/studio-mode/SKILL.md +148 -0
- package/skills/ugc/SKILL.md +386 -0
- package/skills/ui-ux-pro-max/SKILL.md +386 -0
- package/skills/video-analyze-template/SKILL.md +77 -0
- package/skills/video-clone-ad/SKILL.md +67 -0
- package/skills/video-framework/SKILL.md +201 -0
- package/skills/video-portfolio-ops/SKILL.md +84 -0
- package/skills/video-post/SKILL.md +97 -0
- package/skills/video-production-handoff/SKILL.md +130 -0
- package/skills/video-release-readiness/SKILL.md +93 -0
- package/skills/video-replicator/SKILL.md +368 -0
- package/skills/video-review-ui-qa/SKILL.md +103 -0
- package/skills/video-storyboard/SKILL.md +71 -0
- package/skills/video-thumbnail-lab/SKILL.md +67 -0
- package/skills/web-clone/SKILL.md +366 -0
- package/skills/worker/SKILL.md +106 -0
- package/skills/youtube-audio/SKILL.md +114 -0
- package/src/video/analyze-output.ts +10 -0
- package/src/video/archive-project.ts +65 -0
- package/src/video/artifact-history.ts +23 -0
- package/src/video/artifact-store.ts +59 -0
- package/src/video/artifact-validation.ts +268 -0
- package/src/video/artifacts.ts +156 -0
- package/src/video/assemble/animate-slides.ts +199 -0
- package/src/video/assemble/animation-styles.json +97 -0
- package/src/video/assemble/animation-styles.ts +54 -0
- package/src/video/assemble/assemble.ts +547 -0
- package/src/video/assemble/audio-concat.ts +165 -0
- package/src/video/assemble/audio-mix-plan.ts +150 -0
- package/src/video/assemble/audio-utils.ts +101 -0
- package/src/video/assemble/cut-segment.ts +121 -0
- package/src/video/assemble/ffmpeg.ts +333 -0
- package/src/video/assemble/gemini-vision-classify.ts +153 -0
- package/src/video/assemble/index.ts +247 -0
- package/src/video/assemble/media-qc.ts +252 -0
- package/src/video/assemble/music.ts +261 -0
- package/src/video/assemble/narration-fit.ts +90 -0
- package/src/video/assemble/overlay.ts +225 -0
- package/src/video/assemble/pdf.ts +169 -0
- package/src/video/assemble/qa-dialogue-lint.ts +129 -0
- package/src/video/assemble/qa-image-filter.ts +147 -0
- package/src/video/assemble/qa-image-vision.ts +154 -0
- package/src/video/assemble/qa-narration-vision.ts +135 -0
- package/src/video/assemble/qa-narration.ts +139 -0
- package/src/video/assemble/stitch-ad.ts +222 -0
- package/src/video/assemble/stitch.ts +918 -0
- package/src/video/assemble/text-card.ts +285 -0
- package/src/video/assemble/title-card.ts +186 -0
- package/src/video/assemble/transcript.ts +240 -0
- package/src/video/assemble/tts-elevenlabs.ts +182 -0
- package/src/video/assemble/tts.ts +227 -0
- package/src/video/assemble/types.ts +45 -0
- package/src/video/assemble/upscale.ts +80 -0
- package/src/video/asset-spec.ts +53 -0
- package/src/video/asset-tag-lookup.ts +51 -0
- package/src/video/atomic-write.ts +7 -0
- package/src/video/audio-platform/native-elevenlabs-sfx.ts +128 -0
- package/src/video/audio-platform/native-elevenlabs-tts.ts +119 -0
- package/src/video/audio-platform/native-flowmusic.ts +167 -0
- package/src/video/audio-platform/native-gemini-tts.ts +226 -0
- package/src/video/audio-platform/native-lyria.ts +231 -0
- package/src/video/audio-platform/native-lyria3.ts +244 -0
- package/src/video/audio-platform/registry.ts +220 -0
- package/src/video/audio-platform/suno-backend.ts +36 -0
- package/src/video/audio-platform/types.ts +138 -0
- package/src/video/batch-queue.ts +685 -0
- package/src/video/blueprint-prompt.ts +118 -0
- package/src/video/brand-definition.ts +287 -0
- package/src/video/brand-dna.ts +478 -0
- package/src/video/brand-prompt.ts +28 -0
- package/src/video/candidate-migrate.ts +171 -0
- package/src/video/category-registry.ts +224 -0
- package/src/video/chain-fallback.ts +53 -0
- package/src/video/character-auto-create.ts +547 -0
- package/src/video/character-consistency.ts +85 -0
- package/src/video/characters.ts +145 -0
- package/src/video/checkpoints.ts +60 -0
- package/src/video/cinema-profile.ts +120 -0
- package/src/video/cinematography.ts +1229 -0
- package/src/video/cli-output.ts +94 -0
- package/src/video/cli-schema.ts +505 -0
- package/src/video/cost-estimate.ts +150 -0
- package/src/video/csv-export.ts +150 -0
- package/src/video/dependencies.ts +64 -0
- package/src/video/dialogue-fit.ts +62 -0
- package/src/video/dialogue.ts +221 -0
- package/src/video/director-defaults.ts +432 -0
- package/src/video/director-preflight.ts +543 -0
- package/src/video/doctor-portfolio.ts +197 -0
- package/src/video/doctor.ts +499 -0
- package/src/video/emotion-cues.ts +36 -0
- package/src/video/environment-assets.ts +69 -0
- package/src/video/environment-auto-create.ts +122 -0
- package/src/video/errors.ts +133 -0
- package/src/video/events.ts +42 -0
- package/src/video/execute-autochain.ts +226 -0
- package/src/video/execute.ts +569 -0
- package/src/video/execution-cancel.ts +103 -0
- package/src/video/execution-plan.ts +178 -0
- package/src/video/execution-profile.ts +162 -0
- package/src/video/execution-runtime.ts +975 -0
- package/src/video/execution-seed.ts +46 -0
- package/src/video/execution-status.ts +736 -0
- package/src/video/filmmaking-prompts.ts +1789 -0
- package/src/video/final-media.ts +118 -0
- package/src/video/finish.ts +255 -0
- package/src/video/flow-character-library.ts +342 -0
- package/src/video/flow-markers.ts +330 -0
- package/src/video/gemini-analyze.ts +387 -0
- package/src/video/gemini-continuity.ts +186 -0
- package/src/video/gemini-judge.ts +300 -0
- package/src/video/gemini-key-pool.ts +153 -0
- package/src/video/gen-image-flow.ts +491 -0
- package/src/video/gen-image.ts +392 -0
- package/src/video/generation-telemetry.ts +282 -0
- package/src/video/http-error-safety.ts +13 -0
- package/src/video/image-dimensions.ts +69 -0
- package/src/video/legacy-import.ts +197 -0
- package/src/video/library-clean.ts +399 -0
- package/src/video/lipsync.ts +200 -0
- package/src/video/media-host.ts +153 -0
- package/src/video/metrics.ts +116 -0
- package/src/video/motion-overlay/analyze-reel.ts +200 -0
- package/src/video/motion-overlay/animate-render.ts +176 -0
- package/src/video/motion-overlay/animate.ts +93 -0
- package/src/video/motion-overlay/avatar-host-transport.ts +458 -0
- package/src/video/motion-overlay/avatar-host.ts +238 -0
- package/src/video/motion-overlay/compose-prompt.ts +247 -0
- package/src/video/motion-overlay/execute.ts +355 -0
- package/src/video/motion-overlay/flow-pack.ts +223 -0
- package/src/video/motion-overlay/ingest.ts +210 -0
- package/src/video/motion-overlay/motifs.ts +223 -0
- package/src/video/motion-overlay/motion-style.ts +162 -0
- package/src/video/motion-overlay/plan.ts +111 -0
- package/src/video/motion-overlay/preview.ts +191 -0
- package/src/video/motion-overlay/render-local.ts +441 -0
- package/src/video/motion-overlay/run.ts +217 -0
- package/src/video/motion-overlay/slice.ts +98 -0
- package/src/video/motion-overlay/transcribe.ts +211 -0
- package/src/video/motion-overlay/types.ts +89 -0
- package/src/video/motion-overlay/v2v-transport.ts +209 -0
- package/src/video/motion-overlay/write.ts +142 -0
- package/src/video/multi-shot-artifact.ts +64 -0
- package/src/video/multi-shot-prompt.ts +608 -0
- package/src/video/music-video.ts +324 -0
- package/src/video/narrate.ts +221 -0
- package/src/video/native-dreamina.ts +638 -0
- package/src/video/native-flow-r2v.ts +279 -0
- package/src/video/native-runway.ts +647 -0
- package/src/video/native-seedance.ts +615 -0
- package/src/video/native-veo.ts +470 -0
- package/src/video/next-actions.ts +247 -0
- package/src/video/obsidian-export.ts +336 -0
- package/src/video/obsidian-sync.ts +649 -0
- package/src/video/obsidian-vault.ts +96 -0
- package/src/video/outfit-prompts.ts +14 -0
- package/src/video/outpaint-keyframe.ts +365 -0
- package/src/video/pipeline-manifest.ts +16 -0
- package/src/video/pipeline-manifests/director.json +46 -0
- package/src/video/pipeline-manifests/storyboard.json +46 -0
- package/src/video/platform-specs.ts +50 -0
- package/src/video/playbooks.ts +34 -0
- package/src/video/post-production.ts +277 -0
- package/src/video/preview-portal/audit.ts +20 -0
- package/src/video/preview-portal/discovery.ts +787 -0
- package/src/video/preview-portal/generate.ts +138 -0
- package/src/video/preview-portal/index.ts +41 -0
- package/src/video/preview-portal/publish.ts +215 -0
- package/src/video/preview-portal/render.ts +865 -0
- package/src/video/preview-portal/shared-assets.ts +455 -0
- package/src/video/preview-portal/templates.ts +112 -0
- package/src/video/preview-portal/types.ts +288 -0
- package/src/video/product-references.ts +43 -0
- package/src/video/project-blueprint.ts +458 -0
- package/src/video/project-index.ts +179 -0
- package/src/video/projects.ts +18 -0
- package/src/video/prompt-guidance.ts +64 -0
- package/src/video/prompt-library.ts +96 -0
- package/src/video/prompt-lint.ts +568 -0
- package/src/video/prompt-quality.ts +635 -0
- package/src/video/prompt-rules.ts +209 -0
- package/src/video/provider-adapter-runner.ts +104 -0
- package/src/video/provider-platform/index.ts +5 -0
- package/src/video/provider-platform/registry.ts +286 -0
- package/src/video/provider-platform/route-capabilities.ts +327 -0
- package/src/video/provider-platform/router.ts +221 -0
- package/src/video/provider-platform/security.ts +29 -0
- package/src/video/provider-platform/telemetry.ts +116 -0
- package/src/video/provider-platform/types.ts +192 -0
- package/src/video/provider-status.ts +199 -0
- package/src/video/providers/dreamina-useapi.ts +345 -0
- package/src/video/providers/flowmusic-useapi.ts +252 -0
- package/src/video/providers/google-flow.ts +110 -0
- package/src/video/providers/public-host.ts +128 -0
- package/src/video/providers/runway-useapi.ts +377 -0
- package/src/video/providers/xskill.ts +304 -0
- package/src/video/readiness.ts +284 -0
- package/src/video/reference-sheet-store.ts +40 -0
- package/src/video/reference-sheets.ts +224 -0
- package/src/video/remix-narrated.ts +92 -0
- package/src/video/report-diff.ts +299 -0
- package/src/video/report-history.ts +122 -0
- package/src/video/report.ts +35 -0
- package/src/video/review-ui.ts +2495 -0
- package/src/video/scene-candidate-store.ts +152 -0
- package/src/video/scene-candidates.ts +195 -0
- package/src/video/scene-selection-store.ts +36 -0
- package/src/video/scene-selection.ts +306 -0
- package/src/video/scheduling.ts +18 -0
- package/src/video/scorecard.ts +90 -0
- package/src/video/seedance-asset-library.ts +259 -0
- package/src/video/seedance-blocks.ts +118 -0
- package/src/video/seedance-chain-host.ts +152 -0
- package/src/video/seedance-content-filter.ts +354 -0
- package/src/video/seedance-skill-loader.ts +83 -0
- package/src/video/sfx.ts +159 -0
- package/src/video/shot-grammar.ts +349 -0
- package/src/video/show-bible.ts +235 -0
- package/src/video/soundtrack.ts +257 -0
- package/src/video/stage-guards.ts +112 -0
- package/src/video/status.ts +342 -0
- package/src/video/story-bible.ts +302 -0
- package/src/video/storyboard-grid.ts +320 -0
- package/src/video/storyboard-markdown.ts +434 -0
- package/src/video/storyboard-still-candidates.ts +82 -0
- package/src/video/storyboard-templates.ts +249 -0
- package/src/video/studio/execute.ts +401 -0
- package/src/video/studio/planner.ts +114 -0
- package/src/video/studio/project-context.ts +25 -0
- package/src/video/studio/recipes.ts +346 -0
- package/src/video/studio/session.ts +24 -0
- package/src/video/studio/types.ts +76 -0
- package/src/video/template-store.ts +241 -0
- package/src/video/timeline.ts +32 -0
- package/src/video/title-overlay.ts +234 -0
- package/src/video/types.ts +465 -0
- package/src/video/veo-subprocess.ts +77 -0
- package/src/video/verify-env.ts +206 -0
- package/src/video/verify-final.ts +65 -0
- package/src/video/video-context.ts +92 -0
- package/src/video/vocal-map.ts +155 -0
- package/src/video/vocal-sync-plan.ts +224 -0
- package/src/video/voice-clone.ts +410 -0
- package/src/video/with-retry.ts +149 -0
- package/src/video/workload.ts +87 -0
- package/src/video/workspace.ts +255 -0
- package/tmp/review-station/index.html +4194 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,2495 @@
|
|
|
1
|
+
import { createReadStream, existsSync } from 'node:fs';
|
|
2
|
+
import { copyFile, mkdir, readFile, readdir, stat } from 'node:fs/promises';
|
|
3
|
+
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
4
|
+
import { dirname, extname, join, normalize, resolve, sep } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { listCharacterProfiles } from './characters.js';
|
|
7
|
+
import { readReferenceSheetsArtifact } from './reference-sheet-store.js';
|
|
8
|
+
import { readSceneCandidatesArtifact } from './scene-candidate-store.js';
|
|
9
|
+
import { readSceneSelectionArtifact } from './scene-selection-store.js';
|
|
10
|
+
import { listProjects } from './projects.js';
|
|
11
|
+
import { listPlaybooks } from './playbooks.js';
|
|
12
|
+
import { listPromptReferences } from './prompt-library.js';
|
|
13
|
+
import {
|
|
14
|
+
buildStoryboardScenesFromTemplate,
|
|
15
|
+
listStoryboardTemplates,
|
|
16
|
+
readStoryboardTemplate,
|
|
17
|
+
} from './storyboard-templates.js';
|
|
18
|
+
import { writeTextFileAtomic } from './atomic-write.js';
|
|
19
|
+
import { writeArtifact } from './artifact-store.js';
|
|
20
|
+
import { writeStageCheckpoint } from './checkpoints.js';
|
|
21
|
+
import {
|
|
22
|
+
ensureProjectWorkspace,
|
|
23
|
+
readProjectManifest,
|
|
24
|
+
resolveProjectWorkspace,
|
|
25
|
+
updateProjectManifestState,
|
|
26
|
+
} from './workspace.js';
|
|
27
|
+
import { recordStoryboardStillCandidate } from './storyboard-still-candidates.js';
|
|
28
|
+
import { appendProjectEvent } from './events.js';
|
|
29
|
+
import type { StoryboardArtifact } from './artifacts.js';
|
|
30
|
+
import type { SceneCandidate, SceneCandidatesArtifact, SceneSelectionArtifact } from './types.js';
|
|
31
|
+
|
|
32
|
+
export interface ReviewUiOptions {
|
|
33
|
+
root: string;
|
|
34
|
+
projectSlug: string;
|
|
35
|
+
host?: string;
|
|
36
|
+
port?: number;
|
|
37
|
+
uiPath?: string;
|
|
38
|
+
dryRun?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ReviewUiLaunch {
|
|
42
|
+
url: string;
|
|
43
|
+
host: string;
|
|
44
|
+
port: number;
|
|
45
|
+
root: string;
|
|
46
|
+
projectSlug: string;
|
|
47
|
+
uiPath: string;
|
|
48
|
+
dryRun: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ReviewInventory {
|
|
52
|
+
root: string;
|
|
53
|
+
projectSlug: string;
|
|
54
|
+
projectDir: string;
|
|
55
|
+
projectExists: boolean;
|
|
56
|
+
projects: string[];
|
|
57
|
+
characters: Awaited<ReturnType<typeof listCharacterProfiles>>;
|
|
58
|
+
referenceSheets: Awaited<ReturnType<typeof readReferenceSheetsArtifact>>;
|
|
59
|
+
sceneCandidates: Awaited<ReturnType<typeof readSceneCandidatesArtifact>>;
|
|
60
|
+
sceneSelection: Awaited<ReturnType<typeof readSceneSelectionArtifact>>;
|
|
61
|
+
reviewLedger: Record<string, unknown> | null;
|
|
62
|
+
reviewReport: Record<string, unknown> | null;
|
|
63
|
+
brief: Record<string, unknown> | null;
|
|
64
|
+
storyboard: Record<string, unknown> | null;
|
|
65
|
+
assetManifest: Record<string, unknown> | null;
|
|
66
|
+
executionReport: Record<string, unknown> | null;
|
|
67
|
+
publishReport: Record<string, unknown> | null;
|
|
68
|
+
generationQueue: ReviewStoryboardStillGenerationQueue | null;
|
|
69
|
+
characterQueue: ReviewCharacterIterationQueue | null;
|
|
70
|
+
playbooks: Awaited<ReturnType<typeof listPlaybooks>>;
|
|
71
|
+
promptReferences: ReturnType<typeof listPromptReferences>;
|
|
72
|
+
storyboardTemplates: ReturnType<typeof listStoryboardTemplates>;
|
|
73
|
+
mediaAssets: Array<{ path: string; kind: string }>;
|
|
74
|
+
schemas: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ReviewDecisionSaveResult {
|
|
78
|
+
projectSlug: string;
|
|
79
|
+
path: string;
|
|
80
|
+
savedAt: string;
|
|
81
|
+
derivedArtifacts: Array<{
|
|
82
|
+
name: string;
|
|
83
|
+
path: string;
|
|
84
|
+
purpose: string;
|
|
85
|
+
}>;
|
|
86
|
+
lifecycle?: {
|
|
87
|
+
checkpointPath: string;
|
|
88
|
+
status: 'completed' | 'retry-required' | 'failed';
|
|
89
|
+
currentStage: 'publish' | 'review';
|
|
90
|
+
lastCompletedStage: 'review' | 'assets';
|
|
91
|
+
manifestUpdated: boolean;
|
|
92
|
+
};
|
|
93
|
+
reviewReport?: Record<string, unknown>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface ReviewStoryboardStillCandidateResult {
|
|
97
|
+
sceneIndex: number;
|
|
98
|
+
candidate: SceneCandidate;
|
|
99
|
+
reused: boolean;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ReviewUpscaledStillCandidateResult {
|
|
103
|
+
sceneIndex: number;
|
|
104
|
+
sourceCandidateId: string;
|
|
105
|
+
candidate: SceneCandidate;
|
|
106
|
+
reused: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ReviewStoryboardStillGenerationRequest {
|
|
110
|
+
id: string;
|
|
111
|
+
sceneIndex: number;
|
|
112
|
+
provider: 'gobananas';
|
|
113
|
+
route: 'gobananas-storyboard-still';
|
|
114
|
+
status: 'queued' | 'fulfilled';
|
|
115
|
+
prompt: string;
|
|
116
|
+
negativePrompt: string;
|
|
117
|
+
aspectRatio: string;
|
|
118
|
+
requestedAt: string;
|
|
119
|
+
source: 'review-ui';
|
|
120
|
+
notes?: string;
|
|
121
|
+
fulfilledAt?: string;
|
|
122
|
+
candidateId?: string;
|
|
123
|
+
imageUrl?: string;
|
|
124
|
+
imageId?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface ReviewStoryboardStillGenerationQueue {
|
|
128
|
+
schemaVersion: 1;
|
|
129
|
+
projectSlug: string;
|
|
130
|
+
updatedAt: string;
|
|
131
|
+
requests: ReviewStoryboardStillGenerationRequest[];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface ReviewStoryboardStillGenerationRequestResult {
|
|
135
|
+
path: string;
|
|
136
|
+
request: ReviewStoryboardStillGenerationRequest;
|
|
137
|
+
queue: ReviewStoryboardStillGenerationQueue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface ReviewCharacterIterationRequest {
|
|
141
|
+
id: string;
|
|
142
|
+
provider: 'gobananas';
|
|
143
|
+
route: 'gobananas-character-iteration';
|
|
144
|
+
status: 'queued' | 'fulfilled' | 'failed';
|
|
145
|
+
characterName: string;
|
|
146
|
+
prompt: string;
|
|
147
|
+
negativePrompt: string;
|
|
148
|
+
aspectRatio: string;
|
|
149
|
+
count: number;
|
|
150
|
+
requestedAt: string;
|
|
151
|
+
source: 'review-ui';
|
|
152
|
+
notes?: string;
|
|
153
|
+
fulfilledAt?: string;
|
|
154
|
+
characterProfileId?: string;
|
|
155
|
+
goBananasId?: number;
|
|
156
|
+
referenceImageUrl?: string;
|
|
157
|
+
imageId?: string;
|
|
158
|
+
failedAt?: string;
|
|
159
|
+
error?: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface ReviewCharacterIterationQueue {
|
|
163
|
+
schemaVersion: 1;
|
|
164
|
+
projectSlug: string;
|
|
165
|
+
updatedAt: string;
|
|
166
|
+
requests: ReviewCharacterIterationRequest[];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface ReviewCharacterIterationRequestResult {
|
|
170
|
+
path: string;
|
|
171
|
+
request: ReviewCharacterIterationRequest;
|
|
172
|
+
queue: ReviewCharacterIterationQueue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface ReviewAutopilotOptions {
|
|
176
|
+
root: string;
|
|
177
|
+
projectSlug: string;
|
|
178
|
+
template?: string;
|
|
179
|
+
character?: string;
|
|
180
|
+
runId?: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface ReviewAutopilotResult {
|
|
184
|
+
projectSlug: string;
|
|
185
|
+
template: string;
|
|
186
|
+
character: string;
|
|
187
|
+
runId: string;
|
|
188
|
+
lockedStills: Array<{
|
|
189
|
+
sceneIndex: number;
|
|
190
|
+
candidateId: string;
|
|
191
|
+
imageUrl: string;
|
|
192
|
+
}>;
|
|
193
|
+
upscaledStills: Array<{
|
|
194
|
+
sceneIndex: number;
|
|
195
|
+
sourceCandidateId: string;
|
|
196
|
+
candidateId: string;
|
|
197
|
+
imageUrl: string;
|
|
198
|
+
reused: boolean;
|
|
199
|
+
}>;
|
|
200
|
+
decision: ReviewDecisionSaveResult;
|
|
201
|
+
reviewReport: Record<string, unknown> | null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function defaultReviewUiPath(): string {
|
|
205
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'tmp', 'review-station', 'index.html');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function launchReviewUi(options: ReviewUiOptions): Promise<ReviewUiLaunch> {
|
|
209
|
+
const root = resolve(options.root);
|
|
210
|
+
const projectSlug = options.projectSlug;
|
|
211
|
+
const host = options.host ?? '127.0.0.1';
|
|
212
|
+
const port = options.port ?? 4317;
|
|
213
|
+
const uiPath = resolve(options.uiPath ?? defaultReviewUiPath());
|
|
214
|
+
const staticRoot = reviewUiStaticRoot(uiPath);
|
|
215
|
+
const url = `http://${host}:${port}/review-ui?project=${encodeURIComponent(projectSlug)}`;
|
|
216
|
+
const launch: ReviewUiLaunch = {
|
|
217
|
+
url,
|
|
218
|
+
host,
|
|
219
|
+
port,
|
|
220
|
+
root,
|
|
221
|
+
projectSlug,
|
|
222
|
+
uiPath,
|
|
223
|
+
dryRun: options.dryRun ?? false,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
if (options.dryRun) return launch;
|
|
227
|
+
if (!existsSync(uiPath)) {
|
|
228
|
+
throw new Error(`review-ui file not found: ${uiPath}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const server = createServer((request, response) => {
|
|
232
|
+
handleReviewUiRequest(request, response, {
|
|
233
|
+
root,
|
|
234
|
+
projectSlug,
|
|
235
|
+
uiPath,
|
|
236
|
+
staticRoot,
|
|
237
|
+
}).catch((error: unknown) => {
|
|
238
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
239
|
+
if (!response.headersSent) {
|
|
240
|
+
sendText(response, 500, message);
|
|
241
|
+
} else {
|
|
242
|
+
response.end();
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await new Promise<void>((resolveListen, rejectListen) => {
|
|
248
|
+
server.once('error', rejectListen);
|
|
249
|
+
server.listen(port, host, () => {
|
|
250
|
+
server.off('error', rejectListen);
|
|
251
|
+
resolveListen();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return launch;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function runReviewAutopilot(options: ReviewAutopilotOptions): Promise<ReviewAutopilotResult> {
|
|
259
|
+
const root = resolve(options.root);
|
|
260
|
+
const projectSlug = options.projectSlug;
|
|
261
|
+
const template = options.template ?? 'product-commercial-4';
|
|
262
|
+
const runId = sanitizeRunId(options.runId ?? new Date().toISOString());
|
|
263
|
+
const workspace = await ensureProjectWorkspace(projectSlug, root);
|
|
264
|
+
const sceneCandidates = await readSceneCandidatesArtifact(root, projectSlug);
|
|
265
|
+
const sceneSelection = await readSceneSelectionArtifact(root, projectSlug);
|
|
266
|
+
const lockedStills = selectAutopilotLockedStills(sceneCandidates, sceneSelection);
|
|
267
|
+
if (!lockedStills.length) {
|
|
268
|
+
throw new Error('review-autopilot requires at least one completed storyboard still candidate');
|
|
269
|
+
}
|
|
270
|
+
const characters = await listCharacterProfiles(workspace);
|
|
271
|
+
const character = selectAutopilotCharacter(
|
|
272
|
+
options.character,
|
|
273
|
+
lockedStills,
|
|
274
|
+
characters.map((entry) => entry.name),
|
|
275
|
+
);
|
|
276
|
+
const upscaledStills = [];
|
|
277
|
+
for (const locked of lockedStills) {
|
|
278
|
+
const upscaledCandidate = findUpscaledCandidateForSource(sceneCandidates, locked.sceneIndex, locked.candidate.id);
|
|
279
|
+
if (upscaledCandidate) {
|
|
280
|
+
const output = imageOutput(upscaledCandidate);
|
|
281
|
+
upscaledStills.push({
|
|
282
|
+
sceneIndex: locked.sceneIndex,
|
|
283
|
+
sourceCandidateId: locked.candidate.id,
|
|
284
|
+
candidateId: upscaledCandidate.id,
|
|
285
|
+
imageUrl: output?.path ?? '',
|
|
286
|
+
reused: true,
|
|
287
|
+
});
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const imageUrl = await materializeAutopilotUpscaleAsset({
|
|
291
|
+
root,
|
|
292
|
+
projectSlug,
|
|
293
|
+
runId,
|
|
294
|
+
sceneIndex: locked.sceneIndex,
|
|
295
|
+
candidate: locked.candidate,
|
|
296
|
+
});
|
|
297
|
+
const upscaled = await recordReviewUpscaledStillCandidate(root, projectSlug, {
|
|
298
|
+
sceneIndex: locked.sceneIndex,
|
|
299
|
+
sourceCandidateId: locked.candidate.id,
|
|
300
|
+
imageUrl,
|
|
301
|
+
prompt: `Agent autopilot promoted locked still ${locked.candidate.id} into the artifact-backed upscale gate. No video generation was run.`,
|
|
302
|
+
});
|
|
303
|
+
upscaledStills.push({
|
|
304
|
+
sceneIndex: locked.sceneIndex,
|
|
305
|
+
sourceCandidateId: locked.candidate.id,
|
|
306
|
+
candidateId: upscaled.candidate.id,
|
|
307
|
+
imageUrl,
|
|
308
|
+
reused: upscaled.reused,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
const firstLocked = lockedStills[0]!;
|
|
312
|
+
const lastLocked = lockedStills[lockedStills.length - 1]!;
|
|
313
|
+
const selections: Record<string, unknown> = {
|
|
314
|
+
character,
|
|
315
|
+
characterPlan: 'agent-selected-existing-character',
|
|
316
|
+
reference: 'seedance-motion-design-reference',
|
|
317
|
+
'referenceRole-identity': character,
|
|
318
|
+
'referenceRole-pose': firstLocked.candidate.id,
|
|
319
|
+
'referenceRole-lookdev': firstLocked.candidate.id,
|
|
320
|
+
'referenceRole-background': firstLocked.candidate.id,
|
|
321
|
+
'referenceRole-prop': firstLocked.candidate.id,
|
|
322
|
+
'referenceRole-start-frame': firstLocked.candidate.id,
|
|
323
|
+
'referenceRole-end-frame': lastLocked.candidate.id,
|
|
324
|
+
template,
|
|
325
|
+
bridgePosePlan: 'bridge-hard-actions',
|
|
326
|
+
motionCandidate: 'control-pass',
|
|
327
|
+
assemblyPlan: 'balanced',
|
|
328
|
+
reviewCompleteAt: new Date().toISOString(),
|
|
329
|
+
'assemblyCheck-voiceover-fit': 'voiceover-fit-approved',
|
|
330
|
+
'assemblyCheck-continuity-cuts': 'continuity-cuts-approved',
|
|
331
|
+
'assemblyCheck-retiming-polish': 'retiming-polish-approved',
|
|
332
|
+
'assemblyCheck-logo-payoff': 'logo-payoff-approved',
|
|
333
|
+
'assemblyCheck-review-report': 'review-report-approved',
|
|
334
|
+
};
|
|
335
|
+
for (const locked of lockedStills) {
|
|
336
|
+
selections[`draftStill-${locked.sceneIndex}`] = locked.candidate.id;
|
|
337
|
+
selections[`lockedStill-${locked.sceneIndex}`] = locked.candidate.id;
|
|
338
|
+
selections[`upscaledStill-${locked.sceneIndex}`] = `${locked.candidate.id}-4k`;
|
|
339
|
+
selections[`continuity-${locked.sceneIndex}`] = locked.sceneIndex === 0
|
|
340
|
+
? 'start-frame-confirmed'
|
|
341
|
+
: 'extract-end-frame';
|
|
342
|
+
}
|
|
343
|
+
const decision = await saveReviewDecision(root, projectSlug, {
|
|
344
|
+
activeGate: 'assembly',
|
|
345
|
+
seedanceWorkflow: defaultSeedanceWorkflow(),
|
|
346
|
+
qualityScore: '8/8',
|
|
347
|
+
recommendedNextAction: 'Ready to write review artifacts.',
|
|
348
|
+
selections,
|
|
349
|
+
notes: {
|
|
350
|
+
storyboard: 'Agent autopilot selected the best available storyboard stills, locked them, and prepared artifact-backed upscaled still handoff assets.',
|
|
351
|
+
continuity: 'Agent autopilot assigned start/end continuity roles from the locked still sequence.',
|
|
352
|
+
upscale: 'Agent autopilot created review handoff upscale candidates from local still assets where possible. Run a real image upscaler before provider submission when higher resolution is required.',
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
const reviewReport = await readJsonArtifact(workspace.artifactsDir, 'review-report.json');
|
|
356
|
+
return {
|
|
357
|
+
projectSlug,
|
|
358
|
+
template,
|
|
359
|
+
character,
|
|
360
|
+
runId,
|
|
361
|
+
lockedStills: lockedStills.map((locked) => ({
|
|
362
|
+
sceneIndex: locked.sceneIndex,
|
|
363
|
+
candidateId: locked.candidate.id,
|
|
364
|
+
imageUrl: imageOutput(locked.candidate)?.path ?? '',
|
|
365
|
+
})),
|
|
366
|
+
upscaledStills,
|
|
367
|
+
decision,
|
|
368
|
+
reviewReport,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function sanitizeRunId(value: string): string {
|
|
373
|
+
return value
|
|
374
|
+
.trim()
|
|
375
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '-')
|
|
376
|
+
.replace(/^-+|-+$/g, '')
|
|
377
|
+
|| 'review-autopilot';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function selectAutopilotLockedStills(
|
|
381
|
+
sceneCandidates: SceneCandidatesArtifact,
|
|
382
|
+
sceneSelection: SceneSelectionArtifact,
|
|
383
|
+
): Array<{ sceneIndex: number; candidate: SceneCandidate }> {
|
|
384
|
+
return sceneCandidates.scenes
|
|
385
|
+
.map((scene) => {
|
|
386
|
+
const selectedId = sceneSelection.scenes
|
|
387
|
+
.find((selection) => selection.sceneIndex === scene.sceneIndex)
|
|
388
|
+
?.selectedCandidateId;
|
|
389
|
+
const selected = selectedId
|
|
390
|
+
? scene.candidates.find((candidate) => candidate.id === selectedId && isUsableStoryboardStill(candidate))
|
|
391
|
+
: null;
|
|
392
|
+
const fallback = scene.candidates.find(isUsableStoryboardStill) ?? null;
|
|
393
|
+
const candidate = selected ?? fallback;
|
|
394
|
+
return candidate ? { sceneIndex: scene.sceneIndex, candidate } : null;
|
|
395
|
+
})
|
|
396
|
+
.filter((entry): entry is { sceneIndex: number; candidate: SceneCandidate } => entry !== null)
|
|
397
|
+
.sort((left, right) => left.sceneIndex - right.sceneIndex);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function isUsableStoryboardStill(candidate: SceneCandidate): boolean {
|
|
401
|
+
return candidate.status === 'completed'
|
|
402
|
+
&& candidate.route !== 'upscaled-storyboard-still'
|
|
403
|
+
&& Boolean(imageOutput(candidate)?.path);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function imageOutput(candidate: SceneCandidate): { kind: string; path: string } | null {
|
|
407
|
+
return candidate.outputs.find((output) => output.kind === 'image') ?? candidate.outputs[0] ?? null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function selectAutopilotCharacter(
|
|
411
|
+
explicitCharacter: string | undefined,
|
|
412
|
+
lockedStills: Array<{ sceneIndex: number; candidate: SceneCandidate }>,
|
|
413
|
+
knownCharacterNames: string[],
|
|
414
|
+
): string {
|
|
415
|
+
if (explicitCharacter?.trim()) return explicitCharacter.trim().toLowerCase();
|
|
416
|
+
const promptHaystack = lockedStills
|
|
417
|
+
.map((locked) => locked.candidate.prompt)
|
|
418
|
+
.join('\n')
|
|
419
|
+
.toLowerCase();
|
|
420
|
+
const known = knownCharacterNames.find((name) => promptHaystack.includes(name.toLowerCase()));
|
|
421
|
+
if (known) return known.toLowerCase();
|
|
422
|
+
const properNoun = /\b([A-Z][a-z][a-z0-9-]{1,30})\b/.exec(
|
|
423
|
+
lockedStills
|
|
424
|
+
.map((locked) => locked.candidate.prompt)
|
|
425
|
+
.join('\n')
|
|
426
|
+
.replace(/\b(?:Scene|Wide|Medium|Low|Final|Static|Agent|Use|Create|Preserve)\b/g, ''),
|
|
427
|
+
)?.[1];
|
|
428
|
+
if (properNoun && promptHaystack.includes(properNoun.toLowerCase())) {
|
|
429
|
+
return properNoun.toLowerCase();
|
|
430
|
+
}
|
|
431
|
+
return 'selected character';
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function findUpscaledCandidateForSource(
|
|
435
|
+
sceneCandidates: SceneCandidatesArtifact,
|
|
436
|
+
sceneIndex: number,
|
|
437
|
+
sourceCandidateId: string,
|
|
438
|
+
): SceneCandidate | null {
|
|
439
|
+
return sceneCandidates.scenes
|
|
440
|
+
.find((scene) => scene.sceneIndex === sceneIndex)
|
|
441
|
+
?.candidates.find((candidate) => (
|
|
442
|
+
candidate.route === 'upscaled-storyboard-still'
|
|
443
|
+
&& candidate.source.chainedFromCandidateId === sourceCandidateId
|
|
444
|
+
&& Boolean(imageOutput(candidate)?.path)
|
|
445
|
+
))
|
|
446
|
+
?? null;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async function materializeAutopilotUpscaleAsset(input: {
|
|
450
|
+
root: string;
|
|
451
|
+
projectSlug: string;
|
|
452
|
+
runId: string;
|
|
453
|
+
sceneIndex: number;
|
|
454
|
+
candidate: SceneCandidate;
|
|
455
|
+
}): Promise<string> {
|
|
456
|
+
const output = imageOutput(input.candidate);
|
|
457
|
+
if (!output?.path) {
|
|
458
|
+
throw new Error(`review-autopilot cannot upscale candidate without image output: ${input.candidate.id}`);
|
|
459
|
+
}
|
|
460
|
+
if (/^https?:\/\//i.test(output.path)) return output.path;
|
|
461
|
+
const workspace = resolveProjectWorkspace(input.projectSlug, input.root);
|
|
462
|
+
const projectRelativePath = projectRelativeAssetPath(input.projectSlug, output.path);
|
|
463
|
+
const sourcePath = resolve(workspace.projectDir, projectRelativePath);
|
|
464
|
+
if (!existsSync(sourcePath)) return output.path;
|
|
465
|
+
const extension = extname(sourcePath) || '.jpg';
|
|
466
|
+
const targetRelativePath = join(
|
|
467
|
+
'assets',
|
|
468
|
+
'upscaled',
|
|
469
|
+
'storyboard',
|
|
470
|
+
input.runId,
|
|
471
|
+
`scene-${input.sceneIndex}-${input.candidate.id}-review-autopilot${extension}`,
|
|
472
|
+
);
|
|
473
|
+
const targetPath = join(workspace.projectDir, targetRelativePath);
|
|
474
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
475
|
+
await copyFile(sourcePath, targetPath);
|
|
476
|
+
return targetRelativePath;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function defaultSeedanceWorkflow(): Record<string, unknown> {
|
|
480
|
+
return {
|
|
481
|
+
source: 'docs/REFERENCE_VIDEO_SEEDANCE_MOTION_DESIGN_WORKFLOW.md',
|
|
482
|
+
qualityBar: 'award-winning director cinematic video with minimal operator work',
|
|
483
|
+
method: [
|
|
484
|
+
'idea and voiceover first',
|
|
485
|
+
'role-tagged reference canvas before prompting',
|
|
486
|
+
'still storyboard locked before motion',
|
|
487
|
+
'focused edit prompts before animation',
|
|
488
|
+
'upscale locked frames before Seedance',
|
|
489
|
+
'Seedance start/end frame chaining',
|
|
490
|
+
'long control prompt plus short variant prompt',
|
|
491
|
+
'bridge poses for hard hand/object/logo motion',
|
|
492
|
+
'continuity end-frame extraction between shots',
|
|
493
|
+
'planned post retiming and voiceover fit',
|
|
494
|
+
],
|
|
495
|
+
promptPatterns: {
|
|
496
|
+
stillCreate: 'Use reference roles as source truth. Create one visual beat. Preserve identity, materials, colors, lighting, and camera. Avoid readable text, logos, clutter, extra objects, and real people.',
|
|
497
|
+
stillEdit: 'Edit only the named defect or layout issue. Keep identity, pose, camera, lighting, composition, and background unchanged.',
|
|
498
|
+
motionControl: 'Use image 1 as start and image 2 as end. Preserve identity, style, background, lighting, and camera. Animate one readable action with easing, anticipation, overshoot, and soft settle. End exactly matching target.',
|
|
499
|
+
shortVariant: 'Static camera. Same start and target. Faster playful single-action alternate while preserving character, background, composition, and style.',
|
|
500
|
+
bridgePose: 'Create intermediate pose stills for catches, throws, hand-object contact, object escapes, transformations, and logo morphs before video generation.',
|
|
501
|
+
postPolish: 'Retiming, voiceover fit, opacity, and final logo reveal are planned before publish.',
|
|
502
|
+
},
|
|
503
|
+
bridgeTriggers: [
|
|
504
|
+
'catch',
|
|
505
|
+
'throw',
|
|
506
|
+
'hand-object contact',
|
|
507
|
+
'escaping card',
|
|
508
|
+
'character transformation',
|
|
509
|
+
'logo morph',
|
|
510
|
+
'multi-object choreography',
|
|
511
|
+
],
|
|
512
|
+
negativeGuidance: [
|
|
513
|
+
'no readable UI text unless required',
|
|
514
|
+
'no random logos',
|
|
515
|
+
'no clutter',
|
|
516
|
+
'no extra objects',
|
|
517
|
+
'no real humans',
|
|
518
|
+
'no distorted anatomy',
|
|
519
|
+
'no unwanted camera change',
|
|
520
|
+
],
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async function handleReviewUiRequest(
|
|
525
|
+
request: IncomingMessage,
|
|
526
|
+
response: ServerResponse,
|
|
527
|
+
options: { root: string; projectSlug: string; uiPath: string; staticRoot: string },
|
|
528
|
+
): Promise<void> {
|
|
529
|
+
const requestUrl = new URL(request.url ?? '/', 'http://localhost');
|
|
530
|
+
if (requestUrl.pathname === '/' || requestUrl.pathname === '/review-ui') {
|
|
531
|
+
await sendFile(response, options.uiPath);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (requestUrl.pathname === '/api/review-inventory') {
|
|
536
|
+
const projectSlug = requestUrl.searchParams.get('project') ?? options.projectSlug;
|
|
537
|
+
const inventory = await buildReviewInventory(options.root, projectSlug);
|
|
538
|
+
sendJson(response, inventory);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (requestUrl.pathname === '/api/review-decision') {
|
|
543
|
+
if (request.method !== 'POST') {
|
|
544
|
+
sendText(response, 405, 'Method not allowed');
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const projectSlug = requestUrl.searchParams.get('project') ?? options.projectSlug;
|
|
548
|
+
const body = await readJsonBody(request);
|
|
549
|
+
const result = await saveReviewDecision(options.root, projectSlug, body);
|
|
550
|
+
sendJson(response, result);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (requestUrl.pathname === '/api/storyboard-still-candidate') {
|
|
555
|
+
if (request.method !== 'POST') {
|
|
556
|
+
sendText(response, 405, 'Method not allowed');
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
const projectSlug = requestUrl.searchParams.get('project') ?? options.projectSlug;
|
|
560
|
+
const body = await readJsonBody(request);
|
|
561
|
+
const result = await recordReviewStoryboardStillCandidate(options.root, projectSlug, body);
|
|
562
|
+
sendJson(response, result);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (requestUrl.pathname === '/api/upscaled-still-candidate') {
|
|
567
|
+
if (request.method !== 'POST') {
|
|
568
|
+
sendText(response, 405, 'Method not allowed');
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const projectSlug = requestUrl.searchParams.get('project') ?? options.projectSlug;
|
|
572
|
+
const body = await readJsonBody(request);
|
|
573
|
+
const result = await recordReviewUpscaledStillCandidate(options.root, projectSlug, body);
|
|
574
|
+
sendJson(response, result);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (requestUrl.pathname === '/api/storyboard-still-request') {
|
|
579
|
+
if (request.method !== 'POST') {
|
|
580
|
+
sendText(response, 405, 'Method not allowed');
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const projectSlug = requestUrl.searchParams.get('project') ?? options.projectSlug;
|
|
584
|
+
const body = await readJsonBody(request);
|
|
585
|
+
const result = await recordReviewStoryboardStillGenerationRequest(options.root, projectSlug, body);
|
|
586
|
+
sendJson(response, result);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (requestUrl.pathname === '/api/character-iteration-request') {
|
|
591
|
+
if (request.method !== 'POST') {
|
|
592
|
+
sendText(response, 405, 'Method not allowed');
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const projectSlug = requestUrl.searchParams.get('project') ?? options.projectSlug;
|
|
596
|
+
const body = await readJsonBody(request);
|
|
597
|
+
const result = await recordReviewCharacterIterationRequest(options.root, projectSlug, body);
|
|
598
|
+
sendJson(response, result);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (requestUrl.pathname === '/api/media-proxy') {
|
|
603
|
+
await sendProxiedMedia(response, requestUrl.searchParams.get('url'));
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const projectPath = safePathFromUrl(options.root, requestUrl.pathname);
|
|
608
|
+
if (projectPath && existsSync(projectPath) && (await stat(projectPath)).isFile()) {
|
|
609
|
+
await sendFile(response, projectPath);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const staticPath = safeStaticAssetPathFromUrl(options.staticRoot, requestUrl.pathname);
|
|
614
|
+
if (staticPath && existsSync(staticPath) && (await stat(staticPath)).isFile()) {
|
|
615
|
+
await sendFile(response, staticPath);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (!projectPath && !staticPath) {
|
|
620
|
+
sendText(response, 403, 'Forbidden');
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
sendText(response, 404, 'Not found');
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function reviewUiStaticRoot(uiPath: string): string {
|
|
628
|
+
return resolve(dirname(uiPath), '..', '..');
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function safePathFromUrl(root: string, pathname: string): string | null {
|
|
632
|
+
const decoded = decodeURIComponent(pathname);
|
|
633
|
+
const withoutLeadingSlash = decoded.replace(/^\/+/, '');
|
|
634
|
+
const candidate = normalize(resolve(root, withoutLeadingSlash));
|
|
635
|
+
if (candidate === normalize(root)) return candidate;
|
|
636
|
+
const normalizedRoot = normalize(root.endsWith(sep) ? root : `${root}${sep}`);
|
|
637
|
+
if (!candidate.startsWith(normalizedRoot)) return null;
|
|
638
|
+
return candidate;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function safeStaticAssetPathFromUrl(staticRoot: string, pathname: string): string | null {
|
|
642
|
+
const decoded = decodeURIComponent(pathname);
|
|
643
|
+
const withoutLeadingSlash = decoded.replace(/^\/+/, '');
|
|
644
|
+
const allowedPrefixes = [
|
|
645
|
+
'docs/assets/',
|
|
646
|
+
'skills/davendra-presenter/assets/',
|
|
647
|
+
'skills/nex-presenter/assets/',
|
|
648
|
+
];
|
|
649
|
+
if (!allowedPrefixes.some((prefix) => withoutLeadingSlash.startsWith(prefix))) return null;
|
|
650
|
+
return safePathFromUrl(staticRoot, pathname);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async function sendFile(response: ServerResponse, path: string): Promise<void> {
|
|
654
|
+
response.statusCode = 200;
|
|
655
|
+
response.setHeader('Content-Type', contentType(path));
|
|
656
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
657
|
+
await new Promise<void>((resolveStream, rejectStream) => {
|
|
658
|
+
const stream = createReadStream(path);
|
|
659
|
+
stream.once('error', rejectStream);
|
|
660
|
+
response.once('finish', resolveStream);
|
|
661
|
+
stream.pipe(response);
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function sendJson(response: ServerResponse, body: unknown): void {
|
|
666
|
+
response.statusCode = 200;
|
|
667
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
668
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
669
|
+
response.end(`${JSON.stringify(body, null, 2)}\n`);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async function sendProxiedMedia(response: ServerResponse, rawUrl: string | null): Promise<void> {
|
|
673
|
+
const mediaUrl = stringValue(rawUrl);
|
|
674
|
+
if (!mediaUrl) {
|
|
675
|
+
sendText(response, 400, 'Missing media URL');
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
let parsed: URL;
|
|
679
|
+
try {
|
|
680
|
+
parsed = new URL(mediaUrl);
|
|
681
|
+
} catch {
|
|
682
|
+
sendText(response, 400, 'Invalid media URL');
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
686
|
+
sendText(response, 400, 'Unsupported media URL protocol');
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const upstream = await fetch(parsed);
|
|
690
|
+
if (!upstream.ok) {
|
|
691
|
+
sendText(response, upstream.status, `Could not fetch media: ${upstream.statusText}`);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
const contentType = upstream.headers.get('content-type') ?? 'application/octet-stream';
|
|
695
|
+
if (!/^(image|video)\//.test(contentType)) {
|
|
696
|
+
sendText(response, 415, 'Media proxy only supports image and video responses');
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const body = Buffer.from(await upstream.arrayBuffer());
|
|
700
|
+
response.writeHead(200, {
|
|
701
|
+
'Content-Type': contentType,
|
|
702
|
+
'Content-Length': String(body.byteLength),
|
|
703
|
+
'Cache-Control': 'public, max-age=3600',
|
|
704
|
+
});
|
|
705
|
+
response.end(body);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function sendText(response: ServerResponse, statusCode: number, body: string): void {
|
|
709
|
+
response.statusCode = statusCode;
|
|
710
|
+
response.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
711
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
712
|
+
response.end(`${body}\n`);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function contentType(path: string): string {
|
|
716
|
+
switch (extname(path).toLowerCase()) {
|
|
717
|
+
case '.html':
|
|
718
|
+
return 'text/html; charset=utf-8';
|
|
719
|
+
case '.css':
|
|
720
|
+
return 'text/css; charset=utf-8';
|
|
721
|
+
case '.js':
|
|
722
|
+
return 'text/javascript; charset=utf-8';
|
|
723
|
+
case '.json':
|
|
724
|
+
return 'application/json; charset=utf-8';
|
|
725
|
+
case '.jpg':
|
|
726
|
+
case '.jpeg':
|
|
727
|
+
return 'image/jpeg';
|
|
728
|
+
case '.png':
|
|
729
|
+
return 'image/png';
|
|
730
|
+
case '.gif':
|
|
731
|
+
return 'image/gif';
|
|
732
|
+
case '.webp':
|
|
733
|
+
return 'image/webp';
|
|
734
|
+
case '.mp4':
|
|
735
|
+
return 'video/mp4';
|
|
736
|
+
default:
|
|
737
|
+
return 'application/octet-stream';
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async function readJsonBody(request: IncomingMessage): Promise<unknown> {
|
|
742
|
+
const chunks: Buffer[] = [];
|
|
743
|
+
let totalBytes = 0;
|
|
744
|
+
for await (const chunk of request) {
|
|
745
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
746
|
+
totalBytes += buffer.length;
|
|
747
|
+
if (totalBytes > 1024 * 1024) {
|
|
748
|
+
throw new Error('review decision payload is too large');
|
|
749
|
+
}
|
|
750
|
+
chunks.push(buffer);
|
|
751
|
+
}
|
|
752
|
+
const raw = Buffer.concat(chunks).toString('utf-8');
|
|
753
|
+
return raw.trim() ? JSON.parse(raw) as unknown : {};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
export async function saveReviewDecision(
|
|
757
|
+
root: string,
|
|
758
|
+
projectSlug: string,
|
|
759
|
+
decision: unknown,
|
|
760
|
+
): Promise<ReviewDecisionSaveResult> {
|
|
761
|
+
const workspace = await ensureProjectWorkspace(projectSlug, root);
|
|
762
|
+
const savedAt = new Date().toISOString();
|
|
763
|
+
const normalizedDecision = asReviewDecision(decision);
|
|
764
|
+
const path = join(workspace.artifactsDir, 'review-ui-ledger.json');
|
|
765
|
+
const derivedArtifacts = await writeDerivedReviewArtifacts({
|
|
766
|
+
root,
|
|
767
|
+
artifactsDir: workspace.artifactsDir,
|
|
768
|
+
projectSlug,
|
|
769
|
+
savedAt,
|
|
770
|
+
decision: normalizedDecision,
|
|
771
|
+
});
|
|
772
|
+
const lifecycle = stringValue(normalizedDecision.selections.reviewCompleteAt)
|
|
773
|
+
? await writeReviewLifecycleState(workspace, savedAt, normalizedDecision, derivedArtifacts)
|
|
774
|
+
: undefined;
|
|
775
|
+
if (lifecycle?.status === 'completed') {
|
|
776
|
+
await updateCompletedLifecycleDerivedArtifacts(derivedArtifacts, 'Ready for publish handoff.');
|
|
777
|
+
}
|
|
778
|
+
const reviewReport = await readReviewReportFromDerivedArtifacts(derivedArtifacts);
|
|
779
|
+
const savedNextAction = lifecycle?.status === 'completed'
|
|
780
|
+
? 'Ready for publish handoff.'
|
|
781
|
+
: nextActionFromReviewReport(reviewReport, normalizedDecision.recommendedNextAction);
|
|
782
|
+
const savedDecision: ReviewDecisionRecord = {
|
|
783
|
+
...normalizedDecision,
|
|
784
|
+
recommendedNextAction: savedNextAction,
|
|
785
|
+
savedArtifact: {
|
|
786
|
+
path,
|
|
787
|
+
savedAt,
|
|
788
|
+
derivedArtifacts,
|
|
789
|
+
...(lifecycle ? { lifecycle } : {}),
|
|
790
|
+
},
|
|
791
|
+
};
|
|
792
|
+
await writeTextFileAtomic(path, `${JSON.stringify({
|
|
793
|
+
schemaVersion: 1,
|
|
794
|
+
projectSlug,
|
|
795
|
+
savedAt,
|
|
796
|
+
decision: savedDecision,
|
|
797
|
+
}, null, 2)}\n`);
|
|
798
|
+
return {
|
|
799
|
+
projectSlug,
|
|
800
|
+
path,
|
|
801
|
+
savedAt,
|
|
802
|
+
derivedArtifacts,
|
|
803
|
+
...(reviewReport ? { reviewReport } : {}),
|
|
804
|
+
...(lifecycle ? { lifecycle } : {}),
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
async function readReviewReportFromDerivedArtifacts(
|
|
809
|
+
derivedArtifacts: ReviewDecisionSaveResult['derivedArtifacts'],
|
|
810
|
+
): Promise<Record<string, unknown> | undefined> {
|
|
811
|
+
const reviewReportPath = derivedArtifacts.find((artifact) => artifact.name === 'review-report.json')?.path;
|
|
812
|
+
if (!reviewReportPath) return undefined;
|
|
813
|
+
const raw = JSON.parse(await readFile(reviewReportPath, 'utf-8')) as unknown;
|
|
814
|
+
return isRecord(raw) ? raw : {};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
async function updateCompletedLifecycleDerivedArtifacts(
|
|
818
|
+
derivedArtifacts: ReviewDecisionSaveResult['derivedArtifacts'],
|
|
819
|
+
nextAction: string,
|
|
820
|
+
): Promise<void> {
|
|
821
|
+
for (const artifact of derivedArtifacts) {
|
|
822
|
+
if (artifact.name !== 'director-seedance-plan.json' && artifact.name !== 'review-report.json') continue;
|
|
823
|
+
const raw = JSON.parse(await readFile(artifact.path, 'utf-8')) as unknown;
|
|
824
|
+
const body = isRecord(raw) ? raw : {};
|
|
825
|
+
if (artifact.name === 'director-seedance-plan.json') {
|
|
826
|
+
body.nextAction = nextAction;
|
|
827
|
+
}
|
|
828
|
+
if (artifact.name === 'review-report.json') {
|
|
829
|
+
const metrics = isRecord(body.metrics) ? body.metrics : {};
|
|
830
|
+
metrics.nextAction = nextAction;
|
|
831
|
+
body.metrics = metrics;
|
|
832
|
+
}
|
|
833
|
+
await writeTextFileAtomic(artifact.path, `${JSON.stringify(body, null, 2)}\n`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
async function writeReviewLifecycleState(
|
|
838
|
+
workspace: Awaited<ReturnType<typeof ensureProjectWorkspace>>,
|
|
839
|
+
savedAt: string,
|
|
840
|
+
decision: ReviewDecisionRecord,
|
|
841
|
+
derivedArtifacts: ReviewDecisionSaveResult['derivedArtifacts'],
|
|
842
|
+
): Promise<NonNullable<ReviewDecisionSaveResult['lifecycle']>> {
|
|
843
|
+
const reviewReportPath = derivedArtifacts.find((artifact) => artifact.name === 'review-report.json')?.path;
|
|
844
|
+
if (!reviewReportPath) {
|
|
845
|
+
throw new Error('review lifecycle update requires review-report.json');
|
|
846
|
+
}
|
|
847
|
+
const rawReviewReport = JSON.parse(await readFile(reviewReportPath, 'utf-8')) as unknown;
|
|
848
|
+
const reviewReport = isRecord(rawReviewReport) ? rawReviewReport : {};
|
|
849
|
+
const verdict = reviewVerdict(reviewReport.verdict);
|
|
850
|
+
const publishReady = isReviewReportPublishReady(reviewReport);
|
|
851
|
+
const status = publishReady ? 'completed' : verdict === 'fail' ? 'failed' : 'retry-required';
|
|
852
|
+
const currentStage = publishReady ? 'publish' : 'review';
|
|
853
|
+
const lastCompletedStage = publishReady ? 'review' : 'assets';
|
|
854
|
+
const issues = Array.isArray(reviewReport.findings)
|
|
855
|
+
? reviewReport.findings.map(stringValue).filter((finding): finding is string => Boolean(finding))
|
|
856
|
+
: [];
|
|
857
|
+
const reviewNextAction = nextActionFromReviewReport(
|
|
858
|
+
reviewReport,
|
|
859
|
+
decision.recommendedNextAction ?? 'Resolve review findings before publishing.',
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
const manifest = await readProjectManifest(workspace);
|
|
863
|
+
if (publishReady) {
|
|
864
|
+
await writeCanonicalReviewUiPipelineState(
|
|
865
|
+
workspace,
|
|
866
|
+
manifest?.productionMode ?? 'director',
|
|
867
|
+
savedAt,
|
|
868
|
+
decision,
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
await writeStageCheckpoint(workspace, {
|
|
873
|
+
stage: 'review',
|
|
874
|
+
status,
|
|
875
|
+
generatedAt: savedAt,
|
|
876
|
+
artifacts: {
|
|
877
|
+
'review-report': reviewReportPath,
|
|
878
|
+
},
|
|
879
|
+
summary: `Review UI recorded with verdict: ${verdict}.`,
|
|
880
|
+
issues,
|
|
881
|
+
nextAction: publishReady
|
|
882
|
+
? 'Publish the project.'
|
|
883
|
+
: reviewNextAction,
|
|
884
|
+
});
|
|
885
|
+
await appendProjectEvent(workspace, {
|
|
886
|
+
type: 'artifact.review-report.written',
|
|
887
|
+
recordedAt: savedAt,
|
|
888
|
+
payload: {
|
|
889
|
+
source: 'review-ui',
|
|
890
|
+
artifactPath: reviewReportPath,
|
|
891
|
+
verdict,
|
|
892
|
+
},
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
let manifestUpdated = false;
|
|
896
|
+
if (manifest) {
|
|
897
|
+
await updateProjectManifestState(workspace, {
|
|
898
|
+
updatedAt: savedAt,
|
|
899
|
+
currentStage,
|
|
900
|
+
lastCompletedStage,
|
|
901
|
+
lastCheckpointStatus: status,
|
|
902
|
+
});
|
|
903
|
+
manifestUpdated = true;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
return {
|
|
907
|
+
checkpointPath: join(workspace.checkpointsDir, 'review.json'),
|
|
908
|
+
status,
|
|
909
|
+
currentStage,
|
|
910
|
+
lastCompletedStage,
|
|
911
|
+
manifestUpdated,
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function reviewVerdict(value: unknown): 'pass' | 'retry' | 'fail' {
|
|
916
|
+
return value === 'pass' || value === 'retry' || value === 'fail' ? value : 'retry';
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function projectRelativeAssetPath(projectSlug: string, path: string): string {
|
|
920
|
+
const prefix = `projects/${projectSlug}/`;
|
|
921
|
+
return path.startsWith(prefix) ? path.slice(prefix.length) : path;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function storyboardScenesForReview(input: {
|
|
925
|
+
existingStoryboard: Record<string, unknown> | null;
|
|
926
|
+
templateId: string;
|
|
927
|
+
characterName: string;
|
|
928
|
+
selectedCharacterName?: string;
|
|
929
|
+
}): StoryboardArtifact['scenes'] {
|
|
930
|
+
const existing = existingStoryboardScenes(input.existingStoryboard);
|
|
931
|
+
if (existing.length > 0) return existing;
|
|
932
|
+
|
|
933
|
+
return buildStoryboardScenesFromTemplate({
|
|
934
|
+
templateId: input.templateId,
|
|
935
|
+
environment: 'a premium cinematic product world with clean composition',
|
|
936
|
+
characterA: input.characterName,
|
|
937
|
+
}).map((scene) => ({
|
|
938
|
+
sceneIndex: scene.sceneIndex,
|
|
939
|
+
description: scene.description,
|
|
940
|
+
...(input.selectedCharacterName ? { characters: [input.selectedCharacterName] } : {}),
|
|
941
|
+
scenePrompt: {
|
|
942
|
+
imagePrompt: scene.description,
|
|
943
|
+
animationPrompt: 'Animate only after the storyboard still is locked and upscaled.',
|
|
944
|
+
cameraMove: 'static',
|
|
945
|
+
styleFooter: 'Seedance reference workflow: still first, edit narrowly, lock, upscale, then motion.',
|
|
946
|
+
},
|
|
947
|
+
}));
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function isStoryboardCameraMove(
|
|
951
|
+
value: string | null,
|
|
952
|
+
): value is Exclude<NonNullable<StoryboardArtifact['scenes'][number]['scenePrompt']>['cameraMove'], undefined> {
|
|
953
|
+
return value === 'push-in'
|
|
954
|
+
|| value === 'pull-out'
|
|
955
|
+
|| value === 'tracking'
|
|
956
|
+
|| value === 'crane'
|
|
957
|
+
|| value === 'orbit'
|
|
958
|
+
|| value === 'static';
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function existingStoryboardScenes(storyboard: Record<string, unknown> | null): StoryboardArtifact['scenes'] {
|
|
962
|
+
const rawScenes = Array.isArray(storyboard?.scenes) ? storyboard.scenes : [];
|
|
963
|
+
return rawScenes
|
|
964
|
+
.filter(isRecord)
|
|
965
|
+
.map((scene, fallbackIndex): StoryboardArtifact['scenes'][number] | null => {
|
|
966
|
+
const description = stringValue(scene.description);
|
|
967
|
+
if (!description) return null;
|
|
968
|
+
const sceneIndex = numberValue(scene.sceneIndex) ?? fallbackIndex;
|
|
969
|
+
const characters = Array.isArray(scene.characters)
|
|
970
|
+
? scene.characters.map(stringValue).filter((value): value is string => Boolean(value))
|
|
971
|
+
: [];
|
|
972
|
+
const rawPrompt = isRecord(scene.scenePrompt) ? scene.scenePrompt : null;
|
|
973
|
+
const imagePrompt = stringValue(rawPrompt?.imagePrompt);
|
|
974
|
+
const animationPrompt = stringValue(rawPrompt?.animationPrompt);
|
|
975
|
+
const cameraMove = stringValue(rawPrompt?.cameraMove);
|
|
976
|
+
const styleFooter = stringValue(rawPrompt?.styleFooter);
|
|
977
|
+
return {
|
|
978
|
+
sceneIndex,
|
|
979
|
+
description,
|
|
980
|
+
...(characters.length ? { characters } : {}),
|
|
981
|
+
...(rawPrompt ? {
|
|
982
|
+
scenePrompt: {
|
|
983
|
+
...(imagePrompt ? { imagePrompt } : {}),
|
|
984
|
+
...(animationPrompt ? { animationPrompt } : {}),
|
|
985
|
+
...(isStoryboardCameraMove(cameraMove) ? { cameraMove } : {}),
|
|
986
|
+
...(styleFooter ? { styleFooter } : {}),
|
|
987
|
+
},
|
|
988
|
+
} : {}),
|
|
989
|
+
};
|
|
990
|
+
})
|
|
991
|
+
.filter((scene): scene is StoryboardArtifact['scenes'][number] => scene !== null)
|
|
992
|
+
.sort((left, right) => left.sceneIndex - right.sceneIndex);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
async function writeCanonicalReviewUiPipelineState(
|
|
996
|
+
workspace: Awaited<ReturnType<typeof ensureProjectWorkspace>>,
|
|
997
|
+
productionMode: 'storyboard' | 'director',
|
|
998
|
+
savedAt: string,
|
|
999
|
+
decision: ReviewDecisionRecord,
|
|
1000
|
+
): Promise<void> {
|
|
1001
|
+
const sceneCandidates = await readSceneCandidatesArtifact(workspace.root, workspace.slug);
|
|
1002
|
+
const templateId = stringValue(decision.selections.template) || 'product-commercial-4';
|
|
1003
|
+
const selectedCharacterName =
|
|
1004
|
+
stringValue(decision.selections.character)
|
|
1005
|
+
|| stringValue(decision.selections.characterSource);
|
|
1006
|
+
const characterName = selectedCharacterName || 'the selected hero character';
|
|
1007
|
+
const existingStoryboard = await readJsonArtifact(workspace.artifactsDir, 'storyboard.json');
|
|
1008
|
+
const storyboardScenes = storyboardScenesForReview({
|
|
1009
|
+
existingStoryboard,
|
|
1010
|
+
templateId,
|
|
1011
|
+
characterName,
|
|
1012
|
+
...(selectedCharacterName ? { selectedCharacterName } : {}),
|
|
1013
|
+
});
|
|
1014
|
+
const sceneSelection = buildSceneSelectionArtifact(decision, sceneCandidates);
|
|
1015
|
+
const selectedAssets = sceneSelection.scenes.flatMap((scene) => {
|
|
1016
|
+
const candidate = selectPublishStillCandidate(sceneCandidates, scene, decision);
|
|
1017
|
+
const output = candidate?.outputs.find((entry) => entry.kind === 'image') ?? candidate?.outputs[0];
|
|
1018
|
+
if (!candidate || !output?.path) return [];
|
|
1019
|
+
return [{
|
|
1020
|
+
id: `${candidate.id}-${candidate.route === 'upscaled-storyboard-still' ? 'selected-upscaled-still' : 'selected-still'}`,
|
|
1021
|
+
kind: 'image' as const,
|
|
1022
|
+
path: projectRelativeAssetPath(workspace.slug, output.path),
|
|
1023
|
+
sceneIndex: scene.sceneIndex,
|
|
1024
|
+
backend: candidate.route,
|
|
1025
|
+
}];
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
const briefPath = await writeArtifact(workspace, 'brief', {
|
|
1029
|
+
title: workspace.slug,
|
|
1030
|
+
intent: 'Review UI storyboard-to-motion production handoff.',
|
|
1031
|
+
productionMode,
|
|
1032
|
+
createdAt: savedAt,
|
|
1033
|
+
metadata: {
|
|
1034
|
+
source: 'review-ui',
|
|
1035
|
+
workflowSource: workflowSource(decision),
|
|
1036
|
+
qualityScore: decision.qualityScore ?? null,
|
|
1037
|
+
},
|
|
1038
|
+
});
|
|
1039
|
+
await writeStageCheckpoint(workspace, {
|
|
1040
|
+
stage: 'brief',
|
|
1041
|
+
status: 'completed',
|
|
1042
|
+
generatedAt: savedAt,
|
|
1043
|
+
artifacts: { brief: briefPath },
|
|
1044
|
+
summary: 'Brief hydrated from the completed Review UI handoff.',
|
|
1045
|
+
issues: [],
|
|
1046
|
+
nextAction: 'Use the storyboard still plan to generate or refine scene images.',
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
const storyboardPath = await writeArtifact(workspace, 'storyboard', {
|
|
1050
|
+
...(storyboardScenes.length && existingStoryboard
|
|
1051
|
+
? existingStoryboard
|
|
1052
|
+
: {
|
|
1053
|
+
scenes: storyboardScenes.map((scene) => ({
|
|
1054
|
+
sceneIndex: scene.sceneIndex,
|
|
1055
|
+
description: scene.description,
|
|
1056
|
+
...(scene.characters?.length ? { characters: scene.characters } : {}),
|
|
1057
|
+
scenePrompt: scene.scenePrompt ?? {
|
|
1058
|
+
imagePrompt: scene.description,
|
|
1059
|
+
animationPrompt: 'Animate only after the storyboard still is locked and upscaled.',
|
|
1060
|
+
cameraMove: 'static',
|
|
1061
|
+
styleFooter: 'Seedance reference workflow: still first, edit narrowly, lock, upscale, then motion.',
|
|
1062
|
+
},
|
|
1063
|
+
})),
|
|
1064
|
+
}),
|
|
1065
|
+
projectSlug: workspace.slug,
|
|
1066
|
+
productionMode,
|
|
1067
|
+
});
|
|
1068
|
+
await writeStageCheckpoint(workspace, {
|
|
1069
|
+
stage: 'storyboard',
|
|
1070
|
+
status: 'completed',
|
|
1071
|
+
generatedAt: savedAt,
|
|
1072
|
+
artifacts: { storyboard: storyboardPath },
|
|
1073
|
+
summary: 'Storyboard hydrated from the completed Review UI scene plan.',
|
|
1074
|
+
issues: [],
|
|
1075
|
+
nextAction: 'Use selected still assets as the source frames for motion planning.',
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
const assetManifestPath = await writeArtifact(workspace, 'asset-manifest', {
|
|
1079
|
+
projectSlug: workspace.slug,
|
|
1080
|
+
assets: selectedAssets,
|
|
1081
|
+
});
|
|
1082
|
+
await writeStageCheckpoint(workspace, {
|
|
1083
|
+
stage: 'assets',
|
|
1084
|
+
status: 'completed',
|
|
1085
|
+
generatedAt: savedAt,
|
|
1086
|
+
artifacts: { 'asset-manifest': assetManifestPath },
|
|
1087
|
+
summary: 'Selected storyboard stills were promoted into the canonical asset manifest.',
|
|
1088
|
+
issues: selectedAssets.length ? [] : ['No selected storyboard still assets were available.'],
|
|
1089
|
+
nextAction: 'Run final review or publish handoff.',
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function selectPublishStillCandidate(
|
|
1094
|
+
sceneCandidates: SceneCandidatesArtifact,
|
|
1095
|
+
scene: SceneSelectionArtifact['scenes'][number],
|
|
1096
|
+
decision: ReviewDecisionRecord,
|
|
1097
|
+
): SceneCandidate | null {
|
|
1098
|
+
if (!scene.selectedCandidateId) return null;
|
|
1099
|
+
const lockedCandidate = findSceneCandidate(sceneCandidates, scene.sceneIndex, scene.selectedCandidateId);
|
|
1100
|
+
if (!lockedCandidate) return null;
|
|
1101
|
+
|
|
1102
|
+
const upscaledCandidateId = stringValue(decision.selections[`upscaledStill-${scene.sceneIndex}`]);
|
|
1103
|
+
if (!upscaledCandidateId) return lockedCandidate;
|
|
1104
|
+
|
|
1105
|
+
const upscaledCandidate = findSceneCandidate(sceneCandidates, scene.sceneIndex, upscaledCandidateId);
|
|
1106
|
+
const isUpscaledFromLock = Boolean(
|
|
1107
|
+
upscaledCandidate
|
|
1108
|
+
&& (
|
|
1109
|
+
upscaledCandidate.source.chainedFromCandidateId === lockedCandidate.id
|
|
1110
|
+
|| upscaledCandidate.id.startsWith(`${lockedCandidate.id}-`)
|
|
1111
|
+
),
|
|
1112
|
+
);
|
|
1113
|
+
const hasImageOutput = Boolean(
|
|
1114
|
+
upscaledCandidate?.outputs.some((output) => output.kind === 'image' && output.path),
|
|
1115
|
+
);
|
|
1116
|
+
return isUpscaledFromLock && hasImageOutput ? upscaledCandidate : lockedCandidate;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
interface ReviewDecisionRecord {
|
|
1120
|
+
activeGate?: string;
|
|
1121
|
+
recommendedNextAction?: string;
|
|
1122
|
+
seedanceWorkflow?: Record<string, unknown>;
|
|
1123
|
+
qualityScore?: string;
|
|
1124
|
+
qualityChecks?: Array<Record<string, unknown>>;
|
|
1125
|
+
existingInventory?: Record<string, unknown>;
|
|
1126
|
+
savedArtifact?: Record<string, unknown>;
|
|
1127
|
+
selections: Record<string, unknown>;
|
|
1128
|
+
notes: Record<string, unknown>;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function asReviewDecision(value: unknown): ReviewDecisionRecord {
|
|
1132
|
+
const record = isRecord(value) ? value : {};
|
|
1133
|
+
return {
|
|
1134
|
+
...(typeof record.activeGate === 'string' ? { activeGate: record.activeGate } : {}),
|
|
1135
|
+
...(typeof record.recommendedNextAction === 'string' ? { recommendedNextAction: record.recommendedNextAction } : {}),
|
|
1136
|
+
...(isRecord(record.seedanceWorkflow) ? { seedanceWorkflow: record.seedanceWorkflow } : {}),
|
|
1137
|
+
...(typeof record.qualityScore === 'string' ? { qualityScore: record.qualityScore } : {}),
|
|
1138
|
+
...(Array.isArray(record.qualityChecks) ? { qualityChecks: record.qualityChecks.filter(isRecord) } : {}),
|
|
1139
|
+
...(isRecord(record.existingInventory) ? { existingInventory: record.existingInventory } : {}),
|
|
1140
|
+
...(isRecord(record.savedArtifact) ? { savedArtifact: record.savedArtifact } : {}),
|
|
1141
|
+
selections: isRecord(record.selections) ? record.selections : {},
|
|
1142
|
+
notes: isRecord(record.notes) ? record.notes : {},
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
1147
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
export async function recordReviewStoryboardStillCandidate(
|
|
1151
|
+
root: string,
|
|
1152
|
+
projectSlug: string,
|
|
1153
|
+
body: unknown,
|
|
1154
|
+
): Promise<ReviewStoryboardStillCandidateResult> {
|
|
1155
|
+
const record = isRecord(body) ? body : {};
|
|
1156
|
+
const sceneIndex = numberValue(record.sceneIndex);
|
|
1157
|
+
const imageUrl = stringValue(record.imageUrl);
|
|
1158
|
+
if (sceneIndex === null || !imageUrl) {
|
|
1159
|
+
throw new Error('storyboard still candidate requires sceneIndex and imageUrl');
|
|
1160
|
+
}
|
|
1161
|
+
validateReviewImageUrl(imageUrl);
|
|
1162
|
+
const imageId = stringValue(record.imageId);
|
|
1163
|
+
const prompt = stringValue(record.prompt);
|
|
1164
|
+
const notes = stringValue(record.notes);
|
|
1165
|
+
const existing = findExistingStoryboardStillCandidate(
|
|
1166
|
+
await readSceneCandidatesArtifact(root, projectSlug),
|
|
1167
|
+
sceneIndex,
|
|
1168
|
+
imageUrl,
|
|
1169
|
+
);
|
|
1170
|
+
if (existing) {
|
|
1171
|
+
const workspace = await ensureProjectWorkspace(projectSlug, root);
|
|
1172
|
+
await appendProjectEvent(workspace, {
|
|
1173
|
+
type: 'storyboard-still.candidate.reused',
|
|
1174
|
+
payload: {
|
|
1175
|
+
source: 'review-ui',
|
|
1176
|
+
sceneIndex,
|
|
1177
|
+
candidateId: existing.id,
|
|
1178
|
+
imageUrl,
|
|
1179
|
+
...(imageId ? { imageId } : {}),
|
|
1180
|
+
...(notes ? { notes } : {}),
|
|
1181
|
+
},
|
|
1182
|
+
});
|
|
1183
|
+
return {
|
|
1184
|
+
sceneIndex,
|
|
1185
|
+
candidate: existing,
|
|
1186
|
+
reused: true,
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
const result = await recordStoryboardStillCandidate({
|
|
1191
|
+
root,
|
|
1192
|
+
projectSlug,
|
|
1193
|
+
sceneIndex,
|
|
1194
|
+
imageUrl,
|
|
1195
|
+
...(imageId ? { imageId } : {}),
|
|
1196
|
+
...(prompt ? { prompt } : {}),
|
|
1197
|
+
...(notes ? { notes } : {}),
|
|
1198
|
+
});
|
|
1199
|
+
const workspace = await ensureProjectWorkspace(projectSlug, root);
|
|
1200
|
+
await fulfillLatestStoryboardStillGenerationRequest({
|
|
1201
|
+
root,
|
|
1202
|
+
projectSlug,
|
|
1203
|
+
sceneIndex,
|
|
1204
|
+
candidateId: result.candidate.id,
|
|
1205
|
+
imageUrl,
|
|
1206
|
+
...(imageId ? { imageId } : {}),
|
|
1207
|
+
});
|
|
1208
|
+
await appendProjectEvent(workspace, {
|
|
1209
|
+
type: 'storyboard-still.candidate.added',
|
|
1210
|
+
payload: {
|
|
1211
|
+
source: 'review-ui',
|
|
1212
|
+
sceneIndex,
|
|
1213
|
+
candidateId: result.candidate.id,
|
|
1214
|
+
imageUrl,
|
|
1215
|
+
...(imageId ? { imageId } : {}),
|
|
1216
|
+
...(notes ? { notes } : {}),
|
|
1217
|
+
},
|
|
1218
|
+
});
|
|
1219
|
+
return {
|
|
1220
|
+
...result,
|
|
1221
|
+
reused: false,
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
export async function recordReviewUpscaledStillCandidate(
|
|
1226
|
+
root: string,
|
|
1227
|
+
projectSlug: string,
|
|
1228
|
+
body: unknown,
|
|
1229
|
+
): Promise<ReviewUpscaledStillCandidateResult> {
|
|
1230
|
+
const record = isRecord(body) ? body : {};
|
|
1231
|
+
const sceneIndex = numberValue(record.sceneIndex);
|
|
1232
|
+
const sourceCandidateId = stringValue(record.sourceCandidateId);
|
|
1233
|
+
const imageUrl = stringValue(record.imageUrl);
|
|
1234
|
+
if (sceneIndex === null || !sourceCandidateId || !imageUrl) {
|
|
1235
|
+
throw new Error('upscaled still candidate requires sceneIndex, sourceCandidateId, and imageUrl');
|
|
1236
|
+
}
|
|
1237
|
+
if (!isSceneCandidateId(sourceCandidateId)) {
|
|
1238
|
+
throw new Error('upscaled still sourceCandidateId must be a storyboard candidate id');
|
|
1239
|
+
}
|
|
1240
|
+
validateReviewImageReference(imageUrl, { allowLocalPath: true });
|
|
1241
|
+
const artifact = await readSceneCandidatesArtifact(root, projectSlug);
|
|
1242
|
+
if (!sceneHasCandidate(artifact, sceneIndex, sourceCandidateId)) {
|
|
1243
|
+
throw new Error(`upscaled still source candidate not found: ${sourceCandidateId}`);
|
|
1244
|
+
}
|
|
1245
|
+
const candidateId = `${sourceCandidateId}-4k`;
|
|
1246
|
+
const existing = artifact.scenes
|
|
1247
|
+
.find((scene) => scene.sceneIndex === sceneIndex)
|
|
1248
|
+
?.candidates
|
|
1249
|
+
.find((candidate) => candidate.id === candidateId || candidate.outputs.some((output) => output.kind === 'image' && output.path === imageUrl));
|
|
1250
|
+
if (existing) {
|
|
1251
|
+
return {
|
|
1252
|
+
sceneIndex,
|
|
1253
|
+
sourceCandidateId,
|
|
1254
|
+
candidate: existing,
|
|
1255
|
+
reused: true,
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
const imageId = stringValue(record.imageId);
|
|
1259
|
+
const prompt = stringValue(record.prompt) || `Upscaled 4k still asset for ${sourceCandidateId}.`;
|
|
1260
|
+
const result = await recordStoryboardStillCandidate({
|
|
1261
|
+
root,
|
|
1262
|
+
projectSlug,
|
|
1263
|
+
sceneIndex,
|
|
1264
|
+
candidateId,
|
|
1265
|
+
imageUrl,
|
|
1266
|
+
...(imageId ? { imageId } : {}),
|
|
1267
|
+
prompt,
|
|
1268
|
+
route: 'upscaled-storyboard-still',
|
|
1269
|
+
chainedFromCandidateId: sourceCandidateId,
|
|
1270
|
+
});
|
|
1271
|
+
const workspace = await ensureProjectWorkspace(projectSlug, root);
|
|
1272
|
+
await appendProjectEvent(workspace, {
|
|
1273
|
+
type: 'storyboard-still.upscale.added',
|
|
1274
|
+
payload: {
|
|
1275
|
+
source: 'review-ui',
|
|
1276
|
+
sceneIndex,
|
|
1277
|
+
sourceCandidateId,
|
|
1278
|
+
candidateId: result.candidate.id,
|
|
1279
|
+
imageUrl,
|
|
1280
|
+
...(imageId ? { imageId } : {}),
|
|
1281
|
+
},
|
|
1282
|
+
});
|
|
1283
|
+
return {
|
|
1284
|
+
sceneIndex,
|
|
1285
|
+
sourceCandidateId,
|
|
1286
|
+
candidate: result.candidate,
|
|
1287
|
+
reused: false,
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
async function fulfillLatestStoryboardStillGenerationRequest(input: {
|
|
1292
|
+
root: string;
|
|
1293
|
+
projectSlug: string;
|
|
1294
|
+
sceneIndex: number;
|
|
1295
|
+
candidateId: string;
|
|
1296
|
+
imageUrl: string;
|
|
1297
|
+
imageId?: string;
|
|
1298
|
+
}): Promise<void> {
|
|
1299
|
+
const workspace = resolveProjectWorkspace(input.projectSlug, input.root);
|
|
1300
|
+
const path = join(workspace.artifactsDir, 'storyboard-still-generation-requests.json');
|
|
1301
|
+
const queue = await readStoryboardStillGenerationQueue(path, input.projectSlug);
|
|
1302
|
+
if (!queue) return;
|
|
1303
|
+
let requestIndex = -1;
|
|
1304
|
+
for (let index = queue.requests.length - 1; index >= 0; index -= 1) {
|
|
1305
|
+
const request = queue.requests[index];
|
|
1306
|
+
if (request?.sceneIndex === input.sceneIndex && request.status === 'queued') {
|
|
1307
|
+
requestIndex = index;
|
|
1308
|
+
break;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
if (requestIndex < 0) return;
|
|
1312
|
+
const fulfilledAt = new Date().toISOString();
|
|
1313
|
+
const nextRequests = queue.requests.map((request, index) => (
|
|
1314
|
+
index === requestIndex
|
|
1315
|
+
? {
|
|
1316
|
+
...request,
|
|
1317
|
+
status: 'fulfilled' as const,
|
|
1318
|
+
fulfilledAt,
|
|
1319
|
+
candidateId: input.candidateId,
|
|
1320
|
+
imageUrl: input.imageUrl,
|
|
1321
|
+
...(input.imageId ? { imageId: input.imageId } : {}),
|
|
1322
|
+
}
|
|
1323
|
+
: request
|
|
1324
|
+
));
|
|
1325
|
+
const nextQueue: ReviewStoryboardStillGenerationQueue = {
|
|
1326
|
+
...queue,
|
|
1327
|
+
updatedAt: fulfilledAt,
|
|
1328
|
+
requests: nextRequests,
|
|
1329
|
+
};
|
|
1330
|
+
await writeTextFileAtomic(path, `${JSON.stringify(nextQueue, null, 2)}\n`);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
export async function recordReviewStoryboardStillGenerationRequest(
|
|
1334
|
+
root: string,
|
|
1335
|
+
projectSlug: string,
|
|
1336
|
+
body: unknown,
|
|
1337
|
+
): Promise<ReviewStoryboardStillGenerationRequestResult> {
|
|
1338
|
+
const record = isRecord(body) ? body : {};
|
|
1339
|
+
const sceneIndex = numberValue(record.sceneIndex);
|
|
1340
|
+
const prompt = stringValue(record.prompt);
|
|
1341
|
+
if (sceneIndex === null || !prompt) {
|
|
1342
|
+
throw new Error('storyboard still generation request requires sceneIndex and prompt');
|
|
1343
|
+
}
|
|
1344
|
+
const requestedAt = new Date().toISOString();
|
|
1345
|
+
const workspace = await ensureProjectWorkspace(projectSlug, root);
|
|
1346
|
+
const path = join(workspace.artifactsDir, 'storyboard-still-generation-requests.json');
|
|
1347
|
+
const queue = await readStoryboardStillGenerationQueue(path, projectSlug) ?? {
|
|
1348
|
+
schemaVersion: 1,
|
|
1349
|
+
projectSlug,
|
|
1350
|
+
updatedAt: '',
|
|
1351
|
+
requests: [],
|
|
1352
|
+
};
|
|
1353
|
+
const request: ReviewStoryboardStillGenerationRequest = {
|
|
1354
|
+
id: `gobananas-still-${String(sceneIndex).padStart(2, '0')}-${String(queue.requests.length + 1).padStart(3, '0')}`,
|
|
1355
|
+
sceneIndex,
|
|
1356
|
+
provider: 'gobananas',
|
|
1357
|
+
route: 'gobananas-storyboard-still',
|
|
1358
|
+
status: 'queued',
|
|
1359
|
+
prompt,
|
|
1360
|
+
negativePrompt: stringValue(record.negativePrompt)
|
|
1361
|
+
?? 'readable text, random logos, clutter, extra objects, real humans, distorted anatomy, inconsistent character identity, unwanted camera change',
|
|
1362
|
+
aspectRatio: stringValue(record.aspectRatio) ?? '16:9',
|
|
1363
|
+
requestedAt,
|
|
1364
|
+
source: 'review-ui',
|
|
1365
|
+
...(stringValue(record.notes) ? { notes: stringValue(record.notes)! } : {}),
|
|
1366
|
+
};
|
|
1367
|
+
const nextQueue: ReviewStoryboardStillGenerationQueue = {
|
|
1368
|
+
schemaVersion: 1,
|
|
1369
|
+
projectSlug,
|
|
1370
|
+
updatedAt: requestedAt,
|
|
1371
|
+
requests: [...queue.requests, request],
|
|
1372
|
+
};
|
|
1373
|
+
await writeTextFileAtomic(path, `${JSON.stringify(nextQueue, null, 2)}\n`);
|
|
1374
|
+
await appendProjectEvent(workspace, {
|
|
1375
|
+
type: 'storyboard-still.generation-request.queued',
|
|
1376
|
+
payload: {
|
|
1377
|
+
source: 'review-ui',
|
|
1378
|
+
sceneIndex,
|
|
1379
|
+
requestId: request.id,
|
|
1380
|
+
provider: request.provider,
|
|
1381
|
+
route: request.route,
|
|
1382
|
+
},
|
|
1383
|
+
});
|
|
1384
|
+
return {
|
|
1385
|
+
path,
|
|
1386
|
+
request,
|
|
1387
|
+
queue: nextQueue,
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
export async function recordReviewCharacterIterationRequest(
|
|
1392
|
+
root: string,
|
|
1393
|
+
projectSlug: string,
|
|
1394
|
+
body: unknown,
|
|
1395
|
+
): Promise<ReviewCharacterIterationRequestResult> {
|
|
1396
|
+
const record = isRecord(body) ? body : {};
|
|
1397
|
+
const characterName = stringValue(record.characterName) ?? 'Komo';
|
|
1398
|
+
const prompt = stringValue(record.prompt);
|
|
1399
|
+
if (!prompt) {
|
|
1400
|
+
throw new Error('character iteration request requires prompt');
|
|
1401
|
+
}
|
|
1402
|
+
const requestedAt = new Date().toISOString();
|
|
1403
|
+
const workspace = await ensureProjectWorkspace(projectSlug, root);
|
|
1404
|
+
const path = join(workspace.artifactsDir, 'gobananas-character-iteration-requests.json');
|
|
1405
|
+
const queue = await readCharacterIterationQueue(path, projectSlug) ?? {
|
|
1406
|
+
schemaVersion: 1,
|
|
1407
|
+
projectSlug,
|
|
1408
|
+
updatedAt: '',
|
|
1409
|
+
requests: [],
|
|
1410
|
+
};
|
|
1411
|
+
const request: ReviewCharacterIterationRequest = {
|
|
1412
|
+
id: `gobananas-character-${String(queue.requests.length + 1).padStart(3, '0')}`,
|
|
1413
|
+
provider: 'gobananas',
|
|
1414
|
+
route: 'gobananas-character-iteration',
|
|
1415
|
+
status: 'queued',
|
|
1416
|
+
characterName,
|
|
1417
|
+
prompt,
|
|
1418
|
+
negativePrompt: stringValue(record.negativePrompt)
|
|
1419
|
+
?? 'inconsistent identity, realistic human photo, readable text, logos, clutter, distorted anatomy, extra limbs',
|
|
1420
|
+
aspectRatio: normalizeCharacterAspectRatio(stringValue(record.aspectRatio)),
|
|
1421
|
+
count: numberValue(record.count) ?? 4,
|
|
1422
|
+
requestedAt,
|
|
1423
|
+
source: 'review-ui',
|
|
1424
|
+
...(stringValue(record.notes) ? { notes: stringValue(record.notes)! } : {}),
|
|
1425
|
+
};
|
|
1426
|
+
const nextQueue: ReviewCharacterIterationQueue = {
|
|
1427
|
+
schemaVersion: 1,
|
|
1428
|
+
projectSlug,
|
|
1429
|
+
updatedAt: requestedAt,
|
|
1430
|
+
requests: [...queue.requests, request],
|
|
1431
|
+
};
|
|
1432
|
+
await writeTextFileAtomic(path, `${JSON.stringify(nextQueue, null, 2)}\n`);
|
|
1433
|
+
await appendProjectEvent(workspace, {
|
|
1434
|
+
type: 'character-iteration.request.queued',
|
|
1435
|
+
payload: {
|
|
1436
|
+
source: 'review-ui',
|
|
1437
|
+
requestId: request.id,
|
|
1438
|
+
provider: request.provider,
|
|
1439
|
+
route: request.route,
|
|
1440
|
+
characterName: request.characterName,
|
|
1441
|
+
count: request.count,
|
|
1442
|
+
},
|
|
1443
|
+
});
|
|
1444
|
+
return {
|
|
1445
|
+
path,
|
|
1446
|
+
request,
|
|
1447
|
+
queue: nextQueue,
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function findExistingStoryboardStillCandidate(
|
|
1452
|
+
artifact: SceneCandidatesArtifact,
|
|
1453
|
+
sceneIndex: number,
|
|
1454
|
+
imageUrl: string,
|
|
1455
|
+
): SceneCandidate | null {
|
|
1456
|
+
const scene = artifact.scenes.find((entry) => entry.sceneIndex === sceneIndex);
|
|
1457
|
+
if (!scene) return null;
|
|
1458
|
+
return [...scene.candidates].reverse().find((candidate) => (
|
|
1459
|
+
candidate.outputs.some((output) => output.kind === 'image' && output.path === imageUrl)
|
|
1460
|
+
)) ?? null;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
function findSceneCandidate(
|
|
1464
|
+
artifact: SceneCandidatesArtifact,
|
|
1465
|
+
sceneIndex: number,
|
|
1466
|
+
candidateId: string,
|
|
1467
|
+
): SceneCandidate | null {
|
|
1468
|
+
return artifact.scenes
|
|
1469
|
+
.find((scene) => scene.sceneIndex === sceneIndex)
|
|
1470
|
+
?.candidates.find((candidate) => candidate.id === candidateId)
|
|
1471
|
+
?? null;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
function validateReviewImageUrl(imageUrl: string): void {
|
|
1475
|
+
validateReviewImageReference(imageUrl, { allowLocalPath: false });
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
function validateReviewImageReference(imageUrl: string, options: { allowLocalPath: boolean }): void {
|
|
1479
|
+
if (options.allowLocalPath && !/^[a-z][a-z0-9+.-]*:/i.test(imageUrl)) {
|
|
1480
|
+
validateReviewImagePath(imageUrl);
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
let parsed: URL;
|
|
1484
|
+
try {
|
|
1485
|
+
parsed = new URL(imageUrl);
|
|
1486
|
+
} catch {
|
|
1487
|
+
throw new Error('storyboard still image URL must be a valid URL');
|
|
1488
|
+
}
|
|
1489
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
1490
|
+
throw new Error('storyboard still image URL must use http or https');
|
|
1491
|
+
}
|
|
1492
|
+
const host = parsed.hostname.toLowerCase();
|
|
1493
|
+
if (['example.com', 'example.net', 'example.org'].includes(host)) {
|
|
1494
|
+
throw new Error('storyboard still image URL cannot use a placeholder domain');
|
|
1495
|
+
}
|
|
1496
|
+
const path = parsed.pathname.toLowerCase();
|
|
1497
|
+
if (!/\.(jpg|jpeg|png|webp|gif)(?:$|\?)/.test(`${path}${parsed.search}`)) {
|
|
1498
|
+
throw new Error('storyboard still image URL must point to a jpg, png, webp, or gif image');
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function validateReviewImagePath(path: string): void {
|
|
1503
|
+
const normalizedPath = normalize(path);
|
|
1504
|
+
if (normalizedPath.startsWith('..') || normalizedPath.startsWith(sep) || normalizedPath.includes(`${sep}..${sep}`)) {
|
|
1505
|
+
throw new Error('storyboard still image path must stay inside the project workspace');
|
|
1506
|
+
}
|
|
1507
|
+
if (!/\.(jpg|jpeg|png|webp|gif)$/i.test(normalizedPath)) {
|
|
1508
|
+
throw new Error('storyboard still image path must point to a jpg, png, webp, or gif image');
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
async function writeDerivedReviewArtifacts(input: {
|
|
1513
|
+
root: string;
|
|
1514
|
+
artifactsDir: string;
|
|
1515
|
+
projectSlug: string;
|
|
1516
|
+
savedAt: string;
|
|
1517
|
+
decision: ReviewDecisionRecord;
|
|
1518
|
+
}): Promise<ReviewDecisionSaveResult['derivedArtifacts']> {
|
|
1519
|
+
const sceneCandidates = await readSceneCandidatesArtifact(input.root, input.projectSlug);
|
|
1520
|
+
const existingStoryboard = await readJsonArtifact(input.artifactsDir, 'storyboard.json');
|
|
1521
|
+
const reviewReport = buildReviewReportArtifact(
|
|
1522
|
+
input.projectSlug,
|
|
1523
|
+
input.savedAt,
|
|
1524
|
+
input.decision,
|
|
1525
|
+
sceneCandidates,
|
|
1526
|
+
existingStoryboard,
|
|
1527
|
+
);
|
|
1528
|
+
const reviewMetrics = isRecord(reviewReport.metrics) ? reviewReport.metrics : {};
|
|
1529
|
+
const publishReady = reviewMetrics.publishReady === true;
|
|
1530
|
+
const reviewNextAction = nextActionFromReviewReport(reviewReport, input.decision.recommendedNextAction);
|
|
1531
|
+
const artifacts = [
|
|
1532
|
+
{
|
|
1533
|
+
name: 'reference-board.json',
|
|
1534
|
+
purpose: 'Role-tagged reference intent for image prompting and Seedance source images.',
|
|
1535
|
+
body: buildReferenceBoardArtifact(input.projectSlug, input.savedAt, input.decision),
|
|
1536
|
+
},
|
|
1537
|
+
{
|
|
1538
|
+
name: 'director-seedance-plan.json',
|
|
1539
|
+
purpose: 'Director-grade Seedance still, motion, continuity, variant, and bridge-pose plan.',
|
|
1540
|
+
body: buildDirectorSeedancePlanArtifact(input.projectSlug, input.savedAt, input.decision, reviewNextAction),
|
|
1541
|
+
},
|
|
1542
|
+
{
|
|
1543
|
+
name: 'storyboard-stills-plan.json',
|
|
1544
|
+
purpose: 'Scene-by-scene still image prompts, cleanup prompts, and upscale locks before video.',
|
|
1545
|
+
body: buildStoryboardStillsPlanArtifact(
|
|
1546
|
+
input.projectSlug,
|
|
1547
|
+
input.savedAt,
|
|
1548
|
+
input.decision,
|
|
1549
|
+
sceneCandidates,
|
|
1550
|
+
existingStoryboard,
|
|
1551
|
+
),
|
|
1552
|
+
},
|
|
1553
|
+
{
|
|
1554
|
+
name: 'scene-selection.json',
|
|
1555
|
+
purpose: 'Mutable scene candidate selection ledger derived from locked storyboard stills.',
|
|
1556
|
+
body: buildSceneSelectionArtifact(input.decision, sceneCandidates),
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
name: 'gobananas-character-brief.json',
|
|
1560
|
+
purpose: 'Reusable character-iteration brief for Go Bananas character generation.',
|
|
1561
|
+
body: buildGoBananasCharacterBriefArtifact(input.projectSlug, input.savedAt, input.decision),
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
name: 'post-plan.json',
|
|
1565
|
+
purpose: 'Post-production timing, voiceover, retiming, and final assembly plan.',
|
|
1566
|
+
body: buildPostPlanArtifact(input.projectSlug, input.savedAt, input.decision, publishReady),
|
|
1567
|
+
},
|
|
1568
|
+
{
|
|
1569
|
+
name: 'review-report.json',
|
|
1570
|
+
purpose: 'Compact final human review verdict and handoff evidence from the review UI.',
|
|
1571
|
+
body: reviewReport,
|
|
1572
|
+
},
|
|
1573
|
+
];
|
|
1574
|
+
|
|
1575
|
+
const written: ReviewDecisionSaveResult['derivedArtifacts'] = [];
|
|
1576
|
+
for (const artifact of artifacts) {
|
|
1577
|
+
const path = join(input.artifactsDir, artifact.name);
|
|
1578
|
+
await writeTextFileAtomic(path, `${JSON.stringify(artifact.body, null, 2)}\n`);
|
|
1579
|
+
written.push({
|
|
1580
|
+
name: artifact.name,
|
|
1581
|
+
path,
|
|
1582
|
+
purpose: artifact.purpose,
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
return written;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
function buildReferenceBoardArtifact(
|
|
1589
|
+
projectSlug: string,
|
|
1590
|
+
generatedAt: string,
|
|
1591
|
+
decision: ReviewDecisionRecord,
|
|
1592
|
+
): Record<string, unknown> {
|
|
1593
|
+
const selections = decision.selections;
|
|
1594
|
+
const notes = decision.notes;
|
|
1595
|
+
const references = [
|
|
1596
|
+
selectedReference('workflow-guide', selections.reference, {
|
|
1597
|
+
role: 'workflow-guide',
|
|
1598
|
+
allowedUse: 'Prompting and quality guidance only.',
|
|
1599
|
+
notes: stringValue(notes.reference),
|
|
1600
|
+
}),
|
|
1601
|
+
selectedReference('character', selections.character ?? selections.characterSource ?? selections.characterPlan, {
|
|
1602
|
+
role: 'identity',
|
|
1603
|
+
allowedUse: 'Character identity, proportions, texture, and repeatability.',
|
|
1604
|
+
}),
|
|
1605
|
+
selectedReference('operator-asset', selections.asset, {
|
|
1606
|
+
role: 'operator-selected-reference',
|
|
1607
|
+
allowedUse: 'Use only after assigning a precise role before generation.',
|
|
1608
|
+
}),
|
|
1609
|
+
selectedReference('storyboard-template', selections.template, {
|
|
1610
|
+
role: 'story-structure',
|
|
1611
|
+
allowedUse: 'Scene count and beat structure.',
|
|
1612
|
+
}),
|
|
1613
|
+
].filter((entry): entry is Record<string, unknown> => entry !== null);
|
|
1614
|
+
|
|
1615
|
+
return {
|
|
1616
|
+
schemaVersion: 1,
|
|
1617
|
+
projectSlug,
|
|
1618
|
+
generatedAt,
|
|
1619
|
+
source: 'review-ui',
|
|
1620
|
+
workflowSource: workflowSource(decision),
|
|
1621
|
+
references,
|
|
1622
|
+
roleAssignments: referenceRoleAssignments(selections),
|
|
1623
|
+
requiredReferenceRoles: [
|
|
1624
|
+
'identity',
|
|
1625
|
+
'pose',
|
|
1626
|
+
'lookdev',
|
|
1627
|
+
'background',
|
|
1628
|
+
'ui-structure',
|
|
1629
|
+
'prop',
|
|
1630
|
+
'start-frame',
|
|
1631
|
+
'end-frame',
|
|
1632
|
+
'texture',
|
|
1633
|
+
],
|
|
1634
|
+
rule: 'Every image reference must have exactly one job before it influences a prompt.',
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
function selectedReference(
|
|
1639
|
+
id: string,
|
|
1640
|
+
value: unknown,
|
|
1641
|
+
metadata: Record<string, unknown>,
|
|
1642
|
+
): Record<string, unknown> | null {
|
|
1643
|
+
const selected = stringValue(value);
|
|
1644
|
+
if (!selected) return null;
|
|
1645
|
+
return {
|
|
1646
|
+
id,
|
|
1647
|
+
selected,
|
|
1648
|
+
...metadata,
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
function buildDirectorSeedancePlanArtifact(
|
|
1653
|
+
projectSlug: string,
|
|
1654
|
+
generatedAt: string,
|
|
1655
|
+
decision: ReviewDecisionRecord,
|
|
1656
|
+
nextActionOverride?: string,
|
|
1657
|
+
): Record<string, unknown> {
|
|
1658
|
+
const selections = decision.selections;
|
|
1659
|
+
const workflow = isRecord(decision.seedanceWorkflow) ? decision.seedanceWorkflow : {};
|
|
1660
|
+
const promptPatterns = isRecord(workflow.promptPatterns) ? workflow.promptPatterns : {};
|
|
1661
|
+
return {
|
|
1662
|
+
schemaVersion: 1,
|
|
1663
|
+
projectSlug,
|
|
1664
|
+
generatedAt,
|
|
1665
|
+
source: 'review-ui',
|
|
1666
|
+
workflowSource: workflowSource(decision),
|
|
1667
|
+
qualityBar: stringValue(workflow.qualityBar) || 'award-winning director cinematic video with minimal operator work',
|
|
1668
|
+
qualityScore: decision.qualityScore ?? null,
|
|
1669
|
+
qualityChecks: decision.qualityChecks ?? [],
|
|
1670
|
+
stillStoryboard: {
|
|
1671
|
+
template: stringValue(selections.template) || null,
|
|
1672
|
+
lockedStills: Object.entries(selections)
|
|
1673
|
+
.filter(([key]) => key.startsWith('lockedStill-'))
|
|
1674
|
+
.filter(([, value]) => {
|
|
1675
|
+
const candidateId = stringValue(value);
|
|
1676
|
+
return Boolean(candidateId && !rejectedCandidateIds(selections).includes(candidateId));
|
|
1677
|
+
})
|
|
1678
|
+
.map(([key, value]) => ({ key, value })),
|
|
1679
|
+
editInstruction: stringValue(decision.notes.storyboard) || null,
|
|
1680
|
+
mustLockBeforeMotion: true,
|
|
1681
|
+
upscaleBeforeSeedance: true,
|
|
1682
|
+
},
|
|
1683
|
+
motion: {
|
|
1684
|
+
candidateStrategy: stringValue(selections.motionCandidate) || 'control-pass',
|
|
1685
|
+
variantStrategy: stringValue(selections.variantStrategy) || 'control-plus-short-variant',
|
|
1686
|
+
continuityPlan: stringValue(selections.continuityPlan) || 'start-end-frame-chain',
|
|
1687
|
+
continuityFrames: continuityFramePlan(selections),
|
|
1688
|
+
bridgePosePlan: stringValue(selections.bridgePosePlan),
|
|
1689
|
+
bridgePoseRequired: Boolean(stringValue(selections.bridgePosePlan)),
|
|
1690
|
+
cameraRule: 'Static camera unless a deliberate cinematic move is selected.',
|
|
1691
|
+
promptPatterns,
|
|
1692
|
+
bridgeTriggers: Array.isArray(workflow.bridgeTriggers) ? workflow.bridgeTriggers : [],
|
|
1693
|
+
negativeGuidance: Array.isArray(workflow.negativeGuidance) ? workflow.negativeGuidance : [],
|
|
1694
|
+
},
|
|
1695
|
+
nextAction: nextActionOverride ?? decision.recommendedNextAction ?? null,
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
function buildGoBananasCharacterBriefArtifact(
|
|
1700
|
+
projectSlug: string,
|
|
1701
|
+
generatedAt: string,
|
|
1702
|
+
decision: ReviewDecisionRecord,
|
|
1703
|
+
): Record<string, unknown> {
|
|
1704
|
+
const workflow = isRecord(decision.seedanceWorkflow) ? decision.seedanceWorkflow : {};
|
|
1705
|
+
const negativeGuidance = Array.isArray(workflow.negativeGuidance) ? workflow.negativeGuidance : [];
|
|
1706
|
+
return {
|
|
1707
|
+
schemaVersion: 1,
|
|
1708
|
+
projectSlug,
|
|
1709
|
+
generatedAt,
|
|
1710
|
+
source: 'review-ui',
|
|
1711
|
+
workflowSource: workflowSource(decision),
|
|
1712
|
+
status: stringValue(decision.selections.characterPlan) === 'generate-gobananas-iterations'
|
|
1713
|
+
? 'ready-for-character-iterations'
|
|
1714
|
+
: 'operator-selected-character-source',
|
|
1715
|
+
selectedCharacter: stringValue(decision.selections.character) || null,
|
|
1716
|
+
selectedCharacterSource: stringValue(decision.selections.characterSource) || null,
|
|
1717
|
+
characterPlan: stringValue(decision.selections.characterPlan) || null,
|
|
1718
|
+
iterationCount: 4,
|
|
1719
|
+
preferredAspectRatio: 'portrait',
|
|
1720
|
+
basePromptFormula: [
|
|
1721
|
+
'Create a reusable cinematic character design suitable for multi-scene Seedance storyboard-to-motion production.',
|
|
1722
|
+
'Prioritize a clear silhouette, expressive face, stable wardrobe, readable materials, and repeatable proportions.',
|
|
1723
|
+
'Keep the design premium, director-grade, and compatible with role-tagged references.',
|
|
1724
|
+
'Provide full-body, close-up texture, expression, and action-bridge variants.',
|
|
1725
|
+
],
|
|
1726
|
+
negativePrompt: [
|
|
1727
|
+
...negativeGuidance,
|
|
1728
|
+
'inconsistent face',
|
|
1729
|
+
'wardrobe drift',
|
|
1730
|
+
'extra fingers',
|
|
1731
|
+
'unreadable silhouette',
|
|
1732
|
+
'overly busy accessories',
|
|
1733
|
+
].join(', '),
|
|
1734
|
+
requiredOutputs: [
|
|
1735
|
+
'full-body canonical identity reference',
|
|
1736
|
+
'close-up face and material texture reference',
|
|
1737
|
+
'expression variants',
|
|
1738
|
+
'action bridge pose reference',
|
|
1739
|
+
],
|
|
1740
|
+
saveBackToVideoClaw: {
|
|
1741
|
+
command: `vclaw video character-add --project ${projectSlug} --name <name> --gb-id <id> --ref gobananas://character/<id>`,
|
|
1742
|
+
artifact: `projects/${projectSlug}/characters/characters.json`,
|
|
1743
|
+
},
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
function buildStoryboardStillsPlanArtifact(
|
|
1748
|
+
projectSlug: string,
|
|
1749
|
+
generatedAt: string,
|
|
1750
|
+
decision: ReviewDecisionRecord,
|
|
1751
|
+
sceneCandidates: SceneCandidatesArtifact,
|
|
1752
|
+
existingStoryboard: Record<string, unknown> | null = null,
|
|
1753
|
+
): Record<string, unknown> {
|
|
1754
|
+
const selections = decision.selections;
|
|
1755
|
+
const notes = decision.notes;
|
|
1756
|
+
const templateId = stringValue(selections.template) || 'product-commercial-4';
|
|
1757
|
+
const template = readStoryboardTemplate(templateId);
|
|
1758
|
+
const characterName =
|
|
1759
|
+
stringValue(selections.character)
|
|
1760
|
+
|| stringValue(selections.characterSource)
|
|
1761
|
+
|| 'the selected hero character';
|
|
1762
|
+
const scenes = storyboardScenesForReview({
|
|
1763
|
+
existingStoryboard,
|
|
1764
|
+
templateId: template?.id ?? 'product-commercial-4',
|
|
1765
|
+
characterName,
|
|
1766
|
+
selectedCharacterName: characterName,
|
|
1767
|
+
});
|
|
1768
|
+
const environment = 'a premium cinematic product world with clean composition';
|
|
1769
|
+
const workflow = isRecord(decision.seedanceWorkflow) ? decision.seedanceWorkflow : {};
|
|
1770
|
+
const promptPatterns = isRecord(workflow.promptPatterns) ? workflow.promptPatterns : {};
|
|
1771
|
+
const rejected = handoffRejectedCandidateIds(selections, sceneCandidates);
|
|
1772
|
+
const stillCreatePattern = stringValue(promptPatterns.stillCreate)
|
|
1773
|
+
|| 'Use reference roles as source truth. Create one visual beat. Preserve identity, materials, colors, lighting, and camera.';
|
|
1774
|
+
const stillEditPattern = stringValue(promptPatterns.stillEdit)
|
|
1775
|
+
|| 'Edit only the named defect or layout issue. Keep identity, pose, camera, lighting, composition, and background unchanged.';
|
|
1776
|
+
const negativeGuidance = Array.isArray(workflow.negativeGuidance)
|
|
1777
|
+
? workflow.negativeGuidance
|
|
1778
|
+
: [
|
|
1779
|
+
'no readable UI text unless required',
|
|
1780
|
+
'no random logos',
|
|
1781
|
+
'no clutter',
|
|
1782
|
+
'no extra objects',
|
|
1783
|
+
'no real humans',
|
|
1784
|
+
'no distorted anatomy',
|
|
1785
|
+
'no unwanted camera change',
|
|
1786
|
+
];
|
|
1787
|
+
|
|
1788
|
+
return {
|
|
1789
|
+
schemaVersion: 1,
|
|
1790
|
+
projectSlug,
|
|
1791
|
+
generatedAt,
|
|
1792
|
+
source: 'review-ui',
|
|
1793
|
+
workflowSource: workflowSource(decision),
|
|
1794
|
+
template: template
|
|
1795
|
+
? {
|
|
1796
|
+
id: template.id,
|
|
1797
|
+
name: template.name,
|
|
1798
|
+
emotionalArc: template.emotionalArc,
|
|
1799
|
+
bestFor: template.bestFor,
|
|
1800
|
+
}
|
|
1801
|
+
: { id: templateId },
|
|
1802
|
+
qualityBar: 'Storyboard stills must be strong enough to animate before any video generation starts.',
|
|
1803
|
+
globalRules: [
|
|
1804
|
+
'Generate still frames before motion.',
|
|
1805
|
+
'Iterate with narrow edit prompts until the still is clean.',
|
|
1806
|
+
'Lock and upscale each approved still before Seedance.',
|
|
1807
|
+
'Use each locked still as start/end frame material for later video continuity.',
|
|
1808
|
+
'Keep one readable story beat per frame.',
|
|
1809
|
+
],
|
|
1810
|
+
promptPatterns: {
|
|
1811
|
+
stillCreate: stillCreatePattern,
|
|
1812
|
+
stillEdit: stillEditPattern,
|
|
1813
|
+
},
|
|
1814
|
+
negativeGuidance,
|
|
1815
|
+
referenceRolesRequired: [
|
|
1816
|
+
'identity',
|
|
1817
|
+
'pose',
|
|
1818
|
+
'lookdev',
|
|
1819
|
+
'background',
|
|
1820
|
+
'prop',
|
|
1821
|
+
'start-frame',
|
|
1822
|
+
'end-frame',
|
|
1823
|
+
],
|
|
1824
|
+
scenes: scenes.map((scene) => {
|
|
1825
|
+
const sceneIndex = scene.sceneIndex;
|
|
1826
|
+
const lockedKey = `lockedStill-${sceneIndex}`;
|
|
1827
|
+
const selectedLockedStill = stringValue(selections[lockedKey]);
|
|
1828
|
+
const lockedStill = selectedLockedStill && !rejected.includes(selectedLockedStill) ? selectedLockedStill : null;
|
|
1829
|
+
const draftStill = selections[`draftStill-${sceneIndex}`];
|
|
1830
|
+
const editStill = selections[`editStill-${sceneIndex}`];
|
|
1831
|
+
const upscaledStill = stringValue(selections[`upscaledStill-${sceneIndex}`]);
|
|
1832
|
+
const upscaledStillMatchesLock = Boolean(
|
|
1833
|
+
lockedStill
|
|
1834
|
+
&& upscaledStill
|
|
1835
|
+
&& upscaledStill.startsWith(`${lockedStill}-`),
|
|
1836
|
+
);
|
|
1837
|
+
const upscaledStillHasAsset = Boolean(
|
|
1838
|
+
upscaledStillMatchesLock
|
|
1839
|
+
&& upscaledStill
|
|
1840
|
+
&& sceneHasCandidate(sceneCandidates, sceneIndex, upscaledStill),
|
|
1841
|
+
);
|
|
1842
|
+
const panel = template?.panels.find((candidate) => candidate.sceneIndex === scene.sceneIndex);
|
|
1843
|
+
const createPrompt = [
|
|
1844
|
+
stillCreatePattern,
|
|
1845
|
+
`Scene ${scene.sceneIndex}: ${scene.scenePrompt?.imagePrompt ?? scene.description}`,
|
|
1846
|
+
`Character source: ${characterName}.`,
|
|
1847
|
+
`Environment: ${environment}.`,
|
|
1848
|
+
'Composition: cinematic, premium, clear focal hierarchy, no clutter.',
|
|
1849
|
+
`Negative guidance: ${negativeGuidance.join(', ')}.`,
|
|
1850
|
+
].join('\n');
|
|
1851
|
+
return {
|
|
1852
|
+
sceneIndex: scene.sceneIndex,
|
|
1853
|
+
shotType: panel?.shotType ?? 'cinematic still',
|
|
1854
|
+
purpose: panel?.purpose ?? 'story beat',
|
|
1855
|
+
beat: scene.description,
|
|
1856
|
+
createPrompt,
|
|
1857
|
+
editPrompt: [
|
|
1858
|
+
stillEditPattern,
|
|
1859
|
+
'Only fix defects that reduce clarity, continuity, or cinematic quality.',
|
|
1860
|
+
stringValue(notes.storyboard) || 'Preserve identity, camera, lighting, composition, and background during edits.',
|
|
1861
|
+
].join('\n'),
|
|
1862
|
+
goBananas: {
|
|
1863
|
+
tool: 'generate_with_character',
|
|
1864
|
+
characterName,
|
|
1865
|
+
aspectRatio: '16:9',
|
|
1866
|
+
scenePrompt: createPrompt,
|
|
1867
|
+
additionalDetails: 'Generate storyboard still only. Do not animate. Keep the image clean enough to upscale and use as a Seedance start/end frame.',
|
|
1868
|
+
},
|
|
1869
|
+
lockState: lockedStill ? 'operator-marked-locked' : 'needs-generated-still',
|
|
1870
|
+
draftStillReference: stringValue(draftStill),
|
|
1871
|
+
editStillReference: stringValue(editStill),
|
|
1872
|
+
lockedStillReference: lockedStill,
|
|
1873
|
+
upscaledStillReference: upscaledStillMatchesLock ? upscaledStill : null,
|
|
1874
|
+
submittedUpscaledStillReference: upscaledStill,
|
|
1875
|
+
upscaleState: upscaledStillMatchesLock ? 'operator-marked-upscaled' : 'needs-upscale-confirmation',
|
|
1876
|
+
upscaleEvidenceState: upscaledStillHasAsset ? 'artifact-backed-upscale' : upscaledStillMatchesLock ? 'operator-marker-only' : 'missing-upscale',
|
|
1877
|
+
upscaleTarget: '4k-before-seedance',
|
|
1878
|
+
continuityRole: sceneIndex === 0 ? 'opening-start-frame' : 'target-end-frame-for-previous-scene',
|
|
1879
|
+
rejectedStillReferences: rejected.filter((candidateId) => candidateId.startsWith(`scene-${sceneIndex}-take-`)),
|
|
1880
|
+
nextAction: lockedStill
|
|
1881
|
+
? 'Generate or verify upscaled still asset.'
|
|
1882
|
+
: 'Generate first still candidate, review, edit, then lock.',
|
|
1883
|
+
};
|
|
1884
|
+
}),
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
function referenceRoleAssignments(selections: Record<string, unknown>): Array<{
|
|
1889
|
+
role: string;
|
|
1890
|
+
selected: string;
|
|
1891
|
+
}> {
|
|
1892
|
+
return Object.entries(selections)
|
|
1893
|
+
.filter(([key, value]) => key.startsWith('referenceRole-') && stringValue(value))
|
|
1894
|
+
.map(([key, value]) => ({
|
|
1895
|
+
role: key.replace(/^referenceRole-/, ''),
|
|
1896
|
+
selected: stringValue(value) as string,
|
|
1897
|
+
}))
|
|
1898
|
+
.sort((left, right) => left.role.localeCompare(right.role));
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
function continuityFramePlan(selections: Record<string, unknown>): Array<{
|
|
1902
|
+
sceneIndex: number;
|
|
1903
|
+
startFrame: string | null;
|
|
1904
|
+
endFrame: string | null;
|
|
1905
|
+
continuityDecision: string | null;
|
|
1906
|
+
}> {
|
|
1907
|
+
const sceneIndexes = new Set<number>();
|
|
1908
|
+
for (const key of Object.keys(selections)) {
|
|
1909
|
+
const match = /^(?:lockedStill|continuity)-(\d+)$/.exec(key);
|
|
1910
|
+
if (match) sceneIndexes.add(Number(match[1]));
|
|
1911
|
+
}
|
|
1912
|
+
return [...sceneIndexes]
|
|
1913
|
+
.sort((left, right) => left - right)
|
|
1914
|
+
.map((sceneIndex) => ({
|
|
1915
|
+
sceneIndex,
|
|
1916
|
+
startFrame: sceneIndex === 0
|
|
1917
|
+
? stringValue(selections[`lockedStill-${sceneIndex}`])
|
|
1918
|
+
: stringValue(selections[`lockedStill-${sceneIndex - 1}`]),
|
|
1919
|
+
endFrame: stringValue(selections[`lockedStill-${sceneIndex}`]),
|
|
1920
|
+
continuityDecision: stringValue(selections[`continuity-${sceneIndex}`]),
|
|
1921
|
+
}));
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
function buildSceneSelectionArtifact(
|
|
1925
|
+
decision: ReviewDecisionRecord,
|
|
1926
|
+
sceneCandidates: SceneCandidatesArtifact,
|
|
1927
|
+
): SceneSelectionArtifact {
|
|
1928
|
+
const selectionEntries: SceneSelectionArtifact['scenes'] = [];
|
|
1929
|
+
const rejected = handoffRejectedCandidateIds(decision.selections, sceneCandidates);
|
|
1930
|
+
const expectedCharacter = expectedStoryboardCharacter(decision.selections);
|
|
1931
|
+
const sceneIndexes = new Set<number>();
|
|
1932
|
+
for (const [key, value] of Object.entries(decision.selections)) {
|
|
1933
|
+
const match = /^lockedStill-(\d+)$/.exec(key);
|
|
1934
|
+
const selectedCandidateId = stringValue(value);
|
|
1935
|
+
if (match && selectedCandidateId && isSceneCandidateId(selectedCandidateId)) {
|
|
1936
|
+
sceneIndexes.add(Number(match[1]));
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
for (const scene of sceneCandidates.scenes) {
|
|
1940
|
+
if (scene.candidates.some((candidate) => rejected.includes(candidate.id))) {
|
|
1941
|
+
sceneIndexes.add(scene.sceneIndex);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
for (const sceneIndex of [...sceneIndexes].sort((left, right) => left - right)) {
|
|
1945
|
+
const selectedCandidateId = stringValue(decision.selections[`lockedStill-${sceneIndex}`]);
|
|
1946
|
+
const previousSelectedCandidateId = sceneIndex > 0
|
|
1947
|
+
? stringValue(decision.selections[`lockedStill-${sceneIndex - 1}`])
|
|
1948
|
+
: null;
|
|
1949
|
+
const previousSelectedCandidate = previousSelectedCandidateId
|
|
1950
|
+
? findSceneCandidate(sceneCandidates, sceneIndex - 1, previousSelectedCandidateId)
|
|
1951
|
+
: null;
|
|
1952
|
+
const canChainFromPrev = Boolean(
|
|
1953
|
+
sceneIndex > 0
|
|
1954
|
+
&& previousSelectedCandidate?.outputs.some((output) => output.kind === 'video'),
|
|
1955
|
+
);
|
|
1956
|
+
const selectedExists = Boolean(
|
|
1957
|
+
selectedCandidateId
|
|
1958
|
+
&& isSceneCandidateId(selectedCandidateId)
|
|
1959
|
+
&& !rejected.includes(selectedCandidateId)
|
|
1960
|
+
&& sceneHasCandidate(sceneCandidates, sceneIndex, selectedCandidateId)
|
|
1961
|
+
&& sceneCandidateMatchesExpectedCharacter(sceneCandidates, sceneIndex, selectedCandidateId, expectedCharacter),
|
|
1962
|
+
);
|
|
1963
|
+
const rejectedForScene = rejected.filter((candidateId) => sceneHasCandidate(sceneCandidates, sceneIndex, candidateId));
|
|
1964
|
+
const pendingForScene = sceneCandidates.scenes
|
|
1965
|
+
.find((scene) => scene.sceneIndex === sceneIndex)
|
|
1966
|
+
?.candidates
|
|
1967
|
+
.map((candidate) => candidate.id)
|
|
1968
|
+
.filter((candidateId) => (
|
|
1969
|
+
candidateId !== selectedCandidateId
|
|
1970
|
+
&& !isUpscaledStillCandidateId(candidateId)
|
|
1971
|
+
&& !rejectedForScene.includes(candidateId)
|
|
1972
|
+
&& sceneCandidateMatchesExpectedCharacter(sceneCandidates, sceneIndex, candidateId, expectedCharacter)
|
|
1973
|
+
))
|
|
1974
|
+
?? [];
|
|
1975
|
+
if (!selectedExists && rejectedForScene.length === 0 && pendingForScene.length === 0) continue;
|
|
1976
|
+
selectionEntries.push({
|
|
1977
|
+
sceneIndex,
|
|
1978
|
+
selectedCandidateId: selectedExists ? selectedCandidateId : null,
|
|
1979
|
+
rejectedCandidateIds: rejectedForScene,
|
|
1980
|
+
pendingCandidateIds: pendingForScene,
|
|
1981
|
+
rerollRequested: false,
|
|
1982
|
+
chainFromPrev: canChainFromPrev,
|
|
1983
|
+
notes: [
|
|
1984
|
+
selectedExists ? 'Selected from review-ui locked storyboard still.' : 'No locked storyboard still selected yet.',
|
|
1985
|
+
expectedCharacter && selectedCandidateId && !selectedExists
|
|
1986
|
+
? `Selected still must match character "${expectedCharacter}".`
|
|
1987
|
+
: null,
|
|
1988
|
+
rejectedForScene.length ? `Rejected in review UI: ${rejectedForScene.join(', ')}.` : null,
|
|
1989
|
+
stringValue(decision.notes.storyboard),
|
|
1990
|
+
].filter(Boolean).join(' '),
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
selectionEntries.sort((left, right) => left.sceneIndex - right.sceneIndex);
|
|
1994
|
+
|
|
1995
|
+
return {
|
|
1996
|
+
schemaVersion: 1,
|
|
1997
|
+
scenes: selectionEntries,
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
function isSceneCandidateId(value: string): boolean {
|
|
2002
|
+
return /^scene-\d+-take-\d+$/.test(value);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
function isUpscaledStillCandidateId(value: string): boolean {
|
|
2006
|
+
return /^scene-\d+-take-\d+-4k$/.test(value);
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
function expectedStoryboardCharacter(selections: Record<string, unknown>): string | null {
|
|
2010
|
+
const selected = stringValue(selections.character);
|
|
2011
|
+
if (!selected) return null;
|
|
2012
|
+
const normalized = selected.toLowerCase();
|
|
2013
|
+
if (normalized.startsWith('gobananas://character/')) return null;
|
|
2014
|
+
if (normalized === 'generate-gobananas-iterations') return null;
|
|
2015
|
+
if (normalized === 'selected character') return null;
|
|
2016
|
+
return normalized;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
function sceneCandidateMatchesExpectedCharacter(
|
|
2020
|
+
artifact: SceneCandidatesArtifact,
|
|
2021
|
+
sceneIndex: number,
|
|
2022
|
+
candidateId: string,
|
|
2023
|
+
expectedCharacter: string | null,
|
|
2024
|
+
): boolean {
|
|
2025
|
+
if (!expectedCharacter) return true;
|
|
2026
|
+
const candidate = artifact.scenes
|
|
2027
|
+
.find((scene) => scene.sceneIndex === sceneIndex)
|
|
2028
|
+
?.candidates
|
|
2029
|
+
.find((entry) => entry.id === candidateId);
|
|
2030
|
+
if (!candidate) return false;
|
|
2031
|
+
const haystack = [
|
|
2032
|
+
candidate.prompt,
|
|
2033
|
+
candidate.route,
|
|
2034
|
+
candidate.source.externalJobId,
|
|
2035
|
+
...candidate.outputs.map((output) => output.path),
|
|
2036
|
+
].join('\n').toLowerCase();
|
|
2037
|
+
return haystack.includes(expectedCharacter);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
function sceneHasCandidate(
|
|
2041
|
+
artifact: SceneCandidatesArtifact,
|
|
2042
|
+
sceneIndex: number,
|
|
2043
|
+
candidateId: string,
|
|
2044
|
+
): boolean {
|
|
2045
|
+
return artifact.scenes.some((scene) => (
|
|
2046
|
+
scene.sceneIndex === sceneIndex
|
|
2047
|
+
&& scene.candidates.some((candidate) => candidate.id === candidateId)
|
|
2048
|
+
));
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
function rejectedCandidateIds(selections: Record<string, unknown>): string[] {
|
|
2052
|
+
const value = selections.rejectedStillCandidates;
|
|
2053
|
+
return Array.isArray(value)
|
|
2054
|
+
? value.map(stringValue).filter((candidateId): candidateId is string => Boolean(candidateId))
|
|
2055
|
+
: [];
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
function handoffRejectedCandidateIds(
|
|
2059
|
+
selections: Record<string, unknown>,
|
|
2060
|
+
sceneCandidates: SceneCandidatesArtifact,
|
|
2061
|
+
): string[] {
|
|
2062
|
+
const rejected = rejectedCandidateIds(selections);
|
|
2063
|
+
const expectedCharacter = expectedStoryboardCharacter(selections);
|
|
2064
|
+
if (!expectedCharacter) return rejected;
|
|
2065
|
+
return rejected.filter((candidateId) => {
|
|
2066
|
+
const scene = sceneCandidates.scenes.find((entry) => (
|
|
2067
|
+
entry.candidates.some((candidate) => candidate.id === candidateId)
|
|
2068
|
+
));
|
|
2069
|
+
return Boolean(
|
|
2070
|
+
scene
|
|
2071
|
+
&& sceneCandidateMatchesExpectedCharacter(sceneCandidates, scene.sceneIndex, candidateId, expectedCharacter),
|
|
2072
|
+
);
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
function buildPostPlanArtifact(
|
|
2077
|
+
projectSlug: string,
|
|
2078
|
+
generatedAt: string,
|
|
2079
|
+
decision: ReviewDecisionRecord,
|
|
2080
|
+
publishReady: boolean,
|
|
2081
|
+
): Record<string, unknown> {
|
|
2082
|
+
const assemblyApprovals = assemblyApprovalEntries(decision.selections);
|
|
2083
|
+
return {
|
|
2084
|
+
schemaVersion: 1,
|
|
2085
|
+
projectSlug,
|
|
2086
|
+
generatedAt,
|
|
2087
|
+
source: 'review-ui',
|
|
2088
|
+
workflowSource: workflowSource(decision),
|
|
2089
|
+
assemblyPlan: stringValue(decision.selections.assemblyPlan) || 'balanced',
|
|
2090
|
+
voiceoverTiming: stringValue(decision.notes.voiceover) || 'Map every scene to voiceover timing before final assembly.',
|
|
2091
|
+
retiming: 'Use simple time remapping to tighten slow generated clips while preserving readable motion beats.',
|
|
2092
|
+
assemblyApprovals,
|
|
2093
|
+
publishReady,
|
|
2094
|
+
polishChecklist: [
|
|
2095
|
+
'Scene pacing matches voiceover.',
|
|
2096
|
+
'Continuity frames cut cleanly between shots.',
|
|
2097
|
+
'Opacity and transitions are intentional.',
|
|
2098
|
+
'Logo reveal is readable and timed as the final payoff.',
|
|
2099
|
+
'Final render is reviewed before publish.',
|
|
2100
|
+
],
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
function buildReviewReportArtifact(
|
|
2105
|
+
projectSlug: string,
|
|
2106
|
+
generatedAt: string,
|
|
2107
|
+
decision: ReviewDecisionRecord,
|
|
2108
|
+
sceneCandidates: SceneCandidatesArtifact,
|
|
2109
|
+
existingStoryboard: Record<string, unknown> | null = null,
|
|
2110
|
+
): Record<string, unknown> {
|
|
2111
|
+
const selections = decision.selections;
|
|
2112
|
+
const qualityChecks = decision.qualityChecks ?? [];
|
|
2113
|
+
const failedChecks = qualityChecks
|
|
2114
|
+
.filter((check) => check.done === false)
|
|
2115
|
+
.map((check) => stringValue(check.label))
|
|
2116
|
+
.filter((label): label is string => Boolean(label));
|
|
2117
|
+
const lockedSceneSelections = buildSceneSelectionArtifact(decision, sceneCandidates).scenes;
|
|
2118
|
+
const publishApprovals = assemblyApprovalEntries(selections);
|
|
2119
|
+
const reviewCompleteAt = stringValue(selections.reviewCompleteAt);
|
|
2120
|
+
const templateId = stringValue(selections.template) || 'product-commercial-4';
|
|
2121
|
+
const customSceneCount = existingStoryboardScenes(existingStoryboard).length;
|
|
2122
|
+
const expectedSceneCount = customSceneCount || buildStoryboardScenesFromTemplate({
|
|
2123
|
+
templateId,
|
|
2124
|
+
environment: 'a premium cinematic product world with clean composition',
|
|
2125
|
+
characterA: stringValue(selections.character) || stringValue(selections.characterSource) || 'the selected hero character',
|
|
2126
|
+
}).length;
|
|
2127
|
+
const mismatchedLocks = lockedStoryboardMismatchIds(decision, sceneCandidates);
|
|
2128
|
+
const lockedSceneCount = lockedSceneSelections.filter((scene) => scene.selectedCandidateId).length;
|
|
2129
|
+
const upscaleEvidence = storyboardUpscaleEvidence(decision, sceneCandidates);
|
|
2130
|
+
const missingLocks = Math.max(0, expectedSceneCount - lockedSceneCount);
|
|
2131
|
+
const missingApprovals = Math.max(0, 5 - publishApprovals.length);
|
|
2132
|
+
const missingUpscaleAssets = Math.max(0, expectedSceneCount - upscaleEvidence.artifactBackedCount);
|
|
2133
|
+
const publishReady = Boolean(
|
|
2134
|
+
publishApprovals.length >= 5
|
|
2135
|
+
&& !missingLocks
|
|
2136
|
+
&& !missingApprovals
|
|
2137
|
+
&& !mismatchedLocks.length
|
|
2138
|
+
&& !missingUpscaleAssets,
|
|
2139
|
+
);
|
|
2140
|
+
const verdict = failedChecks.length || !reviewCompleteAt || missingLocks || missingApprovals || mismatchedLocks.length || missingUpscaleAssets ? 'retry' : 'pass';
|
|
2141
|
+
const nextAction = buildReviewNextAction({
|
|
2142
|
+
verdict,
|
|
2143
|
+
failedChecks,
|
|
2144
|
+
reviewCompleteAt,
|
|
2145
|
+
missingLocks,
|
|
2146
|
+
missingUpscaleAssets,
|
|
2147
|
+
missingApprovals,
|
|
2148
|
+
mismatchedLocks,
|
|
2149
|
+
});
|
|
2150
|
+
return {
|
|
2151
|
+
projectSlug,
|
|
2152
|
+
verdict,
|
|
2153
|
+
generatedAt,
|
|
2154
|
+
findings: failedChecks.length
|
|
2155
|
+
? failedChecks.map((label) => `Incomplete quality check: ${label}`)
|
|
2156
|
+
: [
|
|
2157
|
+
...(!reviewCompleteAt ? ['Review UI has not been finished by the operator.'] : []),
|
|
2158
|
+
...(mismatchedLocks.length ? [`Storyboard locks do not match selected character: ${mismatchedLocks.join(', ')}.`] : []),
|
|
2159
|
+
...(missingLocks ? [`Missing locked storyboard stills: ${missingLocks}.`] : []),
|
|
2160
|
+
...(missingUpscaleAssets ? [`Missing artifact-backed 4k/upscaled still assets: ${missingUpscaleAssets}.`] : []),
|
|
2161
|
+
...(missingApprovals ? [`Missing publish approvals: ${missingApprovals}.`] : []),
|
|
2162
|
+
...(reviewCompleteAt && !mismatchedLocks.length && !missingLocks && !missingUpscaleAssets && !missingApprovals ? ['Human review UI gates completed and saved.'] : []),
|
|
2163
|
+
],
|
|
2164
|
+
metrics: {
|
|
2165
|
+
qualityScore: decision.qualityScore ?? null,
|
|
2166
|
+
activeGate: decision.activeGate ?? null,
|
|
2167
|
+
reviewCompleteAt,
|
|
2168
|
+
expectedSceneCount,
|
|
2169
|
+
lockedSceneCount,
|
|
2170
|
+
characterMismatchCount: mismatchedLocks.length,
|
|
2171
|
+
upscaleMarkerCount: upscaleEvidence.markerCount,
|
|
2172
|
+
artifactBackedUpscaleCount: upscaleEvidence.artifactBackedCount,
|
|
2173
|
+
operatorOnlyUpscaleCount: upscaleEvidence.operatorOnlyCount,
|
|
2174
|
+
missingUpscaleAssetCount: missingUpscaleAssets,
|
|
2175
|
+
rejectedCandidateCount: lockedSceneSelections.reduce((count, scene) => count + scene.rejectedCandidateIds.length, 0),
|
|
2176
|
+
publishApprovalCount: publishApprovals.length,
|
|
2177
|
+
publishReady,
|
|
2178
|
+
motionCandidate: stringValue(selections.motionCandidate),
|
|
2179
|
+
bridgePosePlan: stringValue(selections.bridgePosePlan),
|
|
2180
|
+
nextAction,
|
|
2181
|
+
},
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
function buildReviewNextAction(input: {
|
|
2186
|
+
verdict: 'pass' | 'retry' | 'fail';
|
|
2187
|
+
failedChecks: string[];
|
|
2188
|
+
reviewCompleteAt: string | null;
|
|
2189
|
+
missingLocks: number;
|
|
2190
|
+
missingUpscaleAssets: number;
|
|
2191
|
+
missingApprovals: number;
|
|
2192
|
+
mismatchedLocks: string[];
|
|
2193
|
+
}): string {
|
|
2194
|
+
if (input.verdict === 'pass') return 'Ready for publish handoff.';
|
|
2195
|
+
if (input.failedChecks.length) {
|
|
2196
|
+
return `Resolve review quality checks: ${input.failedChecks.join(', ')}.`;
|
|
2197
|
+
}
|
|
2198
|
+
if (!input.reviewCompleteAt) return 'Finish the review gate and save the review artifacts.';
|
|
2199
|
+
if (input.mismatchedLocks.length) {
|
|
2200
|
+
return `Replace mismatched storyboard locks before publishing: ${input.mismatchedLocks.join(', ')}.`;
|
|
2201
|
+
}
|
|
2202
|
+
if (input.missingLocks) return `Lock generated storyboard stills for ${input.missingLocks} scene(s).`;
|
|
2203
|
+
if (input.missingUpscaleAssets) return `Attach artifact-backed 4k/upscaled stills for ${input.missingUpscaleAssets} scene(s).`;
|
|
2204
|
+
if (input.missingApprovals) return `Approve ${input.missingApprovals} remaining final assembly check(s).`;
|
|
2205
|
+
return 'Resolve review findings before publishing.';
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
export function isReviewReportPublishReady(reviewReport: Record<string, unknown> | undefined | null): boolean {
|
|
2209
|
+
if (!reviewReport) return false;
|
|
2210
|
+
const metrics = isRecord(reviewReport.metrics) ? reviewReport.metrics : {};
|
|
2211
|
+
return reviewVerdict(reviewReport.verdict) === 'pass' && metrics.publishReady === true;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
export function nextActionFromReviewReport(reviewReport: Record<string, unknown> | undefined | null, fallback?: string): string | undefined {
|
|
2215
|
+
if (!reviewReport) return fallback;
|
|
2216
|
+
const metrics = isRecord(reviewReport.metrics) ? reviewReport.metrics : {};
|
|
2217
|
+
const nextAction = stringValue(metrics.nextAction);
|
|
2218
|
+
const verdict = reviewVerdict(reviewReport.verdict);
|
|
2219
|
+
if (isReviewReportPublishReady(reviewReport)) return 'Ready for publish handoff.';
|
|
2220
|
+
if (nextAction && nextAction !== 'Ready for publish handoff.') return nextAction;
|
|
2221
|
+
const findings = Array.isArray(reviewReport.findings)
|
|
2222
|
+
? reviewReport.findings.map(stringValue).filter((finding): finding is string => Boolean(finding))
|
|
2223
|
+
: [];
|
|
2224
|
+
if (findings.length) return `Resolve review findings: ${findings.join('; ')}.`;
|
|
2225
|
+
if (verdict === 'pass') return 'Complete publish readiness evidence before publishing.';
|
|
2226
|
+
return fallback;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
function storyboardUpscaleEvidence(
|
|
2230
|
+
decision: ReviewDecisionRecord,
|
|
2231
|
+
sceneCandidates: SceneCandidatesArtifact,
|
|
2232
|
+
): { markerCount: number; artifactBackedCount: number; operatorOnlyCount: number } {
|
|
2233
|
+
let markerCount = 0;
|
|
2234
|
+
let artifactBackedCount = 0;
|
|
2235
|
+
for (const [key, value] of Object.entries(decision.selections)) {
|
|
2236
|
+
const match = /^upscaledStill-(\d+)$/.exec(key);
|
|
2237
|
+
const upscaledStill = stringValue(value);
|
|
2238
|
+
if (!match || !upscaledStill) continue;
|
|
2239
|
+
markerCount += 1;
|
|
2240
|
+
if (sceneHasCandidate(sceneCandidates, Number(match[1]), upscaledStill)) {
|
|
2241
|
+
artifactBackedCount += 1;
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
return {
|
|
2245
|
+
markerCount,
|
|
2246
|
+
artifactBackedCount,
|
|
2247
|
+
operatorOnlyCount: Math.max(0, markerCount - artifactBackedCount),
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
function lockedStoryboardMismatchIds(
|
|
2252
|
+
decision: ReviewDecisionRecord,
|
|
2253
|
+
sceneCandidates: SceneCandidatesArtifact,
|
|
2254
|
+
): string[] {
|
|
2255
|
+
const expectedCharacter = expectedStoryboardCharacter(decision.selections);
|
|
2256
|
+
if (!expectedCharacter) return [];
|
|
2257
|
+
return Object.entries(decision.selections)
|
|
2258
|
+
.map(([key, value]) => {
|
|
2259
|
+
const match = /^lockedStill-(\d+)$/.exec(key);
|
|
2260
|
+
const candidateId = stringValue(value);
|
|
2261
|
+
if (!match || !candidateId || !isSceneCandidateId(candidateId)) return null;
|
|
2262
|
+
const sceneIndex = Number(match[1]);
|
|
2263
|
+
return sceneCandidateMatchesExpectedCharacter(sceneCandidates, sceneIndex, candidateId, expectedCharacter)
|
|
2264
|
+
? null
|
|
2265
|
+
: candidateId;
|
|
2266
|
+
})
|
|
2267
|
+
.filter((candidateId): candidateId is string => Boolean(candidateId));
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
function assemblyApprovalEntries(selections: Record<string, unknown>): Array<{
|
|
2271
|
+
id: string;
|
|
2272
|
+
approved: boolean;
|
|
2273
|
+
value: string;
|
|
2274
|
+
}> {
|
|
2275
|
+
return Object.entries(selections)
|
|
2276
|
+
.filter(([key, value]) => key.startsWith('assemblyCheck-') && stringValue(value))
|
|
2277
|
+
.map(([key, value]) => ({
|
|
2278
|
+
id: key.replace(/^assemblyCheck-/, ''),
|
|
2279
|
+
approved: true,
|
|
2280
|
+
value: stringValue(value) as string,
|
|
2281
|
+
}))
|
|
2282
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
function workflowSource(decision: ReviewDecisionRecord): string {
|
|
2286
|
+
const workflow = isRecord(decision.seedanceWorkflow) ? decision.seedanceWorkflow : {};
|
|
2287
|
+
return stringValue(workflow.source) || 'docs/REFERENCE_VIDEO_SEEDANCE_MOTION_DESIGN_WORKFLOW.md';
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
function stringValue(value: unknown): string | null {
|
|
2291
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
function numberValue(value: unknown): number | null {
|
|
2295
|
+
if (typeof value === 'number' && Number.isInteger(value) && value >= 0) return value;
|
|
2296
|
+
if (typeof value === 'string' && /^\d+$/.test(value)) return Number(value);
|
|
2297
|
+
return null;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
export async function buildReviewInventory(root: string, projectSlug: string): Promise<ReviewInventory> {
|
|
2301
|
+
const resolvedRoot = resolve(root);
|
|
2302
|
+
const workspace = resolveProjectWorkspace(projectSlug, resolvedRoot);
|
|
2303
|
+
const projects = await listProjects(resolvedRoot);
|
|
2304
|
+
const characters = existsSync(workspace.projectDir)
|
|
2305
|
+
? await listCharacterProfiles(workspace)
|
|
2306
|
+
: [];
|
|
2307
|
+
const referenceSheets = await readReferenceSheetsArtifact(resolvedRoot, projectSlug);
|
|
2308
|
+
const sceneCandidates = await readSceneCandidatesArtifact(resolvedRoot, projectSlug);
|
|
2309
|
+
const sceneSelection = await readSceneSelectionArtifact(resolvedRoot, projectSlug);
|
|
2310
|
+
const reviewLedger = await readReviewLedger(workspace.artifactsDir);
|
|
2311
|
+
const reviewReport = await readJsonArtifact(workspace.artifactsDir, 'review-report.json');
|
|
2312
|
+
const brief = await readJsonArtifact(workspace.artifactsDir, 'brief.json');
|
|
2313
|
+
const storyboard = await readJsonArtifact(workspace.artifactsDir, 'storyboard.json');
|
|
2314
|
+
const assetManifest = await readJsonArtifact(workspace.artifactsDir, 'asset-manifest.json');
|
|
2315
|
+
const executionReport = await readJsonArtifact(workspace.artifactsDir, 'execution-report.json');
|
|
2316
|
+
const publishReport = await readJsonArtifact(workspace.artifactsDir, 'publish-report.json');
|
|
2317
|
+
const generationQueue = await readStoryboardStillGenerationQueue(
|
|
2318
|
+
join(workspace.artifactsDir, 'storyboard-still-generation-requests.json'),
|
|
2319
|
+
projectSlug,
|
|
2320
|
+
);
|
|
2321
|
+
const characterQueue = await readCharacterIterationQueue(
|
|
2322
|
+
join(workspace.artifactsDir, 'gobananas-character-iteration-requests.json'),
|
|
2323
|
+
projectSlug,
|
|
2324
|
+
);
|
|
2325
|
+
const playbooks = await listPlaybooks(resolvedRoot);
|
|
2326
|
+
const promptReferences = listPromptReferences();
|
|
2327
|
+
const storyboardTemplates = listStoryboardTemplates();
|
|
2328
|
+
const mediaAssets = await findReviewMediaAssets(resolvedRoot);
|
|
2329
|
+
|
|
2330
|
+
return {
|
|
2331
|
+
root: resolvedRoot,
|
|
2332
|
+
projectSlug,
|
|
2333
|
+
projectDir: workspace.projectDir,
|
|
2334
|
+
projectExists: existsSync(workspace.projectDir),
|
|
2335
|
+
projects,
|
|
2336
|
+
characters,
|
|
2337
|
+
referenceSheets,
|
|
2338
|
+
sceneCandidates,
|
|
2339
|
+
sceneSelection,
|
|
2340
|
+
reviewLedger,
|
|
2341
|
+
reviewReport,
|
|
2342
|
+
brief,
|
|
2343
|
+
storyboard,
|
|
2344
|
+
assetManifest,
|
|
2345
|
+
executionReport,
|
|
2346
|
+
publishReport,
|
|
2347
|
+
generationQueue,
|
|
2348
|
+
characterQueue,
|
|
2349
|
+
playbooks,
|
|
2350
|
+
promptReferences,
|
|
2351
|
+
storyboardTemplates,
|
|
2352
|
+
mediaAssets,
|
|
2353
|
+
schemas: [
|
|
2354
|
+
'analyze-output',
|
|
2355
|
+
'asset-manifest',
|
|
2356
|
+
'brief',
|
|
2357
|
+
'clone-plan',
|
|
2358
|
+
'execution-plan',
|
|
2359
|
+
'execution-report',
|
|
2360
|
+
'publish-report',
|
|
2361
|
+
'reference-sheets',
|
|
2362
|
+
'review-report',
|
|
2363
|
+
'scene-candidates',
|
|
2364
|
+
'scene-selection',
|
|
2365
|
+
'storyboard',
|
|
2366
|
+
],
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
async function readReviewLedger(artifactsDir: string): Promise<Record<string, unknown> | null> {
|
|
2371
|
+
return readJsonArtifact(artifactsDir, 'review-ui-ledger.json');
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
async function readJsonArtifact(artifactsDir: string, name: string): Promise<Record<string, unknown> | null> {
|
|
2375
|
+
const path = join(artifactsDir, name);
|
|
2376
|
+
if (!existsSync(path)) return null;
|
|
2377
|
+
const raw = await readFile(path, 'utf-8');
|
|
2378
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
2379
|
+
return isRecord(parsed) ? parsed : null;
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
async function readStoryboardStillGenerationQueue(
|
|
2383
|
+
path: string,
|
|
2384
|
+
projectSlug: string,
|
|
2385
|
+
): Promise<ReviewStoryboardStillGenerationQueue | null> {
|
|
2386
|
+
if (!existsSync(path)) return null;
|
|
2387
|
+
const raw = await readFile(path, 'utf-8');
|
|
2388
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
2389
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.requests)) return null;
|
|
2390
|
+
return {
|
|
2391
|
+
schemaVersion: 1,
|
|
2392
|
+
projectSlug: stringValue(parsed.projectSlug) ?? projectSlug,
|
|
2393
|
+
updatedAt: stringValue(parsed.updatedAt) ?? '',
|
|
2394
|
+
requests: parsed.requests
|
|
2395
|
+
.filter(isRecord)
|
|
2396
|
+
.map((request): ReviewStoryboardStillGenerationRequest | null => {
|
|
2397
|
+
const sceneIndex = numberValue(request.sceneIndex);
|
|
2398
|
+
const prompt = stringValue(request.prompt);
|
|
2399
|
+
const id = stringValue(request.id);
|
|
2400
|
+
if (sceneIndex === null || !prompt || !id) return null;
|
|
2401
|
+
return {
|
|
2402
|
+
id,
|
|
2403
|
+
sceneIndex,
|
|
2404
|
+
provider: 'gobananas',
|
|
2405
|
+
route: 'gobananas-storyboard-still',
|
|
2406
|
+
status: request.status === 'fulfilled' ? 'fulfilled' : 'queued',
|
|
2407
|
+
prompt,
|
|
2408
|
+
negativePrompt: stringValue(request.negativePrompt) ?? '',
|
|
2409
|
+
aspectRatio: stringValue(request.aspectRatio) ?? '16:9',
|
|
2410
|
+
requestedAt: stringValue(request.requestedAt) ?? '',
|
|
2411
|
+
source: 'review-ui',
|
|
2412
|
+
...(stringValue(request.notes) ? { notes: stringValue(request.notes)! } : {}),
|
|
2413
|
+
...(stringValue(request.fulfilledAt) ? { fulfilledAt: stringValue(request.fulfilledAt)! } : {}),
|
|
2414
|
+
...(stringValue(request.candidateId) ? { candidateId: stringValue(request.candidateId)! } : {}),
|
|
2415
|
+
...(stringValue(request.imageUrl) ? { imageUrl: stringValue(request.imageUrl)! } : {}),
|
|
2416
|
+
...(stringValue(request.imageId) ? { imageId: stringValue(request.imageId)! } : {}),
|
|
2417
|
+
};
|
|
2418
|
+
})
|
|
2419
|
+
.filter((request): request is ReviewStoryboardStillGenerationRequest => request !== null),
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
async function readCharacterIterationQueue(
|
|
2424
|
+
path: string,
|
|
2425
|
+
projectSlug: string,
|
|
2426
|
+
): Promise<ReviewCharacterIterationQueue | null> {
|
|
2427
|
+
if (!existsSync(path)) return null;
|
|
2428
|
+
const raw = await readFile(path, 'utf-8');
|
|
2429
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
2430
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.requests)) return null;
|
|
2431
|
+
return {
|
|
2432
|
+
schemaVersion: 1,
|
|
2433
|
+
projectSlug: stringValue(parsed.projectSlug) ?? projectSlug,
|
|
2434
|
+
updatedAt: stringValue(parsed.updatedAt) ?? '',
|
|
2435
|
+
requests: parsed.requests
|
|
2436
|
+
.filter(isRecord)
|
|
2437
|
+
.map((request): ReviewCharacterIterationRequest | null => {
|
|
2438
|
+
const id = stringValue(request.id);
|
|
2439
|
+
const prompt = stringValue(request.prompt);
|
|
2440
|
+
if (!id || !prompt) return null;
|
|
2441
|
+
return {
|
|
2442
|
+
id,
|
|
2443
|
+
provider: 'gobananas',
|
|
2444
|
+
route: 'gobananas-character-iteration',
|
|
2445
|
+
status: request.status === 'failed' ? 'failed' : request.status === 'fulfilled' ? 'fulfilled' : 'queued',
|
|
2446
|
+
characterName: stringValue(request.characterName) ?? 'Komo',
|
|
2447
|
+
prompt,
|
|
2448
|
+
negativePrompt: stringValue(request.negativePrompt) ?? '',
|
|
2449
|
+
aspectRatio: normalizeCharacterAspectRatio(stringValue(request.aspectRatio)),
|
|
2450
|
+
count: numberValue(request.count) ?? 4,
|
|
2451
|
+
requestedAt: stringValue(request.requestedAt) ?? '',
|
|
2452
|
+
source: 'review-ui',
|
|
2453
|
+
...(stringValue(request.notes) ? { notes: stringValue(request.notes)! } : {}),
|
|
2454
|
+
...(stringValue(request.fulfilledAt) ? { fulfilledAt: stringValue(request.fulfilledAt)! } : {}),
|
|
2455
|
+
...(stringValue(request.characterProfileId) ? { characterProfileId: stringValue(request.characterProfileId)! } : {}),
|
|
2456
|
+
...(numberValue(request.goBananasId) ? { goBananasId: numberValue(request.goBananasId)! } : {}),
|
|
2457
|
+
...(stringValue(request.referenceImageUrl) ? { referenceImageUrl: stringValue(request.referenceImageUrl)! } : {}),
|
|
2458
|
+
...(stringValue(request.imageId) ? { imageId: stringValue(request.imageId)! } : {}),
|
|
2459
|
+
...(stringValue(request.failedAt) ? { failedAt: stringValue(request.failedAt)! } : {}),
|
|
2460
|
+
...(stringValue(request.error) ? { error: stringValue(request.error)! } : {}),
|
|
2461
|
+
};
|
|
2462
|
+
})
|
|
2463
|
+
.filter((request): request is ReviewCharacterIterationRequest => request !== null),
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
function normalizeCharacterAspectRatio(value: string | null): string {
|
|
2468
|
+
if (!value || value === '1:1') return 'square';
|
|
2469
|
+
return value;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
async function findReviewMediaAssets(root: string): Promise<Array<{ path: string; kind: string }>> {
|
|
2473
|
+
const roots = [
|
|
2474
|
+
join(root, 'docs', 'assets'),
|
|
2475
|
+
join(root, 'skills', 'davendra-presenter', 'assets'),
|
|
2476
|
+
join(root, 'skills', 'nex-presenter', 'assets'),
|
|
2477
|
+
];
|
|
2478
|
+
const assets: Array<{ path: string; kind: string }> = [];
|
|
2479
|
+
for (const dir of roots) {
|
|
2480
|
+
if (!existsSync(dir)) continue;
|
|
2481
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
2482
|
+
for (const entry of entries) {
|
|
2483
|
+
if (!entry.isFile()) continue;
|
|
2484
|
+
const filePath = join(dir, entry.name);
|
|
2485
|
+
const kind = extname(entry.name).replace('.', '').toLowerCase();
|
|
2486
|
+
if (!['jpg', 'jpeg', 'png', 'gif', 'webp', 'mp4', 'webm', 'mov'].includes(kind)) continue;
|
|
2487
|
+
assets.push({
|
|
2488
|
+
path: filePath.slice(root.length + 1),
|
|
2489
|
+
kind,
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
assets.sort((left, right) => left.path.localeCompare(right.path));
|
|
2494
|
+
return assets;
|
|
2495
|
+
}
|