@mevdragon/vidfarm-devcli 0.18.1 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.agents/skills/editor-capabilities/SKILL.md +166 -0
  2. package/.agents/skills/editor-capabilities/references/re-theme-walkthrough.md +58 -0
  3. package/.agents/skills/vidfarm-media/SKILL.md +2 -0
  4. package/SKILL.director.md +174 -16
  5. package/SKILL.platform.md +7 -3
  6. package/demo/dist/app.css +1 -1
  7. package/demo/dist/app.js +79 -75
  8. package/dist/src/account-pages-legacy.js +1 -1
  9. package/dist/src/app.js +1694 -217
  10. package/dist/src/cli.js +1365 -105
  11. package/dist/src/config.js +8 -0
  12. package/dist/src/devcli/clips.js +3 -0
  13. package/dist/src/devcli/composition-edit.js +401 -10
  14. package/dist/src/devcli/local-backend.js +123 -0
  15. package/dist/src/devcli/migrate-local.js +140 -0
  16. package/dist/src/devcli/sync.js +311 -0
  17. package/dist/src/devcli/timeline-edit.js +208 -1
  18. package/dist/src/editor-chat.js +37 -18
  19. package/dist/src/frontend/file-directory.js +271 -20
  20. package/dist/src/frontend/homepage-view.js +3 -3
  21. package/dist/src/frontend/template-editor-chat.js +6 -3
  22. package/dist/src/page-shell.js +1 -1
  23. package/dist/src/primitive-context.js +31 -2
  24. package/dist/src/primitive-registry.js +409 -4
  25. package/dist/src/reskin/chat-page.js +103 -15
  26. package/dist/src/reskin/discover-page.js +247 -10
  27. package/dist/src/reskin/document.js +147 -10
  28. package/dist/src/reskin/inpaint-clipper-page.js +649 -0
  29. package/dist/src/reskin/inpaint-page.js +2459 -452
  30. package/dist/src/reskin/inpaint-video-page.js +1339 -0
  31. package/dist/src/reskin/library-page.js +324 -82
  32. package/dist/src/reskin/portfolio-page.js +687 -0
  33. package/dist/src/reskin/theme.js +36 -11
  34. package/dist/src/services/billing.js +4 -0
  35. package/dist/src/services/clip-curation/hunt.js +2 -0
  36. package/dist/src/services/clip-records.js +28 -0
  37. package/dist/src/services/file-directory.js +6 -3
  38. package/dist/src/services/hyperframes.js +535 -86
  39. package/dist/src/services/serverless-jobs.js +3 -1
  40. package/dist/src/services/serverless-records.js +43 -0
  41. package/dist/src/services/storage.js +24 -2
  42. package/dist/src/template-editor-pages.js +1 -1
  43. package/package.json +1 -1
  44. package/public/assets/file-directory-app.js +2 -2
  45. package/public/assets/homepage-client-app.js +1 -1
  46. package/public/assets/page-runtime-client-app.js +2 -2
  47. package/public/assets/placeholders/scene-placeholder.png +0 -0
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: editor-capabilities
3
+ description: The in-editor AI copilot's own capability map for the /editor page — what it can DO to a composition and how to think about it. Load this when you're the editor chat and you're about to do heavy or unfamiliar work: a full re-theme of a template, a multi-scene rebuild, restructuring the timeline, or any time you need the exact editor_action verb + params, the editor_context fields that are your source of truth, or the forking rules. Complements vidfarm-director (broad REST/devcli flows) with the tight web-editor action surface. Do NOT load for pure composition-authoring craft (use hyperframes-*), captions identity (embedded-captions), or audio (vidfarm-media).
4
+ ---
5
+
6
+ # editor-capabilities
7
+
8
+ You are the copilot embedded in the `/editor` page. You operate a real, open composition through the **`editor_action`** tool (timeline mutations, generate-and-place, export), the **`http_request`** tool (every `/api/v1/primitives/*` and REST route), **`browse_files`** (the user's file directory), and **`load_skill`** (this and the deeper packs). This pack is your capability map — read it before heavy or unfamiliar work so you carry the whole transformation instead of nibbling one layer at a time.
9
+
10
+ The single most important habit: **`<editor_context>` in the latest user turn is your source of truth**, and an `editor_action` tool result only echoes your own args — it is NOT proof anything landed. Always confirm against the NEXT `<editor_context>`.
11
+
12
+ ## The mental model: three axes × SWAP↔REPLACE
13
+
14
+ Almost every session is a director re-working a template/fork/project, and a re-work only ever touches three independent axes:
15
+
16
+ - **SCENES** — the video/image clips carrying the visuals.
17
+ - **AUDIO** — narration/voiceover, music, SFX.
18
+ - **TEXT** — captions, titles, overlays.
19
+
20
+ Each axis sits somewhere on a **SWAP ↔ REPLACE** spectrum, and the three can sit at different points in one request:
21
+
22
+ - **SWAP (light, cheap, common)** — keep the structure, change content in place: `set_layer_text` / `set_captions` for words, `set_layer_media` to swap one clip's media keeping its timing + geometry, re-voice narration (`/audio/regenerate-speech` → mute old span → add new audio). Many jobs are *only* a text swap — a two-minute change; just do it.
23
+ - **REPLACE (heavy)** — throw out that axis's content and rebuild: restructure scenes (`remove_layer` + `add_layer`/`generate_layer`, or one `replace_composition_html`), lay a new audio bed, or rewrite every caption. The **hardest** end is a full **re-theme** where the ONLY thing preserved is the **viral DNA** (hook shape, pacing, scene-count rhythm, transition/caption style) and every scene, every word, and the audio are replaced for a new subject.
24
+
25
+ Name the plan back in these terms — *"I'll SWAP the captions and REPLACE the scenes"* — then execute axis by axis. Be proactive at the heavy end. (The broad, devcli-and-REST version of this model lives in the `vidfarm-director` pack.)
26
+
27
+ ## Forking is automatic — you never manually create a fork
28
+
29
+ Read the project state straight off `editor_context`:
30
+
31
+ - **`composition_origin`** — `"inspiration"` = a decomposed catalog template opened as a structural reference (the common re-theme case); `"raw"` = the user's own new project.
32
+ - **`has_saved_fork`** — `true` once a real editable fork exists server-side (edits persist).
33
+ - **`is_new_project`** — `true` only on a truly blank project (`composition_id` is the literal `"original"`), which has no template behind it yet.
34
+
35
+ Rules:
36
+
37
+ 1. On any **real** template (a `template_…` id, a catalog reference, any decomposed composition) with `has_saved_fork:false` — **just start editing.** The FIRST mutating `editor_action` auto-mints the user their own private editable fork and every edit after persists normally. Do not ask permission to fork, wait for the user, or announce "forking." Decompose is *not* required — an un-decomposed or public template you don't own forks fine on first edit.
38
+ 2. On a **blank** project (`is_new_project:true`, `composition_id:"original"`) there is nothing to fork yet, so a raw `editor_action` edit cannot persist. Don't promise saves. Instead mint a real starting composition first: help the user pick a template (`GET /discover/feed?q=<their idea>`) **or** create one from scratch (describe the video → the create-video flow). Once a real composition is open, edit it normally.
39
+ 3. A `save_status:"error"` / `is_read_only:true` right after editing means the save is being rejected — surface exactly what `save_error` says (a read-only deep link → tell them to open their own copy; a brand-new project → the auto-mint failed) and stop mutating until it clears. Never report an edit as done without seeing it reflected in the next `editor_context`.
40
+
41
+ **Explicit branching is the one time you fork on purpose.** When the user asks to **branch / duplicate / "save as" / "make a copy" / "try a variation" / "keep this and start another"**, call `editor_action` `action_type=fork_composition`:
42
+
43
+ - `fork_from:"current"` — branch a NEW fork from the current working copy (all edits carry over). The good default for "let me try a variation without losing this."
44
+ - `fork_from:"default"` — start a FRESH fork from the template's original default composition, discarding the current edits (they stay in the copy you're leaving).
45
+
46
+ This mints a new fork and opens it in the editor — the same **New fork** action in the editor's ⋯ menu, and the same `POST /api/v1/compositions/:forkId/fork { from }` REST endpoint the devcli `clone --from` command uses. Never call it just to begin editing (that's the automatic first-edit fork); reserve it for a deliberate branch the user asked for.
47
+
48
+ ## Re-theme playbook (keep the DNA, swap the subject)
49
+
50
+ The most common intent: the user opens a template to reuse its structure and says *"make this for X"* (e.g. a book-recap template → "The Richest Man in Babylon", or a listicle → "my coffee brand"). Treat it as first-class:
51
+
52
+ 1. **Read the DNA first.** `composition_context` carries the format/genre + arc (trend_tagline, hook, retention, payoff, preserve, avoid). For the scene beats/transcript, call the `video_context` tool. Match the template's *voice* — a text-message template's captions read like chat, a story-time template reads like first-person narration; don't default to generic ad copy.
53
+ 2. **Propose, then ask only what you can't infer.** One concise plan plus a couple of questions: which specifics of the new subject to feature (offer to pick the strongest N to match the scene count), keep the current narrator voice?, any brand assets/images to use (else you'll generate), a target length if different.
54
+ 3. **Execute axis by axis, preserving the DNA:**
55
+ - TEXT → rewrite every caption/title layer in the template's voice (`set_layer_text` / `set_captions`), keeping caption animation + timing.
56
+ - AUDIO → regenerate narration in the SAME speaker's voice for the new script (`/audio/regenerate-speech`), then mute the old span and place the new audio. If the original baked **music + narration into one track**, rebuild it as TWO independent audio layers instead — see *Native multi-track audio* below.
57
+ - SCENES → swap visuals to match, keeping each clip's start/duration/geometry and the transitions (`set_layer_media` in place, or `generate_layer` with `intent:"replace_layer"`).
58
+ 4. Keep timings, transitions, and caption style intact so the proven format survives.
59
+
60
+ A full worked example is in `references/re-theme-walkthrough.md`.
61
+
62
+ ## Fueling a SCENES replace — raws before AI
63
+
64
+ A heavy scenes-axis replace needs footage. Source it in **cost order**, and be proactive: when a big scene re-work is asked for but no footage is given, offer to hunt a source *before* defaulting to expensive AI generation.
65
+
66
+ 1. **The user's own library** — `browse_files` search/list over `/raws` (footage) and `/files` (durable assets). Free. **You do not need the user to paste a URL.** When they say "use my raws" / "it's in my file directory" / name a clip, `browse_files action=search` (or `list path='/raws'`) to find it, then `action=read` with its `file_id` — the read's **`view_url` IS the public, ready media URL**. Drop that `view_url` straight into `set_layer_media` `src`. Never stall asking for a URL you can look up, and never pass a placeholder `src` like `"please-ignore"`.
67
+ 2. **HUNT new raws** out of a long-form source (podcast/VOD/webinar or any YouTube/TikTok/IG/X URL): `POST /clips/scan` (aka `/raws/scan`) with `{ source_url|temp_file_id|s3_key, prompt, aspect, target_duration_sec?, ranges?, avoid_text? }` — async (202 + scan_id), bills **AWS compute only** (the AI tagging runs on the user's own keys, never billed). Then browse `/raws/feed?source=<id>` and reuse the picks (`set_layer_media` in place, `add_layer`/`generate_layer intent:"add"` for net-new scenes).
68
+ 3. **`generate_layer` AI generation** — the expensive last resort, only for scenes no real clip can cover. AI *image* gen is cheap (use freely); AI *video* gen is expensive — ask permission / confirm budget first.
69
+
70
+ **Clip ONE exact subrange (not an AI hunt).** When the user hand-picks a precise in/out — "save 0:30–0:45 into my raws", "clip that 8-second bit" — skip `/clips/scan` and call `POST /raws/clip-range` `{ source_url|preview_url|temp_file_id, start_sec, end_sec, tracer?, folder_path?, name? }`. It trims **exactly** that window into a single raw (compute-only ffmpeg, no AI, no key). Same operation as the web `/tools/clipper` page and `vidfarm clipper`; multi-clip by calling it again with a new range under the same `tracer`/`folder_path`. Use `/clips/scan` for open-ended multi-clip hunts, `/raws/clip-range` for surgical single cuts.
71
+
72
+ ## Layering AUDIO — native multi-track mixing
73
+
74
+ Audio is natively **multi-track**. The timeline mixes UNLIMITED simultaneous `<audio>` layers — each on its own `track` with its own `volume` (0–2, default 1) — and the runtime mixes them with per-track volume honored IDENTICALLY in the preview and the exported MP4 (a real ffmpeg `amix` of every audio layer at render). So you never need a pre-mixed file; you compose the mix on the timeline:
75
+
76
+ - **Overlay** by stacking `add_layer kind=audio` calls, each with its own `src`, `start`, `duration`, `track`, and `volume` — narration/voiceover at ~1.0, a music bed at ~0.1–0.2, SFX on their own layers. Re-level any layer later with `set_layer_media` (`volume`, `muted`) — the Inspector's Volume slider writes the same `data-volume`.
77
+ - **The headline move — split a combined original when recreating.** When the user recreates a template whose ORIGINAL had music + narration baked into ONE audio track, do NOT reproduce a single combined bed. Rebuild it as TWO independent tracks: a fresh narration track (`/api/v1/primitives/audio/speech`, or same-voice reword via `/api/v1/primitives/audio/regenerate-speech`) at ~1.0, and a separate real music track at ~0.1–0.2 — then mute or `remove_layer` the original combined source-audio layer so the old voice doesn't play under the new one. This hands the user independent voice/music volume and is the elegant workaround for AI TTS being unable to emit narration+music in one file.
78
+ - **Honesty (ties to the create-media rules):** you cannot un-mix / stem-separate the original's baked audio — the two tracks are BUILT from a fresh narration track PLUS a real music file (owned / user-provided / `browse_files` across `/files` and `/raws`), never a faked "music" layer and never the voice track duplicated. There is no music-generation primitive.
79
+
80
+ ## The `editor_action` verb catalog
81
+
82
+ Every mutation is one `editor_action` call with an `explanation`. Reference keys/tracks/durations from the latest `editor_context`; never invent `layer_key`s. Grouped by axis of work:
83
+
84
+ **Timeline structure**
85
+ - `add_layer` — new layer; set a stable `layer_key` (e.g. `vf-caption-hook`), `kind` (video/image/audio/text/html), `src` (video/image/audio) or `text`/`html`, and a collision-free `start`/`duration`/`track`.
86
+ - `remove_layer` / `duplicate_layer` / `split_layer` (`split_time`) / `group_layers` / `ungroup_layers`.
87
+ - `replace_composition_html` — full-document rewrite; lint-gated (rejected with `lint_errors` if it violates the contract or contains `<script>`). Prefer targeted verbs for small edits.
88
+
89
+ **Timing & stacking**
90
+ - `set_layer_timing` (start, duration, track, playback_start) · `nudge_layers` (relative delta_start/delta_track) · `ripple_edit` (insert/close time at `at_time`, shifts downstream) · `trim_layer` (move one edge to `to_time`) · `set_layer_zindex` (front/back/forward/backward or explicit track — track == z-index).
91
+
92
+ **Geometry, media & style**
93
+ - `set_layer_visual` (x/y/width/height %, font_size, border_radius, **opacity** 0..1 — a constant dim/ghost level; a fade OVER TIME is `set_layer_keyframes`) · `set_layer_media` (swap src, volume, muted, playback_start, `object_fit`, `object_position` — **fit framing for aspect-mismatched media, see below** — `ken_burns` preset) · `set_layer_style` (color, background, background_style, font_family/weight, italic/underline, align, **line_height** unitless leading, **letter_spacing** px tracking — negative tightens, the lever for condensed all-caps / wide-tracked looks the presets can't reach) · `set_layer_text` (rewrite words) · `set_layer_identity` (slug/note — the stable AI-friendly handle).
94
+
95
+ **Composition-level (canvas / theme)**
96
+ - `set_composition` — edits the WHOLE composition (no `layer_key`): `canvas_width` + `canvas_height` resize the output frame in px (e.g. 1080×1920 vertical, 1080×1080 square, 1920×1080 landscape); `canvas_duration` retargets total render length in seconds; `background_color` recolors the canvas behind all layers. Writes the composition root (`data-width`/`data-height`/`data-duration` + a background color) per the hyperframes contract; static + seek-safe, reflected on the next `editor_context`. Use for "make it square/landscape", "black background", "make the whole thing 20s" — NOT a single layer's background (`set_layer_style`) or one clip's length (`set_layer_timing`).
97
+
98
+ ### Fitting imperfect-dimension media (aspect mismatch)
99
+
100
+ A clip/image whose **native aspect differs from the canvas** — a 16:9 landscape source on a 9:16 vertical frame is the classic case, also a tall screenshot on a wide frame, a square logo, etc. — will NOT look good on the default `object-fit:cover`, which fills the frame and **silently center-crops** the overflow (landscape → loses left/right, often cutting the subject in half; portrait → loses top/bottom). Choose the fit deliberately with `set_layer_media`:
101
+
102
+ - **`object_fit:"cover"`** (default) — fills + crops. The right pick for most social footage **when paired with a subject-aware `object_position`**.
103
+ - **`object_position`** — aims WHERE the cover-crop lands. Keywords (`center`, `left`, `right`, `top`, `bottom`, `"top left"`, `"bottom right"`, …) or precise **percentages** (`"30% 50%"` = hold the point 30% from the left, 50% down; `"50% 20%"` = keep the upper-middle). Landscape clip with the subject on the left → `object_position:"left"` (or `"25% 50%"`); tall screenshot with key text up top → `"top"`. Percentages survive round-trips, so use them for exact framing.
104
+ - **`object_fit:"contain"`** — shows the WHOLE media with black letterbox/pillarbox bars. Use ONLY when nothing may be cropped (an infographic, a whole screenshot, a chart, a logo). A 16:9 source full-canvas on a 9:16 frame in `contain` = big black bands top/bottom → usually reads as unfinished.
105
+ - **Blurred-letterbox band** (avoid ugly bars while showing the whole landscape clip): `set_layer_visual` to place it as a centered band (16:9 clip on 1080×1920 → `width:100, height:~34, y:~33`) with `object_fit:"contain"`, and add a DUPLICATE full-canvas copy on a **lower track** behind it at `object_fit:"cover"` with a heavy CSS blur as the backdrop.
106
+ - **`object_fit:"fill"`** — stretches/distorts; avoid unless intentional.
107
+
108
+ You usually already know a clip's aspect from how you sourced it (a hunted raw's `aspect`, a generated clip's requested `aspect_ratio`, the user's description); if you truly need exact dimensions, `GET /api/v1/primitives/videos/probe`. When you REPLACE a full-canvas scene (`is_full_canvas`/`is_timeline_proxy`), keep it full canvas and pick `cover` + a focal `object_position` rather than shrinking it. When you GENERATE media, request the canvas `aspect_ratio` so it needs no crop — this tuning is for existing/mismatched footage.
109
+
110
+ **Motion, transitions & captions** (declarative/CSS only — see below)
111
+ - `set_layer_keyframes` — a script-free CSS `@keyframes` on one layer (offset 0..1 stops with opacity/translate_x/translate_y/scale/rotate, easing, duration).
112
+ - `set_transitions` — whole-timeline entrances/exits in one call (junction preset + optional intro/outro); or `transition`/`transition_out` on a single incoming/outgoing clip via `set_layer_media`.
113
+ - `set_captions` — word-by-word animated captions (spotlight/karaoke/word-pop/stack-up/neon/bounce); REPLACES all caption layers. Feed cues from `video_context` segments, or transcribe via `POST /api/v1/primitives/audio/captions` first.
114
+
115
+ **Generate & export**
116
+ - `generate_layer` — submit an AI generate job, drop a placeholder in the target slot, auto-swap the finished media (no manual poll). `intent`: `fill_gap` (+ start/duration on a `timeline_gaps` span), `replace_layer` (+ `replace_layer_key` — timing/geometry copied), or `add`. Set `aspect_ratio` to the canvas.
117
+ - **Transparent overlays ("Vox-style") — `POST /api/v1/primitives/images/create-overlay`** via `http_request`. This is the go-to way to mint a floating illustration / prop / icon / cut-out character to animate over the video: one job generates an AI image on a forced flat key-color background and chroma-keys it out to a transparent PNG (`result.primary_file_url`). Body `{ tracer, payload: { prompt, aspect_ratio?, key_color?, prompt_attachments? } }` — describe only the subject (the background instruction is injected for you). Then `add_layer` the finished URL as an overlay image and animate it (`ken_burns`/`set_layer_keyframes`/`set_transitions`). Reach for it *often* when a scene wants a transparent graphic element rather than a full-frame still. Cheap-remove sibling for an image already on a flat background: `POST /api/v1/primitives/images/remove-background-greenscreen` (`key_color`/`tolerance`/`softness`); use RapidAPI `images/remove-background` only for messy-background photos.
118
+ - `export_composition` — same as the Export button; queues the render on AWS Lambda. The MP4 appears as `last_export_url` in a later `editor_context`; never fabricate the URL. **Confirm first — never auto-render.** Rendering is slow, costs money, and pops a status modal, so by default STOP after an edit, let the user review the live preview, and *ask* ("Happy with the preview? Render the final MP4?") before firing this. Only export unprompted when the user pre-authorized it ("edit and just render it"). A render started, then the user can keep working — multiple renders are tracked in the Viral DNA panel's "N Rendering…" badge and its render-history modal.
119
+ - `fork_composition` — branch a NEW editable copy and open it (only on an explicit user branch/duplicate/"save as" request — see Forking). `fork_from:"current"` keeps all current edits; `fork_from:"default"` starts fresh from the template's original default. Same as the ⋯ menu's **New fork** and `POST /api/v1/compositions/:forkId/fork { from }`.
120
+
121
+ ## Reading `editor_context` — the fields that matter
122
+
123
+ - `composition_id`, `composition_title`, `composition_origin`, `has_saved_fork`, `is_new_project` (project/fork state — see Forking). ⚠️ `composition_id` is the **template** id (`template_…`), NOT a fork.
124
+ - `fork_id` — the saved cloud fork (`fork_…`) for this session. This is the id every `/api/v1/compositions/:forkId/*` route and the `video_context`/`product_placement` tools require — pass `fork_id`, never `composition_id`. Absent only on a blank `is_new_project` before its first edit mints a fork.
125
+ - `composition_context` (the viral DNA — read before writing any copy).
126
+ - `editor_harness` (the **style-execution brief** — read before any nontrivial edit; see below).
127
+ - `composition_width`/`_height`/`aspect_ratio` (request matching aspect on generate so media fills the canvas), `composition_duration_seconds`.
128
+ - `layers[]` — each with `layer_key`/`element_id`/`slug`, `kind` (video/image/audio/caption/text), current `src`, `track` (= z-index), start/duration/geometry, current `object_fit`/`object_position` (the media's fit framing — read it before re-cropping an aspect-mismatched clip), `transition`/`transition_out`/`ken_burns`/`animation`, and `is_full_canvas`/`is_timeline_proxy` (a proxy renders 100%×100% even if shown tiny — trust the flag on replacement). **This is the ONLY valid source of layer keys.** To swap a clip's media, copy the EXACT `layer_key` (or `slug`) of the *video/image* layer from here — do not invent a semantic scene name (`growth_examples`, `mistakes_reflection`) unless that exact string is a key/slug in this array, and don't target a scene label/group when you mean the video inside it. If `set_layer_media` returns "No change applied" or "Layer not found", the error lists the real media layers — retry with one of those exact keys, and never report a swap done until the next `editor_context` shows the layer's `src` changed.
129
+ - `timeline_gaps[]` — precomputed blank/black spans; map a user's timestamp to the covering gap.
130
+ - `pending_generations[]` — AI gens still rendering (don't double-fire; report progress; `status:"error"` means it failed).
131
+ - `cast[]` — recurring people for character consistency (use `reference_url` on generate).
132
+ - `last_export_url`/`_status`/`_render_id` — the finished Export MP4 for approve/schedule.
133
+ - `save_status`/`save_error`/`is_read_only`, `recent_action_results[]` — the REAL outcome of your last turn; trust over echoed args.
134
+
135
+ ## The editor harness — your style-execution brief
136
+
137
+ `editor_context.editor_harness` is the decompose pass's **technical editing direction**: *how* to edit so the result recreates the source's STYLE. It is the "how to edit like this" companion to `composition_context`'s viral DNA ("why it works"). Read it before any re-theme, multi-scene rebuild, or restyle. Fields:
138
+
139
+ - `one_liner` — the style in one sentence; `format`, `aspect_ratio`.
140
+ - `pacing` — `cut_rhythm` (fast|medium|slow|varied), `avg_scene_seconds`, `cuts_per_10s`, `energy_curve`. Keep this rhythm when adding/splitting scenes.
141
+ - `typography` — `caption_style` (maps straight to a `set_captions` preset: karaoke/word-pop/spotlight/stack-up/neon/bounce), `placement`, `font_character`, `emphasis`, `text_density`.
142
+ - `transitions` — `default`/`intro`/`outro` (map to one `set_transitions` call), `usage`.
143
+ - `broll` — `reliance`, `sourcing`, `shot_kinds`, `cadence`. Drives whether you HUNT raws (`/clips/scan`) vs `generate_layer`, and at what cadence.
144
+ - `audio` — `voiceover`, `music`, `sfx`, `captions_from`.
145
+ - `scenes[]` — each beat's `role` + `importance` + `must_keep` + `edit_bias`. `important_scenes`, `editing_bias`, `do`, `dont`.
146
+
147
+ **How to use it:** map `caption_style` → `set_captions`, `transitions.*` → `set_transitions`, honor `pacing`, follow `broll` for footage sourcing, lay `audio`. **PROTECT** any beat in `important_scenes` or with `must_keep:true` / `importance:"critical"` — swap its subject if needed but preserve its timing, role, and caption cadence; that's where the style lives. Treat `editing_bias`/`do`/`dont` as hard constraints for this composition. It sets defaults, not a straitjacket — an explicit user instruction wins. **In local devcli** the same brief is on disk as `editor-harness.json` (under `.harness`, pulled by `vidfarm pull`/`sync`); read it there before a big edit. If `editor_harness` is absent, the fork wasn't decomposed with the harness pass — fall back to `composition_context` + the `video_context` tool.
148
+
149
+ ## Web-editor motion rule (hard constraint)
150
+
151
+ The editor strips every author `<script>` on save (stored-XSS defense), so the JS runtime adapters (anime.js/GSAP/Lottie/Three.js/WAAPI code) are **not** available here — they're local-devcli-only. Author all motion with the durable script-free vocabulary: preset actions (`ken_burns`, `set_transitions`, `set_captions`) and plain CSS `@keyframes` in a `<style>` block + inline `animation` (the renderer bakes CSS keyframes deterministically). Never claim you added a scripted animation in the web editor; offer the CSS/preset equivalent instead.
152
+
153
+ ## Cost awareness (bands)
154
+
155
+ (a) FREE — reuse a decomposed template, swap captions/images/existing MP4s, render on a local `vidfarm serve`. (b) ~cents — same reuse, cloud render. (c) ~$1 — AI-generate a few scenes. (d) $10+ — heavy AI generation / custom characters. Default to the cheapest approach that meets the goal. Image gen is cheap (use freely); the `create-overlay` and `remove-background-greenscreen` primitives are cheap too (a small platform fee — use freely for transparent overlays); **AI video gen and heavier wallet routes** (RapidAPI remove-background, videos/download, remove-captions, ingest) — surface cost and confirm before spending. Decompose is a one-time ~$0.10 per new source — prefer forking already-decomposed templates.
156
+
157
+ ## When to load a deeper pack
158
+
159
+ - `vidfarm-director` — the broad director playbook (full REST + devcli flows, raws/generate/approve/schedule end to end).
160
+ - `hyperframes-core` — the composition HTML contract (before a `replace_composition_html`).
161
+ - `hyperframes-animation` / `hyperframes-keyframes` — motion craft / seek-safe keyframe patterns (apply only the CSS half in the web editor).
162
+ - `hyperframes-creative` — palettes, typography, house style, narration craft.
163
+ - `embedded-captions` — caption identity catalog for a specific caption look.
164
+ - `vidfarm-media` — narration TTS/STT, voice-matched speech regeneration, BGM/SFX/media resolution.
165
+
166
+ Load only what the task needs; never bulk-load, and never paste large skill content back to the user.
@@ -0,0 +1,58 @@
1
+ # Re-theme walkthrough — book-recap template → "The Richest Man in Babylon"
2
+
3
+ A concrete end-to-end of the most common `/editor` intent: the user opens a decomposed book-recap template as a structural reference and says *"I want to make this for the book 'The Richest Man in Babylon'."* The template's viral DNA is proven; only the subject changes.
4
+
5
+ This is a **REPLACE on SCENES + AUDIO + TEXT**, preserving only the viral DNA (hook shape, scene-count rhythm, pacing, transition + caption style).
6
+
7
+ ## 0. Read state before touching anything
8
+
9
+ From the latest `<editor_context>`:
10
+ - `composition_origin: "inspiration"`, `has_saved_fork: false` → a real reference template, not blank. **The first edit auto-forks it into the user's own copy — no need to ask.**
11
+ - `composition_context` → the format/genre + arc (e.g. `hook`, `retention`, `payoff`, `preserve`, `avoid`). Read it so your new copy matches the book-recap voice (calm, authoritative narration; one lesson per scene) instead of generic ad copy.
12
+ - `layers[]` → count the scene clips and note each one's `start`/`duration`/`geometry`/`transition`/`ken_burns`, and which are caption layers. Say there are 6 scene clips + a hook caption + per-scene title captions.
13
+ - If the beats aren't clear from `composition_context`, call the `video_context` tool for the per-scene transcript + visual descriptions.
14
+
15
+ ## 1. Propose, don't interrogate
16
+
17
+ One message: the plan + only the questions you can't infer.
18
+
19
+ > "I'll keep this template's 6-scene structure, pacing and caption style and re-theme it to *The Richest Man in Babylon* — rewriting the narration around the book's core money lessons and swapping the visuals to match. A few things: any specific lessons you want featured (or I'll pick the strongest 6 — 'pay yourself first', 'control your expenses', 'make your gold multiply', 'guard against loss', 'own your home', 'insure a future income')? Keep the current narrator voice? Have a cover image, or should I generate the visuals?"
20
+
21
+ You can propose the six lessons from general knowledge to match the six scenes — that's inference, not interrogation. Wait for the couple of answers, then execute.
22
+
23
+ ## 2. TEXT axis — rewrite captions/titles in the template's voice
24
+
25
+ For each scene's title/caption layer, `set_layer_text` (or `set_captions` for the animated word-by-word layers) with the new lesson, keeping the caption animation and timing. Match the format: a book-recap reads like punchy lesson headlines + a spoken-narration cadence, not feature bullets.
26
+
27
+ - Hook caption → the book's hook ("The richest man in Babylon was once a poor scribe…").
28
+ - Scene 1 title → "1. Pay yourself first" … Scene 6 → "6. Insure a future income".
29
+
30
+ `set_captions` REPLACES all animated caption layers in one call — use it when you're restyling/rewriting the whole caption track at once; use `set_layer_text` for a single title layer.
31
+
32
+ ## 3. AUDIO axis — re-voice narration, same speaker
33
+
34
+ If the user wants to keep the narrator's voice, use `/api/v1/primitives/audio/regenerate-speech` with the source narration URL + the new per-scene script (`text`) — it approximates the original speaker (preset voices only; tell the user it approximates, not clones). Then for each scene: mute the original narration span (`set_layer_media` muted/volume, or `/videos/mute`) and `add_layer` (kind=audio) the new clip at the scene's start.
35
+
36
+ If they want a fresh voice, `POST /api/v1/primitives/audio/speech` with a matching `instructions` style prompt instead.
37
+
38
+ ## 4. SCENES axis — swap visuals, keep geometry + transitions
39
+
40
+ Fuel the footage in **cost order** (see the SKILL's "Fueling a SCENES replace"):
41
+ 1. `browse_files` search `/raws` and `/files` for anything on-topic the user already has (coins, ancient-city b-roll, money imagery).
42
+ 2. If they point at a long-form source (a lecture/podcast/YT URL on the book), **hunt** it: `POST /clips/scan { source_url, prompt: "ancient Babylon, coins, wealth, saving money", aspect: "9:16" }`, then reuse the picks.
43
+ 3. Only for scenes no real clip can cover, `generate_layer` (AI). Image gen is cheap; **confirm before AI video gen.**
44
+
45
+ Place each replacement with `generate_layer intent:"replace_layer"` + `replace_layer_key` (timing + full-canvas geometry copied automatically) or, for a ready URL, `set_layer_media` in place. Keep every clip's `start`/`duration`/`transition`/`transition_out`/`ken_burns` — that's the DNA you're preserving.
46
+
47
+ ## 5. Verify, then export
48
+
49
+ After each turn, confirm the change is reflected in the NEXT `<editor_context>` (new text on the layer, `recent_action_results` all `ok:true`, no `save_status:"error"`). Don't claim success off the echoed tool args.
50
+
51
+ When the timeline is fully re-themed, `editor_action action_type=export_composition`. The finished MP4 appears as `last_export_url` in a later context — use it to approve/share when the user asks.
52
+
53
+ ## The shape to internalize
54
+
55
+ - **Preserve:** scene count, per-clip timing, transitions, caption animation, aspect ratio, narration cadence — the viral DNA.
56
+ - **Replace:** every word (TEXT), the narration script (AUDIO), every visual (SCENES).
57
+ - **Forking:** automatic on the first edit; you never create a fork by hand.
58
+ - **Blank exception:** if `is_new_project:true` (`composition_id:"original"`) there's no template to re-theme — use the `create_video` tool to mint one first, then re-theme the result.
@@ -55,6 +55,8 @@ Contract points:
55
55
  - Word timings are real (local whisper.cpp) when possible, else estimated by proportional character weighting. Timings never fail the run.
56
56
  - BGM is **not** loop-extended by the engine — the workflow assemblers loop-extend short beds to the video length at mount time. `bgm_pending` is never set; `scripts/wait-bgm.mjs` exists as an exit-0 shim.
57
57
  - Mount the results per `hyperframes-core`: each `voices[].path` and (`bgm.path`, `sfx[]`) as `<audio>` tracks; `voices[].words` drive captions.
58
+ - **Multi-track mixing is native — keep voice, music, and SFX on SEPARATE `<audio>` layers, each at its own `data-volume`** (do NOT pre-mix them into one file). The runtime and both render paths mix all audio layers together with per-track volume honored (a real ffmpeg `amix` at render). Standard balance: **narration/voice ~1.0, BGM ~0.1–0.2** under the voice, SFX per-item (the engine already emits `sfx[].volume`, e.g. `0.35`). This is the whole reason each stem is a distinct track — the user (or a later edit) can re-level any track independently.
59
+ - **De-combining an original on recreate.** When you're recreating a video whose original had music + narration baked into ONE track, don't try to reproduce that single bed — you cannot stem-separate baked audio. Instead build the mix fresh: a new narration track (`tts` / `regenerate-speech`) at ~1.0 **plus** a real music file (owned / user-provided / found via `vidfarm directory search "<query> music"`) at ~0.1–0.2, as two separate `<audio>` layers, and drop the original combined track. Never fake a music layer or duplicate the voice track to stand in for music.
58
60
 
59
61
  ## TTS / STT providers
60
62