@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.
- package/assets/docs/CLAUDE.md +1 -1
- package/assets/docs/PIPELINES.md +7 -10
- package/assets/skills/exodus-meme/SKILL.md +4 -3
- package/assets/skills/exodus-template/SKILL.md +2 -1
- package/assets/skills/exodus-video/SKILL.md +215 -163
- package/assets/skills/exodus-video/scripts/first-cut.mjs +17 -30
- package/assets/skills/exodus-workflow/SKILL.md +1 -1
- package/dist/commands/genesis.js +12 -133
- package/dist/commands/image.js +1 -1
- package/dist/commands/status.js +8 -18
- package/dist/commands/template.js +11 -4
- package/dist/commands/video.d.ts +119 -4
- package/dist/commands/video.js +639 -77
- package/dist/commands/workflow.d.ts +51 -3
- package/dist/commands/workflow.js +62 -9
- package/dist/lib/helperLedgerBase.d.ts +3 -0
- package/dist/lib/helperLedgerBase.js +3 -0
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
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
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
**
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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. `
|
|
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;
|
|
161
|
-
|
|
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
|
|
171
|
-
|
|
172
|
-
Verified against the
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
- **
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
|
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
|
-
|
|
283
|
-
the
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
290
|
-
|
|
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
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
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
|
|
451
|
-
|
|
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
|
-
/
|
|
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
|
|
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.
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
(`convex/workflows.ts
|
|
595
|
-
|
|
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`)
|
|
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
|
|
625
|
-
that
|
|
626
|
-
|
|
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.
|
|
665
|
-
|
|
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
|
|
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
|
|
734
|
-
|
|
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,
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
- **It
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
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
|
|
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."
|
|
894
|
-
`finalWatch: true
|
|
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
|
|
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
|
|