@koda-sl/baker-cli 0.533.0 → 0.534.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/dist/{canvas-G4QXNPPE.js → canvas-U7MZXZKR.js} +11 -11
  3. package/dist/{catalog-PTVQSATT.js → catalog-FLWVVX2U.js} +3 -2
  4. package/dist/{catalog-PTVQSATT.js.map → catalog-FLWVVX2U.js.map} +1 -1
  5. package/dist/{chunk-PE4ZP7MX.js → chunk-I3LRC33M.js} +520 -3963
  6. package/dist/chunk-I3LRC33M.js.map +1 -0
  7. package/dist/chunk-MIRPLAFD.js +1902 -0
  8. package/dist/chunk-MIRPLAFD.js.map +1 -0
  9. package/dist/chunk-NZUMNTAT.js +3510 -0
  10. package/dist/chunk-NZUMNTAT.js.map +1 -0
  11. package/dist/{chunk-JARRF5VX.js → chunk-PV2JVXFM.js} +2 -2
  12. package/dist/{chunk-N6WHUUFH.js → chunk-R6WEPF63.js} +2 -2
  13. package/dist/{chunk-NA5Q473J.js → chunk-RQB4XLSF.js} +90 -1749
  14. package/dist/chunk-RQB4XLSF.js.map +1 -0
  15. package/dist/{chunk-DJTWY7GL.js → chunk-T33M3PK7.js} +6 -18
  16. package/dist/chunk-T33M3PK7.js.map +1 -0
  17. package/dist/{chunk-VV6FDIP5.js → chunk-TNKKK7XD.js} +2 -2
  18. package/dist/{chunk-7GGLZOQN.js → chunk-TNOOWNN7.js} +2 -2
  19. package/dist/{chunk-SEYCF446.js → chunk-UAB4CYBH.js} +3 -3
  20. package/dist/cli.js +2 -2
  21. package/dist/{creatives-ADH6HCCA.js → creatives-NTAKM5TG.js} +5 -5
  22. package/dist/engine/index.js +2 -1
  23. package/dist/{feedback-MORAVN52.js → feedback-I5FZZAJR.js} +8 -4
  24. package/dist/{feedback-MORAVN52.js.map → feedback-I5FZZAJR.js.map} +1 -1
  25. package/dist/{feedback-record-M3RB7H2C.js → feedback-record-U2O24ITU.js} +8 -4
  26. package/dist/{feedback-record-M3RB7H2C.js.map → feedback-record-U2O24ITU.js.map} +1 -1
  27. package/dist/{make-7CIVNW77.js → make-JPXGF44U.js} +6 -5
  28. package/dist/{make-7CIVNW77.js.map → make-JPXGF44U.js.map} +1 -1
  29. package/dist/{rerun-ON6YCJAL.js → rerun-HCGLAUYW.js} +8 -7
  30. package/dist/{rerun-ON6YCJAL.js.map → rerun-HCGLAUYW.js.map} +1 -1
  31. package/dist/{run-YORKHK6S.js → run-2CSG6NIY.js} +8 -7
  32. package/dist/{scaffold-ad-RHSHBCVV.js → scaffold-ad-AQK4PX7C.js} +4 -3
  33. package/dist/{scaffold-ad-RHSHBCVV.js.map → scaffold-ad-AQK4PX7C.js.map} +1 -1
  34. package/dist/{scaffold-static-ad-XNOGCMPF.js → scaffold-static-ad-ORMMKUWY.js} +9 -8
  35. package/dist/{scaffold-static-ad-XNOGCMPF.js.map → scaffold-static-ad-ORMMKUWY.js.map} +1 -1
  36. package/dist/{scaffold-video-XEVJQ7QZ.js → scaffold-video-525OYZYI.js} +4 -3
  37. package/dist/{scaffold-video-XEVJQ7QZ.js.map → scaffold-video-525OYZYI.js.map} +1 -1
  38. package/dist/{select-BXBNFGVL.js → select-XAC7G4ZT.js} +7 -3
  39. package/dist/{select-BXBNFGVL.js.map → select-XAC7G4ZT.js.map} +1 -1
  40. package/dist/{set-prompt-JJWBB23C.js → set-prompt-H3UEDJWS.js} +3 -2
  41. package/dist/{set-prompt-JJWBB23C.js.map → set-prompt-H3UEDJWS.js.map} +1 -1
  42. package/dist/stale-TPLB2P4E.js +90 -0
  43. package/dist/stale-TPLB2P4E.js.map +1 -0
  44. package/dist/{sync-definition-7NBT6LTK.js → sync-definition-K5TJORL5.js} +4 -3
  45. package/dist/{translate-VMQHPX4B.js → translate-R2DXDXUY.js} +8 -5
  46. package/dist/{translate-VMQHPX4B.js.map → translate-R2DXDXUY.js.map} +1 -1
  47. package/dist/{validate-NB6PS5PF.js → validate-FGETFHYN.js} +7 -4
  48. package/dist/{validate-NB6PS5PF.js.map → validate-FGETFHYN.js.map} +1 -1
  49. package/package.json +1 -1
  50. package/dist/chunk-DJTWY7GL.js.map +0 -1
  51. package/dist/chunk-NA5Q473J.js.map +0 -1
  52. package/dist/chunk-OPUEQSKM.js +0 -122
  53. package/dist/chunk-OPUEQSKM.js.map +0 -1
  54. package/dist/chunk-PE4ZP7MX.js.map +0 -1
  55. package/dist/stale-SK2SDGOT.js +0 -76
  56. package/dist/stale-SK2SDGOT.js.map +0 -1
  57. /package/dist/{canvas-G4QXNPPE.js.map → canvas-U7MZXZKR.js.map} +0 -0
  58. /package/dist/{chunk-JARRF5VX.js.map → chunk-PV2JVXFM.js.map} +0 -0
  59. /package/dist/{chunk-N6WHUUFH.js.map → chunk-R6WEPF63.js.map} +0 -0
  60. /package/dist/{chunk-VV6FDIP5.js.map → chunk-TNKKK7XD.js.map} +0 -0
  61. /package/dist/{chunk-7GGLZOQN.js.map → chunk-TNOOWNN7.js.map} +0 -0
  62. /package/dist/{chunk-SEYCF446.js.map → chunk-UAB4CYBH.js.map} +0 -0
  63. /package/dist/{creatives-ADH6HCCA.js.map → creatives-NTAKM5TG.js.map} +0 -0
  64. /package/dist/{run-YORKHK6S.js.map → run-2CSG6NIY.js.map} +0 -0
  65. /package/dist/{sync-definition-7NBT6LTK.js.map → sync-definition-K5TJORL5.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/canvas/scaffold-video.ts","../src/engine/scaffold/lib/model-router.ts","../src/engine/nodes/local/lib/sceneDetect.ts","../src/commands/canvas/gitignore.ts"],"sourcesContent":["import { cp, mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport {\n AD_IMAGE_MODEL,\n BLUEPRINT_ANALYSIS_MODEL,\n DEFAULT_VIDEO_GENERATE_MODEL,\n SEEDANCE_DURATIONS,\n VIDEO_DECONSTRUCT_MODEL,\n} from \"@baker/canvas-contract\";\nimport { defineCommand } from \"citty\";\nimport { fetchExternalBytes } from \"../../lib/externalFetch.ts\";\nimport { MAX_ASSET_BYTES } from \"../../engine/storage/asset-store.ts\";\nimport { routeVideoModel, type VideoBudget } from \"../../engine/scaffold/lib/model-router.ts\";\nimport { sha256Hex } from \"../../engine/storage/sha256.ts\";\nimport { createEngineFromEnv, defaultRegistry, ValidationError, validateCanvasDeep } from \"../../engine/index.ts\";\nimport { detectSceneCutsPySceneDetect } from \"../../engine/nodes/local/lib/sceneDetect.ts\";\nimport {\n annotateBlueprintWithElements,\n avatarsNotCast,\n buildOverlayHtml,\n canvasDims,\n castAvatarsOnElements,\n genAspectsFor,\n resolveAspect,\n type ScaffoldVideoOpts,\n scaffoldVideoCanvas,\n slimBlueprintForSelection,\n videoReport,\n} from \"../../engine/scaffold/video.ts\";\nimport { stampCompositionDims } from \"./composition-dims.ts\";\nimport {\n fail,\n fileExists,\n loadAssetText,\n parseElements,\n resolveModel,\n SHIPPED_CAPTIONS_DIR,\n SHIPPED_OVERLAY_COMPOSITION_DIR,\n} from \"./scaffold-common.ts\";\nimport { REBUILD_FILE, writeSceneFiles } from \"./scene-files.ts\";\nimport { renderStyleProjectionFromValue } from \"./style-projection.ts\";\nimport { buildCreativeDefinition, definitionPlatformsOf, resolveFormats, titleFromSlug } from \"./creative-definition.ts\";\nimport { ensureGitignore } from \"./gitignore.ts\";\nimport { computeSourceSha } from \"./source-version.ts\";\nimport { readDefinitionPlatforms, syncCreativeDefinitionBestEffort } from \"./sync-definition.ts\";\nimport { resolveRelativeCanvasPaths } from \"./resolve-paths.ts\";\nimport { isValidScaffoldSlug } from \"./scaffold-static-ad-paths.ts\";\nimport { resolveCastAvatars } from \"../avatars/resolveCast.ts\";\nimport { askJevQuestions } from \"../../engine/nodes/local/lib/jev.ts\";\nimport { cachedJev } from \"../../engine/nodes/local/lib/jev-cache.ts\";\nimport { voiceGenderModel } from \"../../engine/scaffold/video/dialogue/voice-gender.ts\";\nimport type { ReviewUsage } from \"../../engine/nodes/local/lib/render-review.ts\";\nimport { languageCodeNamed, spokenLanguage } from \"../../engine/scaffold/lib/language.ts\";\nimport { readBlueprintSpeech } from \"../../engine/scaffold/video/speech-reading.ts\";\nimport { findWorkspaceRoot } from \"./normalize-paths.ts\";\nimport { holdUnbilledSpend } from \"./unbilled-spend.ts\";\n\nconst SELECT_SYSTEM =\n \"You identify the RECURRING, identity-critical visual elements of a video ad from its deconstruction blueprint — \" +\n \"the people, animals, products, and logos that must stay consistent every time they appear, so a faithful \" +\n \"reproduction grounds each in ONE real reference image reused across every frame it appears in. Output ONLY a JSON \" +\n 'object of the form { \"elements\": [ ... ] }, no prose.';\n\nconst SELECT_PROMPT =\n \"VIDEO DECONSTRUCTION BLUEPRINT (video-deconstruct/1):\\n{{blueprint}}\\n\\n\" +\n \"List ONLY the elements worth keeping consistent across frames — the ones a reproduction must ground in a real asset:\\n\" +\n '- each recurring person from global.cast (a presenter/actor/persona) -> type \"person\"\\n' +\n '- a recurring animal/pet/character with a stable identity -> type \"animal\"\\n' +\n '- a showcased product, package, card, or device the ad sells or demonstrates -> type \"product\"\\n' +\n '- the advertiser brand logo/wordmark (from global.branding) -> type \"logo\"\\n' +\n '- a recurring trust/rating/certification badge -> type \"badge\"\\n' +\n '- the dominant recording set/location the scenes share (e.g. the same living room, car interior, kitchen) -> type \"location\"\\n\\n' +\n \"DROP one-off background extras and incidental props — but the shared set/location is NOT generic scenery: pin it as ONE \" +\n '\"location\" element so the room stays identical across scenes. A person in global.cast is almost always recurring. Keep at most ~8.\\n\\n' +\n \"ONE PERSON, MULTIPLE LOOKS: if a single individual plays MULTIPLE personas or wardrobes (e.g. a creator as a skeptic in \" +\n \"one outfit and a believer in another, or a before/after), emit ONE element PER look (so each outfit gets its own real \" +\n 'reference image) and link the extra looks to the first via \"same_as\" — they are the SAME person, only the wardrobe/state ' +\n \"differs, so the face must stay identical.\\n\\n\" +\n 'For each kept element return: { \"type\": one of person|animal|product|logo|badge|location, \"label\": a short ' +\n 'UPPER_SNAKE_CASE name (e.g. HERO, CREATOR_SKEPTIC, INSURANCE_CARD, LOGO), \"description\": a concrete reusable ' +\n \"description to source/shoot the real asset — for a person/animal give a NEUTRAL castable role (e.g. \" +\n '\"hero pet-owner, woman in her 30s\" or \"a small beagle\"), NOT the original individual\\'s literal face/identity: ' +\n \"we RECAST with a FRESH person/animal, so never tell the agent to reuse the original. \" +\n '\"expression\": a living subject\\'s typical expression or null, \"cast_id\": the global.cast id if it maps to one else ' +\n 'null, \"same_as\": the label of another element this is the SAME individual as (different wardrobe/persona) else null, ' +\n '\"scenes\": the 0-based indices of ONLY the scenes where the element is ACTUALLY VISIBLE ON SCREEN — judged from ' +\n \"that scene's start_frame_prompt / end_frame_prompt subjects and its action_detail, NOT from who is merely \" +\n \"speaking. A narrator heard over b-roll is NOT present in that b-roll scene; a dog-running cutaway does NOT \" +\n \"contain the couch creator just because she talks across it. Do NOT pad the list — an element wrongly listed in a \" +\n \"scene makes the reproduction render the wrong subject there (e.g. the creator appearing in a pure-dog b-roll). \" +\n \"When in doubt, leave a scene OUT. Output ONLY the JSON object.\";\n\n/** Load the deconstruct's word-level transcript as text; a missing/unreadable one just\n * means no caption track is wired (best-effort — never fails the scaffold). */\nasync function loadTranscriptBestEffort(ref: unknown): Promise<string | undefined> {\n if (!ref) return undefined;\n try {\n return await loadAssetText(ref, \"deconstruct transcript\");\n } catch {\n return undefined;\n }\n}\n\n/**\n * Stage the burned-in caption track next to the canvas: copy the TikTok caption\n * composition. The caption TEXT is transcribed from the rendered audio at run time\n * (`video_transcribe`, wired by the scaffolder) — not the source transcript — so we don't\n * persist the deconstruct transcript here; we only use its presence to decide whether the\n * ad has speech worth captioning. Returns `compositionPath` (undefined ⇒ no caption track).\n */\nasync function stageCaptions(outDir: string, transcript: string | undefined): Promise<{ compositionPath?: string }> {\n const text = transcript?.trim();\n if (!text || text === \"[]\") return {};\n const compositionPath = path.join(outDir, \"tiktok-captions-composition\");\n await cp(SHIPPED_CAPTIONS_DIR, compositionPath, { recursive: true });\n return { compositionPath };\n}\n\n/**\n * Detect hard shot cuts in the local video with PySceneDetect (`scenedetect`, installed\n * in the canvas sandbox). Best-effort: a detection failure just falls back to LLM-only\n * scene boundaries, so a transient `scenedetect` error never blocks the scaffold.\n */\nasync function detectShotCutsBestEffort(videoPath: string, threshold?: number): Promise<number[] | undefined> {\n try {\n const cuts = await detectSceneCutsPySceneDetect(videoPath, threshold ? { threshold } : {});\n if (cuts.length > 0) {\n process.stderr.write(`Detected ${cuts.length} shot cut(s) via PySceneDetect: ${cuts.join(\", \")}s\\n`);\n } else {\n process.stderr.write(\"PySceneDetect ran but found no hard cuts; using LLM scene boundaries.\\n\");\n }\n return cuts;\n } catch (e) {\n // Stay non-fatal (the deconstruct can still segment with LLM-only boundaries),\n // but a MISSING `scenedetect` binary is an actionable setup gap, not a transient\n // blip — without cuts the segmentation tends to be too coarse, so shout LOUDLY\n // and tell the operator how to fix it instead of degrading silently.\n const msg = e instanceof Error ? e.message : String(e);\n const code = (e as { code?: unknown })?.code;\n const missing = code === \"ENOENT\" || /ENOENT|not found|command not found/i.test(msg);\n if (missing) {\n process.stderr.write(\n \"WARNING: `scenedetect` (PySceneDetect) is NOT installed — falling back to LLM-only scene boundaries, \" +\n \"which under-segments (coarse 9-15s scenes instead of the real 1-4s cuts). \" +\n \"Install it (`pipx install scenedetect[opencv]` or `pip install scenedetect[opencv]`) for accurate shot-cut detection.\\n\",\n );\n } else {\n process.stderr.write(`Shot-cut detection skipped (${msg}); using LLM boundaries.\\n`);\n }\n // Detection FAILED (vs \"ran and found none\") — undefined tells the deconstruct\n // it cannot treat the video as verified cut-less.\n return undefined;\n }\n}\n\nconst VIDEO_EXT_BY_MIME: Record<string, string> = {\n \"video/mp4\": \".mp4\",\n \"video/quicktime\": \".mov\",\n \"video/webm\": \".webm\",\n \"video/x-matroska\": \".mkv\",\n};\n\n/** Extension for a downloaded reference: URL pathname first, then content-type, else .mp4. */\nexport function referenceVideoExt(url: string, contentType: string | null): string {\n const fromPath = path.extname(new URL(url).pathname).toLowerCase();\n if (fromPath && fromPath.length <= 5) return fromPath;\n const mime = (contentType ?? \"\").split(\";\")[0]?.trim().toLowerCase();\n return (mime && VIDEO_EXT_BY_MIME[mime]) || \".mp4\";\n}\n\n/**\n * The reference the scaffold reproduces. Accept a URL (e.g. a `baker winning-ads`\n * presigned link) directly and download it once to a temp file — the local bytes\n * are needed anyway for PySceneDetect shot-cut detection before the billed\n * deconstruct — so the caller never has to `curl` it first. A local path passes\n * straight through.\n */\n/**\n * The INSPIRATION video's provenance for the creative's `_definition.md` + dashboard: the\n * durable, content-addressed R2 URL the deconstruct already uploaded the source to\n * (`blueprint.source.url` — survives even after a presigned winning-ads URL expires),\n * falling back to the original URL argument; plus the advertiser the deconstruct identified.\n * We record the URL rather than committing the file: a reference video can be up to 2 GiB\n * and the video canvas never re-ingests the source at run time (it uses the extracted frame\n * URLs), so a git copy would be pure bloat with no re-run benefit.\n */\nfunction videoSourceReference(blueprint: unknown, fileArg: string): { url?: string; advertiser?: string } {\n const bp = (blueprint ?? {}) as { source?: { url?: unknown }; global?: { branding?: { brand_name?: unknown } } };\n const durable = typeof bp.source?.url === \"string\" ? bp.source.url : undefined;\n const original = /^https?:\\/\\//i.test(fileArg) ? fileArg : undefined;\n const brand = bp.global?.branding?.brand_name;\n return { url: durable ?? original, advertiser: typeof brand === \"string\" && brand.trim() ? brand.trim() : undefined };\n}\n\n/** A short human summary for the video creative's `_definition.md` body (deconstruct-derived). */\nfunction videoDefinitionDescription(blueprint: unknown): string | undefined {\n const g = ((blueprint ?? {}) as { global?: Record<string, unknown> }).global ?? {};\n const notes = g.reproduction_notes;\n if (typeof notes === \"string\" && notes.trim()) return notes.trim();\n const product = (g.branding as { product?: unknown } | undefined)?.product;\n return typeof product === \"string\" && product.trim() ? product.trim() : undefined;\n}\n\n/**\n * The frames mode the canvas actually uses. `--frames reuse` on a video with people in it\n * is refused by the scaffold (their faces would reach our ad), so report that and why\n * rather than echo the flag back.\n */\nfunction framesModeReport(\n canvas: { metadata?: unknown },\n requested: \"generate\" | \"reuse\",\n): { frames_mode: string; frames_reuse_refused?: string } {\n const todo = (canvas.metadata as { todo?: { frames_mode?: unknown; frames_reuse_refused?: unknown } } | undefined)\n ?.todo;\n const mode = typeof todo?.frames_mode === \"string\" ? todo.frames_mode : requested;\n const refused = typeof todo?.frames_reuse_refused === \"string\" ? todo.frames_reuse_refused : undefined;\n return { frames_mode: mode, ...(refused ? { frames_reuse_refused: refused } : {}) };\n}\n\nasync function materializeReferenceVideo(fileArg: string): Promise<string> {\n if (!/^https?:\\/\\//i.test(fileArg)) return path.resolve(fileArg);\n let bytes: Buffer;\n let contentType: string | null;\n try {\n // No challenge sniff: this is definitionally a video file, and decoding the\n // first 64 KB of one as text to look for a bot wall is waste, not caution.\n // The canvas asset ceiling, not the image one: this is a reference video,\n // and it is the surface most likely to meet a limit at all.\n ({ buffer: bytes, contentType } = await fetchExternalBytes(fileArg, {\n sniffChallenge: false,\n maxBytes: MAX_ASSET_BYTES,\n }));\n } catch (e) {\n throw new Error(`failed to download reference video: ${e instanceof Error ? e.message : String(e)}`);\n }\n if (bytes.length === 0) throw new Error(\"reference video download was empty\");\n const dest = path.join(\n tmpdir(),\n `baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg, contentType)}`,\n );\n await writeFile(dest, bytes);\n return dest;\n}\n\n/** Parse the `--seam-dedup` flag; defaults to \"head\". Rejects any other value so a typo fails loud. */\nfunction resolveSeamDedup(raw: unknown): ScaffoldVideoOpts[\"seamDedup\"] {\n if (raw === undefined) return \"head\";\n const v = String(raw);\n if (v === \"head\" || v === \"tail\" || v === \"off\") return v;\n throw new Error(`--seam-dedup must be \"head\", \"tail\", or \"off\" (got \"${v}\")`);\n}\n\nconst VIDEO_BUDGETS: readonly VideoBudget[] = [\"economy\", \"standard\", \"premium\"];\nfunction parseBudget(raw: unknown): VideoBudget {\n const v = raw === undefined ? \"standard\" : String(raw);\n if ((VIDEO_BUDGETS as readonly string[]).includes(v)) return v as VideoBudget;\n throw new Error(`--budget must be one of ${VIDEO_BUDGETS.join(\", \")} (got \"${v}\")`);\n}\n\n/** Resolve the model ids that DON'T depend on the deconstruct output. The video\n * model is resolved separately (`resolveVideoModel`) once the cast is known. */\nfunction resolveModels(args: Record<string, unknown>): {\n deconstructModel: string;\n selectModel: string;\n imageModel: string;\n} {\n return {\n // Flash by default (the `videoDeconstruct` role). Override with --deconstruct-model\n // ~google/gemini-pro-latest for the densest extraction when speed matters less.\n deconstructModel: resolveModel(\"video_deconstruct\", args[\"deconstruct-model\"], VIDEO_DECONSTRUCT_MODEL),\n selectModel: resolveModel(\"text_generate\", args[\"select-model\"], BLUEPRINT_ANALYSIS_MODEL),\n // The ad-frame model (matches the static-ad scaffold); the frame generators need the\n // most faithful text/identity reproduction. Override with --image-model for a\n // cheaper/faster pass.\n imageModel: resolveModel(\"image_generate\", args[\"image-model\"], AD_IMAGE_MODEL),\n };\n}\n\n/** True when the deconstruct surfaced a photoreal on-camera person/animal cast —\n * exactly the frames ByteDance's real-person filter blocks on Seedance. */\nexport function hasPhotorealCast(elements: unknown): boolean {\n return (\n Array.isArray(elements) &&\n elements.some((e) => {\n const type = typeof e === \"object\" && e !== null ? (e as { type?: unknown }).type : undefined;\n return type === \"person\" || type === \"animal\";\n })\n );\n}\n\n/** Resolve the video model once the cast is known. An explicit --video-model\n * always wins. Otherwise the scored router picks from the curated roster — and a\n * detected photoreal on-camera cast counts as a real-face brief, so it routes to\n * Veo up front. That cast WOULD be blocked on Seedance by ByteDance's real-person\n * filter, and a blocked frame-started clip is not moved to another model at run\n * time, so picking Seedance burns an attempt and dead-ends. Mirrors Higgsfield's models_explore\n * and explains its pick via the returned reason. */\nexport function resolveVideoModel(\n args: Record<string, unknown>,\n elements: unknown,\n): { videoModel: string; videoRouteReason?: string } {\n if (args[\"video-model\"]) {\n return { videoModel: resolveModel(\"video_generate\", args[\"video-model\"], DEFAULT_VIDEO_GENERATE_MODEL) };\n }\n const photorealCast = hasPhotorealCast(elements);\n const route = routeVideoModel({\n hasRealFace: Boolean(args[\"real-face\"]) || photorealCast,\n needsIdentity: Boolean(args.identity),\n needsMotionTransfer: Boolean(args[\"motion-transfer\"]),\n budget: parseBudget(args.budget),\n });\n // Make the auto-detection legible when it (not an explicit flag) drove the pick.\n const reason =\n photorealCast && !args[\"real-face\"]\n ? `${route.reason} — detected a photoreal on-camera cast, which Seedance's real-person filter would block`\n : route.reason;\n return {\n videoModel: resolveModel(\"video_generate\", route.model, DEFAULT_VIDEO_GENERATE_MODEL),\n videoRouteReason: reason,\n };\n}\n\n/** The deconstruct canvas: ingest → full scene-by-scene blueprint. Run first, on its own. */\nfunction buildDeconstructCanvas(\n videoPath: string,\n deconstructModel: string,\n opts: { maxScenes?: number; language?: string; focus?: string; shotCuts?: number[] },\n) {\n const deconstructParams: Record<string, unknown> = { model: deconstructModel, mode: \"full\" };\n if (typeof opts.maxScenes === \"number\") deconstructParams.max_scenes = opts.maxScenes;\n if (opts.language) deconstructParams.language = opts.language;\n if (opts.focus) deconstructParams.focus = opts.focus;\n // Real visual shot cuts (detected locally with PySceneDetect) so the deconstruct\n // snaps scene boundaries onto them and never lets a scene span a hard cut. An\n // EMPTY list is meaningful (detection ran, the video is verified cut-less — every\n // LLM boundary is a same-take beat); only a failed detection omits the param.\n if (opts.shotCuts) deconstructParams.shot_cuts = opts.shotCuts;\n // The Seedance per-clip ceiling: a shot longer than this is split into seamless\n // continuation sub-scenes (shared splice frame) instead of being truncated.\n deconstructParams.max_clip_s = SEEDANCE_DURATIONS[SEEDANCE_DURATIONS.length - 1];\n return {\n schema: \"baker-canvas/1\" as const,\n metadata: { name: \"video deconstruct pass\" },\n nodes: [\n { id: \"src\", type: \"ingest\", params: { source: \"path\", path: videoPath, expect: \"video\" } },\n { id: \"deconstruct\", type: \"video_deconstruct\", inputs: { video: \"$ref:src.asset\" }, params: deconstructParams },\n ],\n output: { node: \"deconstruct\", output: \"analysis\" },\n };\n}\n\n/**\n * The element-selection canvas: a single text_generate over a SLIMMED blueprint. The slim\n * is inlined into the prompt as a literal STRING (no `$ref` slot), so it never trips the\n * engine's 256KB inline-slot limit no matter how long the ad — a full 42-scene blueprint\n * inlined as a slot blew past it. A function replacer keeps `$`/`{{` in the JSON literal.\n */\nfunction buildSelectCanvas(selectModel: string, slimmedBlueprintJson: string) {\n return {\n schema: \"baker-canvas/1\" as const,\n metadata: { name: \"element selection pass\" },\n nodes: [\n {\n id: \"select\",\n type: \"text_generate\",\n params: {\n model: selectModel,\n max_tokens: 6000,\n temperature: 0,\n response_format: \"json_object\",\n system: SELECT_SYSTEM,\n prompt: SELECT_PROMPT.replace(\"{{blueprint}}\", () => slimmedBlueprintJson),\n },\n },\n ],\n output: { node: \"select\", output: \"text\" },\n };\n}\n\n/**\n * Run the deconstruct pass, then the element-selection pass over a SLIMMED blueprint.\n * Two passes (not one chained canvas) so the CLI can slim the blueprint between them —\n * the selector only reads cast/branding + a few per-scene prompt fields, and inlining the\n * full blueprint busts the engine's inline-prompt limit on long ads. The FULL blueprint is\n * still returned (the source of truth for prompt.json + scaffolding). Credits sum both runs.\n */\nasync function runAnalysisPasses(\n deconstructCanvas: ReturnType<typeof buildDeconstructCanvas>,\n selectModel: string,\n): Promise<{\n blueprint: unknown;\n elements: unknown[];\n transcript: string | undefined;\n /** The language the deconstruct's transcriber heard, as it named it (\"es\", or \"spanish\"). */\n detectedLanguage: string | undefined;\n creditsSpent: number | undefined;\n}> {\n const engine = createEngineFromEnv({ log: (line) => process.stderr.write(`${line}\\n`) });\n let credits = 0;\n let sawCredits = false;\n const addCredits = (stats: unknown) => {\n const c = (stats as { total_credits?: number })?.total_credits;\n if (typeof c === \"number\") {\n credits += c;\n sawCredits = true;\n }\n };\n\n // Pass 1: the full deconstruct blueprint (+ its word-level transcript, used to drive\n // the burned-in caption track).\n let blueprint: unknown;\n let transcript: string | undefined;\n let detectedLanguage: string | undefined;\n try {\n const r1 = await engine.run(deconstructCanvas, {});\n addCredits(r1.stats);\n blueprint = JSON.parse(await loadAssetText(r1.outputs_by_node.deconstruct?.analysis, \"deconstruct output\"));\n transcript = await loadTranscriptBestEffort(r1.outputs_by_node.deconstruct?.transcript);\n const heard = (r1.outputs_by_node.deconstruct?.transcript as { metadata?: { detected_language?: unknown } } | undefined)\n ?.metadata?.detected_language;\n detectedLanguage = typeof heard === \"string\" && heard.trim() ? heard.trim() : undefined;\n } catch (e) {\n if (e instanceof ValidationError) return fail(\"validation\", JSON.stringify(e.issues));\n if (e instanceof SyntaxError) return fail(\"read_outputs\", e.message);\n return fail(\"deconstruct\", e instanceof Error ? e.message : String(e));\n }\n\n // Pass 2: element selection over the slimmed blueprint (inlined as a literal prompt).\n const slimJson = JSON.stringify(slimBlueprintForSelection(blueprint));\n try {\n const r2 = await engine.run(buildSelectCanvas(selectModel, slimJson), {});\n addCredits(r2.stats);\n const elements = parseElements(await loadAssetText(r2.outputs_by_node.select?.text, \"selection output\"));\n return { blueprint, elements, transcript, detectedLanguage, creditsSpent: sawCredits ? credits : undefined };\n } catch (e) {\n if (e instanceof ValidationError) return fail(\"validation\", JSON.stringify(e.issues));\n if (e instanceof SyntaxError) return fail(\"read_outputs\", e.message);\n return fail(\"deconstruct\", e instanceof Error ? e.message : String(e));\n }\n}\n\nexport const scaffoldVideoCommand = defineCommand({\n meta: {\n name: \"scaffold-video\",\n description:\n \"Turn a reference video into a runnable reproduction canvas in one command. Runs billed passes — video_deconstruct \" +\n \"(the full scene-by-scene blueprint + transcript, baked to a split, editable blueprint: a global prompt.json plus one \" +\n \"small scenes/sNN.json per scene) and an AI selection \" +\n \"of the video's RECURRING identity elements (person/animal/product/logo) — then scaffolds a pipeline where every \" +\n \"scene boundary is a static-ad-grade frame (the blueprint as target_blueprint, a reference legend, the real frame as \" +\n \"anchor) and each recurring element gets ONE shared [TODO] ingest slot wired into every frame it appears in. The \" +\n \"clips feed Seedance an ultra-detailed motion brief (action, camera, dialogue, transcript). Edit a scene's \" +\n \"scenes/sNN.json (or prompt.json for global cast/palette/brand), fill the remaining [TODO] images, then `baker canvas run`. \" +\n \"A person in the reference is played by an invented, realistic person generated from their description — never the \" +\n \"source actor and never a photo of anyone real; pass `--avatar <handle>` (`baker avatars list`) when the same actor \" +\n \"has to recur across the company's ads.\",\n },\n args: {\n file: {\n type: \"positional\",\n required: true,\n description:\n \"Reference video — a local path OR an http(s) URL (e.g. a winning-ads link). A URL is downloaded for you; pass --slug or --out with it.\",\n },\n out: { type: \"string\", description: \"Output canvas path (default <video-dir>/<name>.video.canvas.json)\" },\n slug: {\n type: \"string\",\n description: \"Creative slug — writes the canvas to src/creatives/<slug>/<slug>.canvas.json (repo convention)\",\n },\n frames: {\n type: \"string\",\n description:\n '\"generate\" (default, every frame regenerated for our ad) or \"reuse\" (wire the source\\'s real frames in). Reuse applies only to a video with nobody in it — on a video showing people it is refused and every frame is generated, so the source\\'s actors never reach our ad.',\n },\n ambient: {\n type: \"boolean\",\n description: \"Give silent b-roll scenes native diegetic ambient mixed deep under the music bed (off by default)\",\n },\n \"seam-dedup\": {\n type: \"string\",\n description:\n 'How to dedup the frame two clips SHARE when a long shot is split for length: \"head\" (default, drop the second clip\\'s first frame), \"tail\" (drop the first clip\\'s last frame), or \"off\" (keep both).',\n },\n \"max-scenes\": { type: \"string\", description: \"Cap the number of scenes the deconstruct emits\" },\n \"shot-threshold\": {\n type: \"string\",\n description:\n \"PySceneDetect content threshold. Default is adaptive (18, auto re-checked at 27 when a continuous shot looks over-segmented); pinning a value disables the re-check. Lower = more/softer cuts, higher = fewer.\",\n },\n language: { type: \"string\", description: \"Transcript/dialogue language hint (e.g. fr, en)\" },\n focus: { type: \"string\", description: \"Known provenance/emphasis to ground the deconstruct\" },\n advertiser: {\n type: \"string\",\n description: \"Source advertiser recorded in _definition.md (default: the brand the deconstruct identified)\",\n },\n platforms: {\n type: \"string\",\n description:\n \"Where the ad runs, the main platform first (meta, linkedin, tiktok, …): recorded in _definition.md. Take it from the person's request or the idea.\",\n },\n platform: { type: \"string\", description: \"Retired: use --platforms. Still recorded in _definition.md.\" },\n \"deconstruct-model\": { type: \"string\", description: \"Override the video_deconstruct model id\" },\n \"select-model\": { type: \"string\", description: \"Override the text_generate model id for element selection\" },\n \"image-model\": { type: \"string\", description: \"Override the image_generate model id for frames\" },\n \"video-model\": { type: \"string\", description: \"Override the video_generate model id for clips (skips the scored router)\" },\n \"real-face\": {\n type: \"boolean\",\n description:\n \"The ad has a photoreal human face on camera → the router picks Veo (dodges the ByteDance real-person filter). Set this for ANY photoreal presenter/creator/testimonial cast — the filter blocks AI-GENERATED photoreal faces too, not just real ones.\",\n },\n \"motion-transfer\": {\n type: \"boolean\",\n description: \"Motion driven from a reference video / deliberately hyper-dynamic → the router picks Kling.\",\n },\n identity: {\n type: \"boolean\",\n description: \"The same character/product must stay consistent across clips → the router picks the Seedance workhorse.\",\n },\n budget: {\n type: \"string\",\n description: 'Cost posture for the router: \"economy\" | \"standard\" | \"premium\" (default standard).',\n },\n aspect: {\n type: \"string\",\n description:\n 'Output aspect ratio (e.g. \"4:5\"). Default: the source video\\'s detected ratio. When the video model ' +\n \"can't generate the ratio (Seedance has no 4:5) clips generate at the nearest supported ratio and are \" +\n \"center-cropped onto the output canvas.\",\n },\n resolution: {\n type: \"string\",\n description:\n 'Output resolution for generated clips (e.g. \"1080p\"). Default 1080p — the highest the video model supports — so clips keep the keyframe sharpness instead of the model\\'s low default.',\n },\n avatar: {\n type: \"string\",\n description:\n \"Cast company avatars as the reference's people — pass this when the same actor has to recur across ads (without it, \" +\n \"each person is invented from their description). \" +\n 'One handle, or several comma-separated in the order the people appear (`--avatar marta` / `--avatar marta,joan`). ' +\n \"Each recurring person grounds on that avatar's identity sheet, so the ad shows the company's own cast instead of \" +\n \"the source ad's actors. `baker avatars list` shows who there is; checked before anything is billed.\",\n },\n },\n async run({ args }) {\n const fileArg = String(args.file);\n const slug = args.slug ? String(args.slug) : undefined;\n if (slug && !isValidScaffoldSlug(slug)) {\n process.stderr.write(\n `${JSON.stringify({ ok: false, error: { code: \"invalid_slug\", message: \"--slug must be lowercase kebab (a-z, 0-9, hyphens), max 100 chars\" } }, null, 2)}\\n`,\n );\n process.exit(2);\n }\n // A URL is downloaded to a temp file, which is no sensible home for the output\n // canvas — require an explicit target so the reproduction lands in the repo.\n const isUrl = /^https?:\\/\\//i.test(fileArg);\n if (isUrl && !slug && !args.out) {\n return fail(\n \"missing_output_target\",\n \"When the reference is a URL, pass --slug (writes src/creatives/<slug>/) or --out <path> so the scaffolded canvas has a home in the repo.\",\n );\n }\n let videoPath: string;\n try {\n videoPath = await materializeReferenceVideo(fileArg);\n } catch (e) {\n return fail(\"download\", e instanceof Error ? e.message : String(e));\n }\n const base = path.basename(videoPath, path.extname(videoPath));\n const outPath = args.out\n ? path.resolve(String(args.out))\n : slug\n ? path.join(process.cwd(), \"src\", \"creatives\", slug, `${slug}.canvas.json`)\n : path.join(path.dirname(videoPath), `${base}.video.canvas.json`);\n const outDir = path.dirname(outPath);\n const blueprintPath = path.join(outDir, \"prompt.json\");\n // The slim style projection every frame's target_blueprint reads (global cast/palette/\n // brand only). Written alongside prompt.json so the full blueprint stays hand-editable.\n const blueprintStylePath = path.join(outDir, \"prompt.style.json\");\n const frames = args.frames === \"reuse\" ? \"reuse\" : \"generate\";\n const maxScenes = args[\"max-scenes\"] ? Number(args[\"max-scenes\"]) : undefined;\n\n // A faithful reproduction defaults to ALL source scenes. Capping is a cost\n // lever that silently DROPS scenes (the deconstruct merges everything past the\n // cap into the last kept scene), so make it a loud, explicit operator choice.\n if (Number.isFinite(maxScenes)) {\n process.stderr.write(\n `⚠️ --max-scenes ${maxScenes} caps the deconstruct: any scenes beyond ${maxScenes} are MERGED away, ` +\n `reducing fidelity (fewer cuts, lost beats). Omit it to reproduce every scene.\\n`,\n );\n }\n\n let models: ReturnType<typeof resolveModels>;\n try {\n models = resolveModels(args);\n // Checked now, before the billed passes, although the video model is only\n // routed once the cast is known.\n if (args[\"video-model\"]) resolveModel(\"video_generate\", args[\"video-model\"], DEFAULT_VIDEO_GENERATE_MODEL);\n } catch (e) {\n return fail(\"unknown_model\", e instanceof Error ? e.message : String(e));\n }\n const { deconstructModel, selectModel, imageModel } = models;\n\n // Resolved before the billed passes: a handle that cannot be cast fails for free.\n const cast = await resolveCastAvatars(args.avatar);\n if (\"error\" in cast) return fail(\"avatar\", cast.error);\n const { handles: avatarHandles, avatars } = cast;\n\n // Detect real visual shot cuts locally with PySceneDetect so the deconstruct can\n // snap scene boundaries onto them (a scene must never span a hard cut). Lower\n // `--shot-threshold` to catch more (softer) cuts on a fast-cut ad; higher to catch\n // fewer.\n const shotThreshold = args[\"shot-threshold\"] ? Number(args[\"shot-threshold\"]) : undefined;\n const shotCuts = await detectShotCutsBestEffort(videoPath, shotThreshold);\n\n // Two passes: ingest → video_deconstruct, then element selection over a SLIMMED\n // blueprint (so a long ad's blueprint never busts the inline-prompt limit).\n const deconstructCanvas = buildDeconstructCanvas(videoPath, deconstructModel, {\n maxScenes: Number.isFinite(maxScenes) ? maxScenes : undefined,\n language: args.language ? String(args.language) : undefined,\n focus: args.focus ? String(args.focus) : undefined,\n shotCuts,\n });\n const analysis = await runAnalysisPasses(deconstructCanvas, selectModel);\n const { blueprint, transcript, creditsSpent } = analysis;\n // The language the lines are spoken in: `--language` when given, else what the transcriber\n // heard, else Jev's reading of the lines. Read with each voice's gender and the words a\n // caption card may not end on, in one request, kept in the workspace's cache.\n const heard = analysis.detectedLanguage;\n const declaredLanguage =\n (args.language ? spokenLanguage(String(args.language))?.tag : undefined) ??\n (heard ? (spokenLanguage(heard)?.tag ?? languageCodeNamed(heard)) : undefined);\n const speechCache = path.join(findWorkspaceRoot(outDir) ?? process.cwd(), \"canvas\", \".cache\");\n const speechSpent: ReviewUsage[] = [];\n const speech = await readBlueprintSpeech(\n blueprint,\n declaredLanguage ? { declaredLanguage } : {},\n {\n ask: cachedJev(speechCache, (questions) => askJevQuestions(questions, (u) => speechSpent.push(u))),\n genders: voiceGenderModel(speechCache, (u) => speechSpent.push(u)),\n },\n );\n await holdUnbilledSpend(speechCache, speechSpent).catch(() => {});\n const elements = castAvatarsOnElements(analysis.elements, avatars);\n const uncast = avatarsNotCast(analysis.elements, avatarHandles);\n const uncastHint = uncast.length\n ? {\n hints: [\n `NOT CAST: ${uncast.map((h) => `\\`${h}\\``).join(\", \")} — the element selection found no more recurring people than it cast, so ${uncast.length === 1 ? \"this avatar plays\" : \"these avatars play\"} nobody in this canvas. A person shown in only one scene is not a recurring element, so those frames render whoever their scene file describes — tell the user who is cast and who is not.`,\n ],\n }\n : {};\n\n // Route the video model now that the cast is known: a photoreal on-camera\n // person/animal is auto-routed to Veo (Seedance's real-person filter would\n // block it — no point burning a wasted attempt before the runtime fallback).\n let routed: ReturnType<typeof resolveVideoModel>;\n try {\n routed = resolveVideoModel(args, elements);\n } catch (e) {\n return fail(\"unknown_model\", e instanceof Error ? e.message : String(e));\n }\n const { videoModel, videoRouteReason } = routed;\n if (videoRouteReason && !args[\"video-model\"] && !args[\"real-face\"] && hasPhotorealCast(elements)) {\n process.stderr.write(`🎬 Photoreal on-camera cast detected → generating clips on ${videoModel} (dodges Seedance's real-person filter).\\n`);\n }\n\n // Bake the deconstruct blueprint as the editable prompt, annotated so the human\n // editing it sees which wired slot grounds which recurring element.\n await mkdir(outDir, { recursive: true });\n const annotated = annotateBlueprintWithElements(blueprint, elements);\n // Split the blueprint into the global slice (`prompt.json`) + one `scenes/sNN.json`\n // per scene. An agent edits ONE small scene file at a time instead of a 2000-line\n // monolith; `run`/`validate` re-assemble and re-flow each scene into the graph\n // (see scene-projection.ts). blueprintPath still names prompt.json — now scene-less.\n await writeSceneFiles(outDir, annotated);\n // The slim style projection frames actually read via target_blueprint (no per-scene\n // array), derived from the SAME annotated blueprint so brand/cast/palette stay in\n // sync. Written through the ONE renderer `validate`/`run` also use to re-derive it\n // from an edited prompt.json — byte-identical formatting is what makes their\n // \"already up to date\" comparison (and the ingest cache) work.\n await writeFile(blueprintStylePath, renderStyleProjectionFromValue(annotated), \"utf8\");\n\n // Copy the overlay composition next to the canvas, then BAKE the reference's\n // overlays into its index.html as real, editable HTML at the <!--OVERLAYS-->\n // marker. The operator edits that HTML to paint the broadcast look — overlays\n // are authored markup, never props fed to a styling engine.\n // Resolve the output vs generation aspect up front: the overlay/caption composition\n // dims, the ffmpeg canvas, and the clip/frame generation params must all agree.\n let aspect: ReturnType<typeof resolveAspect>;\n try {\n aspect = resolveAspect(\n (blueprint as { source?: { aspect_ratio?: string } }).source?.aspect_ratio,\n args.aspect ? String(args.aspect) : undefined,\n genAspectsFor(videoModel),\n );\n } catch (e) {\n return fail(\"aspect\", e instanceof Error ? e.message : String(e));\n }\n const outDims = canvasDims(aspect.outAr);\n if (aspect.remapped) {\n process.stderr.write(\n `⚠️ ${aspect.outAr} output → clips generate at ${aspect.genAr} (the video model's nearest ratio) and are ` +\n `center-cropped to ${aspect.outAr} (${outDims.w}×${outDims.h}) — keep subjects centered in frame prompts.\\n`,\n );\n }\n\n const compositionDest = path.join(outDir, \"video-overlay-composition\");\n await cp(SHIPPED_OVERLAY_COMPOSITION_DIR, compositionDest, { recursive: true });\n await stampCompositionDims(compositionDest, outDims);\n const indexPath = path.join(compositionDest, \"index.html\");\n // With a transcript the scaffold wires a word-synced caption track (stageCaptions\n // below) — the reference's caption-role overlay fragments must then NOT also seed\n // as frozen static overlays (they'd double the same words on screen).\n const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript), videoModel });\n const indexHtml = await readFile(indexPath, \"utf8\");\n // Function replacer (not the string form) so `$`-sequences in overlay copy\n // (e.g. `save $$$!`, a literal `$&`) are inserted verbatim, not interpreted as\n // replacement patterns by String.prototype.replace.\n const injected = indexHtml.replace(\"<!--OVERLAYS-->\", () => overlayHtml);\n // replace() returns the string unchanged when the marker is absent — fail loud\n // rather than silently shipping a composition with no overlay layer.\n if (injected === indexHtml && overlayHtml.trim()) {\n fail(\n \"composition_marker_missing\",\n `video-overlay-composition/index.html is missing the <!--OVERLAYS--> marker — cannot inject the overlay layer`,\n );\n }\n await writeFile(indexPath, injected, \"utf8\");\n\n // Burned-in caption track: a sound-off feed needs the spoken words on screen, so the\n // scaffold wires a word-synced TikTok track by default whenever the deconstruct\n // returned a transcript (staged below). No transcript ⇒ no caption track.\n const captions = await stageCaptions(outDir, transcript);\n if (captions.compositionPath) await stampCompositionDims(captions.compositionPath, outDims);\n\n // Embed paths RELATIVE to the canvas file so the written canvas is portable —\n // `run`/`validate` resolve them against the canvas dir at load time. Baking\n // absolutes here is what made a moved/renamed workspace read a sibling's stale\n // composition + assets (the `memphis` cross-workspace bug).\n const opts: ScaffoldVideoOpts = {\n imageModel,\n videoModel,\n overlayCompositionPath: path.relative(outDir, compositionDest),\n captionsCompositionPath: captions.compositionPath\n ? path.relative(outDir, captions.compositionPath)\n : undefined,\n blueprintPath: path.relative(outDir, blueprintPath),\n blueprintStylePath: path.relative(outDir, blueprintStylePath),\n frames,\n ambient: Boolean(args.ambient),\n seamDedup: resolveSeamDedup(args[\"seam-dedup\"]),\n ...(speech.spokenLanguage ? { spokenLanguage: speech.spokenLanguage } : {}),\n voiceGenders: speech.voiceGenders,\n ...(speech.captionCarriesOn.length ? { captionCarriesOn: speech.captionCarriesOn } : {}),\n ...(args.aspect ? { aspect: String(args.aspect) } : {}),\n ...(args.resolution ? { resolution: String(args.resolution) } : {}),\n };\n\n let canvas: ReturnType<typeof scaffoldVideoCanvas>;\n let report: ReturnType<typeof videoReport>;\n try {\n canvas = scaffoldVideoCanvas(blueprint, elements, opts);\n report = videoReport(blueprint, elements, videoModel);\n } catch (e) {\n return fail(\"scaffold\", e instanceof Error ? e.message : String(e));\n }\n\n // MERGED overlay+captions pass: when the scaffold rendered the caption track\n // inside the overlay composition (no standalone `captions` node), the staged\n // TikTok caption dir is unused — remove it so the workspace holds no dead\n // composition an operator would edit to zero effect.\n if (captions.compositionPath && !canvas.nodes.some((n) => n.id === \"captions\")) {\n await rm(captions.compositionPath, { recursive: true, force: true });\n }\n\n // Validate against a copy whose relative paths are resolved to the output dir\n // (the on-disk canvas keeps the portable relatives).\n const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(canvas, outDir), defaultRegistry());\n\n // ALWAYS write the canvas — the deconstruct behind it is already paid for. A\n // blocking validation issue becomes part of the operator's checklist (fix, then\n // `baker canvas validate`), never a hard exit that discards the artifact.\n if (!validation.ok) {\n const meta = canvas.metadata as Record<string, unknown>;\n const todo = (meta.todo ?? {}) as Record<string, unknown>;\n todo.blocking_validation_issues = validation.issues;\n meta.todo = todo;\n }\n await writeFile(outPath, `${JSON.stringify(canvas, null, 2)}\\n`, \"utf8\");\n // The frozen rebuild context the scene sync feeds back into scaffoldVideoCanvas to\n // regenerate scene prompts deterministically (NO model call): the element-selection\n // artifact + the exact scaffold opts. Written once, never hand-edited. Its absence\n // (an old canvas) makes the sync degrade to legacy behavior, never fail.\n await writeFile(\n path.join(outDir, REBUILD_FILE),\n `${JSON.stringify({ elements, opts }, null, 2)}\\n`,\n \"utf8\",\n );\n if (!validation.ok) {\n process.stderr.write(\n `${JSON.stringify(\n {\n ok: false,\n error: { code: \"validation\", issues: validation.issues },\n canvas_path: outPath,\n prompt_path: blueprintPath,\n ...framesModeReport(canvas, frames),\n note: \"The canvas WAS written despite the blocking issue(s) — fix them in place, then `baker canvas validate` before running.\",\n ...uncastHint,\n },\n null,\n 2,\n )}\\n`,\n );\n process.exit(2);\n }\n\n // Keep the engine's run cache + the scaffold's working files out of git.\n await ensureGitignore(process.cwd(), [\"canvas/\", \".context/\"]);\n\n // Preserve the INSPIRATION video the same way the static flow preserves its reference\n // image — so the creative (and the dashboard) shows what it was built from. We record the\n // durable R2 URL the deconstruct already produced, not a committed copy (see helper).\n const sourceRef = videoSourceReference(blueprint, fileArg);\n if (slug) {\n // Auto-write _definition.md so the video creative joins the content collection with its\n // source provenance (the video command never wrote one before). Preserve on re-scaffold.\n const definitionPath = path.join(outDir, \"_definition.md\");\n if (!(await fileExists(definitionPath))) {\n await writeFile(\n definitionPath,\n buildCreativeDefinition({\n title: titleFromSlug(slug),\n kind: \"video\",\n ...definitionPlatformsOf(\n args.platforms ? String(args.platforms) : undefined,\n args.platform ? String(args.platform) : undefined,\n ),\n formats: resolveFormats(aspect.outAr),\n sourceReferenceUrl: sourceRef.url,\n sourceAdvertiser: args.advertiser ? String(args.advertiser) : sourceRef.advertiser,\n sourceKind: \"video\",\n description: videoDefinitionDescription(blueprint),\n }),\n \"utf8\",\n );\n }\n }\n\n // Push the parsed node-graph so the dashboard can draw this video's full\n // workflow immediately — before spending any of the ~200-credit render. Best-effort.\n if (slug) {\n await syncCreativeDefinitionBestEffort({\n slug,\n title: titleFromSlug(slug),\n ...(await readDefinitionPlatforms(outPath)),\n formats: [aspect.outAr],\n canvas,\n sourceReferenceUrl: sourceRef.url,\n sourceSha: await computeSourceSha(outPath),\n });\n }\n\n process.stdout.write(\n `${JSON.stringify(\n {\n ok: true,\n canvas_path: outPath,\n prompt_path: blueprintPath,\n source_reference: sourceRef.url,\n composition_dir: compositionDest,\n output: canvas.output,\n ...uncastHint,\n ...framesModeReport(canvas, frames),\n models: {\n deconstruct: deconstructModel,\n select: selectModel,\n image: imageModel,\n video: videoModel,\n ...(videoRouteReason ? { video_route_reason: videoRouteReason } : {}),\n },\n stats: {\n scene_count: report.scene_count,\n total_nodes: canvas.nodes.length,\n analysis_credits_spent: creditsSpent,\n run_estimated_credits: validation.estimatedCredits,\n // WHY the graph has its nodes: per-scene routes + the node-type histogram\n // (mirrors metadata.video.graph_stats in the canvas).\n graph: (canvas.metadata as { video?: { graph_stats?: unknown } })?.video?.graph_stats,\n },\n checklist: {\n edit_prompt: `The blueprint is split so you edit ONE small file at a time — never a giant one. Per-scene content (a scene's dialogue, action, frame prompts, overlays) lives in \\`scenes/sNN.json\\` — edit the single scene you want to change. Global cast/palette/brand/copy lives in \\`${path.basename(blueprintPath)}\\`. \\`baker canvas validate\\`/\\`run\\` re-assemble the blueprint and re-flow every edited scene back into the render (and regenerate ${path.basename(blueprintStylePath)}, the projection each frame's target_blueprint reads) — so your scene edits reach the render automatically. Never hand-edit the inlined node prompts in the canvas or the derived ${path.basename(blueprintStylePath)}; both are regenerated.`,\n recurring_elements_to_supply: report.elements,\n voices_to_confirm: report.dialogue.map((d) => ({\n scene: d.scene,\n speaker: d.speaker,\n voice_description: d.voice_description,\n line: d.line,\n })),\n sfx_count: report.sfx_count,\n overlay_count: report.overlay_count,\n has_music: report.has_music,\n scenes_clamped_to_clip_max: report.clamped_scenes,\n oversize_scenes: report.oversize_scenes,\n overstuffed_scenes: report.overstuffed_scenes,\n // Steer the edit toward disciplined prompting + on-brand MOTION, not just\n // on-brand color. The overlays inherit the reference's motion unless the\n // operator restyles easing/transition/accent from the brand's motion language.\n prompt_discipline:\n \"Rewrite each scene with the six-decision structure (Route / Spec / Beats / Copy / Technique / Negatives): quote exact copy, give every beat a timestamp, and name the moves. Pull easing, transition (cut vs fade), pacing, and the accent moment from BRAND.md § Brand in Motion so the overlays move like THIS brand — not the reference's. If BRAND.md has no Brand-in-Motion section, seed it with brand-build first. See the creative-canvas-ads skill: references/prompt-anatomy.md + references/hyperframes/.\",\n // Photoreal on-camera casts auto-route to Veo above, so this only fires\n // when `--video-model bytedance/seedance-2.0` was EXPLICITLY forced despite\n // a person/animal cast — overriding that safety route. Warn that every such\n // clip will hit ByteDance's real-person filter and burn a wasted attempt.\n ...(report.elements.some((e) => e.type === \"person\" || e.type === \"animal\") && /seedance/i.test(videoModel)\n ? {\n content_policy_risk:\n \"You FORCED Seedance (`--video-model bytedance/seedance-2.0`) on an ad with a photoreal on-camera cast. ByteDance's real-person filter will reject those clips (content_policy_blocked, E005) — no prompt change clears it. Each clip auto-recovers on Veo at render time so the run still completes, but you burn a wasted Seedance attempt per clip. Drop `--video-model` to let the scaffold route the cast to Veo up front, or make the frames less photorealistic.\",\n }\n : {}),\n note: \"Fill each el_* [TODO] — a person needs none (invented from their description, or cast with `--avatar <handle>`), never a photo of a real person; anything else with ONE real source image (reused across every frame that element appears in) — confirm each voice_select casting, then `baker canvas validate` and `baker canvas run`. Running generates many billed image/video/audio assets — it is not free.\",\n },\n },\n null,\n 2,\n )}\\n`,\n );\n },\n});\n","/**\n * A SCORED video-model router — the curated-and-explainable answer to\n * Higgsfield's `models_explore(recommend)`. Instead of a single hardcoded\n * default, a small weighted scorer picks between five curated video models (the\n * `videoRouter*` and `videoDefault` roles in the contract's `MODEL_ROLES`) and\n * returns a human-readable `reason` (mirroring Higgsfield's `match_reason`).\n *\n * The roster stays deliberately tiny (curation over breadth); this only makes the\n * pick correct and transparent:\n * - a real human likeness → Veo (the ONLY model that dodges ByteDance's\n * real-person filter), so a real-face brief never wastes a billed Seedance 422.\n * - motion-transfer / deliberately dynamic multi-shot beats → Kling.\n * - identity/product consistency → Seedance, the workhorse.\n * - anything else → the default video model (Gemini Omni 1.1).\n * - budget \"economy\" prefers the cheap Veo-fast tier over the 3.1 ceiling.\n *\n * Pure function, no I/O — tier-1 unit-testable per `.claude/rules/tdd.md`.\n */\n\nimport { MODEL_ROLES } from \"@baker/canvas-contract\";\n\nexport type VideoBudget = \"economy\" | \"standard\" | \"premium\";\n\nexport type VideoRouterInput = {\n /** The brief needs a real human likeness preserved (from a real reference face). */\n hasRealFace?: boolean;\n /** The same character/product must stay consistent across clips. */\n needsIdentity?: boolean;\n /** Motion is driven from a reference video, or the ad is deliberately hyper-dynamic. */\n needsMotionTransfer?: boolean;\n /** Cost posture. `economy` favors the cheap iteration tier. */\n budget?: VideoBudget;\n};\n\nexport type VideoRoute = {\n model: string;\n reason: string;\n /** Per-candidate score, exposed so the pick is fully auditable. */\n scores: Record<string, number>;\n};\n\nconst SEEDANCE = MODEL_ROLES.videoRouterWorkhorse.model;\nconst VEO = MODEL_ROLES.videoRouterRealFace.model;\nconst VEO_FAST = MODEL_ROLES.videoRouterRealFaceEconomy.model;\nconst KLING = MODEL_ROLES.videoRouterMotion.model;\n// The default video model (Omni 1.1, never the legacy preview): the router picks for\n// NEW work, and the preview exists only so stored canvases re-run verbatim.\nconst GEMINI_OMNI = MODEL_ROLES.videoDefault.model;\n\n/** A single weighted contribution to a candidate's score, with the reason it fired. */\ntype Signal = { model: string; weight: number; because: string };\n\n/**\n * Score every curated video model against the brief and return the winner plus\n * the dominant reason. Weights are intentionally coarse and readable — the goal\n * is a correct, explainable pick, not a tuned ML score.\n */\nexport function routeVideoModel(input: VideoRouterInput): VideoRoute {\n const budget = input.budget ?? \"standard\";\n const signals: Signal[] = [];\n\n // Hard rule: a real face MUST route to Veo — Seedance/Kling hit ByteDance-style\n // real-person filters. This dominates everything else.\n if (input.hasRealFace) {\n signals.push({ model: VEO, weight: 1200, because: \"real human likeness → Veo (dodges the real-person filter)\" });\n }\n\n // Motion transfer / hyper-dynamic multi-shot beats are Kling's lane.\n if (input.needsMotionTransfer) {\n signals.push({ model: KLING, weight: 800, because: \"motion-transfer / dynamic multi-shot → Kling\" });\n }\n\n // Identity/product consistency is the Seedance workhorse's strength (cheap, strong i2v).\n if (input.needsIdentity && !input.hasRealFace) {\n signals.push({ model: SEEDANCE, weight: 300, because: \"identity/product consistency → Seedance workhorse\" });\n }\n\n // Baseline preference so a signal-free brief still has a sensible default.\n // Omni wins the default because it renders picture + synchronized audio in one\n // call. It is deliberately a WEAK signal: it loses to every specific need above,\n // and it should: it stops at 10s and takes only 16:9 or 9:16, so any brief with a\n // real requirement is better served elsewhere.\n signals.push({ model: GEMINI_OMNI, weight: 100, because: \"default — native audio in one call\" });\n\n // Budget nudges: economy shifts a Veo pick to the cheap iteration tier; premium\n // keeps the 3.1 ceiling. Applied as a swap, not a competing candidate.\n const scores: Record<string, number> = { [SEEDANCE]: 0, [VEO]: 0, [VEO_FAST]: 0, [KLING]: 0, [GEMINI_OMNI]: 0 };\n let top: Signal = signals[0] as Signal;\n for (const s of signals) {\n scores[s.model] = (scores[s.model] ?? 0) + s.weight;\n if ((scores[s.model] ?? 0) >= (scores[top.model] ?? 0)) top = s;\n }\n\n let model = top.model;\n let reason = top.because;\n if (model === VEO && budget === \"economy\") {\n model = VEO_FAST;\n reason = `${reason}, on the economy fast tier`;\n scores[VEO_FAST] = scores[VEO] ?? 0;\n }\n\n return { model, reason, scores };\n}\n","import { execFile } from \"node:child_process\";\nimport { mkdtemp, readdir, readFile, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * PySceneDetect `detect-content` threshold (HSV per-frame content difference, 0–255\n * scale; NOT comparable to ffmpeg's `scene` luma score). PySceneDetect's own default is\n * 27, but social ads cut fast and often on SOFT reframes (same set, slight angle change)\n * that 27 misses — leaving the deconstruct under-segmented. 18 recovers those soft cuts\n * while staying clear of motion-driven false positives; tune per video with\n * `--shot-threshold` (lower = more cuts).\n */\nconst PYSCENEDETECT_THRESHOLD = 18;\n/**\n * Minimum scene length (seconds) for PySceneDetect. Its own default is ~0.6s, which\n * merges away rapid montage flashes; 0.25 matches the backend's `snapAndSplitToCuts`\n * minimum scene duration so fast cuts survive end-to-end (and sub-2s silent beats become\n * cheap still-holds). Below ~0.2s is single-frame flicker, not a real cut.\n */\nconst PYSCENEDETECT_MIN_SCENE_LEN_S = 0.25;\n/**\n * Adaptive re-check threshold. The low default (18) recovers soft reframes on montage\n * ads, but on a CONTINUOUS talking-head selfie (no real cuts) the creator's motion and\n * the held subject trip it dozens of times — shredding one shot into 20+ micro-scenes.\n * PySceneDetect's own default (27) is the discriminator: a real montage's cuts are\n * genuine HSV jumps that SURVIVE 27, while a continuous shot's motion artifacts VANISH.\n * So when the first pass looks over-segmented we re-run at 27 and trust that result —\n * it keeps real cuts and drops false ones. Paired with a longer min-scene-len.\n */\nconst PYSCENEDETECT_RECHECK_THRESHOLD = 27;\nconst PYSCENEDETECT_RECHECK_MIN_SCENE_LEN_S = 0.6;\n\n/**\n * Does a cut list look like a continuous shot shredded by motion rather than a real\n * montage? True when there are many cuts packed close together — the MEDIAN gap between\n * consecutive boundaries (counting 0 → first cut) is short. This is only a TRIGGER to\n * re-check at a higher threshold; the re-check itself is what decides montage vs\n * continuous, so a false trigger on a genuinely fast montage is harmless (its real cuts\n * survive the re-check). Pure + deterministic.\n */\nexport function isLikelyOverSegmented(cuts: number[], opts: { minCuts?: number; medianGapS?: number } = {}): boolean {\n const minCuts = opts.minCuts ?? 6;\n const maxMedianGap = opts.medianGapS ?? 2.0;\n const sorted = [...cuts].filter((c) => Number.isFinite(c) && c > 0).sort((a, b) => a - b);\n if (sorted.length < minCuts) return false;\n const gaps: number[] = [];\n let prev = 0;\n for (const c of sorted) {\n gaps.push(c - prev);\n prev = c;\n }\n gaps.sort((a, b) => a - b);\n const mid = Math.floor(gaps.length / 2);\n const median = gaps.length % 2 ? (gaps[mid] as number) : ((gaps[mid - 1] as number) + (gaps[mid] as number)) / 2;\n return median < maxMedianGap;\n}\n\n/**\n * Merge the over-segmentation re-check with the first pass instead of discarding it\n * wholesale. The high-threshold set is the base (it dropped the motion shred), but the\n * low pass ALSO found real SOFT transitions (blur-morphs, gradual reframes) that vanish\n * at 27 — losing them chains scenes across real shot changes. An extra is added back\n * only when it is ISOLATED: ≥ `isolationS` from every kept cut and from every other\n * extra. Motion shred clusters (sub-second neighbors); genuine soft cuts stand alone.\n * Pure + deterministic.\n */\nexport function mergeRecheckCuts(firstPass: number[], recheck: number[], isolationS = 0.75): number[] {\n const kept = [...new Set(recheck)].sort((a, b) => a - b);\n const extras = [...new Set(firstPass)]\n .filter((c) => kept.every((k) => Math.abs(k - c) >= isolationS))\n .sort((a, b) => a - b);\n const isolated = extras.filter((c, i) => {\n const prev = extras[i - 1];\n const next = extras[i + 1];\n return (prev === undefined || c - prev >= isolationS) && (next === undefined || next - c >= isolationS);\n });\n return [...new Set([...kept, ...isolated])].sort((a, b) => a - b);\n}\n\n/** Parse an `HH:MM:SS.mmm` PySceneDetect timecode into seconds, or null if malformed. */\nexport function timecodeToSeconds(tc: string): number | null {\n const m = tc.trim().match(/^(\\d+):(\\d{1,2}):(\\d{1,2}(?:\\.\\d+)?)$/);\n if (!m) return null;\n const h = Number.parseInt(m[1] ?? \"\", 10);\n const min = Number.parseInt(m[2] ?? \"\", 10);\n const s = Number.parseFloat(m[3] ?? \"\");\n if (!Number.isFinite(h) || !Number.isFinite(min) || !Number.isFinite(s)) return null;\n return h * 3600 + min * 60 + s;\n}\n\n/**\n * Parse hard-cut timestamps (seconds) out of a PySceneDetect `*-Scenes.csv`. The\n * first CSV row is the cutting list: `Timecode List:,00:00:08.900,00:00:11.333,…`\n * — each entry is a scene boundary (internal hard cut). Sorted, deduped, positive. Pure.\n */\nexport function parsePySceneDetectCsvCuts(csv: string): number[] {\n const firstLine = csv.split(/\\r?\\n/, 1)[0] ?? \"\";\n if (!/^\\s*Timecode List:/i.test(firstLine)) return [];\n const cuts: number[] = [];\n for (const cell of firstLine.split(\",\").slice(1)) {\n const t = timecodeToSeconds(cell);\n if (t !== null && t > 0) cuts.push(Math.round(t * 1000) / 1000);\n }\n return [...new Set(cuts)].sort((a, b) => a - b);\n}\n\n/**\n * Detect hard visual shot cuts with PySceneDetect's `scenedetect` CLI (battle-tested\n * ContentDetector: RGB→HSV per-frame average difference, default threshold 27). Writes\n * the scene CSV to a temp dir, parses the cutting list, then cleans up. Returns the cut\n * timestamps in seconds. Throws if `scenedetect` is missing or fails (caller falls back).\n */\n/** One `scenedetect detect-content` invocation at a fixed threshold; returns the cuts. */\nasync function runSceneDetectOnce(\n filePath: string,\n threshold: number,\n minSceneLenS: number,\n timeoutMs: number,\n): Promise<number[]> {\n const outDir = await mkdtemp(join(tmpdir(), \"baker-scenedetect-\"));\n try {\n await execFileAsync(\n \"scenedetect\",\n [\n \"--input\",\n filePath,\n \"--output\",\n outDir,\n \"detect-content\",\n \"--threshold\",\n String(threshold),\n \"--min-scene-len\",\n String(minSceneLenS),\n \"list-scenes\",\n \"--quiet\",\n ],\n { encoding: \"utf-8\", maxBuffer: 32 * 1024 * 1024, timeout: timeoutMs },\n );\n const csvName = (await readdir(outDir)).find((f) => f.toLowerCase().endsWith(\".csv\"));\n if (!csvName) return [];\n return parsePySceneDetectCsvCuts(await readFile(join(outDir, csvName), \"utf-8\"));\n } finally {\n await rm(outDir, { recursive: true, force: true });\n }\n}\n\nexport async function detectSceneCutsPySceneDetect(\n filePath: string,\n opts: { threshold?: number; minSceneLenS?: number; timeout_ms?: number } = {},\n): Promise<number[]> {\n // An operator-pinned threshold disables the adaptive re-check (they know best).\n const pinned = opts.threshold !== undefined;\n const threshold = opts.threshold ?? PYSCENEDETECT_THRESHOLD;\n // Minimum scene length (seconds). PySceneDetect's default (~0.6s) MERGES cuts closer\n // than that, silently dropping super-fast montage flashes. We floor it at the backend's\n // own minimum scene duration so genuine sub-second flashes survive as their own scenes\n // (then become cheap still-holds downstream). A float string is parsed as seconds.\n const minSceneLenS = opts.minSceneLenS ?? PYSCENEDETECT_MIN_SCENE_LEN_S;\n const timeoutMs = opts.timeout_ms ?? 120_000;\n const cuts = await runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs);\n // Adaptive guard: if the first pass shredded a continuous shot into many micro-cuts,\n // re-run at PySceneDetect's own (higher) default and trust THAT — real montage cuts\n // survive it, motion artifacts don't. Only when the operator didn't pin a threshold.\n if (!pinned && isLikelyOverSegmented(cuts)) {\n const rechecked = await runSceneDetectOnce(\n filePath,\n PYSCENEDETECT_RECHECK_THRESHOLD,\n PYSCENEDETECT_RECHECK_MIN_SCENE_LEN_S,\n timeoutMs,\n );\n // Merge instead of replace: the re-check drops the motion shred, but the low pass\n // also caught real SOFT transitions invisible at 27 — keep its isolated extras.\n return mergeRecheckCuts(cuts, rechecked);\n }\n return cuts;\n}\n","import { appendFile, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/**\n * Which of `entries` are not already ignored by `existing` .gitignore content.\n * Matches a whole non-comment line (trimmed, optional trailing slash) so `canvas`\n * and `canvas/` both count as present. Pure — unit-tested without the filesystem.\n */\nexport function missingGitignoreEntries(existing: string, entries: string[]): string[] {\n const present = new Set(\n existing\n .split(\"\\n\")\n .map((l) => l.trim().replace(/\\/+$/, \"\"))\n .filter((l) => l.length > 0 && !l.startsWith(\"#\")),\n );\n return entries.filter((e) => !present.has(e.trim().replace(/\\/+$/, \"\")));\n}\n\n/**\n * Append any missing `entries` to `<dir>/.gitignore`. The canvas engine writes a\n * content-addressed cache + per-run dirs under `canvas/`, and the scaffold writes\n * working files under `.context/` — committing either would bloat the repo. Only\n * touches an EXISTING .gitignore (never creates surprise files); best-effort.\n */\nexport async function ensureGitignore(dir: string, entries: string[]): Promise<void> {\n const file = path.join(dir, \".gitignore\");\n let existing: string;\n try {\n existing = await readFile(file, \"utf8\");\n } catch {\n return; // no .gitignore here — don't conjure one\n }\n const missing = missingGitignoreEntries(existing, entries);\n if (missing.length === 0) return;\n const prefix = existing.endsWith(\"\\n\") || existing.length === 0 ? \"\" : \"\\n\";\n await appendFile(file, `${prefix}\\n# Baker canvas (engine cache + scaffold working files)\\n${missing.join(\"\\n\")}\\n`);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,IAAI,OAAO,YAAAA,WAAU,MAAAC,KAAI,iBAAiB;AACnD,SAAS,UAAAC,eAAc;AACvB,OAAOC,WAAU;AAQjB,SAAS,qBAAqB;;;AC+B9B,IAAM,WAAW,YAAY,qBAAqB;AAClD,IAAM,MAAM,YAAY,oBAAoB;AAC5C,IAAM,WAAW,YAAY,2BAA2B;AACxD,IAAM,QAAQ,YAAY,kBAAkB;AAG5C,IAAM,cAAc,YAAY,aAAa;AAUtC,SAAS,gBAAgB,OAAqC;AACnE,QAAM,SAAS,MAAM,UAAU;AAC/B,QAAM,UAAoB,CAAC;AAI3B,MAAI,MAAM,aAAa;AACrB,YAAQ,KAAK,EAAE,OAAO,KAAK,QAAQ,MAAM,SAAS,iEAA4D,CAAC;AAAA,EACjH;AAGA,MAAI,MAAM,qBAAqB;AAC7B,YAAQ,KAAK,EAAE,OAAO,OAAO,QAAQ,KAAK,SAAS,oDAA+C,CAAC;AAAA,EACrG;AAGA,MAAI,MAAM,iBAAiB,CAAC,MAAM,aAAa;AAC7C,YAAQ,KAAK,EAAE,OAAO,UAAU,QAAQ,KAAK,SAAS,yDAAoD,CAAC;AAAA,EAC7G;AAOA,UAAQ,KAAK,EAAE,OAAO,aAAa,QAAQ,KAAK,SAAS,0CAAqC,CAAC;AAI/F,QAAM,SAAiC,EAAE,CAAC,QAAQ,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,WAAW,GAAG,EAAE;AAC9G,MAAI,MAAc,QAAQ,CAAC;AAC3B,aAAW,KAAK,SAAS;AACvB,WAAO,EAAE,KAAK,KAAK,OAAO,EAAE,KAAK,KAAK,KAAK,EAAE;AAC7C,SAAK,OAAO,EAAE,KAAK,KAAK,OAAO,OAAO,IAAI,KAAK,KAAK,GAAI,OAAM;AAAA,EAChE;AAEA,MAAI,QAAQ,IAAI;AAChB,MAAI,SAAS,IAAI;AACjB,MAAI,UAAU,OAAO,WAAW,WAAW;AACzC,YAAQ;AACR,aAAS,GAAG,MAAM;AAClB,WAAO,QAAQ,IAAI,OAAO,GAAG,KAAK;AAAA,EACpC;AAEA,SAAO,EAAE,OAAO,QAAQ,OAAO;AACjC;;;ACtGA,SAAS,gBAAgB;AACzB,SAAS,SAAS,SAAS,UAAU,UAAU;AAC/C,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAE1B,IAAM,gBAAgB,UAAU,QAAQ;AAUxC,IAAM,0BAA0B;AAOhC,IAAM,gCAAgC;AAUtC,IAAM,kCAAkC;AACxC,IAAM,wCAAwC;AAUvC,SAAS,sBAAsB,MAAgB,OAAkD,CAAC,GAAY;AACnH,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,eAAe,KAAK,cAAc;AACxC,QAAM,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,MAAM,OAAO,SAAS,CAAC,KAAK,IAAI,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACxF,MAAI,OAAO,SAAS,QAAS,QAAO;AACpC,QAAM,OAAiB,CAAC;AACxB,MAAI,OAAO;AACX,aAAW,KAAK,QAAQ;AACtB,SAAK,KAAK,IAAI,IAAI;AAClB,WAAO;AAAA,EACT;AACA,OAAK,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACzB,QAAM,MAAM,KAAK,MAAM,KAAK,SAAS,CAAC;AACtC,QAAM,SAAS,KAAK,SAAS,IAAK,KAAK,GAAG,KAAiB,KAAK,MAAM,CAAC,IAAgB,KAAK,GAAG,KAAgB;AAC/G,SAAO,SAAS;AAClB;AAWO,SAAS,iBAAiB,WAAqB,SAAmB,aAAa,MAAgB;AACpG,QAAM,OAAO,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACvD,QAAM,SAAS,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC,EAClC,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,UAAU,CAAC,EAC9D,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACvB,QAAM,WAAW,OAAO,OAAO,CAAC,GAAG,MAAM;AACvC,UAAM,OAAO,OAAO,IAAI,CAAC;AACzB,UAAM,OAAO,OAAO,IAAI,CAAC;AACzB,YAAQ,SAAS,UAAa,IAAI,QAAQ,gBAAgB,SAAS,UAAa,OAAO,KAAK;AAAA,EAC9F,CAAC;AACD,SAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAClE;AAGO,SAAS,kBAAkB,IAA2B;AAC3D,QAAM,IAAI,GAAG,KAAK,EAAE,MAAM,uCAAuC;AACjE,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,IAAI,OAAO,SAAS,EAAE,CAAC,KAAK,IAAI,EAAE;AACxC,QAAM,MAAM,OAAO,SAAS,EAAE,CAAC,KAAK,IAAI,EAAE;AAC1C,QAAM,IAAI,OAAO,WAAW,EAAE,CAAC,KAAK,EAAE;AACtC,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,CAAC,OAAO,SAAS,GAAG,KAAK,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AAChF,SAAO,IAAI,OAAO,MAAM,KAAK;AAC/B;AAOO,SAAS,0BAA0B,KAAuB;AAC/D,QAAM,YAAY,IAAI,MAAM,SAAS,CAAC,EAAE,CAAC,KAAK;AAC9C,MAAI,CAAC,sBAAsB,KAAK,SAAS,EAAG,QAAO,CAAC;AACpD,QAAM,OAAiB,CAAC;AACxB,aAAW,QAAQ,UAAU,MAAM,GAAG,EAAE,MAAM,CAAC,GAAG;AAChD,UAAM,IAAI,kBAAkB,IAAI;AAChC,QAAI,MAAM,QAAQ,IAAI,EAAG,MAAK,KAAK,KAAK,MAAM,IAAI,GAAI,IAAI,GAAI;AAAA,EAChE;AACA,SAAO,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAChD;AASA,eAAe,mBACb,UACA,WACA,cACA,WACmB;AACnB,QAAM,SAAS,MAAM,QAAQ,KAAK,OAAO,GAAG,oBAAoB,CAAC;AACjE,MAAI;AACF,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,SAAS;AAAA,QAChB;AAAA,QACA,OAAO,YAAY;AAAA,QACnB;AAAA,QACA;AAAA,MACF;AAAA,MACA,EAAE,UAAU,SAAS,WAAW,KAAK,OAAO,MAAM,SAAS,UAAU;AAAA,IACvE;AACA,UAAM,WAAW,MAAM,QAAQ,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,MAAM,CAAC;AACpF,QAAI,CAAC,QAAS,QAAO,CAAC;AACtB,WAAO,0BAA0B,MAAM,SAAS,KAAK,QAAQ,OAAO,GAAG,OAAO,CAAC;AAAA,EACjF,UAAE;AACA,UAAM,GAAG,QAAQ,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACnD;AACF;AAEA,eAAsB,6BACpB,UACA,OAA2E,CAAC,GACzD;AAEnB,QAAM,SAAS,KAAK,cAAc;AAClC,QAAM,YAAY,KAAK,aAAa;AAKpC,QAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAM,YAAY,KAAK,cAAc;AACrC,QAAM,OAAO,MAAM,mBAAmB,UAAU,WAAW,cAAc,SAAS;AAIlF,MAAI,CAAC,UAAU,sBAAsB,IAAI,GAAG;AAC1C,UAAM,YAAY,MAAM;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAGA,WAAO,iBAAiB,MAAM,SAAS;AAAA,EACzC;AACA,SAAO;AACT;;;ACnLA,SAAS,YAAY,YAAAC,iBAAgB;AACrC,OAAO,UAAU;AAOV,SAAS,wBAAwB,UAAkB,SAA6B;AACrF,QAAM,UAAU,IAAI;AAAA,IAClB,SACG,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,QAAQ,EAAE,CAAC,EACvC,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,EAAE,WAAW,GAAG,CAAC;AAAA,EACrD;AACA,SAAO,QAAQ,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,KAAK,EAAE,QAAQ,QAAQ,EAAE,CAAC,CAAC;AACzE;AAQA,eAAsB,gBAAgB,KAAa,SAAkC;AACnF,QAAM,OAAO,KAAK,KAAK,KAAK,YAAY;AACxC,MAAI;AACJ,MAAI;AACF,eAAW,MAAMA,UAAS,MAAM,MAAM;AAAA,EACxC,QAAQ;AACN;AAAA,EACF;AACA,QAAM,UAAU,wBAAwB,UAAU,OAAO;AACzD,MAAI,QAAQ,WAAW,EAAG;AAC1B,QAAM,SAAS,SAAS,SAAS,IAAI,KAAK,SAAS,WAAW,IAAI,KAAK;AACvE,QAAM,WAAW,MAAM,GAAG,MAAM;AAAA;AAAA,EAA6D,QAAQ,KAAK,IAAI,CAAC;AAAA,CAAI;AACrH;;;AHsBA,IAAM,gBACJ;AAKF,IAAM,gBACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BF,eAAe,yBAAyB,KAA2C;AACjF,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,WAAO,MAAM,cAAc,KAAK,wBAAwB;AAAA,EAC1D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,eAAe,cAAc,QAAgB,YAAuE;AAClH,QAAM,OAAO,YAAY,KAAK;AAC9B,MAAI,CAAC,QAAQ,SAAS,KAAM,QAAO,CAAC;AACpC,QAAM,kBAAkBC,MAAK,KAAK,QAAQ,6BAA6B;AACvE,QAAM,GAAG,sBAAsB,iBAAiB,EAAE,WAAW,KAAK,CAAC;AACnE,SAAO,EAAE,gBAAgB;AAC3B;AAOA,eAAe,yBAAyB,WAAmB,WAAmD;AAC5G,MAAI;AACF,UAAM,OAAO,MAAM,6BAA6B,WAAW,YAAY,EAAE,UAAU,IAAI,CAAC,CAAC;AACzF,QAAI,KAAK,SAAS,GAAG;AACnB,cAAQ,OAAO,MAAM,YAAY,KAAK,MAAM,mCAAmC,KAAK,KAAK,IAAI,CAAC;AAAA,CAAK;AAAA,IACrG,OAAO;AACL,cAAQ,OAAO,MAAM,yEAAyE;AAAA,IAChG;AACA,WAAO;AAAA,EACT,SAAS,GAAG;AAKV,UAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACrD,UAAM,OAAQ,GAA0B;AACxC,UAAM,UAAU,SAAS,YAAY,sCAAsC,KAAK,GAAG;AACnF,QAAI,SAAS;AACX,cAAQ,OAAO;AAAA,QACb;AAAA,MAGF;AAAA,IACF,OAAO;AACL,cAAQ,OAAO,MAAM,+BAA+B,GAAG;AAAA,CAA4B;AAAA,IACrF;AAGA,WAAO;AAAA,EACT;AACF;AAEA,IAAM,oBAA4C;AAAA,EAChD,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,oBAAoB;AACtB;AAGO,SAAS,kBAAkB,KAAa,aAAoC;AACjF,QAAM,WAAWA,MAAK,QAAQ,IAAI,IAAI,GAAG,EAAE,QAAQ,EAAE,YAAY;AACjE,MAAI,YAAY,SAAS,UAAU,EAAG,QAAO;AAC7C,QAAM,QAAQ,eAAe,IAAI,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,EAAE,YAAY;AACnE,SAAQ,QAAQ,kBAAkB,IAAI,KAAM;AAC9C;AAkBA,SAAS,qBAAqB,WAAoB,SAAwD;AACxG,QAAM,KAAM,aAAa,CAAC;AAC1B,QAAM,UAAU,OAAO,GAAG,QAAQ,QAAQ,WAAW,GAAG,OAAO,MAAM;AACrE,QAAM,WAAW,gBAAgB,KAAK,OAAO,IAAI,UAAU;AAC3D,QAAM,QAAQ,GAAG,QAAQ,UAAU;AACnC,SAAO,EAAE,KAAK,WAAW,UAAU,YAAY,OAAO,UAAU,YAAY,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI,OAAU;AACtH;AAGA,SAAS,2BAA2B,WAAwC;AAC1E,QAAM,KAAM,aAAa,CAAC,GAA4C,UAAU,CAAC;AACjF,QAAM,QAAQ,EAAE;AAChB,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAG,QAAO,MAAM,KAAK;AACjE,QAAM,UAAW,EAAE,UAAgD;AACnE,SAAO,OAAO,YAAY,YAAY,QAAQ,KAAK,IAAI,QAAQ,KAAK,IAAI;AAC1E;AAOA,SAAS,iBACP,QACA,WACwD;AACxD,QAAM,OAAQ,OAAO,UACjB;AACJ,QAAM,OAAO,OAAO,MAAM,gBAAgB,WAAW,KAAK,cAAc;AACxE,QAAM,UAAU,OAAO,MAAM,yBAAyB,WAAW,KAAK,uBAAuB;AAC7F,SAAO,EAAE,aAAa,MAAM,GAAI,UAAU,EAAE,sBAAsB,QAAQ,IAAI,CAAC,EAAG;AACpF;AAEA,eAAe,0BAA0B,SAAkC;AACzE,MAAI,CAAC,gBAAgB,KAAK,OAAO,EAAG,QAAOA,MAAK,QAAQ,OAAO;AAC/D,MAAI;AACJ,MAAI;AACJ,MAAI;AAKF,KAAC,EAAE,QAAQ,OAAO,YAAY,IAAI,MAAM,mBAAmB,SAAS;AAAA,MAClE,gBAAgB;AAAA,MAChB,UAAU;AAAA,IACZ,CAAC;AAAA,EACH,SAAS,GAAG;AACV,UAAM,IAAI,MAAM,uCAAuC,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,EACrG;AACA,MAAI,MAAM,WAAW,EAAG,OAAM,IAAI,MAAM,oCAAoC;AAC5E,QAAM,OAAOA,MAAK;AAAA,IAChBC,QAAO;AAAA,IACP,aAAa,UAAU,KAAK,EAAE,MAAM,GAAG,EAAE,CAAC,GAAG,kBAAkB,SAAS,WAAW,CAAC;AAAA,EACtF;AACA,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO;AACT;AAGA,SAAS,iBAAiB,KAA8C;AACtE,MAAI,QAAQ,OAAW,QAAO;AAC9B,QAAM,IAAI,OAAO,GAAG;AACpB,MAAI,MAAM,UAAU,MAAM,UAAU,MAAM,MAAO,QAAO;AACxD,QAAM,IAAI,MAAM,uDAAuD,CAAC,IAAI;AAC9E;AAEA,IAAM,gBAAwC,CAAC,WAAW,YAAY,SAAS;AAC/E,SAAS,YAAY,KAA2B;AAC9C,QAAM,IAAI,QAAQ,SAAY,aAAa,OAAO,GAAG;AACrD,MAAK,cAAoC,SAAS,CAAC,EAAG,QAAO;AAC7D,QAAM,IAAI,MAAM,2BAA2B,cAAc,KAAK,IAAI,CAAC,UAAU,CAAC,IAAI;AACpF;AAIA,SAAS,cAAc,MAIrB;AACA,SAAO;AAAA;AAAA;AAAA,IAGL,kBAAkB,aAAa,qBAAqB,KAAK,mBAAmB,GAAG,uBAAuB;AAAA,IACtG,aAAa,aAAa,iBAAiB,KAAK,cAAc,GAAG,wBAAwB;AAAA;AAAA;AAAA;AAAA,IAIzF,YAAY,aAAa,kBAAkB,KAAK,aAAa,GAAG,cAAc;AAAA,EAChF;AACF;AAIO,SAAS,iBAAiB,UAA4B;AAC3D,SACE,MAAM,QAAQ,QAAQ,KACtB,SAAS,KAAK,CAAC,MAAM;AACnB,UAAM,OAAO,OAAO,MAAM,YAAY,MAAM,OAAQ,EAAyB,OAAO;AACpF,WAAO,SAAS,YAAY,SAAS;AAAA,EACvC,CAAC;AAEL;AASO,SAAS,kBACd,MACA,UACmD;AACnD,MAAI,KAAK,aAAa,GAAG;AACvB,WAAO,EAAE,YAAY,aAAa,kBAAkB,KAAK,aAAa,GAAG,4BAA4B,EAAE;AAAA,EACzG;AACA,QAAM,gBAAgB,iBAAiB,QAAQ;AAC/C,QAAM,QAAQ,gBAAgB;AAAA,IAC5B,aAAa,QAAQ,KAAK,WAAW,CAAC,KAAK;AAAA,IAC3C,eAAe,QAAQ,KAAK,QAAQ;AAAA,IACpC,qBAAqB,QAAQ,KAAK,iBAAiB,CAAC;AAAA,IACpD,QAAQ,YAAY,KAAK,MAAM;AAAA,EACjC,CAAC;AAED,QAAM,SACJ,iBAAiB,CAAC,KAAK,WAAW,IAC9B,GAAG,MAAM,MAAM,iGACf,MAAM;AACZ,SAAO;AAAA,IACL,YAAY,aAAa,kBAAkB,MAAM,OAAO,4BAA4B;AAAA,IACpF,kBAAkB;AAAA,EACpB;AACF;AAGA,SAAS,uBACP,WACA,kBACA,MACA;AACA,QAAM,oBAA6C,EAAE,OAAO,kBAAkB,MAAM,OAAO;AAC3F,MAAI,OAAO,KAAK,cAAc,SAAU,mBAAkB,aAAa,KAAK;AAC5E,MAAI,KAAK,SAAU,mBAAkB,WAAW,KAAK;AACrD,MAAI,KAAK,MAAO,mBAAkB,QAAQ,KAAK;AAK/C,MAAI,KAAK,SAAU,mBAAkB,YAAY,KAAK;AAGtD,oBAAkB,aAAa,mBAAmB,mBAAmB,SAAS,CAAC;AAC/E,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,UAAU,EAAE,MAAM,yBAAyB;AAAA,IAC3C,OAAO;AAAA,MACL,EAAE,IAAI,OAAO,MAAM,UAAU,QAAQ,EAAE,QAAQ,QAAQ,MAAM,WAAW,QAAQ,QAAQ,EAAE;AAAA,MAC1F,EAAE,IAAI,eAAe,MAAM,qBAAqB,QAAQ,EAAE,OAAO,iBAAiB,GAAG,QAAQ,kBAAkB;AAAA,IACjH;AAAA,IACA,QAAQ,EAAE,MAAM,eAAe,QAAQ,WAAW;AAAA,EACpD;AACF;AAQA,SAAS,kBAAkB,aAAqB,sBAA8B;AAC5E,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,UAAU,EAAE,MAAM,yBAAyB;AAAA,IAC3C,OAAO;AAAA,MACL;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO;AAAA,UACP,YAAY;AAAA,UACZ,aAAa;AAAA,UACb,iBAAiB;AAAA,UACjB,QAAQ;AAAA,UACR,QAAQ,cAAc,QAAQ,iBAAiB,MAAM,oBAAoB;AAAA,QAC3E;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ,EAAE,MAAM,UAAU,QAAQ,OAAO;AAAA,EAC3C;AACF;AASA,eAAe,kBACb,mBACA,aAQC;AACD,QAAM,SAAS,oBAAoB,EAAE,KAAK,CAAC,SAAS,QAAQ,OAAO,MAAM,GAAG,IAAI;AAAA,CAAI,EAAE,CAAC;AACvF,MAAI,UAAU;AACd,MAAI,aAAa;AACjB,QAAM,aAAa,CAAC,UAAmB;AACrC,UAAM,IAAK,OAAsC;AACjD,QAAI,OAAO,MAAM,UAAU;AACzB,iBAAW;AACX,mBAAa;AAAA,IACf;AAAA,EACF;AAIA,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,MAAM,OAAO,IAAI,mBAAmB,CAAC,CAAC;AACjD,eAAW,GAAG,KAAK;AACnB,gBAAY,KAAK,MAAM,MAAM,cAAc,GAAG,gBAAgB,aAAa,UAAU,oBAAoB,CAAC;AAC1G,iBAAa,MAAM,yBAAyB,GAAG,gBAAgB,aAAa,UAAU;AACtF,UAAM,QAAS,GAAG,gBAAgB,aAAa,YAC3C,UAAU;AACd,uBAAmB,OAAO,UAAU,YAAY,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI;AAAA,EAChF,SAAS,GAAG;AACV,QAAI,aAAa,gBAAiB,QAAO,KAAK,cAAc,KAAK,UAAU,EAAE,MAAM,CAAC;AACpF,QAAI,aAAa,YAAa,QAAO,KAAK,gBAAgB,EAAE,OAAO;AACnE,WAAO,KAAK,eAAe,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,EACvE;AAGA,QAAM,WAAW,KAAK,UAAU,0BAA0B,SAAS,CAAC;AACpE,MAAI;AACF,UAAM,KAAK,MAAM,OAAO,IAAI,kBAAkB,aAAa,QAAQ,GAAG,CAAC,CAAC;AACxE,eAAW,GAAG,KAAK;AACnB,UAAM,WAAW,cAAc,MAAM,cAAc,GAAG,gBAAgB,QAAQ,MAAM,kBAAkB,CAAC;AACvG,WAAO,EAAE,WAAW,UAAU,YAAY,kBAAkB,cAAc,aAAa,UAAU,OAAU;AAAA,EAC7G,SAAS,GAAG;AACV,QAAI,aAAa,gBAAiB,QAAO,KAAK,cAAc,KAAK,UAAU,EAAE,MAAM,CAAC;AACpF,QAAI,aAAa,YAAa,QAAO,KAAK,gBAAgB,EAAE,OAAO;AACnE,WAAO,KAAK,eAAe,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,EACvE;AACF;AAEO,IAAM,uBAAuB,cAAc;AAAA,EAChD,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EAWJ;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,UAAU;AAAA,MACV,aACE;AAAA,IACJ;AAAA,IACA,KAAK,EAAE,MAAM,UAAU,aAAa,oEAAoE;AAAA,IACxG,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,cAAc;AAAA,MACZ,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA,cAAc,EAAE,MAAM,UAAU,aAAa,iDAAiD;AAAA,IAC9F,kBAAkB;AAAA,MAChB,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA,UAAU,EAAE,MAAM,UAAU,aAAa,kDAAkD;AAAA,IAC3F,OAAO,EAAE,MAAM,UAAU,aAAa,sDAAsD;AAAA,IAC5F,YAAY;AAAA,MACV,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,WAAW;AAAA,MACT,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA,UAAU,EAAE,MAAM,UAAU,aAAa,8DAA8D;AAAA,IACvG,qBAAqB,EAAE,MAAM,UAAU,aAAa,0CAA0C;AAAA,IAC9F,gBAAgB,EAAE,MAAM,UAAU,aAAa,4DAA4D;AAAA,IAC3G,eAAe,EAAE,MAAM,UAAU,aAAa,kDAAkD;AAAA,IAChG,eAAe,EAAE,MAAM,UAAU,aAAa,2EAA2E;AAAA,IACzH,aAAa;AAAA,MACX,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA,mBAAmB;AAAA,MACjB,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aAAa;AAAA,IACf;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aACE;AAAA,IAGJ;AAAA,IACA,YAAY;AAAA,MACV,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,aACE;AAAA,IAKJ;AAAA,EACF;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,UAAM,UAAU,OAAO,KAAK,IAAI;AAChC,UAAM,OAAO,KAAK,OAAO,OAAO,KAAK,IAAI,IAAI;AAC7C,QAAI,QAAQ,CAAC,oBAAoB,IAAI,GAAG;AACtC,cAAQ,OAAO;AAAA,QACb,GAAG,KAAK,UAAU,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,gBAAgB,SAAS,oEAAoE,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,MAC1J;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,UAAM,QAAQ,gBAAgB,KAAK,OAAO;AAC1C,QAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,KAAK;AAC/B,aAAO;AAAA,QACL;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,kBAAY,MAAM,0BAA0B,OAAO;AAAA,IACrD,SAAS,GAAG;AACV,aAAO,KAAK,YAAY,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACpE;AACA,UAAM,OAAOD,MAAK,SAAS,WAAWA,MAAK,QAAQ,SAAS,CAAC;AAC7D,UAAM,UAAU,KAAK,MACjBA,MAAK,QAAQ,OAAO,KAAK,GAAG,CAAC,IAC7B,OACEA,MAAK,KAAK,QAAQ,IAAI,GAAG,OAAO,aAAa,MAAM,GAAG,IAAI,cAAc,IACxEA,MAAK,KAAKA,MAAK,QAAQ,SAAS,GAAG,GAAG,IAAI,oBAAoB;AACpE,UAAM,SAASA,MAAK,QAAQ,OAAO;AACnC,UAAM,gBAAgBA,MAAK,KAAK,QAAQ,aAAa;AAGrD,UAAM,qBAAqBA,MAAK,KAAK,QAAQ,mBAAmB;AAChE,UAAM,SAAS,KAAK,WAAW,UAAU,UAAU;AACnD,UAAM,YAAY,KAAK,YAAY,IAAI,OAAO,KAAK,YAAY,CAAC,IAAI;AAKpE,QAAI,OAAO,SAAS,SAAS,GAAG;AAC9B,cAAQ,OAAO;AAAA,QACb,8BAAoB,SAAS,4CAA4C,SAAS;AAAA;AAAA,MAEpF;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,eAAS,cAAc,IAAI;AAG3B,UAAI,KAAK,aAAa,EAAG,cAAa,kBAAkB,KAAK,aAAa,GAAG,4BAA4B;AAAA,IAC3G,SAAS,GAAG;AACV,aAAO,KAAK,iBAAiB,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACzE;AACA,UAAM,EAAE,kBAAkB,aAAa,WAAW,IAAI;AAGtD,UAAM,OAAO,MAAM,mBAAmB,KAAK,MAAM;AACjD,QAAI,WAAW,KAAM,QAAO,KAAK,UAAU,KAAK,KAAK;AACrD,UAAM,EAAE,SAAS,eAAe,QAAQ,IAAI;AAM5C,UAAM,gBAAgB,KAAK,gBAAgB,IAAI,OAAO,KAAK,gBAAgB,CAAC,IAAI;AAChF,UAAM,WAAW,MAAM,yBAAyB,WAAW,aAAa;AAIxE,UAAM,oBAAoB,uBAAuB,WAAW,kBAAkB;AAAA,MAC5E,WAAW,OAAO,SAAS,SAAS,IAAI,YAAY;AAAA,MACpD,UAAU,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AAAA,MAClD,OAAO,KAAK,QAAQ,OAAO,KAAK,KAAK,IAAI;AAAA,MACzC;AAAA,IACF,CAAC;AACD,UAAM,WAAW,MAAM,kBAAkB,mBAAmB,WAAW;AACvE,UAAM,EAAE,WAAW,YAAY,aAAa,IAAI;AAIhD,UAAM,QAAQ,SAAS;AACvB,UAAM,oBACH,KAAK,WAAW,eAAe,OAAO,KAAK,QAAQ,CAAC,GAAG,MAAM,YAC7D,QAAS,eAAe,KAAK,GAAG,OAAO,kBAAkB,KAAK,IAAK;AACtE,UAAM,cAAcA,MAAK,KAAK,kBAAkB,MAAM,KAAK,QAAQ,IAAI,GAAG,UAAU,QAAQ;AAC5F,UAAM,cAA6B,CAAC;AACpC,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA,mBAAmB,EAAE,iBAAiB,IAAI,CAAC;AAAA,MAC3C;AAAA,QACE,KAAK,UAAU,aAAa,CAAC,cAAc,gBAAgB,WAAW,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC;AAAA,QACjG,SAAS,iBAAiB,aAAa,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC;AAAA,MACnE;AAAA,IACF;AACA,UAAM,kBAAkB,aAAa,WAAW,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAChE,UAAM,WAAW,sBAAsB,SAAS,UAAU,OAAO;AACjE,UAAM,SAAS,eAAe,SAAS,UAAU,aAAa;AAC9D,UAAM,aAAa,OAAO,SACtB;AAAA,MACE,OAAO;AAAA,QACL,aAAa,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,iFAA4E,OAAO,WAAW,IAAI,sBAAsB,oBAAoB;AAAA,MACnM;AAAA,IACF,IACA,CAAC;AAKL,QAAI;AACJ,QAAI;AACF,eAAS,kBAAkB,MAAM,QAAQ;AAAA,IAC3C,SAAS,GAAG;AACV,aAAO,KAAK,iBAAiB,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACzE;AACA,UAAM,EAAE,YAAY,iBAAiB,IAAI;AACzC,QAAI,oBAAoB,CAAC,KAAK,aAAa,KAAK,CAAC,KAAK,WAAW,KAAK,iBAAiB,QAAQ,GAAG;AAChG,cAAQ,OAAO,MAAM,0EAA8D,UAAU;AAAA,CAA4C;AAAA,IAC3I;AAIA,UAAM,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AACvC,UAAM,YAAY,8BAA8B,WAAW,QAAQ;AAKnE,UAAM,gBAAgB,QAAQ,SAAS;AAMvC,UAAM,UAAU,oBAAoB,+BAA+B,SAAS,GAAG,MAAM;AAQrF,QAAI;AACJ,QAAI;AACF,eAAS;AAAA,QACN,UAAqD,QAAQ;AAAA,QAC9D,KAAK,SAAS,OAAO,KAAK,MAAM,IAAI;AAAA,QACpC,cAAc,UAAU;AAAA,MAC1B;AAAA,IACF,SAAS,GAAG;AACV,aAAO,KAAK,UAAU,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IAClE;AACA,UAAM,UAAU,WAAW,OAAO,KAAK;AACvC,QAAI,OAAO,UAAU;AACnB,cAAQ,OAAO;AAAA,QACb,iBAAO,OAAO,KAAK,oCAA+B,OAAO,KAAK,gEACvC,OAAO,KAAK,KAAK,QAAQ,CAAC,OAAI,QAAQ,CAAC;AAAA;AAAA,MAChE;AAAA,IACF;AAEA,UAAM,kBAAkBA,MAAK,KAAK,QAAQ,2BAA2B;AACrE,UAAM,GAAG,iCAAiC,iBAAiB,EAAE,WAAW,KAAK,CAAC;AAC9E,UAAM,qBAAqB,iBAAiB,OAAO;AACnD,UAAM,YAAYA,MAAK,KAAK,iBAAiB,YAAY;AAIzD,UAAM,cAAc,iBAAiB,WAAW,EAAE,gBAAgB,QAAQ,UAAU,GAAG,WAAW,CAAC;AACnG,UAAM,YAAY,MAAME,UAAS,WAAW,MAAM;AAIlD,UAAM,WAAW,UAAU,QAAQ,mBAAmB,MAAM,WAAW;AAGvE,QAAI,aAAa,aAAa,YAAY,KAAK,GAAG;AAChD;AAAA,QACE;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,UAAU,WAAW,UAAU,MAAM;AAK3C,UAAM,WAAW,MAAM,cAAc,QAAQ,UAAU;AACvD,QAAI,SAAS,gBAAiB,OAAM,qBAAqB,SAAS,iBAAiB,OAAO;AAM1F,UAAM,OAA0B;AAAA,MAC9B;AAAA,MACA;AAAA,MACA,wBAAwBF,MAAK,SAAS,QAAQ,eAAe;AAAA,MAC7D,yBAAyB,SAAS,kBAC9BA,MAAK,SAAS,QAAQ,SAAS,eAAe,IAC9C;AAAA,MACJ,eAAeA,MAAK,SAAS,QAAQ,aAAa;AAAA,MAClD,oBAAoBA,MAAK,SAAS,QAAQ,kBAAkB;AAAA,MAC5D;AAAA,MACA,SAAS,QAAQ,KAAK,OAAO;AAAA,MAC7B,WAAW,iBAAiB,KAAK,YAAY,CAAC;AAAA,MAC9C,GAAI,OAAO,iBAAiB,EAAE,gBAAgB,OAAO,eAAe,IAAI,CAAC;AAAA,MACzE,cAAc,OAAO;AAAA,MACrB,GAAI,OAAO,iBAAiB,SAAS,EAAE,kBAAkB,OAAO,iBAAiB,IAAI,CAAC;AAAA,MACtF,GAAI,KAAK,SAAS,EAAE,QAAQ,OAAO,KAAK,MAAM,EAAE,IAAI,CAAC;AAAA,MACrD,GAAI,KAAK,aAAa,EAAE,YAAY,OAAO,KAAK,UAAU,EAAE,IAAI,CAAC;AAAA,IACnE;AAEA,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,eAAS,oBAAoB,WAAW,UAAU,IAAI;AACtD,eAAS,YAAY,WAAW,UAAU,UAAU;AAAA,IACtD,SAAS,GAAG;AACV,aAAO,KAAK,YAAY,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACpE;AAMA,QAAI,SAAS,mBAAmB,CAAC,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU,GAAG;AAC9E,YAAMG,IAAG,SAAS,iBAAiB,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACrE;AAIA,UAAM,aAAa,MAAM,mBAAmB,2BAA2B,QAAQ,MAAM,GAAG,gBAAgB,CAAC;AAKzG,QAAI,CAAC,WAAW,IAAI;AAClB,YAAM,OAAO,OAAO;AACpB,YAAM,OAAQ,KAAK,QAAQ,CAAC;AAC5B,WAAK,6BAA6B,WAAW;AAC7C,WAAK,OAAO;AAAA,IACd;AACA,UAAM,UAAU,SAAS,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,GAAM,MAAM;AAKvE,UAAM;AAAA,MACJH,MAAK,KAAK,QAAQ,YAAY;AAAA,MAC9B,GAAG,KAAK,UAAU,EAAE,UAAU,KAAK,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,MAC9C;AAAA,IACF;AACA,QAAI,CAAC,WAAW,IAAI;AAClB,cAAQ,OAAO;AAAA,QACb,GAAG,KAAK;AAAA,UACN;AAAA,YACE,IAAI;AAAA,YACJ,OAAO,EAAE,MAAM,cAAc,QAAQ,WAAW,OAAO;AAAA,YACvD,aAAa;AAAA,YACb,aAAa;AAAA,YACb,GAAG,iBAAiB,QAAQ,MAAM;AAAA,YAClC,MAAM;AAAA,YACN,GAAG;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAAA;AAAA,MACH;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,UAAM,gBAAgB,QAAQ,IAAI,GAAG,CAAC,WAAW,WAAW,CAAC;AAK7D,UAAM,YAAY,qBAAqB,WAAW,OAAO;AACzD,QAAI,MAAM;AAGR,YAAM,iBAAiBA,MAAK,KAAK,QAAQ,gBAAgB;AACzD,UAAI,CAAE,MAAM,WAAW,cAAc,GAAI;AACvC,cAAM;AAAA,UACJ;AAAA,UACA,wBAAwB;AAAA,YACtB,OAAO,cAAc,IAAI;AAAA,YACzB,MAAM;AAAA,YACN,GAAG;AAAA,cACD,KAAK,YAAY,OAAO,KAAK,SAAS,IAAI;AAAA,cAC1C,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AAAA,YAC1C;AAAA,YACA,SAAS,eAAe,OAAO,KAAK;AAAA,YACpC,oBAAoB,UAAU;AAAA,YAC9B,kBAAkB,KAAK,aAAa,OAAO,KAAK,UAAU,IAAI,UAAU;AAAA,YACxE,YAAY;AAAA,YACZ,aAAa,2BAA2B,SAAS;AAAA,UACnD,CAAC;AAAA,UACD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAIA,QAAI,MAAM;AACR,YAAM,iCAAiC;AAAA,QACrC;AAAA,QACA,OAAO,cAAc,IAAI;AAAA,QACzB,GAAI,MAAM,wBAAwB,OAAO;AAAA,QACzC,SAAS,CAAC,OAAO,KAAK;AAAA,QACtB;AAAA,QACA,oBAAoB,UAAU;AAAA,QAC9B,WAAW,MAAM,iBAAiB,OAAO;AAAA,MAC3C,CAAC;AAAA,IACH;AAEA,YAAQ,OAAO;AAAA,MACb,GAAG,KAAK;AAAA,QACN;AAAA,UACE,IAAI;AAAA,UACJ,aAAa;AAAA,UACb,aAAa;AAAA,UACb,kBAAkB,UAAU;AAAA,UAC5B,iBAAiB;AAAA,UACjB,QAAQ,OAAO;AAAA,UACf,GAAG;AAAA,UACH,GAAG,iBAAiB,QAAQ,MAAM;AAAA,UAClC,QAAQ;AAAA,YACN,aAAa;AAAA,YACb,QAAQ;AAAA,YACR,OAAO;AAAA,YACP,OAAO;AAAA,YACP,GAAI,mBAAmB,EAAE,oBAAoB,iBAAiB,IAAI,CAAC;AAAA,UACrE;AAAA,UACA,OAAO;AAAA,YACL,aAAa,OAAO;AAAA,YACpB,aAAa,OAAO,MAAM;AAAA,YAC1B,wBAAwB;AAAA,YACxB,uBAAuB,WAAW;AAAA;AAAA;AAAA,YAGlC,OAAQ,OAAO,UAAoD,OAAO;AAAA,UAC5E;AAAA,UACA,WAAW;AAAA,YACT,aAAa,yRAA+QA,MAAK,SAAS,aAAa,CAAC,uIAAuIA,MAAK,SAAS,kBAAkB,CAAC,0LAAqLA,MAAK,SAAS,kBAAkB,CAAC;AAAA,YACtrB,8BAA8B,OAAO;AAAA,YACrC,mBAAmB,OAAO,SAAS,IAAI,CAAC,OAAO;AAAA,cAC7C,OAAO,EAAE;AAAA,cACT,SAAS,EAAE;AAAA,cACX,mBAAmB,EAAE;AAAA,cACrB,MAAM,EAAE;AAAA,YACV,EAAE;AAAA,YACF,WAAW,OAAO;AAAA,YAClB,eAAe,OAAO;AAAA,YACtB,WAAW,OAAO;AAAA,YAClB,4BAA4B,OAAO;AAAA,YACnC,iBAAiB,OAAO;AAAA,YACxB,oBAAoB,OAAO;AAAA;AAAA;AAAA;AAAA,YAI3B,mBACE;AAAA;AAAA;AAAA;AAAA;AAAA,YAKF,GAAI,OAAO,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,SAAS,QAAQ,KAAK,YAAY,KAAK,UAAU,IACtG;AAAA,cACE,qBACE;AAAA,YACJ,IACA,CAAC;AAAA,YACL,MAAM;AAAA,UACR;AAAA,QACF;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA;AAAA,IACH;AAAA,EACF;AACF,CAAC;","names":["readFile","rm","tmpdir","path","readFile","path","tmpdir","readFile","rm"]}
1
+ {"version":3,"sources":["../src/commands/canvas/scaffold-video.ts","../src/engine/scaffold/lib/model-router.ts","../src/engine/nodes/local/lib/sceneDetect.ts","../src/commands/canvas/gitignore.ts"],"sourcesContent":["import { cp, mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport {\n AD_IMAGE_MODEL,\n BLUEPRINT_ANALYSIS_MODEL,\n DEFAULT_VIDEO_GENERATE_MODEL,\n SEEDANCE_DURATIONS,\n VIDEO_DECONSTRUCT_MODEL,\n} from \"@baker/canvas-contract\";\nimport { defineCommand } from \"citty\";\nimport { fetchExternalBytes } from \"../../lib/externalFetch.ts\";\nimport { MAX_ASSET_BYTES } from \"../../engine/storage/asset-store.ts\";\nimport { routeVideoModel, type VideoBudget } from \"../../engine/scaffold/lib/model-router.ts\";\nimport { sha256Hex } from \"../../engine/storage/sha256.ts\";\nimport { createEngineFromEnv, defaultRegistry, ValidationError, validateCanvasDeep } from \"../../engine/index.ts\";\nimport { detectSceneCutsPySceneDetect } from \"../../engine/nodes/local/lib/sceneDetect.ts\";\nimport {\n annotateBlueprintWithElements,\n avatarsNotCast,\n buildOverlayHtml,\n canvasDims,\n castAvatarsOnElements,\n genAspectsFor,\n resolveAspect,\n type ScaffoldVideoOpts,\n scaffoldVideoCanvas,\n slimBlueprintForSelection,\n videoReport,\n} from \"../../engine/scaffold/video.ts\";\nimport { stampCompositionDims } from \"./composition-dims.ts\";\nimport {\n fail,\n fileExists,\n loadAssetText,\n parseElements,\n resolveModel,\n SHIPPED_CAPTIONS_DIR,\n SHIPPED_OVERLAY_COMPOSITION_DIR,\n} from \"./scaffold-common.ts\";\nimport { REBUILD_FILE, writeSceneFiles } from \"./scene-files.ts\";\nimport { renderStyleProjectionFromValue } from \"./style-projection.ts\";\nimport { buildCreativeDefinition, definitionPlatformsOf, resolveFormats, titleFromSlug } from \"./creative-definition.ts\";\nimport { ensureGitignore } from \"./gitignore.ts\";\nimport { computeSourceSha } from \"./source-version.ts\";\nimport { readDefinitionPlatforms, syncCreativeDefinitionBestEffort } from \"./sync-definition.ts\";\nimport { resolveRelativeCanvasPaths } from \"./resolve-paths.ts\";\nimport { isValidScaffoldSlug } from \"./scaffold-static-ad-paths.ts\";\nimport { resolveCastAvatars } from \"../avatars/resolveCast.ts\";\nimport { askJevQuestions } from \"../../engine/nodes/local/lib/jev.ts\";\nimport { cachedJev } from \"../../engine/nodes/local/lib/jev-cache.ts\";\nimport { voiceGenderModel } from \"../../engine/scaffold/video/dialogue/voice-gender.ts\";\nimport type { ReviewUsage } from \"../../engine/nodes/local/lib/render-review.ts\";\nimport { languageCodeNamed, spokenLanguage } from \"../../engine/scaffold/lib/language.ts\";\nimport { readBlueprintSpeech } from \"../../engine/scaffold/video/speech-reading.ts\";\nimport { findWorkspaceRoot } from \"./normalize-paths.ts\";\nimport { holdUnbilledSpend } from \"./unbilled-spend.ts\";\n\nconst SELECT_SYSTEM =\n \"You identify the RECURRING, identity-critical visual elements of a video ad from its deconstruction blueprint — \" +\n \"the people, animals, products, and logos that must stay consistent every time they appear, so a faithful \" +\n \"reproduction grounds each in ONE real reference image reused across every frame it appears in. Output ONLY a JSON \" +\n 'object of the form { \"elements\": [ ... ] }, no prose.';\n\nconst SELECT_PROMPT =\n \"VIDEO DECONSTRUCTION BLUEPRINT (video-deconstruct/1):\\n{{blueprint}}\\n\\n\" +\n \"List ONLY the elements worth keeping consistent across frames — the ones a reproduction must ground in a real asset:\\n\" +\n '- each recurring person from global.cast (a presenter/actor/persona) -> type \"person\"\\n' +\n '- a recurring animal/pet/character with a stable identity -> type \"animal\"\\n' +\n '- a showcased product, package, card, or device the ad sells or demonstrates -> type \"product\"\\n' +\n '- the advertiser brand logo/wordmark (from global.branding) -> type \"logo\"\\n' +\n '- a recurring trust/rating/certification badge -> type \"badge\"\\n' +\n '- the dominant recording set/location the scenes share (e.g. the same living room, car interior, kitchen) -> type \"location\"\\n\\n' +\n \"DROP one-off background extras and incidental props — but the shared set/location is NOT generic scenery: pin it as ONE \" +\n '\"location\" element so the room stays identical across scenes. A person in global.cast is almost always recurring. Keep at most ~8.\\n\\n' +\n \"ONE PERSON, MULTIPLE LOOKS: if a single individual plays MULTIPLE personas or wardrobes (e.g. a creator as a skeptic in \" +\n \"one outfit and a believer in another, or a before/after), emit ONE element PER look (so each outfit gets its own real \" +\n 'reference image) and link the extra looks to the first via \"same_as\" — they are the SAME person, only the wardrobe/state ' +\n \"differs, so the face must stay identical.\\n\\n\" +\n 'For each kept element return: { \"type\": one of person|animal|product|logo|badge|location, \"label\": a short ' +\n 'UPPER_SNAKE_CASE name (e.g. HERO, CREATOR_SKEPTIC, INSURANCE_CARD, LOGO), \"description\": a concrete reusable ' +\n \"description to source/shoot the real asset — for a person/animal give a NEUTRAL castable role (e.g. \" +\n '\"hero pet-owner, woman in her 30s\" or \"a small beagle\"), NOT the original individual\\'s literal face/identity: ' +\n \"we RECAST with a FRESH person/animal, so never tell the agent to reuse the original. \" +\n '\"expression\": a living subject\\'s typical expression or null, \"cast_id\": the global.cast id if it maps to one else ' +\n 'null, \"same_as\": the label of another element this is the SAME individual as (different wardrobe/persona) else null, ' +\n '\"scenes\": the 0-based indices of ONLY the scenes where the element is ACTUALLY VISIBLE ON SCREEN — judged from ' +\n \"that scene's start_frame_prompt / end_frame_prompt subjects and its action_detail, NOT from who is merely \" +\n \"speaking. A narrator heard over b-roll is NOT present in that b-roll scene; a dog-running cutaway does NOT \" +\n \"contain the couch creator just because she talks across it. Do NOT pad the list — an element wrongly listed in a \" +\n \"scene makes the reproduction render the wrong subject there (e.g. the creator appearing in a pure-dog b-roll). \" +\n \"When in doubt, leave a scene OUT. Output ONLY the JSON object.\";\n\n/** Load the deconstruct's word-level transcript as text; a missing/unreadable one just\n * means no caption track is wired (best-effort — never fails the scaffold). */\nasync function loadTranscriptBestEffort(ref: unknown): Promise<string | undefined> {\n if (!ref) return undefined;\n try {\n return await loadAssetText(ref, \"deconstruct transcript\");\n } catch {\n return undefined;\n }\n}\n\n/**\n * Stage the burned-in caption track next to the canvas: copy the TikTok caption\n * composition. The caption TEXT is transcribed from the rendered audio at run time\n * (`video_transcribe`, wired by the scaffolder) — not the source transcript — so we don't\n * persist the deconstruct transcript here; we only use its presence to decide whether the\n * ad has speech worth captioning. Returns `compositionPath` (undefined ⇒ no caption track).\n */\nasync function stageCaptions(outDir: string, transcript: string | undefined): Promise<{ compositionPath?: string }> {\n const text = transcript?.trim();\n if (!text || text === \"[]\") return {};\n const compositionPath = path.join(outDir, \"tiktok-captions-composition\");\n await cp(SHIPPED_CAPTIONS_DIR, compositionPath, { recursive: true });\n return { compositionPath };\n}\n\n/**\n * Detect hard shot cuts in the local video with PySceneDetect (`scenedetect`, installed\n * in the canvas sandbox). Best-effort: a detection failure just falls back to LLM-only\n * scene boundaries, so a transient `scenedetect` error never blocks the scaffold.\n */\nasync function detectShotCutsBestEffort(videoPath: string, threshold?: number): Promise<number[] | undefined> {\n try {\n const cuts = await detectSceneCutsPySceneDetect(videoPath, threshold ? { threshold } : {});\n if (cuts.length > 0) {\n process.stderr.write(`Detected ${cuts.length} shot cut(s) via PySceneDetect: ${cuts.join(\", \")}s\\n`);\n } else {\n process.stderr.write(\"PySceneDetect ran but found no hard cuts; using LLM scene boundaries.\\n\");\n }\n return cuts;\n } catch (e) {\n // Stay non-fatal (the deconstruct can still segment with LLM-only boundaries),\n // but a MISSING `scenedetect` binary is an actionable setup gap, not a transient\n // blip — without cuts the segmentation tends to be too coarse, so shout LOUDLY\n // and tell the operator how to fix it instead of degrading silently.\n const msg = e instanceof Error ? e.message : String(e);\n const code = (e as { code?: unknown })?.code;\n const missing = code === \"ENOENT\" || /ENOENT|not found|command not found/i.test(msg);\n if (missing) {\n process.stderr.write(\n \"WARNING: `scenedetect` (PySceneDetect) is NOT installed — falling back to LLM-only scene boundaries, \" +\n \"which under-segments (coarse 9-15s scenes instead of the real 1-4s cuts). \" +\n \"Install it (`pipx install scenedetect[opencv]` or `pip install scenedetect[opencv]`) for accurate shot-cut detection.\\n\",\n );\n } else {\n process.stderr.write(`Shot-cut detection skipped (${msg}); using LLM boundaries.\\n`);\n }\n // Detection FAILED (vs \"ran and found none\") — undefined tells the deconstruct\n // it cannot treat the video as verified cut-less.\n return undefined;\n }\n}\n\nconst VIDEO_EXT_BY_MIME: Record<string, string> = {\n \"video/mp4\": \".mp4\",\n \"video/quicktime\": \".mov\",\n \"video/webm\": \".webm\",\n \"video/x-matroska\": \".mkv\",\n};\n\n/** Extension for a downloaded reference: URL pathname first, then content-type, else .mp4. */\nexport function referenceVideoExt(url: string, contentType: string | null): string {\n const fromPath = path.extname(new URL(url).pathname).toLowerCase();\n if (fromPath && fromPath.length <= 5) return fromPath;\n const mime = (contentType ?? \"\").split(\";\")[0]?.trim().toLowerCase();\n return (mime && VIDEO_EXT_BY_MIME[mime]) || \".mp4\";\n}\n\n/**\n * The reference the scaffold reproduces. Accept a URL (e.g. a `baker winning-ads`\n * presigned link) directly and download it once to a temp file — the local bytes\n * are needed anyway for PySceneDetect shot-cut detection before the billed\n * deconstruct — so the caller never has to `curl` it first. A local path passes\n * straight through.\n */\n/**\n * The INSPIRATION video's provenance for the creative's `_definition.md` + dashboard: the\n * durable, content-addressed R2 URL the deconstruct already uploaded the source to\n * (`blueprint.source.url` — survives even after a presigned winning-ads URL expires),\n * falling back to the original URL argument; plus the advertiser the deconstruct identified.\n * We record the URL rather than committing the file: a reference video can be up to 2 GiB\n * and the video canvas never re-ingests the source at run time (it uses the extracted frame\n * URLs), so a git copy would be pure bloat with no re-run benefit.\n */\nfunction videoSourceReference(blueprint: unknown, fileArg: string): { url?: string; advertiser?: string } {\n const bp = (blueprint ?? {}) as { source?: { url?: unknown }; global?: { branding?: { brand_name?: unknown } } };\n const durable = typeof bp.source?.url === \"string\" ? bp.source.url : undefined;\n const original = /^https?:\\/\\//i.test(fileArg) ? fileArg : undefined;\n const brand = bp.global?.branding?.brand_name;\n return { url: durable ?? original, advertiser: typeof brand === \"string\" && brand.trim() ? brand.trim() : undefined };\n}\n\n/** A short human summary for the video creative's `_definition.md` body (deconstruct-derived). */\nfunction videoDefinitionDescription(blueprint: unknown): string | undefined {\n const g = ((blueprint ?? {}) as { global?: Record<string, unknown> }).global ?? {};\n const notes = g.reproduction_notes;\n if (typeof notes === \"string\" && notes.trim()) return notes.trim();\n const product = (g.branding as { product?: unknown } | undefined)?.product;\n return typeof product === \"string\" && product.trim() ? product.trim() : undefined;\n}\n\n/**\n * The frames mode the canvas actually uses. `--frames reuse` on a video with people in it\n * is refused by the scaffold (their faces would reach our ad), so report that and why\n * rather than echo the flag back.\n */\nfunction framesModeReport(\n canvas: { metadata?: unknown },\n requested: \"generate\" | \"reuse\",\n): { frames_mode: string; frames_reuse_refused?: string } {\n const todo = (canvas.metadata as { todo?: { frames_mode?: unknown; frames_reuse_refused?: unknown } } | undefined)\n ?.todo;\n const mode = typeof todo?.frames_mode === \"string\" ? todo.frames_mode : requested;\n const refused = typeof todo?.frames_reuse_refused === \"string\" ? todo.frames_reuse_refused : undefined;\n return { frames_mode: mode, ...(refused ? { frames_reuse_refused: refused } : {}) };\n}\n\nasync function materializeReferenceVideo(fileArg: string): Promise<string> {\n if (!/^https?:\\/\\//i.test(fileArg)) return path.resolve(fileArg);\n let bytes: Buffer;\n let contentType: string | null;\n try {\n // No challenge sniff: this is definitionally a video file, and decoding the\n // first 64 KB of one as text to look for a bot wall is waste, not caution.\n // The canvas asset ceiling, not the image one: this is a reference video,\n // and it is the surface most likely to meet a limit at all.\n ({ buffer: bytes, contentType } = await fetchExternalBytes(fileArg, {\n sniffChallenge: false,\n maxBytes: MAX_ASSET_BYTES,\n }));\n } catch (e) {\n throw new Error(`failed to download reference video: ${e instanceof Error ? e.message : String(e)}`);\n }\n if (bytes.length === 0) throw new Error(\"reference video download was empty\");\n const dest = path.join(\n tmpdir(),\n `baker-ref-${sha256Hex(bytes).slice(0, 16)}${referenceVideoExt(fileArg, contentType)}`,\n );\n await writeFile(dest, bytes);\n return dest;\n}\n\n/** Parse the `--seam-dedup` flag; defaults to \"head\". Rejects any other value so a typo fails loud. */\nfunction resolveSeamDedup(raw: unknown): ScaffoldVideoOpts[\"seamDedup\"] {\n if (raw === undefined) return \"head\";\n const v = String(raw);\n if (v === \"head\" || v === \"tail\" || v === \"off\") return v;\n throw new Error(`--seam-dedup must be \"head\", \"tail\", or \"off\" (got \"${v}\")`);\n}\n\nconst VIDEO_BUDGETS: readonly VideoBudget[] = [\"economy\", \"standard\", \"premium\"];\nfunction parseBudget(raw: unknown): VideoBudget {\n const v = raw === undefined ? \"standard\" : String(raw);\n if ((VIDEO_BUDGETS as readonly string[]).includes(v)) return v as VideoBudget;\n throw new Error(`--budget must be one of ${VIDEO_BUDGETS.join(\", \")} (got \"${v}\")`);\n}\n\n/** Resolve the model ids that DON'T depend on the deconstruct output. The video\n * model is resolved separately (`resolveVideoModel`) once the cast is known. */\nfunction resolveModels(args: Record<string, unknown>): {\n deconstructModel: string;\n selectModel: string;\n imageModel: string;\n} {\n return {\n // Flash by default (the `videoDeconstruct` role). Override with --deconstruct-model\n // ~google/gemini-pro-latest for the densest extraction when speed matters less.\n deconstructModel: resolveModel(\"video_deconstruct\", args[\"deconstruct-model\"], VIDEO_DECONSTRUCT_MODEL),\n selectModel: resolveModel(\"text_generate\", args[\"select-model\"], BLUEPRINT_ANALYSIS_MODEL),\n // The ad-frame model (matches the static-ad scaffold); the frame generators need the\n // most faithful text/identity reproduction. Override with --image-model for a\n // cheaper/faster pass.\n imageModel: resolveModel(\"image_generate\", args[\"image-model\"], AD_IMAGE_MODEL),\n };\n}\n\n/** True when the deconstruct surfaced a photoreal on-camera person/animal cast —\n * exactly the frames ByteDance's real-person filter blocks on Seedance. */\nexport function hasPhotorealCast(elements: unknown): boolean {\n return (\n Array.isArray(elements) &&\n elements.some((e) => {\n const type = typeof e === \"object\" && e !== null ? (e as { type?: unknown }).type : undefined;\n return type === \"person\" || type === \"animal\";\n })\n );\n}\n\n/** Resolve the video model once the cast is known. An explicit --video-model\n * always wins. Otherwise the scored router picks from the curated roster — and a\n * detected photoreal on-camera cast counts as a real-face brief, so it routes to\n * Veo up front. That cast WOULD be blocked on Seedance by ByteDance's real-person\n * filter, and a blocked frame-started clip is not moved to another model at run\n * time, so picking Seedance burns an attempt and dead-ends. Mirrors Higgsfield's models_explore\n * and explains its pick via the returned reason. */\nexport function resolveVideoModel(\n args: Record<string, unknown>,\n elements: unknown,\n): { videoModel: string; videoRouteReason?: string } {\n if (args[\"video-model\"]) {\n return { videoModel: resolveModel(\"video_generate\", args[\"video-model\"], DEFAULT_VIDEO_GENERATE_MODEL) };\n }\n const photorealCast = hasPhotorealCast(elements);\n const route = routeVideoModel({\n hasRealFace: Boolean(args[\"real-face\"]) || photorealCast,\n needsIdentity: Boolean(args.identity),\n needsMotionTransfer: Boolean(args[\"motion-transfer\"]),\n budget: parseBudget(args.budget),\n });\n // Make the auto-detection legible when it (not an explicit flag) drove the pick.\n const reason =\n photorealCast && !args[\"real-face\"]\n ? `${route.reason} — detected a photoreal on-camera cast, which Seedance's real-person filter would block`\n : route.reason;\n return {\n videoModel: resolveModel(\"video_generate\", route.model, DEFAULT_VIDEO_GENERATE_MODEL),\n videoRouteReason: reason,\n };\n}\n\n/** The deconstruct canvas: ingest → full scene-by-scene blueprint. Run first, on its own. */\nfunction buildDeconstructCanvas(\n videoPath: string,\n deconstructModel: string,\n opts: { maxScenes?: number; language?: string; focus?: string; shotCuts?: number[] },\n) {\n const deconstructParams: Record<string, unknown> = { model: deconstructModel, mode: \"full\" };\n if (typeof opts.maxScenes === \"number\") deconstructParams.max_scenes = opts.maxScenes;\n if (opts.language) deconstructParams.language = opts.language;\n if (opts.focus) deconstructParams.focus = opts.focus;\n // Real visual shot cuts (detected locally with PySceneDetect) so the deconstruct\n // snaps scene boundaries onto them and never lets a scene span a hard cut. An\n // EMPTY list is meaningful (detection ran, the video is verified cut-less — every\n // LLM boundary is a same-take beat); only a failed detection omits the param.\n if (opts.shotCuts) deconstructParams.shot_cuts = opts.shotCuts;\n // The Seedance per-clip ceiling: a shot longer than this is split into seamless\n // continuation sub-scenes (shared splice frame) instead of being truncated.\n deconstructParams.max_clip_s = SEEDANCE_DURATIONS[SEEDANCE_DURATIONS.length - 1];\n return {\n schema: \"baker-canvas/1\" as const,\n metadata: { name: \"video deconstruct pass\" },\n nodes: [\n { id: \"src\", type: \"ingest\", params: { source: \"path\", path: videoPath, expect: \"video\" } },\n { id: \"deconstruct\", type: \"video_deconstruct\", inputs: { video: \"$ref:src.asset\" }, params: deconstructParams },\n ],\n output: { node: \"deconstruct\", output: \"analysis\" },\n };\n}\n\n/**\n * The element-selection canvas: a single text_generate over a SLIMMED blueprint. The slim\n * is inlined into the prompt as a literal STRING (no `$ref` slot), so it never trips the\n * engine's 256KB inline-slot limit no matter how long the ad — a full 42-scene blueprint\n * inlined as a slot blew past it. A function replacer keeps `$`/`{{` in the JSON literal.\n */\nfunction buildSelectCanvas(selectModel: string, slimmedBlueprintJson: string) {\n return {\n schema: \"baker-canvas/1\" as const,\n metadata: { name: \"element selection pass\" },\n nodes: [\n {\n id: \"select\",\n type: \"text_generate\",\n params: {\n model: selectModel,\n max_tokens: 6000,\n temperature: 0,\n response_format: \"json_object\",\n system: SELECT_SYSTEM,\n prompt: SELECT_PROMPT.replace(\"{{blueprint}}\", () => slimmedBlueprintJson),\n },\n },\n ],\n output: { node: \"select\", output: \"text\" },\n };\n}\n\n/**\n * Run the deconstruct pass, then the element-selection pass over a SLIMMED blueprint.\n * Two passes (not one chained canvas) so the CLI can slim the blueprint between them —\n * the selector only reads cast/branding + a few per-scene prompt fields, and inlining the\n * full blueprint busts the engine's inline-prompt limit on long ads. The FULL blueprint is\n * still returned (the source of truth for prompt.json + scaffolding). Credits sum both runs.\n */\nasync function runAnalysisPasses(\n deconstructCanvas: ReturnType<typeof buildDeconstructCanvas>,\n selectModel: string,\n): Promise<{\n blueprint: unknown;\n elements: unknown[];\n transcript: string | undefined;\n /** The language the deconstruct's transcriber heard, as it named it (\"es\", or \"spanish\"). */\n detectedLanguage: string | undefined;\n creditsSpent: number | undefined;\n}> {\n const engine = createEngineFromEnv({ log: (line) => process.stderr.write(`${line}\\n`) });\n let credits = 0;\n let sawCredits = false;\n const addCredits = (stats: unknown) => {\n const c = (stats as { total_credits?: number })?.total_credits;\n if (typeof c === \"number\") {\n credits += c;\n sawCredits = true;\n }\n };\n\n // Pass 1: the full deconstruct blueprint (+ its word-level transcript, used to drive\n // the burned-in caption track).\n let blueprint: unknown;\n let transcript: string | undefined;\n let detectedLanguage: string | undefined;\n try {\n const r1 = await engine.run(deconstructCanvas, {});\n addCredits(r1.stats);\n blueprint = JSON.parse(await loadAssetText(r1.outputs_by_node.deconstruct?.analysis, \"deconstruct output\"));\n transcript = await loadTranscriptBestEffort(r1.outputs_by_node.deconstruct?.transcript);\n const heard = (r1.outputs_by_node.deconstruct?.transcript as { metadata?: { detected_language?: unknown } } | undefined)\n ?.metadata?.detected_language;\n detectedLanguage = typeof heard === \"string\" && heard.trim() ? heard.trim() : undefined;\n } catch (e) {\n if (e instanceof ValidationError) return fail(\"validation\", JSON.stringify(e.issues));\n if (e instanceof SyntaxError) return fail(\"read_outputs\", e.message);\n return fail(\"deconstruct\", e instanceof Error ? e.message : String(e));\n }\n\n // Pass 2: element selection over the slimmed blueprint (inlined as a literal prompt).\n const slimJson = JSON.stringify(slimBlueprintForSelection(blueprint));\n try {\n const r2 = await engine.run(buildSelectCanvas(selectModel, slimJson), {});\n addCredits(r2.stats);\n const elements = parseElements(await loadAssetText(r2.outputs_by_node.select?.text, \"selection output\"));\n return { blueprint, elements, transcript, detectedLanguage, creditsSpent: sawCredits ? credits : undefined };\n } catch (e) {\n if (e instanceof ValidationError) return fail(\"validation\", JSON.stringify(e.issues));\n if (e instanceof SyntaxError) return fail(\"read_outputs\", e.message);\n return fail(\"deconstruct\", e instanceof Error ? e.message : String(e));\n }\n}\n\nexport const scaffoldVideoCommand = defineCommand({\n meta: {\n name: \"scaffold-video\",\n description:\n \"Turn a reference video into a runnable reproduction canvas in one command. Runs billed passes — video_deconstruct \" +\n \"(the full scene-by-scene blueprint + transcript, baked to a split, editable blueprint: a global prompt.json plus one \" +\n \"small scenes/sNN.json per scene) and an AI selection \" +\n \"of the video's RECURRING identity elements (person/animal/product/logo) — then scaffolds a pipeline where every \" +\n \"scene boundary is a static-ad-grade frame (the blueprint as target_blueprint, a reference legend, the real frame as \" +\n \"anchor) and each recurring element gets ONE shared [TODO] ingest slot wired into every frame it appears in. The \" +\n \"clips feed Seedance an ultra-detailed motion brief (action, camera, dialogue, transcript). Edit a scene's \" +\n \"scenes/sNN.json (or prompt.json for global cast/palette/brand), fill the remaining [TODO] images, then `baker canvas run`. \" +\n \"A person in the reference is played by an invented, realistic person generated from their description — never the \" +\n \"source actor and never a photo of anyone real; pass `--avatar <handle>` (`baker avatars list`) when the same actor \" +\n \"has to recur across the company's ads.\",\n },\n args: {\n file: {\n type: \"positional\",\n required: true,\n description:\n \"Reference video — a local path OR an http(s) URL (e.g. a winning-ads link). A URL is downloaded for you; pass --slug or --out with it.\",\n },\n out: { type: \"string\", description: \"Output canvas path (default <video-dir>/<name>.video.canvas.json)\" },\n slug: {\n type: \"string\",\n description: \"Creative slug — writes the canvas to src/creatives/<slug>/<slug>.canvas.json (repo convention)\",\n },\n frames: {\n type: \"string\",\n description:\n '\"generate\" (default, every frame regenerated for our ad) or \"reuse\" (wire the source\\'s real frames in). Reuse applies only to a video with nobody in it — on a video showing people it is refused and every frame is generated, so the source\\'s actors never reach our ad.',\n },\n ambient: {\n type: \"boolean\",\n description: \"Give silent b-roll scenes native diegetic ambient mixed deep under the music bed (off by default)\",\n },\n \"seam-dedup\": {\n type: \"string\",\n description:\n 'How to dedup the frame two clips SHARE when a long shot is split for length: \"head\" (default, drop the second clip\\'s first frame), \"tail\" (drop the first clip\\'s last frame), or \"off\" (keep both).',\n },\n \"max-scenes\": { type: \"string\", description: \"Cap the number of scenes the deconstruct emits\" },\n \"shot-threshold\": {\n type: \"string\",\n description:\n \"PySceneDetect content threshold. Default is adaptive (18, auto re-checked at 27 when a continuous shot looks over-segmented); pinning a value disables the re-check. Lower = more/softer cuts, higher = fewer.\",\n },\n language: { type: \"string\", description: \"Transcript/dialogue language hint (e.g. fr, en)\" },\n focus: { type: \"string\", description: \"Known provenance/emphasis to ground the deconstruct\" },\n advertiser: {\n type: \"string\",\n description: \"Source advertiser recorded in _definition.md (default: the brand the deconstruct identified)\",\n },\n platforms: {\n type: \"string\",\n description:\n \"Where the ad runs, the main platform first (meta, linkedin, tiktok, …): recorded in _definition.md. Take it from the person's request or the idea.\",\n },\n platform: { type: \"string\", description: \"Retired: use --platforms. Still recorded in _definition.md.\" },\n \"deconstruct-model\": { type: \"string\", description: \"Override the video_deconstruct model id\" },\n \"select-model\": { type: \"string\", description: \"Override the text_generate model id for element selection\" },\n \"image-model\": { type: \"string\", description: \"Override the image_generate model id for frames\" },\n \"video-model\": { type: \"string\", description: \"Override the video_generate model id for clips (skips the scored router)\" },\n \"real-face\": {\n type: \"boolean\",\n description:\n \"The ad has a photoreal human face on camera → the router picks Veo (dodges the ByteDance real-person filter). Set this for ANY photoreal presenter/creator/testimonial cast — the filter blocks AI-GENERATED photoreal faces too, not just real ones.\",\n },\n \"motion-transfer\": {\n type: \"boolean\",\n description: \"Motion driven from a reference video / deliberately hyper-dynamic → the router picks Kling.\",\n },\n identity: {\n type: \"boolean\",\n description: \"The same character/product must stay consistent across clips → the router picks the Seedance workhorse.\",\n },\n budget: {\n type: \"string\",\n description: 'Cost posture for the router: \"economy\" | \"standard\" | \"premium\" (default standard).',\n },\n aspect: {\n type: \"string\",\n description:\n 'Output aspect ratio (e.g. \"4:5\"). Default: the source video\\'s detected ratio. When the video model ' +\n \"can't generate the ratio (Seedance has no 4:5) clips generate at the nearest supported ratio and are \" +\n \"center-cropped onto the output canvas.\",\n },\n resolution: {\n type: \"string\",\n description:\n 'Output resolution for generated clips (e.g. \"1080p\"). Default 1080p — the highest the video model supports — so clips keep the keyframe sharpness instead of the model\\'s low default.',\n },\n avatar: {\n type: \"string\",\n description:\n \"Cast company avatars as the reference's people — pass this when the same actor has to recur across ads (without it, \" +\n \"each person is invented from their description). \" +\n 'One handle, or several comma-separated in the order the people appear (`--avatar marta` / `--avatar marta,joan`). ' +\n \"Each recurring person grounds on that avatar's identity sheet, so the ad shows the company's own cast instead of \" +\n \"the source ad's actors. `baker avatars list` shows who there is; checked before anything is billed.\",\n },\n },\n async run({ args }) {\n const fileArg = String(args.file);\n const slug = args.slug ? String(args.slug) : undefined;\n if (slug && !isValidScaffoldSlug(slug)) {\n process.stderr.write(\n `${JSON.stringify({ ok: false, error: { code: \"invalid_slug\", message: \"--slug must be lowercase kebab (a-z, 0-9, hyphens), max 100 chars\" } }, null, 2)}\\n`,\n );\n process.exit(2);\n }\n // A URL is downloaded to a temp file, which is no sensible home for the output\n // canvas — require an explicit target so the reproduction lands in the repo.\n const isUrl = /^https?:\\/\\//i.test(fileArg);\n if (isUrl && !slug && !args.out) {\n return fail(\n \"missing_output_target\",\n \"When the reference is a URL, pass --slug (writes src/creatives/<slug>/) or --out <path> so the scaffolded canvas has a home in the repo.\",\n );\n }\n let videoPath: string;\n try {\n videoPath = await materializeReferenceVideo(fileArg);\n } catch (e) {\n return fail(\"download\", e instanceof Error ? e.message : String(e));\n }\n const base = path.basename(videoPath, path.extname(videoPath));\n const outPath = args.out\n ? path.resolve(String(args.out))\n : slug\n ? path.join(process.cwd(), \"src\", \"creatives\", slug, `${slug}.canvas.json`)\n : path.join(path.dirname(videoPath), `${base}.video.canvas.json`);\n const outDir = path.dirname(outPath);\n const blueprintPath = path.join(outDir, \"prompt.json\");\n // The slim style projection every frame's target_blueprint reads (global cast/palette/\n // brand only). Written alongside prompt.json so the full blueprint stays hand-editable.\n const blueprintStylePath = path.join(outDir, \"prompt.style.json\");\n const frames = args.frames === \"reuse\" ? \"reuse\" : \"generate\";\n const maxScenes = args[\"max-scenes\"] ? Number(args[\"max-scenes\"]) : undefined;\n\n // A faithful reproduction defaults to ALL source scenes. Capping is a cost\n // lever that silently DROPS scenes (the deconstruct merges everything past the\n // cap into the last kept scene), so make it a loud, explicit operator choice.\n if (Number.isFinite(maxScenes)) {\n process.stderr.write(\n `⚠️ --max-scenes ${maxScenes} caps the deconstruct: any scenes beyond ${maxScenes} are MERGED away, ` +\n `reducing fidelity (fewer cuts, lost beats). Omit it to reproduce every scene.\\n`,\n );\n }\n\n let models: ReturnType<typeof resolveModels>;\n try {\n models = resolveModels(args);\n // Checked now, before the billed passes, although the video model is only\n // routed once the cast is known.\n if (args[\"video-model\"]) resolveModel(\"video_generate\", args[\"video-model\"], DEFAULT_VIDEO_GENERATE_MODEL);\n } catch (e) {\n return fail(\"unknown_model\", e instanceof Error ? e.message : String(e));\n }\n const { deconstructModel, selectModel, imageModel } = models;\n\n // Resolved before the billed passes: a handle that cannot be cast fails for free.\n const cast = await resolveCastAvatars(args.avatar);\n if (\"error\" in cast) return fail(\"avatar\", cast.error);\n const { handles: avatarHandles, avatars } = cast;\n\n // Detect real visual shot cuts locally with PySceneDetect so the deconstruct can\n // snap scene boundaries onto them (a scene must never span a hard cut). Lower\n // `--shot-threshold` to catch more (softer) cuts on a fast-cut ad; higher to catch\n // fewer.\n const shotThreshold = args[\"shot-threshold\"] ? Number(args[\"shot-threshold\"]) : undefined;\n const shotCuts = await detectShotCutsBestEffort(videoPath, shotThreshold);\n\n // Two passes: ingest → video_deconstruct, then element selection over a SLIMMED\n // blueprint (so a long ad's blueprint never busts the inline-prompt limit).\n const deconstructCanvas = buildDeconstructCanvas(videoPath, deconstructModel, {\n maxScenes: Number.isFinite(maxScenes) ? maxScenes : undefined,\n language: args.language ? String(args.language) : undefined,\n focus: args.focus ? String(args.focus) : undefined,\n shotCuts,\n });\n const analysis = await runAnalysisPasses(deconstructCanvas, selectModel);\n const { blueprint, transcript, creditsSpent } = analysis;\n // The language the lines are spoken in: `--language` when given, else what the transcriber\n // heard, else Jev's reading of the lines. Read with each voice's gender and the words a\n // caption card may not end on, in one request, kept in the workspace's cache.\n const heard = analysis.detectedLanguage;\n const declaredLanguage =\n (args.language ? spokenLanguage(String(args.language))?.tag : undefined) ??\n (heard ? (spokenLanguage(heard)?.tag ?? languageCodeNamed(heard)) : undefined);\n const speechCache = path.join(findWorkspaceRoot(outDir) ?? process.cwd(), \"canvas\", \".cache\");\n const speechSpent: ReviewUsage[] = [];\n const speech = await readBlueprintSpeech(\n blueprint,\n declaredLanguage ? { declaredLanguage } : {},\n {\n ask: cachedJev(speechCache, (questions) => askJevQuestions(questions, (u) => speechSpent.push(u))),\n genders: voiceGenderModel(speechCache, (u) => speechSpent.push(u)),\n },\n );\n await holdUnbilledSpend(speechCache, speechSpent).catch(() => {});\n const elements = castAvatarsOnElements(analysis.elements, avatars);\n const uncast = avatarsNotCast(analysis.elements, avatarHandles);\n const uncastHint = uncast.length\n ? {\n hints: [\n `NOT CAST: ${uncast.map((h) => `\\`${h}\\``).join(\", \")} — the element selection found no more recurring people than it cast, so ${uncast.length === 1 ? \"this avatar plays\" : \"these avatars play\"} nobody in this canvas. A person shown in only one scene is not a recurring element, so those frames render whoever their scene file describes — tell the user who is cast and who is not.`,\n ],\n }\n : {};\n\n // Route the video model now that the cast is known: a photoreal on-camera\n // person/animal is auto-routed to Veo (Seedance's real-person filter would\n // block it — no point burning a wasted attempt before the runtime fallback).\n let routed: ReturnType<typeof resolveVideoModel>;\n try {\n routed = resolveVideoModel(args, elements);\n } catch (e) {\n return fail(\"unknown_model\", e instanceof Error ? e.message : String(e));\n }\n const { videoModel, videoRouteReason } = routed;\n if (videoRouteReason && !args[\"video-model\"] && !args[\"real-face\"] && hasPhotorealCast(elements)) {\n process.stderr.write(`🎬 Photoreal on-camera cast detected → generating clips on ${videoModel} (dodges Seedance's real-person filter).\\n`);\n }\n\n // Bake the deconstruct blueprint as the editable prompt, annotated so the human\n // editing it sees which wired slot grounds which recurring element.\n await mkdir(outDir, { recursive: true });\n const annotated = annotateBlueprintWithElements(blueprint, elements);\n // Split the blueprint into the global slice (`prompt.json`) + one `scenes/sNN.json`\n // per scene. An agent edits ONE small scene file at a time instead of a 2000-line\n // monolith; `run`/`validate` re-assemble and re-flow each scene into the graph\n // (see scene-projection.ts). blueprintPath still names prompt.json — now scene-less.\n await writeSceneFiles(outDir, annotated);\n // The slim style projection frames actually read via target_blueprint (no per-scene\n // array), derived from the SAME annotated blueprint so brand/cast/palette stay in\n // sync. Written through the ONE renderer `validate`/`run` also use to re-derive it\n // from an edited prompt.json — byte-identical formatting is what makes their\n // \"already up to date\" comparison (and the ingest cache) work.\n await writeFile(blueprintStylePath, renderStyleProjectionFromValue(annotated), \"utf8\");\n\n // Copy the overlay composition next to the canvas, then BAKE the reference's\n // overlays into its index.html as real, editable HTML at the <!--OVERLAYS-->\n // marker. The operator edits that HTML to paint the broadcast look — overlays\n // are authored markup, never props fed to a styling engine.\n // Resolve the output vs generation aspect up front: the overlay/caption composition\n // dims, the ffmpeg canvas, and the clip/frame generation params must all agree.\n let aspect: ReturnType<typeof resolveAspect>;\n try {\n aspect = resolveAspect(\n (blueprint as { source?: { aspect_ratio?: string } }).source?.aspect_ratio,\n args.aspect ? String(args.aspect) : undefined,\n genAspectsFor(videoModel),\n );\n } catch (e) {\n return fail(\"aspect\", e instanceof Error ? e.message : String(e));\n }\n const outDims = canvasDims(aspect.outAr);\n if (aspect.remapped) {\n process.stderr.write(\n `⚠️ ${aspect.outAr} output → clips generate at ${aspect.genAr} (the video model's nearest ratio) and are ` +\n `center-cropped to ${aspect.outAr} (${outDims.w}×${outDims.h}) — keep subjects centered in frame prompts.\\n`,\n );\n }\n\n const compositionDest = path.join(outDir, \"video-overlay-composition\");\n await cp(SHIPPED_OVERLAY_COMPOSITION_DIR, compositionDest, { recursive: true });\n await stampCompositionDims(compositionDest, outDims);\n const indexPath = path.join(compositionDest, \"index.html\");\n // With a transcript the scaffold wires a word-synced caption track (stageCaptions\n // below) — the reference's caption-role overlay fragments must then NOT also seed\n // as frozen static overlays (they'd double the same words on screen).\n const overlayHtml = buildOverlayHtml(blueprint, { captionsActive: Boolean(transcript), videoModel });\n const indexHtml = await readFile(indexPath, \"utf8\");\n // Function replacer (not the string form) so `$`-sequences in overlay copy\n // (e.g. `save $$$!`, a literal `$&`) are inserted verbatim, not interpreted as\n // replacement patterns by String.prototype.replace.\n const injected = indexHtml.replace(\"<!--OVERLAYS-->\", () => overlayHtml);\n // replace() returns the string unchanged when the marker is absent — fail loud\n // rather than silently shipping a composition with no overlay layer.\n if (injected === indexHtml && overlayHtml.trim()) {\n fail(\n \"composition_marker_missing\",\n `video-overlay-composition/index.html is missing the <!--OVERLAYS--> marker — cannot inject the overlay layer`,\n );\n }\n await writeFile(indexPath, injected, \"utf8\");\n\n // Burned-in caption track: a sound-off feed needs the spoken words on screen, so the\n // scaffold wires a word-synced TikTok track by default whenever the deconstruct\n // returned a transcript (staged below). No transcript ⇒ no caption track.\n const captions = await stageCaptions(outDir, transcript);\n if (captions.compositionPath) await stampCompositionDims(captions.compositionPath, outDims);\n\n // Embed paths RELATIVE to the canvas file so the written canvas is portable —\n // `run`/`validate` resolve them against the canvas dir at load time. Baking\n // absolutes here is what made a moved/renamed workspace read a sibling's stale\n // composition + assets (the `memphis` cross-workspace bug).\n const opts: ScaffoldVideoOpts = {\n imageModel,\n videoModel,\n overlayCompositionPath: path.relative(outDir, compositionDest),\n captionsCompositionPath: captions.compositionPath\n ? path.relative(outDir, captions.compositionPath)\n : undefined,\n blueprintPath: path.relative(outDir, blueprintPath),\n blueprintStylePath: path.relative(outDir, blueprintStylePath),\n frames,\n ambient: Boolean(args.ambient),\n seamDedup: resolveSeamDedup(args[\"seam-dedup\"]),\n ...(speech.spokenLanguage ? { spokenLanguage: speech.spokenLanguage } : {}),\n voiceGenders: speech.voiceGenders,\n ...(speech.captionCarriesOn.length ? { captionCarriesOn: speech.captionCarriesOn } : {}),\n ...(args.aspect ? { aspect: String(args.aspect) } : {}),\n ...(args.resolution ? { resolution: String(args.resolution) } : {}),\n };\n\n let canvas: ReturnType<typeof scaffoldVideoCanvas>;\n let report: ReturnType<typeof videoReport>;\n try {\n canvas = scaffoldVideoCanvas(blueprint, elements, opts);\n report = videoReport(blueprint, elements, videoModel);\n } catch (e) {\n return fail(\"scaffold\", e instanceof Error ? e.message : String(e));\n }\n\n // MERGED overlay+captions pass: when the scaffold rendered the caption track\n // inside the overlay composition (no standalone `captions` node), the staged\n // TikTok caption dir is unused — remove it so the workspace holds no dead\n // composition an operator would edit to zero effect.\n if (captions.compositionPath && !canvas.nodes.some((n) => n.id === \"captions\")) {\n await rm(captions.compositionPath, { recursive: true, force: true });\n }\n\n // Validate against a copy whose relative paths are resolved to the output dir\n // (the on-disk canvas keeps the portable relatives).\n const validation = await validateCanvasDeep(resolveRelativeCanvasPaths(canvas, outDir), defaultRegistry());\n\n // ALWAYS write the canvas — the deconstruct behind it is already paid for. A\n // blocking validation issue becomes part of the operator's checklist (fix, then\n // `baker canvas validate`), never a hard exit that discards the artifact.\n if (!validation.ok) {\n const meta = canvas.metadata as Record<string, unknown>;\n const todo = (meta.todo ?? {}) as Record<string, unknown>;\n todo.blocking_validation_issues = validation.issues;\n meta.todo = todo;\n }\n await writeFile(outPath, `${JSON.stringify(canvas, null, 2)}\\n`, \"utf8\");\n // The frozen rebuild context the scene sync feeds back into scaffoldVideoCanvas to\n // regenerate scene prompts deterministically (NO model call): the element-selection\n // artifact + the exact scaffold opts. Written once, never hand-edited. Its absence\n // (an old canvas) makes the sync degrade to legacy behavior, never fail.\n await writeFile(\n path.join(outDir, REBUILD_FILE),\n `${JSON.stringify({ elements, opts }, null, 2)}\\n`,\n \"utf8\",\n );\n if (!validation.ok) {\n process.stderr.write(\n `${JSON.stringify(\n {\n ok: false,\n error: { code: \"validation\", issues: validation.issues },\n canvas_path: outPath,\n prompt_path: blueprintPath,\n ...framesModeReport(canvas, frames),\n note: \"The canvas WAS written despite the blocking issue(s) — fix them in place, then `baker canvas validate` before running.\",\n ...uncastHint,\n },\n null,\n 2,\n )}\\n`,\n );\n process.exit(2);\n }\n\n // Keep the engine's run cache + the scaffold's working files out of git.\n await ensureGitignore(process.cwd(), [\"canvas/\", \".context/\"]);\n\n // Preserve the INSPIRATION video the same way the static flow preserves its reference\n // image — so the creative (and the dashboard) shows what it was built from. We record the\n // durable R2 URL the deconstruct already produced, not a committed copy (see helper).\n const sourceRef = videoSourceReference(blueprint, fileArg);\n if (slug) {\n // Auto-write _definition.md so the video creative joins the content collection with its\n // source provenance (the video command never wrote one before). Preserve on re-scaffold.\n const definitionPath = path.join(outDir, \"_definition.md\");\n if (!(await fileExists(definitionPath))) {\n await writeFile(\n definitionPath,\n buildCreativeDefinition({\n title: titleFromSlug(slug),\n kind: \"video\",\n ...definitionPlatformsOf(\n args.platforms ? String(args.platforms) : undefined,\n args.platform ? String(args.platform) : undefined,\n ),\n formats: resolveFormats(aspect.outAr),\n sourceReferenceUrl: sourceRef.url,\n sourceAdvertiser: args.advertiser ? String(args.advertiser) : sourceRef.advertiser,\n sourceKind: \"video\",\n description: videoDefinitionDescription(blueprint),\n }),\n \"utf8\",\n );\n }\n }\n\n // Push the parsed node-graph so the dashboard can draw this video's full\n // workflow immediately — before spending any of the ~200-credit render. Best-effort.\n if (slug) {\n await syncCreativeDefinitionBestEffort({\n slug,\n title: titleFromSlug(slug),\n ...(await readDefinitionPlatforms(outPath)),\n formats: [aspect.outAr],\n canvas,\n sourceReferenceUrl: sourceRef.url,\n sourceSha: await computeSourceSha(outPath),\n });\n }\n\n process.stdout.write(\n `${JSON.stringify(\n {\n ok: true,\n canvas_path: outPath,\n prompt_path: blueprintPath,\n source_reference: sourceRef.url,\n composition_dir: compositionDest,\n output: canvas.output,\n ...uncastHint,\n ...framesModeReport(canvas, frames),\n models: {\n deconstruct: deconstructModel,\n select: selectModel,\n image: imageModel,\n video: videoModel,\n ...(videoRouteReason ? { video_route_reason: videoRouteReason } : {}),\n },\n stats: {\n scene_count: report.scene_count,\n total_nodes: canvas.nodes.length,\n analysis_credits_spent: creditsSpent,\n run_estimated_credits: validation.estimatedCredits,\n // WHY the graph has its nodes: per-scene routes + the node-type histogram\n // (mirrors metadata.video.graph_stats in the canvas).\n graph: (canvas.metadata as { video?: { graph_stats?: unknown } })?.video?.graph_stats,\n },\n checklist: {\n edit_prompt: `The blueprint is split so you edit ONE small file at a time — never a giant one. Per-scene content (a scene's dialogue, action, frame prompts, overlays) lives in \\`scenes/sNN.json\\` — edit the single scene you want to change. Global cast/palette/brand/copy lives in \\`${path.basename(blueprintPath)}\\`. \\`baker canvas validate\\`/\\`run\\` re-assemble the blueprint and re-flow every edited scene back into the render (and regenerate ${path.basename(blueprintStylePath)}, the projection each frame's target_blueprint reads) — so your scene edits reach the render automatically. Never hand-edit the inlined node prompts in the canvas or the derived ${path.basename(blueprintStylePath)}; both are regenerated.`,\n recurring_elements_to_supply: report.elements,\n voices_to_confirm: report.dialogue.map((d) => ({\n scene: d.scene,\n speaker: d.speaker,\n voice_description: d.voice_description,\n line: d.line,\n })),\n sfx_count: report.sfx_count,\n overlay_count: report.overlay_count,\n has_music: report.has_music,\n scenes_clamped_to_clip_max: report.clamped_scenes,\n oversize_scenes: report.oversize_scenes,\n overstuffed_scenes: report.overstuffed_scenes,\n // Steer the edit toward disciplined prompting + on-brand MOTION, not just\n // on-brand color. The overlays inherit the reference's motion unless the\n // operator restyles easing/transition/accent from the brand's motion language.\n prompt_discipline:\n \"Rewrite each scene with the six-decision structure (Route / Spec / Beats / Copy / Technique / Negatives): quote exact copy, give every beat a timestamp, and name the moves. Pull easing, transition (cut vs fade), pacing, and the accent moment from BRAND.md § Brand in Motion so the overlays move like THIS brand — not the reference's. If BRAND.md has no Brand-in-Motion section, seed it with brand-build first. See the creative-canvas-ads skill: references/prompt-anatomy.md + references/hyperframes/.\",\n // Photoreal on-camera casts auto-route to Veo above, so this only fires\n // when `--video-model bytedance/seedance-2.0` was EXPLICITLY forced despite\n // a person/animal cast — overriding that safety route. Warn that every such\n // clip will hit ByteDance's real-person filter and burn a wasted attempt.\n ...(report.elements.some((e) => e.type === \"person\" || e.type === \"animal\") && /seedance/i.test(videoModel)\n ? {\n content_policy_risk:\n \"You FORCED Seedance (`--video-model bytedance/seedance-2.0`) on an ad with a photoreal on-camera cast. ByteDance's real-person filter will reject those clips (content_policy_blocked, E005) — no prompt change clears it. Each clip auto-recovers on Veo at render time so the run still completes, but you burn a wasted Seedance attempt per clip. Drop `--video-model` to let the scaffold route the cast to Veo up front, or make the frames less photorealistic.\",\n }\n : {}),\n note: \"Fill each el_* [TODO] — a person needs none (invented from their description, or cast with `--avatar <handle>`), never a photo of a real person; anything else with ONE real source image (reused across every frame that element appears in) — confirm each voice_select casting, then `baker canvas validate` and `baker canvas run`. Running generates many billed image/video/audio assets — it is not free.\",\n },\n },\n null,\n 2,\n )}\\n`,\n );\n },\n});\n","/**\n * A SCORED video-model router — the curated-and-explainable answer to\n * Higgsfield's `models_explore(recommend)`. Instead of a single hardcoded\n * default, a small weighted scorer picks between five curated video models (the\n * `videoRouter*` and `videoDefault` roles in the contract's `MODEL_ROLES`) and\n * returns a human-readable `reason` (mirroring Higgsfield's `match_reason`).\n *\n * The roster stays deliberately tiny (curation over breadth); this only makes the\n * pick correct and transparent:\n * - a real human likeness → Veo (the ONLY model that dodges ByteDance's\n * real-person filter), so a real-face brief never wastes a billed Seedance 422.\n * - motion-transfer / deliberately dynamic multi-shot beats → Kling.\n * - identity/product consistency → Seedance, the workhorse.\n * - anything else → the default video model (Gemini Omni 1.1).\n * - budget \"economy\" prefers the cheap Veo-fast tier over the 3.1 ceiling.\n *\n * Pure function, no I/O — tier-1 unit-testable per `.claude/rules/tdd.md`.\n */\n\nimport { MODEL_ROLES } from \"@baker/canvas-contract\";\n\nexport type VideoBudget = \"economy\" | \"standard\" | \"premium\";\n\nexport type VideoRouterInput = {\n /** The brief needs a real human likeness preserved (from a real reference face). */\n hasRealFace?: boolean;\n /** The same character/product must stay consistent across clips. */\n needsIdentity?: boolean;\n /** Motion is driven from a reference video, or the ad is deliberately hyper-dynamic. */\n needsMotionTransfer?: boolean;\n /** Cost posture. `economy` favors the cheap iteration tier. */\n budget?: VideoBudget;\n};\n\nexport type VideoRoute = {\n model: string;\n reason: string;\n /** Per-candidate score, exposed so the pick is fully auditable. */\n scores: Record<string, number>;\n};\n\nconst SEEDANCE = MODEL_ROLES.videoRouterWorkhorse.model;\nconst VEO = MODEL_ROLES.videoRouterRealFace.model;\nconst VEO_FAST = MODEL_ROLES.videoRouterRealFaceEconomy.model;\nconst KLING = MODEL_ROLES.videoRouterMotion.model;\n// The default video model (Omni 1.1, never the legacy preview): the router picks for\n// NEW work, and the preview exists only so stored canvases re-run verbatim.\nconst GEMINI_OMNI = MODEL_ROLES.videoDefault.model;\n\n/** A single weighted contribution to a candidate's score, with the reason it fired. */\ntype Signal = { model: string; weight: number; because: string };\n\n/**\n * Score every curated video model against the brief and return the winner plus\n * the dominant reason. Weights are intentionally coarse and readable — the goal\n * is a correct, explainable pick, not a tuned ML score.\n */\nexport function routeVideoModel(input: VideoRouterInput): VideoRoute {\n const budget = input.budget ?? \"standard\";\n const signals: Signal[] = [];\n\n // Hard rule: a real face MUST route to Veo — Seedance/Kling hit ByteDance-style\n // real-person filters. This dominates everything else.\n if (input.hasRealFace) {\n signals.push({ model: VEO, weight: 1200, because: \"real human likeness → Veo (dodges the real-person filter)\" });\n }\n\n // Motion transfer / hyper-dynamic multi-shot beats are Kling's lane.\n if (input.needsMotionTransfer) {\n signals.push({ model: KLING, weight: 800, because: \"motion-transfer / dynamic multi-shot → Kling\" });\n }\n\n // Identity/product consistency is the Seedance workhorse's strength (cheap, strong i2v).\n if (input.needsIdentity && !input.hasRealFace) {\n signals.push({ model: SEEDANCE, weight: 300, because: \"identity/product consistency → Seedance workhorse\" });\n }\n\n // Baseline preference so a signal-free brief still has a sensible default.\n // Omni wins the default because it renders picture + synchronized audio in one\n // call. It is deliberately a WEAK signal: it loses to every specific need above,\n // and it should: it stops at 10s and takes only 16:9 or 9:16, so any brief with a\n // real requirement is better served elsewhere.\n signals.push({ model: GEMINI_OMNI, weight: 100, because: \"default — native audio in one call\" });\n\n // Budget nudges: economy shifts a Veo pick to the cheap iteration tier; premium\n // keeps the 3.1 ceiling. Applied as a swap, not a competing candidate.\n const scores: Record<string, number> = { [SEEDANCE]: 0, [VEO]: 0, [VEO_FAST]: 0, [KLING]: 0, [GEMINI_OMNI]: 0 };\n let top: Signal = signals[0] as Signal;\n for (const s of signals) {\n scores[s.model] = (scores[s.model] ?? 0) + s.weight;\n if ((scores[s.model] ?? 0) >= (scores[top.model] ?? 0)) top = s;\n }\n\n let model = top.model;\n let reason = top.because;\n if (model === VEO && budget === \"economy\") {\n model = VEO_FAST;\n reason = `${reason}, on the economy fast tier`;\n scores[VEO_FAST] = scores[VEO] ?? 0;\n }\n\n return { model, reason, scores };\n}\n","import { execFile } from \"node:child_process\";\nimport { mkdtemp, readdir, readFile, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * PySceneDetect `detect-content` threshold (HSV per-frame content difference, 0–255\n * scale; NOT comparable to ffmpeg's `scene` luma score). PySceneDetect's own default is\n * 27, but social ads cut fast and often on SOFT reframes (same set, slight angle change)\n * that 27 misses — leaving the deconstruct under-segmented. 18 recovers those soft cuts\n * while staying clear of motion-driven false positives; tune per video with\n * `--shot-threshold` (lower = more cuts).\n */\nconst PYSCENEDETECT_THRESHOLD = 18;\n/**\n * Minimum scene length (seconds) for PySceneDetect. Its own default is ~0.6s, which\n * merges away rapid montage flashes; 0.25 matches the backend's `snapAndSplitToCuts`\n * minimum scene duration so fast cuts survive end-to-end (and sub-2s silent beats become\n * cheap still-holds). Below ~0.2s is single-frame flicker, not a real cut.\n */\nconst PYSCENEDETECT_MIN_SCENE_LEN_S = 0.25;\n/**\n * Adaptive re-check threshold. The low default (18) recovers soft reframes on montage\n * ads, but on a CONTINUOUS talking-head selfie (no real cuts) the creator's motion and\n * the held subject trip it dozens of times — shredding one shot into 20+ micro-scenes.\n * PySceneDetect's own default (27) is the discriminator: a real montage's cuts are\n * genuine HSV jumps that SURVIVE 27, while a continuous shot's motion artifacts VANISH.\n * So when the first pass looks over-segmented we re-run at 27 and trust that result —\n * it keeps real cuts and drops false ones. Paired with a longer min-scene-len.\n */\nconst PYSCENEDETECT_RECHECK_THRESHOLD = 27;\nconst PYSCENEDETECT_RECHECK_MIN_SCENE_LEN_S = 0.6;\n\n/**\n * Does a cut list look like a continuous shot shredded by motion rather than a real\n * montage? True when there are many cuts packed close together — the MEDIAN gap between\n * consecutive boundaries (counting 0 → first cut) is short. This is only a TRIGGER to\n * re-check at a higher threshold; the re-check itself is what decides montage vs\n * continuous, so a false trigger on a genuinely fast montage is harmless (its real cuts\n * survive the re-check). Pure + deterministic.\n */\nexport function isLikelyOverSegmented(cuts: number[], opts: { minCuts?: number; medianGapS?: number } = {}): boolean {\n const minCuts = opts.minCuts ?? 6;\n const maxMedianGap = opts.medianGapS ?? 2.0;\n const sorted = [...cuts].filter((c) => Number.isFinite(c) && c > 0).sort((a, b) => a - b);\n if (sorted.length < minCuts) return false;\n const gaps: number[] = [];\n let prev = 0;\n for (const c of sorted) {\n gaps.push(c - prev);\n prev = c;\n }\n gaps.sort((a, b) => a - b);\n const mid = Math.floor(gaps.length / 2);\n const median = gaps.length % 2 ? (gaps[mid] as number) : ((gaps[mid - 1] as number) + (gaps[mid] as number)) / 2;\n return median < maxMedianGap;\n}\n\n/**\n * Merge the over-segmentation re-check with the first pass instead of discarding it\n * wholesale. The high-threshold set is the base (it dropped the motion shred), but the\n * low pass ALSO found real SOFT transitions (blur-morphs, gradual reframes) that vanish\n * at 27 — losing them chains scenes across real shot changes. An extra is added back\n * only when it is ISOLATED: ≥ `isolationS` from every kept cut and from every other\n * extra. Motion shred clusters (sub-second neighbors); genuine soft cuts stand alone.\n * Pure + deterministic.\n */\nexport function mergeRecheckCuts(firstPass: number[], recheck: number[], isolationS = 0.75): number[] {\n const kept = [...new Set(recheck)].sort((a, b) => a - b);\n const extras = [...new Set(firstPass)]\n .filter((c) => kept.every((k) => Math.abs(k - c) >= isolationS))\n .sort((a, b) => a - b);\n const isolated = extras.filter((c, i) => {\n const prev = extras[i - 1];\n const next = extras[i + 1];\n return (prev === undefined || c - prev >= isolationS) && (next === undefined || next - c >= isolationS);\n });\n return [...new Set([...kept, ...isolated])].sort((a, b) => a - b);\n}\n\n/** Parse an `HH:MM:SS.mmm` PySceneDetect timecode into seconds, or null if malformed. */\nexport function timecodeToSeconds(tc: string): number | null {\n const m = tc.trim().match(/^(\\d+):(\\d{1,2}):(\\d{1,2}(?:\\.\\d+)?)$/);\n if (!m) return null;\n const h = Number.parseInt(m[1] ?? \"\", 10);\n const min = Number.parseInt(m[2] ?? \"\", 10);\n const s = Number.parseFloat(m[3] ?? \"\");\n if (!Number.isFinite(h) || !Number.isFinite(min) || !Number.isFinite(s)) return null;\n return h * 3600 + min * 60 + s;\n}\n\n/**\n * Parse hard-cut timestamps (seconds) out of a PySceneDetect `*-Scenes.csv`. The\n * first CSV row is the cutting list: `Timecode List:,00:00:08.900,00:00:11.333,…`\n * — each entry is a scene boundary (internal hard cut). Sorted, deduped, positive. Pure.\n */\nexport function parsePySceneDetectCsvCuts(csv: string): number[] {\n const firstLine = csv.split(/\\r?\\n/, 1)[0] ?? \"\";\n if (!/^\\s*Timecode List:/i.test(firstLine)) return [];\n const cuts: number[] = [];\n for (const cell of firstLine.split(\",\").slice(1)) {\n const t = timecodeToSeconds(cell);\n if (t !== null && t > 0) cuts.push(Math.round(t * 1000) / 1000);\n }\n return [...new Set(cuts)].sort((a, b) => a - b);\n}\n\n/**\n * Detect hard visual shot cuts with PySceneDetect's `scenedetect` CLI (battle-tested\n * ContentDetector: RGB→HSV per-frame average difference, default threshold 27). Writes\n * the scene CSV to a temp dir, parses the cutting list, then cleans up. Returns the cut\n * timestamps in seconds. Throws if `scenedetect` is missing or fails (caller falls back).\n */\n/** One `scenedetect detect-content` invocation at a fixed threshold; returns the cuts. */\nasync function runSceneDetectOnce(\n filePath: string,\n threshold: number,\n minSceneLenS: number,\n timeoutMs: number,\n): Promise<number[]> {\n const outDir = await mkdtemp(join(tmpdir(), \"baker-scenedetect-\"));\n try {\n await execFileAsync(\n \"scenedetect\",\n [\n \"--input\",\n filePath,\n \"--output\",\n outDir,\n \"detect-content\",\n \"--threshold\",\n String(threshold),\n \"--min-scene-len\",\n String(minSceneLenS),\n \"list-scenes\",\n \"--quiet\",\n ],\n { encoding: \"utf-8\", maxBuffer: 32 * 1024 * 1024, timeout: timeoutMs },\n );\n const csvName = (await readdir(outDir)).find((f) => f.toLowerCase().endsWith(\".csv\"));\n if (!csvName) return [];\n return parsePySceneDetectCsvCuts(await readFile(join(outDir, csvName), \"utf-8\"));\n } finally {\n await rm(outDir, { recursive: true, force: true });\n }\n}\n\nexport async function detectSceneCutsPySceneDetect(\n filePath: string,\n opts: { threshold?: number; minSceneLenS?: number; timeout_ms?: number } = {},\n): Promise<number[]> {\n // An operator-pinned threshold disables the adaptive re-check (they know best).\n const pinned = opts.threshold !== undefined;\n const threshold = opts.threshold ?? PYSCENEDETECT_THRESHOLD;\n // Minimum scene length (seconds). PySceneDetect's default (~0.6s) MERGES cuts closer\n // than that, silently dropping super-fast montage flashes. We floor it at the backend's\n // own minimum scene duration so genuine sub-second flashes survive as their own scenes\n // (then become cheap still-holds downstream). A float string is parsed as seconds.\n const minSceneLenS = opts.minSceneLenS ?? PYSCENEDETECT_MIN_SCENE_LEN_S;\n const timeoutMs = opts.timeout_ms ?? 120_000;\n const cuts = await runSceneDetectOnce(filePath, threshold, minSceneLenS, timeoutMs);\n // Adaptive guard: if the first pass shredded a continuous shot into many micro-cuts,\n // re-run at PySceneDetect's own (higher) default and trust THAT — real montage cuts\n // survive it, motion artifacts don't. Only when the operator didn't pin a threshold.\n if (!pinned && isLikelyOverSegmented(cuts)) {\n const rechecked = await runSceneDetectOnce(\n filePath,\n PYSCENEDETECT_RECHECK_THRESHOLD,\n PYSCENEDETECT_RECHECK_MIN_SCENE_LEN_S,\n timeoutMs,\n );\n // Merge instead of replace: the re-check drops the motion shred, but the low pass\n // also caught real SOFT transitions invisible at 27 — keep its isolated extras.\n return mergeRecheckCuts(cuts, rechecked);\n }\n return cuts;\n}\n","import { appendFile, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/**\n * Which of `entries` are not already ignored by `existing` .gitignore content.\n * Matches a whole non-comment line (trimmed, optional trailing slash) so `canvas`\n * and `canvas/` both count as present. Pure — unit-tested without the filesystem.\n */\nexport function missingGitignoreEntries(existing: string, entries: string[]): string[] {\n const present = new Set(\n existing\n .split(\"\\n\")\n .map((l) => l.trim().replace(/\\/+$/, \"\"))\n .filter((l) => l.length > 0 && !l.startsWith(\"#\")),\n );\n return entries.filter((e) => !present.has(e.trim().replace(/\\/+$/, \"\")));\n}\n\n/**\n * Append any missing `entries` to `<dir>/.gitignore`. The canvas engine writes a\n * content-addressed cache + per-run dirs under `canvas/`, and the scaffold writes\n * working files under `.context/` — committing either would bloat the repo. Only\n * touches an EXISTING .gitignore (never creates surprise files); best-effort.\n */\nexport async function ensureGitignore(dir: string, entries: string[]): Promise<void> {\n const file = path.join(dir, \".gitignore\");\n let existing: string;\n try {\n existing = await readFile(file, \"utf8\");\n } catch {\n return; // no .gitignore here — don't conjure one\n }\n const missing = missingGitignoreEntries(existing, entries);\n if (missing.length === 0) return;\n const prefix = existing.endsWith(\"\\n\") || existing.length === 0 ? \"\" : \"\\n\";\n await appendFile(file, `${prefix}\\n# Baker canvas (engine cache + scaffold working files)\\n${missing.join(\"\\n\")}\\n`);\n}\n"],"mappings":"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","names":["readFile","rm","tmpdir","path","readFile","path","tmpdir","readFile","rm"]}
@@ -1,11 +1,13 @@
1
1
  import {
2
2
  versionOf
3
- } from "./chunk-N6WHUUFH.js";
4
- import "./chunk-OPUEQSKM.js";
3
+ } from "./chunk-R6WEPF63.js";
4
+ import "./chunk-MIRPLAFD.js";
5
+ import "./chunk-YE4RCEEJ.js";
5
6
  import "./chunk-3OSBCYKA.js";
6
7
  import "./chunk-3JV4T6XW.js";
7
8
  import "./chunk-EKX37C3T.js";
8
9
  import "./chunk-ENUCJ6AO.js";
10
+ import "./chunk-IU5K42Q3.js";
9
11
  import "./chunk-UROKPFIS.js";
10
12
  import "./chunk-3QQHPRRB.js";
11
13
  import "./chunk-ZOH672S6.js";
@@ -14,9 +16,11 @@ import "./chunk-DWW5QSUF.js";
14
16
  import "./chunk-FSTDWVXG.js";
15
17
  import "./chunk-FUR7R4D7.js";
16
18
  import "./chunk-QOBA6PHB.js";
19
+ import "./chunk-G6XKD7QE.js";
17
20
  import "./chunk-GYMZCJX6.js";
18
21
  import "./chunk-AGU4KGT6.js";
19
22
  import "./chunk-R5LRBDEM.js";
23
+ import "./chunk-NZUMNTAT.js";
20
24
  import "./chunk-4Y7IJ2PU.js";
21
25
  import "./chunk-3WTCMBDC.js";
22
26
  import "./chunk-DGCUPJZD.js";
@@ -103,4 +107,4 @@ var selectCommand = defineCommand({
103
107
  export {
104
108
  selectCommand
105
109
  };
106
- //# sourceMappingURL=select-BXBNFGVL.js.map
110
+ //# sourceMappingURL=select-XAC7G4ZT.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/creatives/select.ts"],"sourcesContent":["import { type CreativesSelectResponse, creativesSelectRequestSchema } from \"@baker/api\";\nimport { defineCommand } from \"citty\";\nimport { ApiError, apiPost } from \"../../client.ts\";\nimport { writeJson } from \"../../output.ts\";\nimport { registerSchema } from \"../../schemas.ts\";\nimport { versionOf } from \"./version-of.ts\";\n\nconst DESCRIPTION = `Choose the version of a creative to keep, and optionally which of its options, only when the user says to keep this version in this chat. The creative shows that version as the chosen one, and the choice counts as the user's approval. A version you render after it replaces it when the chat is published (the user saw that one last). It does not save the image to their library (that is baker creatives publish) and puts nothing into an ad.\nStart here: baker creatives select --slug spring-offer\nOnly some options of a version that made several: --options \"ugc,2\" (labels or numbers). Naming options again replaces the earlier pick.`;\n\nconst ARGS = {\n slug: { type: \"string\", description: \"The creative: its folder name under src/creatives/.\", required: true },\n run: {\n type: \"string\",\n description:\n \"The version to keep: its canvas run id (r_…) Left out, the version the creative shows now (its last render).\",\n required: false,\n },\n options: {\n type: \"string\",\n description:\n 'Keep only these options of the version, comma-separated: labels (\"ugc\"), numbers (\"2\") or slots. Left out, the whole version.',\n required: false,\n },\n} as const;\n\nregisterSchema({ command: \"creatives.select\", description: DESCRIPTION, args: ARGS });\n\nfunction fail(code: string, message: string): never {\n writeJson({ ok: false, error: { code, message } });\n process.exit(1);\n}\n\nexport const selectCommand = defineCommand({\n meta: { name: \"select\", description: DESCRIPTION },\n args: ARGS,\n run: async ({ args }) => {\n const options =\n typeof args.options === \"string\"\n ? args.options\n .split(\",\")\n .map((option) => option.trim())\n .filter((option) => option.length > 0)\n : [];\n const parsed = creativesSelectRequestSchema.safeParse({\n slug: args.slug,\n runId: await versionOf(String(args.slug), args.run).then(\n (run) => run ?? fail(\"VALIDATION_ERROR\", \"--run: this creative has no rendered version here; name one (r_…).\"),\n ),\n ...(options.length > 0 ? { options } : {}),\n });\n if (!parsed.success) {\n const issue = parsed.error.issues[0];\n fail(\"VALIDATION_ERROR\", `--${String(issue?.path[0] ?? \"args\")}: ${issue?.message ?? \"invalid\"}`);\n }\n try {\n const response = await apiPost<CreativesSelectResponse>(\"/api/creatives/select\", parsed.data);\n writeJson(response);\n } catch (err) {\n if (err instanceof ApiError) fail(err.code, err.message);\n fail(\"INTERNAL_ERROR\", \"Unexpected error\");\n }\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,SAAS,qBAAqB;AAM9B,IAAM,cAAc;AAAA;AAAA;AAIpB,IAAM,OAAO;AAAA,EACX,MAAM,EAAE,MAAM,UAAU,aAAa,uDAAuD,UAAU,KAAK;AAAA,EAC3G,KAAK;AAAA,IACH,MAAM;AAAA,IACN,aACE;AAAA,IACF,UAAU;AAAA,EACZ;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,aACE;AAAA,IACF,UAAU;AAAA,EACZ;AACF;AAEA,eAAe,EAAE,SAAS,oBAAoB,aAAa,aAAa,MAAM,KAAK,CAAC;AAEpF,SAAS,KAAK,MAAc,SAAwB;AAClD,YAAU,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,QAAQ,EAAE,CAAC;AACjD,UAAQ,KAAK,CAAC;AAChB;AAEO,IAAM,gBAAgB,cAAc;AAAA,EACzC,MAAM,EAAE,MAAM,UAAU,aAAa,YAAY;AAAA,EACjD,MAAM;AAAA,EACN,KAAK,OAAO,EAAE,KAAK,MAAM;AACvB,UAAM,UACJ,OAAO,KAAK,YAAY,WACpB,KAAK,QACF,MAAM,GAAG,EACT,IAAI,CAAC,WAAW,OAAO,KAAK,CAAC,EAC7B,OAAO,CAAC,WAAW,OAAO,SAAS,CAAC,IACvC,CAAC;AACP,UAAM,SAAS,6BAA6B,UAAU;AAAA,MACpD,MAAM,KAAK;AAAA,MACX,OAAO,MAAM,UAAU,OAAO,KAAK,IAAI,GAAG,KAAK,GAAG,EAAE;AAAA,QAClD,CAAC,QAAQ,OAAO,KAAK,oBAAoB,yEAAoE;AAAA,MAC/G;AAAA,MACA,GAAI,QAAQ,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1C,CAAC;AACD,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,QAAQ,OAAO,MAAM,OAAO,CAAC;AACnC,WAAK,oBAAoB,KAAK,OAAO,OAAO,KAAK,CAAC,KAAK,MAAM,CAAC,KAAK,OAAO,WAAW,SAAS,EAAE;AAAA,IAClG;AACA,QAAI;AACF,YAAM,WAAW,MAAM,QAAiC,yBAAyB,OAAO,IAAI;AAC5F,gBAAU,QAAQ;AAAA,IACpB,SAAS,KAAK;AACZ,UAAI,eAAe,SAAU,MAAK,IAAI,MAAM,IAAI,OAAO;AACvD,WAAK,kBAAkB,kBAAkB;AAAA,IAC3C;AAAA,EACF;AACF,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/commands/creatives/select.ts"],"sourcesContent":["import { type CreativesSelectResponse, creativesSelectRequestSchema } from \"@baker/api\";\nimport { defineCommand } from \"citty\";\nimport { ApiError, apiPost } from \"../../client.ts\";\nimport { writeJson } from \"../../output.ts\";\nimport { registerSchema } from \"../../schemas.ts\";\nimport { versionOf } from \"./version-of.ts\";\n\nconst DESCRIPTION = `Choose the version of a creative to keep, and optionally which of its options, only when the user says to keep this version in this chat. The creative shows that version as the chosen one, and the choice counts as the user's approval. A version you render after it replaces it when the chat is published (the user saw that one last). It does not save the image to their library (that is baker creatives publish) and puts nothing into an ad.\nStart here: baker creatives select --slug spring-offer\nOnly some options of a version that made several: --options \"ugc,2\" (labels or numbers). Naming options again replaces the earlier pick.`;\n\nconst ARGS = {\n slug: { type: \"string\", description: \"The creative: its folder name under src/creatives/.\", required: true },\n run: {\n type: \"string\",\n description:\n \"The version to keep: its canvas run id (r_…) Left out, the version the creative shows now (its last render).\",\n required: false,\n },\n options: {\n type: \"string\",\n description:\n 'Keep only these options of the version, comma-separated: labels (\"ugc\"), numbers (\"2\") or slots. Left out, the whole version.',\n required: false,\n },\n} as const;\n\nregisterSchema({ command: \"creatives.select\", description: DESCRIPTION, args: ARGS });\n\nfunction fail(code: string, message: string): never {\n writeJson({ ok: false, error: { code, message } });\n process.exit(1);\n}\n\nexport const selectCommand = defineCommand({\n meta: { name: \"select\", description: DESCRIPTION },\n args: ARGS,\n run: async ({ args }) => {\n const options =\n typeof args.options === \"string\"\n ? args.options\n .split(\",\")\n .map((option) => option.trim())\n .filter((option) => option.length > 0)\n : [];\n const parsed = creativesSelectRequestSchema.safeParse({\n slug: args.slug,\n runId: await versionOf(String(args.slug), args.run).then(\n (run) => run ?? fail(\"VALIDATION_ERROR\", \"--run: this creative has no rendered version here; name one (r_…).\"),\n ),\n ...(options.length > 0 ? { options } : {}),\n });\n if (!parsed.success) {\n const issue = parsed.error.issues[0];\n fail(\"VALIDATION_ERROR\", `--${String(issue?.path[0] ?? \"args\")}: ${issue?.message ?? \"invalid\"}`);\n }\n try {\n const response = await apiPost<CreativesSelectResponse>(\"/api/creatives/select\", parsed.data);\n writeJson(response);\n } catch (err) {\n if (err instanceof ApiError) fail(err.code, err.message);\n fail(\"INTERNAL_ERROR\", \"Unexpected error\");\n }\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,SAAS,qBAAqB;AAM9B,IAAM,cAAc;AAAA;AAAA;AAIpB,IAAM,OAAO;AAAA,EACX,MAAM,EAAE,MAAM,UAAU,aAAa,uDAAuD,UAAU,KAAK;AAAA,EAC3G,KAAK;AAAA,IACH,MAAM;AAAA,IACN,aACE;AAAA,IACF,UAAU;AAAA,EACZ;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,aACE;AAAA,IACF,UAAU;AAAA,EACZ;AACF;AAEA,eAAe,EAAE,SAAS,oBAAoB,aAAa,aAAa,MAAM,KAAK,CAAC;AAEpF,SAAS,KAAK,MAAc,SAAwB;AAClD,YAAU,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,QAAQ,EAAE,CAAC;AACjD,UAAQ,KAAK,CAAC;AAChB;AAEO,IAAM,gBAAgB,cAAc;AAAA,EACzC,MAAM,EAAE,MAAM,UAAU,aAAa,YAAY;AAAA,EACjD,MAAM;AAAA,EACN,KAAK,OAAO,EAAE,KAAK,MAAM;AACvB,UAAM,UACJ,OAAO,KAAK,YAAY,WACpB,KAAK,QACF,MAAM,GAAG,EACT,IAAI,CAAC,WAAW,OAAO,KAAK,CAAC,EAC7B,OAAO,CAAC,WAAW,OAAO,SAAS,CAAC,IACvC,CAAC;AACP,UAAM,SAAS,6BAA6B,UAAU;AAAA,MACpD,MAAM,KAAK;AAAA,MACX,OAAO,MAAM,UAAU,OAAO,KAAK,IAAI,GAAG,KAAK,GAAG,EAAE;AAAA,QAClD,CAAC,QAAQ,OAAO,KAAK,oBAAoB,yEAAoE;AAAA,MAC/G;AAAA,MACA,GAAI,QAAQ,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC1C,CAAC;AACD,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,QAAQ,OAAO,MAAM,OAAO,CAAC;AACnC,WAAK,oBAAoB,KAAK,OAAO,OAAO,KAAK,CAAC,KAAK,MAAM,CAAC,KAAK,OAAO,WAAW,SAAS,EAAE;AAAA,IAClG;AACA,QAAI;AACF,YAAM,WAAW,MAAM,QAAiC,yBAAyB,OAAO,IAAI;AAC5F,gBAAU,QAAQ;AAAA,IACpB,SAAS,KAAK;AACZ,UAAI,eAAe,SAAU,MAAK,IAAI,MAAM,IAAI,OAAO;AACvD,WAAK,kBAAkB,kBAAkB;AAAA,IAC3C;AAAA,EACF;AACF,CAAC;","names":[]}
@@ -22,7 +22,8 @@ import "./chunk-R5LRBDEM.js";
22
22
  import {
23
23
  defaultRegistry,
24
24
  validateCanvasDeep
25
- } from "./chunk-PE4ZP7MX.js";
25
+ } from "./chunk-I3LRC33M.js";
26
+ import "./chunk-NZUMNTAT.js";
26
27
  import "./chunk-4Y7IJ2PU.js";
27
28
  import "./chunk-3WTCMBDC.js";
28
29
  import "./chunk-DGCUPJZD.js";
@@ -195,4 +196,4 @@ export {
195
196
  setPromptCommand,
196
197
  setPromptInCanvasFile
197
198
  };
198
- //# sourceMappingURL=set-prompt-JJWBB23C.js.map
199
+ //# sourceMappingURL=set-prompt-H3UEDJWS.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/canvas/set-prompt.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { defineCommand } from \"citty\";\nimport { defaultRegistry, validateCanvasDeep } from \"../../engine/index.ts\";\nimport { COPY_DIRECTIVE_HEADER } from \"../../engine/scaffold/static-copy.ts\";\nimport { adaptNodes } from \"../../engine/scaffold/static-adapt-briefs.ts\";\nimport { compiledPromptNodeIds } from \"../../engine/scaffold/static-render-briefs.ts\";\nimport { CanvasPrepareError, prepareCanvas } from \"./prepare-canvas.ts\";\nimport { sceneOwnedPrompts } from \"./scene-projection.ts\";\n\n/**\n * Set one node's `params.prompt` in a parsed canvas, returning a NEW canvas. The\n * structured alternative to hand-editing a 133 KB machine JSON with jq/sed — which\n * is where `\"prompt\": null` pollution (a global `gsub` writing prompt onto every\n * node) and the French-accent corruption (shell `''` eating apostrophes) came from.\n * Throws when the node id is unknown. Pure — unit-tested without the filesystem.\n */\nexport function setNodePrompt(canvas: unknown, nodeId: string, text: string): unknown {\n const nodes = (canvas as { nodes?: unknown })?.nodes;\n if (!Array.isArray(nodes)) throw new Error(\"canvas has no nodes array\");\n const idx = nodes.findIndex((n) => (n as { id?: unknown })?.id === nodeId);\n if (idx < 0) {\n const ids = nodes.map((n) => (n as { id?: unknown })?.id).filter((id): id is string => typeof id === \"string\");\n throw new Error(`node \"${nodeId}\" not found. Known nodes: ${ids.join(\", \")}`);\n }\n const node = nodes[idx] as { params?: Record<string, unknown> };\n const newNode = { ...node, params: { ...(node.params ?? {}), prompt: text } };\n const newNodes = [...nodes];\n newNodes[idx] = newNode;\n return { ...(canvas as object), nodes: newNodes };\n}\n\n/** Why `set-prompt` did not write: the node's prompt is owned elsewhere, or the edit does not validate. */\nexport class SetPromptError extends Error {\n readonly code: string;\n readonly fix: string | undefined;\n readonly details: Record<string, unknown> | undefined;\n constructor(code: string, message: string, opts: { fix?: string; details?: Record<string, unknown> } = {}) {\n super(message);\n this.name = \"SetPromptError\";\n this.code = code;\n this.fix = opts.fix;\n this.details = opts.details;\n }\n}\n\n/**\n * Set a node's prompt in the canvas FILE, where it will survive.\n *\n * The canvas is a projection of its blueprint, and `prepareCanvas` rewrites the projected\n * parts on every validate and run. So:\n * - a frame or clip prompt a scene file rebuilds is refused, naming the scene file to edit\n * (writing it here would be silently overwritten by the next preflight);\n * - a static ad's scaffolded render prompt (a take, a re-lay, the judge) is refused, naming\n * prompt.json: it is compiled from the ad spec there on every run;\n * - a hand-written static render prompt keeps its copy block: the text replaces the scene\n * description, and the \"render these exact strings\" block after it is re-read from\n * prompt.json, as always;\n * - anything else is the canvas's own, and is written.\n * The result is validated; an edit that does not validate is undone.\n */\nexport async function setPromptInCanvasFile(canvasPath: string, nodeId: string, text: string): Promise<void> {\n const filePath = path.resolve(canvasPath);\n const original = await readFile(filePath, \"utf8\");\n let canvas: unknown;\n try {\n canvas = JSON.parse(original);\n } catch (e) {\n throw new SetPromptError(\"parse\", `${filePath}: JSON parse error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n const sceneFile = (await sceneOwnedPrompts(filePath)).get(nodeId);\n if (sceneFile) {\n throw new SetPromptError(\n \"projected_prompt\",\n `The prompt of \"${nodeId}\" is rebuilt from ${sceneFile} on every validate and run, so setting it in the canvas would be overwritten.`,\n {\n fix: `Edit the scene's description in ${sceneFile} (its frame and motion fields, e.g. start_frame_prompt, end_frame_prompt, frame_prompt, motion_prompt), then run \\`baker canvas validate\\` to see the rebuilt prompt.`,\n },\n );\n }\n\n if (compiledPromptNodeIds(canvas).has(nodeId) || adaptNodes(canvas).some((a) => a.node.id === nodeId)) {\n throw new SetPromptError(\n \"projected_prompt\",\n `The prompt of \"${nodeId}\" is compiled from prompt.json on every validate and run, so setting it in the canvas would be overwritten.`,\n {\n fix: `Edit the ad in ${path.join(path.dirname(filePath), \"prompt.json\")} (its scene, copy, references or direction), then run \\`baker canvas validate\\` to see the compiled prompt on the node.`,\n },\n );\n }\n\n let updated: unknown;\n try {\n updated = setNodePrompt(canvas, nodeId, withCopyBlock(canvas, nodeId, text));\n } catch (e) {\n throw new SetPromptError(\"node_not_found\", e instanceof Error ? e.message : String(e));\n }\n\n await writeFile(filePath, `${JSON.stringify(updated, null, 2)}\\n`, \"utf8\");\n const restore = () => writeFile(filePath, original, \"utf8\");\n try {\n const prepared = await prepareCanvas(filePath);\n const validation = await validateCanvasDeep(prepared.canvas, defaultRegistry());\n if (!validation.ok) {\n await restore();\n throw new SetPromptError(\"validation\", \"The edited canvas does not validate; nothing was changed.\", {\n details: { issues: validation.issues },\n });\n }\n } catch (e) {\n if (e instanceof SetPromptError) throw e;\n await restore();\n if (e instanceof CanvasPrepareError) {\n throw new SetPromptError(e.code, e.message, { ...(e.fix ? { fix: e.fix } : {}), ...(e.details ? { details: e.details } : {}) });\n }\n throw e;\n }\n}\n\n/**\n * A static ad's render prompt ends with the copy block the preflight fills from prompt.json.\n * New text that leaves it out keeps it, so replacing the scene description never drops the\n * words the ad must show.\n */\nfunction withCopyBlock(canvas: unknown, nodeId: string, text: string): string {\n const nodes = (canvas as { nodes?: unknown })?.nodes;\n const current = Array.isArray(nodes)\n ? (nodes as Array<{ id?: unknown; params?: { prompt?: unknown } }>).find((n) => n?.id === nodeId)?.params?.prompt\n : undefined;\n if (typeof current !== \"string\" || !current.includes(COPY_DIRECTIVE_HEADER) || text.includes(COPY_DIRECTIVE_HEADER)) {\n return text;\n }\n return `${text.trimEnd()}\\n\\n${COPY_DIRECTIVE_HEADER}`;\n}\n\nexport const setPromptCommand = defineCommand({\n meta: {\n name: \"set-prompt\",\n description:\n \"Safely set a node's params.prompt (a frame description, motion prompt, etc.) without hand-editing the JSON. Prefer --text-file for multi-line/accented copy — it preserves UTF-8 exactly, unlike shell-quoted jq. A prompt rebuilt from a scene file (scenes/sNN.json), or compiled from a static ad's prompt.json, is refused with the file to edit instead.\",\n },\n args: {\n file: { type: \"positional\", required: true, description: \"Path to canvas JSON\" },\n node: { type: \"positional\", required: true, description: \"Node id to edit (e.g. s0_start)\" },\n text: { type: \"string\", description: \"New prompt text (inline)\" },\n \"text-file\": { type: \"string\", description: \"Read the new prompt from a UTF-8 file (preserves accents/newlines)\" },\n },\n async run({ args }) {\n let text: string;\n if (args[\"text-file\"]) text = await readFile(path.resolve(String(args[\"text-file\"])), \"utf8\");\n else if (args.text !== undefined) text = String(args.text);\n else {\n process.stderr.write(\n `${JSON.stringify({ ok: false, error: { code: \"no_text\", message: \"pass --text or --text-file\" } }, null, 2)}\\n`,\n );\n process.exit(2);\n return;\n }\n\n try {\n await setPromptInCanvasFile(String(args.file), String(args.node), text);\n } catch (e) {\n if (!(e instanceof SetPromptError)) throw e;\n process.stderr.write(\n `${JSON.stringify(\n { ok: false, error: { code: e.code, message: e.message, ...(e.fix ? { fix: e.fix } : {}), ...(e.details ?? {}) } },\n null,\n 2,\n )}\\n`,\n );\n process.exit(2);\n return;\n }\n process.stdout.write(`${JSON.stringify({ ok: true, node: String(args.node), bytes: text.length }, null, 2)}\\n`);\n },\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/commands/canvas/set-prompt.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { defineCommand } from \"citty\";\nimport { defaultRegistry, validateCanvasDeep } from \"../../engine/index.ts\";\nimport { COPY_DIRECTIVE_HEADER } from \"../../engine/scaffold/static-copy.ts\";\nimport { adaptNodes } from \"../../engine/scaffold/static-adapt-briefs.ts\";\nimport { compiledPromptNodeIds } from \"../../engine/scaffold/static-render-briefs.ts\";\nimport { CanvasPrepareError, prepareCanvas } from \"./prepare-canvas.ts\";\nimport { sceneOwnedPrompts } from \"./scene-projection.ts\";\n\n/**\n * Set one node's `params.prompt` in a parsed canvas, returning a NEW canvas. The\n * structured alternative to hand-editing a 133 KB machine JSON with jq/sed — which\n * is where `\"prompt\": null` pollution (a global `gsub` writing prompt onto every\n * node) and the French-accent corruption (shell `''` eating apostrophes) came from.\n * Throws when the node id is unknown. Pure — unit-tested without the filesystem.\n */\nexport function setNodePrompt(canvas: unknown, nodeId: string, text: string): unknown {\n const nodes = (canvas as { nodes?: unknown })?.nodes;\n if (!Array.isArray(nodes)) throw new Error(\"canvas has no nodes array\");\n const idx = nodes.findIndex((n) => (n as { id?: unknown })?.id === nodeId);\n if (idx < 0) {\n const ids = nodes.map((n) => (n as { id?: unknown })?.id).filter((id): id is string => typeof id === \"string\");\n throw new Error(`node \"${nodeId}\" not found. Known nodes: ${ids.join(\", \")}`);\n }\n const node = nodes[idx] as { params?: Record<string, unknown> };\n const newNode = { ...node, params: { ...(node.params ?? {}), prompt: text } };\n const newNodes = [...nodes];\n newNodes[idx] = newNode;\n return { ...(canvas as object), nodes: newNodes };\n}\n\n/** Why `set-prompt` did not write: the node's prompt is owned elsewhere, or the edit does not validate. */\nexport class SetPromptError extends Error {\n readonly code: string;\n readonly fix: string | undefined;\n readonly details: Record<string, unknown> | undefined;\n constructor(code: string, message: string, opts: { fix?: string; details?: Record<string, unknown> } = {}) {\n super(message);\n this.name = \"SetPromptError\";\n this.code = code;\n this.fix = opts.fix;\n this.details = opts.details;\n }\n}\n\n/**\n * Set a node's prompt in the canvas FILE, where it will survive.\n *\n * The canvas is a projection of its blueprint, and `prepareCanvas` rewrites the projected\n * parts on every validate and run. So:\n * - a frame or clip prompt a scene file rebuilds is refused, naming the scene file to edit\n * (writing it here would be silently overwritten by the next preflight);\n * - a static ad's scaffolded render prompt (a take, a re-lay, the judge) is refused, naming\n * prompt.json: it is compiled from the ad spec there on every run;\n * - a hand-written static render prompt keeps its copy block: the text replaces the scene\n * description, and the \"render these exact strings\" block after it is re-read from\n * prompt.json, as always;\n * - anything else is the canvas's own, and is written.\n * The result is validated; an edit that does not validate is undone.\n */\nexport async function setPromptInCanvasFile(canvasPath: string, nodeId: string, text: string): Promise<void> {\n const filePath = path.resolve(canvasPath);\n const original = await readFile(filePath, \"utf8\");\n let canvas: unknown;\n try {\n canvas = JSON.parse(original);\n } catch (e) {\n throw new SetPromptError(\"parse\", `${filePath}: JSON parse error: ${e instanceof Error ? e.message : String(e)}`);\n }\n\n const sceneFile = (await sceneOwnedPrompts(filePath)).get(nodeId);\n if (sceneFile) {\n throw new SetPromptError(\n \"projected_prompt\",\n `The prompt of \"${nodeId}\" is rebuilt from ${sceneFile} on every validate and run, so setting it in the canvas would be overwritten.`,\n {\n fix: `Edit the scene's description in ${sceneFile} (its frame and motion fields, e.g. start_frame_prompt, end_frame_prompt, frame_prompt, motion_prompt), then run \\`baker canvas validate\\` to see the rebuilt prompt.`,\n },\n );\n }\n\n if (compiledPromptNodeIds(canvas).has(nodeId) || adaptNodes(canvas).some((a) => a.node.id === nodeId)) {\n throw new SetPromptError(\n \"projected_prompt\",\n `The prompt of \"${nodeId}\" is compiled from prompt.json on every validate and run, so setting it in the canvas would be overwritten.`,\n {\n fix: `Edit the ad in ${path.join(path.dirname(filePath), \"prompt.json\")} (its scene, copy, references or direction), then run \\`baker canvas validate\\` to see the compiled prompt on the node.`,\n },\n );\n }\n\n let updated: unknown;\n try {\n updated = setNodePrompt(canvas, nodeId, withCopyBlock(canvas, nodeId, text));\n } catch (e) {\n throw new SetPromptError(\"node_not_found\", e instanceof Error ? e.message : String(e));\n }\n\n await writeFile(filePath, `${JSON.stringify(updated, null, 2)}\\n`, \"utf8\");\n const restore = () => writeFile(filePath, original, \"utf8\");\n try {\n const prepared = await prepareCanvas(filePath);\n const validation = await validateCanvasDeep(prepared.canvas, defaultRegistry());\n if (!validation.ok) {\n await restore();\n throw new SetPromptError(\"validation\", \"The edited canvas does not validate; nothing was changed.\", {\n details: { issues: validation.issues },\n });\n }\n } catch (e) {\n if (e instanceof SetPromptError) throw e;\n await restore();\n if (e instanceof CanvasPrepareError) {\n throw new SetPromptError(e.code, e.message, { ...(e.fix ? { fix: e.fix } : {}), ...(e.details ? { details: e.details } : {}) });\n }\n throw e;\n }\n}\n\n/**\n * A static ad's render prompt ends with the copy block the preflight fills from prompt.json.\n * New text that leaves it out keeps it, so replacing the scene description never drops the\n * words the ad must show.\n */\nfunction withCopyBlock(canvas: unknown, nodeId: string, text: string): string {\n const nodes = (canvas as { nodes?: unknown })?.nodes;\n const current = Array.isArray(nodes)\n ? (nodes as Array<{ id?: unknown; params?: { prompt?: unknown } }>).find((n) => n?.id === nodeId)?.params?.prompt\n : undefined;\n if (typeof current !== \"string\" || !current.includes(COPY_DIRECTIVE_HEADER) || text.includes(COPY_DIRECTIVE_HEADER)) {\n return text;\n }\n return `${text.trimEnd()}\\n\\n${COPY_DIRECTIVE_HEADER}`;\n}\n\nexport const setPromptCommand = defineCommand({\n meta: {\n name: \"set-prompt\",\n description:\n \"Safely set a node's params.prompt (a frame description, motion prompt, etc.) without hand-editing the JSON. Prefer --text-file for multi-line/accented copy — it preserves UTF-8 exactly, unlike shell-quoted jq. A prompt rebuilt from a scene file (scenes/sNN.json), or compiled from a static ad's prompt.json, is refused with the file to edit instead.\",\n },\n args: {\n file: { type: \"positional\", required: true, description: \"Path to canvas JSON\" },\n node: { type: \"positional\", required: true, description: \"Node id to edit (e.g. s0_start)\" },\n text: { type: \"string\", description: \"New prompt text (inline)\" },\n \"text-file\": { type: \"string\", description: \"Read the new prompt from a UTF-8 file (preserves accents/newlines)\" },\n },\n async run({ args }) {\n let text: string;\n if (args[\"text-file\"]) text = await readFile(path.resolve(String(args[\"text-file\"])), \"utf8\");\n else if (args.text !== undefined) text = String(args.text);\n else {\n process.stderr.write(\n `${JSON.stringify({ ok: false, error: { code: \"no_text\", message: \"pass --text or --text-file\" } }, null, 2)}\\n`,\n );\n process.exit(2);\n return;\n }\n\n try {\n await setPromptInCanvasFile(String(args.file), String(args.node), text);\n } catch (e) {\n if (!(e instanceof SetPromptError)) throw e;\n process.stderr.write(\n `${JSON.stringify(\n { ok: false, error: { code: e.code, message: e.message, ...(e.fix ? { fix: e.fix } : {}), ...(e.details ?? {}) } },\n null,\n 2,\n )}\\n`,\n );\n process.exit(2);\n return;\n }\n process.stdout.write(`${JSON.stringify({ ok: true, node: String(args.node), bytes: text.length }, null, 2)}\\n`);\n },\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,UAAU,iBAAiB;AACpC,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAevB,SAAS,cAAc,QAAiB,QAAgB,MAAuB;AACpF,QAAM,QAAS,QAAgC;AAC/C,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,OAAM,IAAI,MAAM,2BAA2B;AACtE,QAAM,MAAM,MAAM,UAAU,CAAC,MAAO,GAAwB,OAAO,MAAM;AACzE,MAAI,MAAM,GAAG;AACX,UAAM,MAAM,MAAM,IAAI,CAAC,MAAO,GAAwB,EAAE,EAAE,OAAO,CAAC,OAAqB,OAAO,OAAO,QAAQ;AAC7G,UAAM,IAAI,MAAM,SAAS,MAAM,6BAA6B,IAAI,KAAK,IAAI,CAAC,EAAE;AAAA,EAC9E;AACA,QAAM,OAAO,MAAM,GAAG;AACtB,QAAM,UAAU,EAAE,GAAG,MAAM,QAAQ,EAAE,GAAI,KAAK,UAAU,CAAC,GAAI,QAAQ,KAAK,EAAE;AAC5E,QAAM,WAAW,CAAC,GAAG,KAAK;AAC1B,WAAS,GAAG,IAAI;AAChB,SAAO,EAAE,GAAI,QAAmB,OAAO,SAAS;AAClD;AAGO,IAAM,iBAAN,cAA6B,MAAM;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY,MAAc,SAAiB,OAA4D,CAAC,GAAG;AACzG,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,MAAM,KAAK;AAChB,SAAK,UAAU,KAAK;AAAA,EACtB;AACF;AAiBA,eAAsB,sBAAsB,YAAoB,QAAgB,MAA6B;AAC3G,QAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,QAAM,WAAW,MAAM,SAAS,UAAU,MAAM;AAChD,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,QAAQ;AAAA,EAC9B,SAAS,GAAG;AACV,UAAM,IAAI,eAAe,SAAS,GAAG,QAAQ,uBAAuB,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,EAAE;AAAA,EAClH;AAEA,QAAM,aAAa,MAAM,kBAAkB,QAAQ,GAAG,IAAI,MAAM;AAChE,MAAI,WAAW;AACb,UAAM,IAAI;AAAA,MACR;AAAA,MACA,kBAAkB,MAAM,qBAAqB,SAAS;AAAA,MACtD;AAAA,QACE,KAAK,mCAAmC,SAAS;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AAEA,MAAI,sBAAsB,MAAM,EAAE,IAAI,MAAM,KAAK,WAAW,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,OAAO,MAAM,GAAG;AACrG,UAAM,IAAI;AAAA,MACR;AAAA,MACA,kBAAkB,MAAM;AAAA,MACxB;AAAA,QACE,KAAK,kBAAkB,KAAK,KAAK,KAAK,QAAQ,QAAQ,GAAG,aAAa,CAAC;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,cAAU,cAAc,QAAQ,QAAQ,cAAc,QAAQ,QAAQ,IAAI,CAAC;AAAA,EAC7E,SAAS,GAAG;AACV,UAAM,IAAI,eAAe,kBAAkB,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,EACvF;AAEA,QAAM,UAAU,UAAU,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,GAAM,MAAM;AACzE,QAAM,UAAU,MAAM,UAAU,UAAU,UAAU,MAAM;AAC1D,MAAI;AACF,UAAM,WAAW,MAAM,cAAc,QAAQ;AAC7C,UAAM,aAAa,MAAM,mBAAmB,SAAS,QAAQ,gBAAgB,CAAC;AAC9E,QAAI,CAAC,WAAW,IAAI;AAClB,YAAM,QAAQ;AACd,YAAM,IAAI,eAAe,cAAc,6DAA6D;AAAA,QAClG,SAAS,EAAE,QAAQ,WAAW,OAAO;AAAA,MACvC,CAAC;AAAA,IACH;AAAA,EACF,SAAS,GAAG;AACV,QAAI,aAAa,eAAgB,OAAM;AACvC,UAAM,QAAQ;AACd,QAAI,aAAa,oBAAoB;AACnC,YAAM,IAAI,eAAe,EAAE,MAAM,EAAE,SAAS,EAAE,GAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,GAAI,GAAI,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC,EAAG,CAAC;AAAA,IAChI;AACA,UAAM;AAAA,EACR;AACF;AAOA,SAAS,cAAc,QAAiB,QAAgB,MAAsB;AAC5E,QAAM,QAAS,QAAgC;AAC/C,QAAM,UAAU,MAAM,QAAQ,KAAK,IAC9B,MAAiE,KAAK,CAAC,MAAM,GAAG,OAAO,MAAM,GAAG,QAAQ,SACzG;AACJ,MAAI,OAAO,YAAY,YAAY,CAAC,QAAQ,SAAS,qBAAqB,KAAK,KAAK,SAAS,qBAAqB,GAAG;AACnH,WAAO;AAAA,EACT;AACA,SAAO,GAAG,KAAK,QAAQ,CAAC;AAAA;AAAA,EAAO,qBAAqB;AACtD;AAEO,IAAM,mBAAmB,cAAc;AAAA,EAC5C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,EAAE,MAAM,cAAc,UAAU,MAAM,aAAa,sBAAsB;AAAA,IAC/E,MAAM,EAAE,MAAM,cAAc,UAAU,MAAM,aAAa,kCAAkC;AAAA,IAC3F,MAAM,EAAE,MAAM,UAAU,aAAa,2BAA2B;AAAA,IAChE,aAAa,EAAE,MAAM,UAAU,aAAa,qEAAqE;AAAA,EACnH;AAAA,EACA,MAAM,IAAI,EAAE,KAAK,GAAG;AAClB,QAAI;AACJ,QAAI,KAAK,WAAW,EAAG,QAAO,MAAM,SAAS,KAAK,QAAQ,OAAO,KAAK,WAAW,CAAC,CAAC,GAAG,MAAM;AAAA,aACnF,KAAK,SAAS,OAAW,QAAO,OAAO,KAAK,IAAI;AAAA,SACpD;AACH,cAAQ,OAAO;AAAA,QACb,GAAG,KAAK,UAAU,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,WAAW,SAAS,6BAA6B,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,MAC9G;AACA,cAAQ,KAAK,CAAC;AACd;AAAA,IACF;AAEA,QAAI;AACF,YAAM,sBAAsB,OAAO,KAAK,IAAI,GAAG,OAAO,KAAK,IAAI,GAAG,IAAI;AAAA,IACxE,SAAS,GAAG;AACV,UAAI,EAAE,aAAa,gBAAiB,OAAM;AAC1C,cAAQ,OAAO;AAAA,QACb,GAAG,KAAK;AAAA,UACN,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,SAAS,GAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,GAAI,GAAI,EAAE,WAAW,CAAC,EAAG,EAAE;AAAA,UACjH;AAAA,UACA;AAAA,QACF,CAAC;AAAA;AAAA,MACH;AACA,cAAQ,KAAK,CAAC;AACd;AAAA,IACF;AACA,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,IAAI,MAAM,MAAM,OAAO,KAAK,IAAI,GAAG,OAAO,KAAK,OAAO,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAChH;AACF,CAAC;","names":[]}