@aicopycoders/exodus 2026.9.2000-beta.0 → 2026.9.2000-beta.10

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: exodus-video
3
- description: Make a video ad with Exodus from the terminal. The main path is script-first: run a saved video workflow with `npx @aicopycoders/exodus workflow run`, no Show involved. Starting from a Show with `exodus video start --show` is the other way in. Either way you read and approve the storyboard, pull every finished piece (per-scene clips, the narration track and per-scene voice, keyframes, cast identity stills, word timings, the music bed) into a folder with `exodus video pull`, and upload a finished cut for approval. Exodus makes the pieces; the cut is made outside it, in whatever editor the user wants — this skill hands over the pieces and the facts about them, it does not prescribe an editor. Use it whenever the user has invoked Exodus and wants anything to do with a video ad, an ad run's clips or storyboard, a Show, "pull the pieces", "stitch it together", "make the cut", "upload my cut", or checking on a video run ("exodus, make a video ad from this script", "exodus, is my video run done", "exodus, pull the clips for run X and cut them", "exodus, what's wrong with scene 3", "exodus video status"). Also use it when the user ran an `npx @aicopycoders/exodus video` command or the `exodus` hub skill routed here. Video is admin-only: "video isn't enabled for this key" means either the dashboard user lacks the admin role or the run id is wrong, so check the id and then say so; do not retry. Never claim generic video-editing asks ("edit this mp4", "add captions to my reel") without Exodus context; in shared folders those belong to the user's other tools. Running or authoring workflows generally is `exodus-workflow`; static image ads are `exodus-image`; copy is `exodus-write`.
3
+ description: Make a video ad with Exodus from the terminal. The main path is script-first: run a saved video workflow with `npx @aicopycoders/exodus workflow run`. You read and approve the storyboard, pull every finished piece (per-scene clips, the narration track and per-scene voice, keyframes, cast identity stills, word timings, the music bed) into a folder with `exodus video pull`, and upload a finished cut for approval. Exodus makes the pieces; the cut is made outside it, in whatever editor the user wants — this skill hands over the pieces and the facts about them, it does not prescribe an editor. Use it whenever the user has invoked Exodus and wants anything to do with a video ad, an ad run's clips or storyboard, "pull the pieces", "stitch it together", "make the cut", "upload my cut", or checking on a video run ("exodus, make a video ad from this script", "exodus, is my video run done", "exodus, pull the clips for run X and cut them", "exodus, what's wrong with scene 3", "exodus video status"). Also use it when the user ran an `npx @aicopycoders/exodus video` command or the `exodus` hub skill routed here. Video is admin-only: "video isn't enabled for this key" means either the dashboard user lacks the admin role or the run id is wrong, so check the id and then say so; do not retry. Never claim generic video-editing asks ("edit this mp4", "add captions to my reel") without Exodus context; in shared folders those belong to the user's other tools. Running or authoring workflows generally is `exodus-workflow`; static image ads are `exodus-image`; copy is `exodus-write`.
4
4
  ---
5
5
 
6
6
  # Video: make the pieces, cut the ad, hand it back
@@ -21,29 +21,33 @@ Your job in this skill is to drive that loop, hand over the pieces with the
21
21
  facts about them, and make whatever cut the user asked for. **How to edit is the
22
22
  user's call, not this skill's** — see "Cutting the ad".
23
23
 
24
- ## Two routes in, one set of pieces out
24
+ This file ships on the member's machine. It holds commands, the manifest's
25
+ shape, and recipes for the cut. It does not hold prompt text, house primers,
26
+ judge wording, or model instructions.
25
27
 
26
- **The workflow route (no Show) is the main path.** The ad is a saved workflow in
27
- the brand, started script-first with `exodus workflow run`. This is where the
28
- real work happens and what to assume unless the user says otherwise.
28
+ ## How a run starts
29
29
 
30
- **The Show route** starts from a locked Show — its cast, rooms and voices set up
31
- on the dashboard beforehand — with `exodus video start --show`. It buys two
32
- things the workflow route does not have: `video flag` for sending a storyboard
33
- back with a note, and script-turn edits at the gate.
30
+ The ad is a saved workflow in the brand, started script-first with `exodus
31
+ workflow run`. From there the same nodes render, `video status` reads the run,
32
+ `video pull` writes the folder, and `video upload` and `approve` finish it.
34
33
 
35
- Everything after the storyboard is the same on both: the same nodes render, the
36
- same `video status` reads the run, the same `video pull` writes the same folder,
37
- the same `video upload` and `approve` finish it. Where a behaviour differs, this
38
- skill says which route it belongs to. **Do not assume the routes are symmetric**
39
- — see "What each route can and cannot do".
34
+ A Show is no longer required. `exodus video start` starts from a script, a
35
+ conceit, and a style, and the same parks follow. The full line is under
36
+ "Starting a run". An optional `--direction` note is whole-ad rules, and that
37
+ note cannot contain a colon. "The script file" shows the right form and the
38
+ wrong one.
39
+
40
+ The Rig box on that workflow is where the run's Conceit and Style come from.
41
+ Conceit is the family plus Wrapper, Narrator Person, and Delivery. Style is the
42
+ look. Narrated Story is the voice-over family and Acted Story is the dialogue
43
+ family (`familyTree.ts`). Those family names stay the code's names.
40
44
 
41
45
  ## Before anything
42
46
 
43
47
  - `npx @aicopycoders/exodus video --help` and `workflow --help` are the
44
48
  authoritative flag lists. The `video` verb is admin-only and hidden from the
45
49
  top-level `--help`; that is expected, not a broken install.
46
- - **Video is admin-only on both routes**, and says so two different ways.
50
+ - **Video is admin-only**, and says so two different ways.
47
51
  - "video isn't enabled for this key", from a `video` verb, has two causes the
48
52
  CLI cannot tell apart: the user is not an admin on this brand, or the run id
49
53
  does not exist. Check the id first; if it is right, they need the admin role.
@@ -58,17 +62,17 @@ skill says which route it belongs to. **Do not assume the routes are symmetric**
58
62
  you must pass `--duration <sec>`. Editing tools have their own requirements on
59
63
  top of that — the bundled script needs a full `ffmpeg` with `libx264` and
60
64
  exits 2 if it is missing.
61
- - The brand matters. A workflow and a Show both belong to one brand, so
62
- `exodus workflow list` and `exodus video shows` show the active brand's only.
63
- Wrong list means wrong brand; `exodus brand use <slug>`.
65
+ - The brand matters. A workflow belongs to one brand, so `exodus workflow list`
66
+ shows the active brand's only. Wrong list means wrong brand; `exodus brand use
67
+ <slug>`.
64
68
 
65
69
  ## The loop
66
70
 
67
71
  Run each line as `npx @aicopycoders/exodus …`; `exodus` below is shorthand.
68
72
 
69
- **Starting on the workflow route (the main path).** The workflow is already
70
- saved in the brand — find it and read what it wants before you run it, because
71
- the script's input field is named by the workflow, not by this skill.
73
+ **Starting a run.** The workflow is already saved in the brand — find it and
74
+ read what it wants before you run it, because the script's input field is named
75
+ by the workflow, not by this skill.
72
76
 
73
77
  ```operator-guide
74
78
  exodus workflow list the brand's workflows
@@ -80,23 +84,20 @@ exodus workflow run <workflowId|name> --input <field>=@script.txt --voices @voic
80
84
  exodus workflow run <workflowId|name> --input <field>=@script.txt --voice-treatment @voice-treatment.json --wait
81
85
  same, with the video model speaking voices you WROTE
82
86
  exodus video status <runId> where it is, per scene
87
+ exodus video start --script <file> --conceit <podcast|ugc|stage|street|personification> --style <slug> [--direction "…"] [--voice-path <path>] [--video-model <id>] [--voice native] [--music] [--wait]
88
+ start from a script, a conceit and a look.
89
+ No music bed unless you pass --music.
90
+ Each conceit has its own video model and voice;
91
+ --video-model / --voice override them for one run
83
92
  ```
84
93
 
85
- **Starting on the Show route.**
86
-
87
- ```operator-guide
88
- exodus video shows which Shows are ready
89
- exodus video start --show <id> --script script.txt --wait start, wait for the storyboard gate
90
- ```
91
-
92
- **From the storyboard on, both routes are the same.**
94
+ **From the storyboard on.**
93
95
 
94
96
  ```operator-guide
95
97
  exodus video storyboard <runId> read the scene cards
96
98
  exodus video voices <runId> who speaks and with whose voice
97
99
  exodus video voices <runId> --set <character>=<voiceId> give a character a voice
98
100
  exodus video approve <runId> keep going
99
- exodus video flag <runId> --note "what's wrong" send it back — SHOW RUNS ONLY
100
101
  exodus video retry-frame <runId> --node <nodeId> --scene <n> redo one still at the pixel gate
101
102
  exodus video status <runId> where it is, per scene
102
103
  exodus video pull <runId> --out ./ad-<runId> every piece + manifest.json
@@ -111,31 +112,48 @@ exodus video approve <runId> make it the ad
111
112
  Every command takes `--json`. Read `--json` when you need to decide something
112
113
  from the output; read the plain form when you are relaying it to the user.
113
114
 
114
- **Read a run's position with `exodus video status`, on either route.** It is the
115
- richest reader — every scene's clip, voice and picture, with the findings. A
116
- `--wait` that landed already printed the same stop block, and `workflow inbox`
117
- lists a parked video run (its row carries `hasShow`), but neither shows the
118
- per-scene detail, and the checkpoint verbs cannot resolve a video park — see
119
- "What each route can and cannot do".
120
-
121
- **Where a run can stop.** Five stops, each with the exact next command printed
115
+ **Read a run's position with `exodus video status`.** It is the richest reader —
116
+ every scene's clip, voice and picture, with the findings. A `--wait` that landed
117
+ already printed the same stop block, and `workflow inbox` lists a parked video
118
+ run, but neither shows the per-scene detail, and the checkpoint verbs cannot
119
+ resolve a video park — see "What the CLI can and cannot do".
120
+
121
+ **Where a run can stop.** The two video parks come in order when the run has
122
+ both. The storyboard gate is first. You read the cards there and you set the
123
+ voices there. Approving that gate releases the voices and the clips. That is the
124
+ expensive part. The final watch is next, after the pieces exist, and only when
125
+ the video node sets `finalWatch: true`. That park is for the cut. You pull, you
126
+ cut, and you upload. Approving the upload makes it the ad. A node with
127
+ `finalWatch` off skips the second park and the run finishes.
128
+
129
+ Five stops, each with the exact next command printed
122
130
  under it:
123
131
 
124
- - storyboard gate: needs `approve`, or `flag` on a Show run.
125
- - final watch: every piece is made; needs `pull`, a cut, and `upload`. It is also
126
- the one park where a finished clip can be redone, with `retry-clip`. A
127
- workflow run only reaches this park if its video node sets `finalWatch: true`
128
- — otherwise it skips straight to finished.
132
+ - storyboard gate: needs `approve`. When the stop block prints a `Redo one
133
+ frame:` line with a node id, one still can be redone first with `retry-frame`;
134
+ a gate parked before the stills are drawn has nothing to redraw.
135
+ - final watch: every piece is made; needs `pull`, a cut, and `upload`. Once a
136
+ cut is uploaded the stop block leads with `approve` instead, and `status
137
+ --json` says which state it is in (`stop.cutAttached`, plus the same next-step
138
+ lines under `guidance`). It is also
139
+ the one park where a finished clip can be redone, with `retry-clip`. A run
140
+ only reaches this park if its video node sets `finalWatch: true` — otherwise
141
+ it skips straight to finished.
129
142
  - paused: a builder checkpoint neither video verb resolves —
130
143
  `exodus workflow checkpoint <runId>` is the verb for that one.
131
144
  - failed: `status` shows how far it got, and a `--wait` that was running exits
132
145
  1 with the error. Tell the user; do not start another run on your own.
146
+ When the storyboard is the step that failed, `status` adds a "What happened
147
+ to the storyboard" block: which parts of the plan were accepted, which part
148
+ was turned down, and why (#1902). Relay it as written. On an admin key it may
149
+ also offer `status <runId> --rejected-draft`. That prints the draft the system
150
+ threw away. It is a record only: never build on it, edit it, or re-run it.
133
151
  - finished: pull what it made.
134
152
 
135
- **`--wait` stops at both video parks on either route** (#1818). `workflow run
136
- --wait` and `video start --wait` both end at the storyboard gate and again at
137
- the final watch, exit 0, and close with the same `Parked:` block and next
138
- commands that `exodus video status` prints. Under `--json` the line carries an
153
+ **`--wait` stops at both video parks** (#1818). `workflow run --wait` ends at
154
+ the storyboard gate and again at the final watch, exits 0, and closes with the
155
+ same `Parked:` block and next commands that `exodus video status` prints. Under
156
+ `--json` the line carries an
139
157
  additive `stop` field in the same shape `video status --json` uses, so branch on
140
158
  `stop.at` rather than re-deriving the park. Every other park still prints its
141
159
  notice once and keeps polling, so a resolve from another shell carries the run
@@ -155,10 +173,10 @@ part: the voices and the clips. So a run that has not paused yet is already
155
173
  spending a little, and a run still drawing pictures is healthy. `exodus workflow
156
174
  status` and `exodus video status` both print "This run will pause for your
157
175
  approval once the frames are ready" on a run that is headed for the gate. Do not
158
- cancel it because the pause has not arrived. `shows`, `workflow list`, `workflow describe`, `video status`,
176
+ cancel it because the pause has not arrived. `workflow list`, `workflow describe`, `video status`,
159
177
  `storyboard` and `pull` are free reads; run them as often as you like. Never
160
- start a second run to "retry" without the user asking; flag the storyboard (Show
161
- runs) or tell them what failed instead.
178
+ start a second run to "retry" without the user asking; tell them what failed
179
+ instead.
162
180
 
163
181
  **`--auto-approve` does not cover either video gate.** It releases Checkpoint
164
182
  boxes only (`convex/workflows.ts:7266` keys on `pauseReason === "checkpoint"`),
@@ -167,30 +185,11 @@ and the storyboard gate and the final watch carry no pause reason. An unattended
167
185
  safe behaviour, since nothing should approve paid rendering or ship an uncut ad
168
186
  on its own.
169
187
 
170
- ## What each route can and cannot do
171
-
172
- Verified against the routes, not assumed. Everything here is admin-gated on top.
173
-
174
- | Verb | Workflow run (no Show) | Show run |
175
- |---|---|---|
176
- | `video shows` | nothing to list | lists the brand's Shows |
177
- | `video start --show` | not the entry point | starts the run |
178
- | `workflow run` | starts the run | not the entry point |
179
- | `video status` | works | works |
180
- | `video storyboard` | works | works |
181
- | `video voices` (read) | works | works |
182
- | `video voices --set` | works | **refused** — the Show owns its voices |
183
- | `video approve` | works, both gates | works, both gates |
184
- | `video flag --note` | **refused** | works |
185
- | script-turn edits at the gate | **refused** | dashboard only |
186
- | `video retry-frame` | works | works |
187
- | `video retry-clip` | works | works |
188
- | `video revoice` | works | works |
189
- | `video pull` | works | works |
190
- | `video upload` | only if the video node sets `finalWatch: true` | works |
191
- | `workflow checkpoint approve` | **refused at a video gate** | **refused at a video gate** |
192
-
193
- Five consequences worth knowing before you promise a user anything:
188
+ ## What the CLI can and cannot do
189
+
190
+ Verified against the CLI and the server, not assumed. Everything here is
191
+ admin-gated on top. Five things worth knowing before you promise a user
192
+ anything:
194
193
 
195
194
  - **A workflow only stops for your cut if its video node asks to.** `finalWatch`
196
195
  defaults to `false` (`convex/lib/workflow/catalog.ts:780`), and the run only
@@ -210,22 +209,20 @@ Five consequences worth knowing before you promise a user anything:
210
209
  `pauseReason === "checkpoint"` (`exodus/commands/workflow.ts:4487-4519`), and
211
210
  both video gates park with no pause reason at all, so those verbs refuse
212
211
  client-side with "Run … is not parked for a checkpoint approval — it is parked
213
- at the cost gate (legacy)." `exodus video approve` posts straight through and
214
- works on either route. `exodus workflow cancel` also works at a video gate; it
215
- has no preflight.
216
-
217
- - **`flag` is Show-only and fails loudly.** `exodus video flag` on a workflow run
218
- answers "Flag-with-note is only available on Video-module ads."
219
- (`convex/videoModule.ts`, guarded on `!run.showId`). Script-turn edits refuse
220
- the same way. There is **no** CLI path that sends a workflow run's storyboard
221
- back with a note, and none that rewrites its scenes, script or prompts — the
222
- whole-envelope edit mutation is wired only to the dashboard's gate screen, not
223
- to any API route. The one exception is voices: `exodus video voices --set`
224
- writes the cast's voice choices in place and nothing else (see "Giving the
225
- characters voices"). For everything else the storyboard is approve-or-cancel
226
- from the terminal: `exodus workflow cancel <runId>` and run again with a better
227
- script, or edit the workflow. Do not offer `flag` to a user on the workflow
228
- route.
212
+ at the cost gate (legacy)." `exodus video approve` posts straight through.
213
+ `exodus workflow cancel` also works at a video gate; it has no preflight.
214
+
215
+ - **Nothing sends the storyboard back with a note.** There is **no** CLI path
216
+ that returns a run's storyboard for a rewrite, and none that rewrites its
217
+ scenes, script or prompts — the whole-envelope edit mutation is wired only to
218
+ the dashboard's gate screen, not to any API route. The one exception is
219
+ voices: `exodus video voices --set` writes the cast's voice choices in place
220
+ and nothing else (see "Giving the characters voices"). A single still can be
221
+ redrawn with `exodus video retry-frame`. For everything else the storyboard is
222
+ approve-or-cancel from the terminal: `exodus workflow cancel <runId>` and run
223
+ again with a better script, or edit the workflow.
224
+ The stop block a parked run prints may still offer a `Send back:` line. A
225
+ workflow ad refuses it, so never run it and never offer it to the user.
229
226
  - **`--wait` lands on both video parks, but only those.** Since #1818 every
230
227
  `--wait` caller — `workflow run`, `triggers fire`, `checkpoint approve|retry`,
231
228
  `repair retry|skip` — ends at a storyboard gate or a final watch, exits 0, and
@@ -234,23 +231,50 @@ Five consequences worth knowing before you promise a user anything:
234
231
  slots park is still not a landing: it prints its notice once and the loop
235
232
  keeps polling, so a resolve from another shell carries the run on. Do not read
236
233
  a park banner mid-poll as the wait having finished.
237
- - **The run's page link differs, and every link the CLI prints follows the run.**
238
- A Show ad opens on the `/video` page. A showless workflow run opens on
239
- `/workflows/<workflowId>/runs/<runId>`, because `/video` answers "That ad isn't
240
- here" for a run with no Show (`getAdDetail` rejects it;
241
- `convex/http.ts:9204-9211`). Since #1851 the manifest's `dashboardUrl`, the
242
- link in every park block and the link `upload` prints are all minted that way,
243
- so they agree on both routes. When all the CLI holds is a run id it mints
244
- `/runs/<runId>`, the canonical forwarder, which lands on the same page.
234
+ - **Every link the CLI prints follows the run.** A run opens on
235
+ `/workflows/<workflowId>/runs/<runId>`. Since #1851 the manifest's
236
+ `dashboardUrl`, the link in every park block and the link `upload` prints are
237
+ all minted that way, so they agree. When all the CLI holds is a run id it
238
+ mints `/runs/<runId>`, the canonical forwarder, which lands on the same page.
245
239
 
246
240
  ## The script file
247
241
 
248
- Plain text. A `CAST:` block naming the speakers, one line per turn, then a
249
- `CTA:` block. On a Show run the names must match what the Show locked. The
250
- planner sizes every line at
251
- 2.5 words per second (`WORD_LADDER_WPS`), so a turn near 14 words plans a clip
252
- near 6 seconds. If a line does get truncated in its clip, the manifest says so
253
- (`speech-cutoff`).
242
+ Plain text. An optional `DIRECTION:` block may open the file, before `CAST:` or
243
+ the first spoken line. Then a `CAST:` block naming the speakers, one line per
244
+ turn, then a `CTA:` block.
245
+
246
+ `DIRECTION:` carries whole-ad rules and no spoken words. The header is its own
247
+ line. The lines under it are the rules. The block ends at a blank line, at
248
+ `CAST:`, at `CTA:`, or at the first line that names a speaker. A second
249
+ `DIRECTION:` block is refused. So is a `DIRECTION:` after the first spoken line.
250
+ An empty block is refused too. The error code is `malformed-direction`.
251
+
252
+ Do not put a colon inside direction prose. The reader splits a line at its first
253
+ colon to find a speaker, so a colon in that prose is read as a name.
254
+
255
+ Right.
256
+
257
+ ```
258
+ DIRECTION:
259
+ Nobody looks at the camera. The product stays on the table until the last line.
260
+ ```
261
+
262
+ Wrong.
263
+
264
+ ```
265
+ DIRECTION:
266
+ Rule: nobody looks at the camera.
267
+ ```
268
+
269
+ The wrong line is not direction. `Rule` is read as the speaker, and the
270
+ block above it is empty, so the file is refused.
271
+
272
+ The planner sizes every line at 2.5 words per second (`WORD_LADDER_WPS`), so a
273
+ turn near 14 words plans a clip near 6 seconds. If a line does get truncated in
274
+ its clip, the manifest says so (`speech-cutoff`). That finding means the clip
275
+ cut the words off. Redo the clip. Do not speed the speech or cut it short to
276
+ hide the finding. The product rule is under "What the product actually
277
+ requires".
254
278
 
255
279
  ```
256
280
  CAST:
@@ -264,6 +288,15 @@ CTA:
264
288
  THE SOFTGEL: Tap below and give your eyes ClearBlink.
265
289
  ```
266
290
 
291
+ The dash in a `CAST:` line is the parser's own separator. The example above
292
+ already shows that dash. A cast line may separate the name from the description
293
+ with an em dash, an en dash, or a hyphen with a space on each side. Keep that
294
+ dash on cast lines. A reader who strips it makes the line fail.
295
+
296
+ Do not use a long dash in words a character says, or in words shown on screen.
297
+ Synthesised speech reads a long dash oddly, and so does burned-in text. A spoken
298
+ line stays `NAME: what they say`.
299
+
267
300
  **How many speakers you may have depends on the rig.** Three by default. A rig
268
301
  that carries saved format rules sets its own number: an ensemble rig seats more,
269
302
  a talking-head rig seats one. Over the number, the launch refuses before
@@ -273,34 +306,36 @@ in `manifest.json` under `provenance`.
273
306
 
274
307
  ## Reading the storyboard gate
275
308
 
276
- `storyboard <runId>` prints one card per scene on both routes: who speaks, the
309
+ `storyboard <runId>` prints one card per scene: who speaks, the
277
310
  line, the planned duration, and whether its picture is drawn. Under the cards it
278
311
  also lists the voices — one line per character who speaks or already has a voice
279
312
  — so the user can see who sounds like whom before paying for the clips. Read it
280
313
  for the user, then ask one question.
281
314
 
282
- On a **workflow run** the question is approve or don't — there is no flag. If
283
- the storyboard is wrong, say so plainly and let the user decide between
284
- approving anyway, cancelling (`exodus workflow cancel <runId>`) and re-running
285
- with a better script, or changing the workflow. Approving is what releases the
286
- voices and the clips — the expensive part — so do not approve a storyboard you
287
- have just told the user is wrong.
315
+ The question is approve or don't. If the storyboard is wrong, say so plainly and
316
+ let the user decide between approving anyway, redrawing a single still when
317
+ the stop block offers it (`exodus video retry-frame <runId> --node <nodeId>
318
+ --scene <n>`, with the node id that block prints), cancelling
319
+ (`exodus workflow cancel <runId>`) and re-running with a better script, or
320
+ changing the workflow. Approving is what releases the voices and the clips — the
321
+ expensive part — so do not approve a storyboard you have just told the user is
322
+ wrong.
288
323
 
289
- On a **Show run** you can also send it back: `video flag <runId> --note "…"`.
290
- The note is read by the model that rewrites the storyboard, so make it concrete
291
- ("scene 2 should be a close-up of the softgel, not the eyes").
292
-
293
- Either way, do not approve on the user's behalf unless they told you to run the
294
- whole loop unattended.
324
+ Do not approve on the user's behalf unless they told you to run the whole loop
325
+ unattended.
295
326
 
296
327
  ## Giving the characters voices
297
328
 
298
329
  By default a workflow run's characters have no voice of their own. A character
299
330
  without one gets the run's default voice (the workflow's narrator voice) when the
300
331
  run has one; with no default either, the clip keeps the voice the video model
301
- invents. `exodus video voices` shows which of the three applies and is where it
302
- gets fixed, and it only works **before** the storyboard is approved — once clips start
303
- it refuses.
332
+ invents. `exodus video voices` shows which of the three applies, and it is where you
333
+ fix the mapping. Fix it before you approve the storyboard gate. Approving that
334
+ gate locks the mapping, and `exodus video voices` refuses afterwards. On a
335
+ finished clip whose voice was never applied, the later command is `exodus video
336
+ revoice`, and that pass bills again. A clip that already went through the voice
337
+ pass is refused there. Use `retry-clip` for that one. See "Redoing only the
338
+ voice on finished clips".
304
339
 
305
340
  ```
306
341
  exodus video voices <runId>
@@ -374,7 +409,7 @@ nobody knows the speaker names yet. When the storyboard arrives, the voices are
374
409
  already set and `exodus video voices <runId>` shows them.
375
410
 
376
411
  **Choosing how the voices are made.** `--voice-treatment` on `workflow run` says
377
- HOW a run makes its voices, and it is the only door on the workflow route. Two
412
+ HOW a run makes its voices, and it is the only way. Two
378
413
  kinds of answer.
379
414
 
380
415
  A name on its own picks a way of working that plays a voice you already have,
@@ -447,8 +482,8 @@ person made at a keyboard for one run, and nobody made it for the sub-workflow.
447
482
  **Written voices cannot be changed at the review.** They are chosen when the run
448
483
  starts. `exodus video voices <runId>` on a described run names the treatment and
449
484
  prints each written voice, and says plainly that `--voice-treatment` at launch is
450
- what changes them. Do not tell anyone to create a Show for this, and do not set a
451
- voice path in node config — neither is a door.
485
+ what changes them. Do not set a voice path in node config. Node config does
486
+ not set the treatment.
452
487
 
453
488
  **Where it shows up afterwards.** The launch receipt and `exodus workflow status`
454
489
  both print one line naming the treatment and the speakers who got a written
@@ -524,10 +559,17 @@ manifest.json the index below
524
559
 
525
560
  ```
526
561
  runId, pulledAt, dashboardUrl the run and the page that opens it:
527
- /video?ad=<runId> for a Show ad,
528
- /workflows/<workflowId>/runs/<runId> for a
529
- workflow run
562
+ /workflows/<workflowId>/runs/<runId>
530
563
  storyboard, reference, music filenames, or null when not delivered
564
+ musicBed on | off | unknown — whether the RUN has a
565
+ music bed, which is not the same question
566
+ as the `music` filename above. "unknown" is
567
+ a run made before the run carried the
568
+ answer; it is not "off"
569
+ musicHeardScenes[] scene numbers whose clip came back with
570
+ music the video model baked in. A run with
571
+ the bed off can still list scenes here, and
572
+ those are the ones to listen to
531
573
  narration { file, timing }, or null on a per-scene run
532
574
  cast[] one per identity still
533
575
  characterId, name from the run's cast lock; null for an
@@ -564,8 +606,7 @@ provenance which saved rulebook this run followed:
564
606
  {format: {via, nodeId, rigId, rigName,
565
607
  rulesFrom, specVersion}}. Absent when the
566
608
  run followed none. `nodeId` is the Rig box
567
- that chose it (absent on a Show ad, which
568
- has no box); `rulesFrom` names the rig the
609
+ that chose it; `rulesFrom` names the rig the
569
610
  rules were borrowed from when the picked
570
611
  rig is a copy. Names, ids and a version
571
612
  only — never the rules themselves
@@ -588,12 +629,11 @@ it is a picture-only takeover of the frame while the spoken track underneath
588
629
  keeps running, so it adds no audio and takes no place in the running order. The
589
630
  envelope also carries `overlays[]` (each with a `cueLineId` and a `placement`)
590
631
  and `audio` (`roomTone`, `music`, `sfxCues[]`) — the planner's notes on what
591
- should sit on top. **Route difference:** on a Show run the image and motion
592
- prompts (`framePrompt`, `videoPrompt`, `referencePrompt`) are stripped before
593
- the file reaches you, for everyone including admins — they are style-pack IP
594
- (`convex/workflows.ts:13847`, `isModuleRun = !!run.showId`). A workflow run has
595
- no `showId`, so an admin key gets them in full. Their absence on a Show run is
596
- by design, not a broken pull.
632
+ should sit on top. The image and motion prompts (`framePrompt`, `videoPrompt`,
633
+ `referencePrompt`) reach an admin key in full. A caller who cannot use video on
634
+ this brand gets them stripped before the file arrives. They are private Style
635
+ fragment IP (`redactVideoIp` in `convex/workflows.ts`). Style pack is the
636
+ retired name.
597
637
 
598
638
  **The narration track** (`narration.mp3` + `narration.json`). Present when the
599
639
  run recorded continuously: every narrated scene's `voText` joined into one
@@ -605,8 +645,7 @@ narration.mp3**. Runs go per-scene instead when any scene has on-camera
605
645
  dialogue, when the voiceover node is configured `take: "per-scene"`, or on an
606
646
  older storyboard carrying no `script[]`; then there is no `narration.mp3` and
607
647
  each scene's voice is its own recording. That choice is made from the storyboard
608
- and the node config (`planNarration`), not from the route — a workflow run and a
609
- Show run reach it the same way.
648
+ and the node config (`planNarration`).
610
649
 
611
650
  **The per-scene voice tracks** (`scene-NN.voice.<ext>`). On a continuous run
612
651
  these are cut out of `narration.mp3` itself — the same audio, sliced at the
@@ -621,10 +660,9 @@ speech inside the clip — under the default voice treatment the video model's
621
660
  invented voice is then replaced, in place, with the cast member's pinned
622
661
  ElevenLabs voice (that is `revoiced: true`). Other treatments leave the render
623
662
  owning its own audio, e.g. lip-sync retargeting a VO track onto the video. The
624
- treatment is frozen on the run when it starts, and there are exactly two doors
625
- that set one: `--voice-path` on `video start` for a Show run, and
626
- `--voice-treatment` on `workflow run` for a workflow run (see **Choosing how the
627
- voices are made**). Node config is never a door — nothing on the canvas sets a
663
+ treatment is frozen on the run when it starts, and there is exactly one flag
664
+ that sets it: `--voice-treatment` on `workflow run` (see **Choosing how the
665
+ voices are made**). Node config never sets it. Nothing on the canvas sets a
628
666
  voice treatment, and a run that was given none renders the default. Read
629
667
  `wordsFrom` and `revoiced` per scene rather than assuming.
630
668
 
@@ -661,8 +699,8 @@ finding says `wrong-character`.
661
699
  generated by Eleven Music after the last clip lands, at the landed clips' summed
662
700
  length plus a 3-second safety pad. One track per ad, never per-scene. It is
663
701
  never mixed into any clip and never levelled — gain, fade and ducking are the
664
- cut-maker's decisions. `--no-music` on `start` means no artifact at all, and a
665
- music failure degrades to a warning rather than blocking the run.
702
+ cut-maker's decisions. A music failure degrades to a warning rather than
703
+ blocking the run.
666
704
 
667
705
  ### Reading it before you cut
668
706
 
@@ -686,6 +724,11 @@ music failure degrades to a warning rather than blocking the run.
686
724
  A `speech-cutoff` on the CTA scene is the one to worry about; the ad's last
687
725
  words are missing. `status` shows the checker's own wording behind each
688
726
  finding; the manifest carries only the verdict.
727
+ - A `pass` verdict is not a review. The checks are often optimistic. Watch the
728
+ frames and listen to the audio before you call the run good. Two checks fail
729
+ the wrong way. A cast-count check can reject a silent
730
+ listener who belongs in the shot. A clip check can flag a clip that is fine.
731
+ Read the finding, then watch the scene.
689
732
  - `clipStatus: "failed"` or `"missing"` with a `keyframe`: `durationSec` is null
690
733
  (it comes from the clip), but the scene can still hold its place as a still —
691
734
  for the length of its voice track, or the storyboard's planned duration when
@@ -725,13 +768,16 @@ revoiced and trimmed the same way, with fresh findings and fresh word timings.
725
768
  scene's motion prompt only (`withRedoDirection`,
726
769
  `scout/src/trigger/workflow-scene-retry.ts`); the spoken line still comes from
727
770
  the approved storyboard. Asking for different words in a note will not change
728
- them. If the words are wrong, that is a storyboard flag or a new run.
771
+ them. If the words are wrong, that is a new run.
729
772
 
730
773
  **A redo whose take fails the checks keeps the original clip.** On a finished
731
774
  clip the row stays `done` with the ORIGINAL take and only its findings are
732
775
  refreshed, because a worse take must never replace a delivered one. So never
733
- report that the clip changed. Read `status` again for the new findings, then pull
734
- and compare the file before and after.
776
+ report that the clip changed without reading `status` again: under the scene it
777
+ prints what the redo did, `new take accepted (take N)` or `new take rejected,
778
+ original kept (take N)`, and `status --json` carries the same sentence on the
779
+ row as `lastRedo.label`. A row with no `lastRedo` was redone before outcomes
780
+ were recorded; for those, pull and compare the file before and after.
735
781
 
736
782
  **Re-cut afterwards.** A cut you already uploaded still holds the old clip, and
737
783
  the CLI says so when that is the case. Pull again, re-cut and upload again before
@@ -805,12 +851,14 @@ they want; if they have not said and are not around, say which you used and why.
805
851
  - **The bundled script** — `node .claude/skills/exodus-video/scripts/first-cut.mjs
806
852
  ./ad-<runId>`, path from the workspace root (from a brand subfolder, prefix
807
853
  `../`). A plain assembly with no editing decisions in it, for seeing the whole
808
- ad end to end quickly. Read "What the bundled script does" before you run it:
809
- it changes the audio.
854
+ ad end to end quickly. Read "What the bundled script does" before you run it.
810
855
  - **HyperFrames** — HTML/CSS compositions rendered to video. Suits an ad that
811
856
  needs captions, title cards, overlays or motion graphics laid over the clips.
812
857
  It is a separate skill pack; check that a `hyperframes` skill is installed
813
- before offering it.
858
+ before offering it. The cut is made outside Exodus. `exodus video pull` is the
859
+ handover. The folder it writes is what the next tool reads. When a HyperFrames
860
+ skill pack is installed, that pack is the route for an HTML and CSS
861
+ composition. Do not look for a stitch inside Exodus.
814
862
  - **An NLE over MCP** — DaVinci Resolve publishes an MCP server, so a timeline
815
863
  can be built in a real editor and the user can carry on by hand afterwards.
816
864
  Suits a user who wants to keep editing after you stop. Check what MCP servers
@@ -839,17 +887,15 @@ Read these out of `storyboard.json` before you build anything, in any tool:
839
887
 
840
888
  ### What the bundled script does
841
889
 
842
- State this to the user before you run it, because an editor does not expect a
843
- cut tool to alter the performance:
844
-
845
- - **It changes the narration's speed.** When a narrated scene's voice runs
846
- longer than its clip, it speeds that scene's voice up — to at most 1.26×
847
- (`ATEMPO_CAP`, `scripts/first-cut.mjs:39`) — and holds the last frame for the
848
- remainder. The amount differs per scene, so one ad can play scene 1 at normal
849
- speed and scene 3 a quarter faster. **This is the script's behaviour, not the
850
- system's**; nothing in the pipeline does it. It is the defect tracked in
851
- #1814. If the user cares about a single consistent narrator pace, cut
852
- elsewhere, or skip the scenes it stretches.
890
+ State this to the user before you run it, so they know what the cut does with
891
+ the pieces:
892
+
893
+ - **It cuts the picture to the voice, and never the other way round.** A
894
+ narrated scene's segment runs exactly as long as that scene's voice track plus
895
+ a beat of air. A clip that runs longer is trimmed; a clip that runs shorter
896
+ holds its last frame for the difference. The narration is never sped up,
897
+ slowed down or cut short, so every scene plays at the speed it was recorded,
898
+ and the word times are handed on as they were measured.
853
899
  - **It ignores the continuous master.** It reads only `scene-NN.voice` files;
854
900
  it never opens `narration.mp3` or `manifest.narration`. A tool that lays the
855
901
  master down whole and cuts the pictures to it keeps the read intact.
@@ -866,7 +912,8 @@ cut tool to alter the performance:
866
912
  - Beside the MP4 it writes `cut.words.json` (every spoken word of the finished
867
913
  ad with its start and end) and `cut.srt` (the same words grouped into subtitle
868
914
  lines), named from the `--out` basename. Those times are on the finished ad's
869
- timeline and include the speed changes above. Scenes with no word times leave
915
+ timeline, each scene's own word times offset by where that scene starts in the
916
+ cut and otherwise unaltered. Scenes with no word times leave
870
917
  gaps and are named in the report; when no scene delivered any, neither file is
871
918
  written. These sidecars are the script's own output — another tool will not
872
919
  produce them.
@@ -890,8 +937,8 @@ give a real one.
890
937
 
891
938
  **`upload` only works at the final watch.** The run has to be parked there with
892
939
  its video step done, or the server answers "This ad isn't waiting for your final
893
- cut right now." On a workflow run that park exists only when the video node sets
894
- `finalWatch: true`; a Show run always has it. Uploading again replaces the
940
+ cut right now." That park exists only when the video node sets
941
+ `finalWatch: true`. Uploading again replaces the
895
942
  previous cut in place rather than adding a second one. Upload does not move the
896
943
  run; it stays parked until someone approves.
897
944
 
@@ -906,7 +953,12 @@ compares the cut against the script or the storyboard. The approved spoken copy
906
953
  and the approved narration are therefore not the editor's to change — reordering
907
954
  lines, re-recording narration, or cutting words out of the CTA will ship,
908
955
  silently. Cut the pictures to the words, not the words to the pictures. If the
909
- words are wrong, that is a storyboard flag or a new run, not an edit.
956
+ words are wrong, that is a new run, not an edit.
957
+
958
+ Never stretch speech, speed it up, slow it down, or cut it short so it fits a
959
+ clip. That is a product rule, and it holds for every tool that makes the cut,
960
+ including the bundled script. If the audio does not fit, redo the clip with
961
+ `exodus video retry-clip`. Do not repair the fit in the editor.
910
962
 
911
963
  ## Reporting to the user
912
964