@aicopycoders/exodus 2026.9.2000-beta.2 → 2026.9.2000-beta.3
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/dist/commands/video.d.ts +5 -2
- package/dist/commands/video.js +44 -14
- package/dist/commands/workflow.d.ts +51 -3
- package/dist/commands/workflow.js +19 -5
- package/package.json +1 -1
package/dist/commands/video.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ export type ArtifactSubset = {
|
|
|
72
72
|
export interface VideoRunNode {
|
|
73
73
|
nodeId: string;
|
|
74
74
|
kind: string;
|
|
75
|
-
status: "idle" | "running" | "done" | "failed" | "skipped";
|
|
75
|
+
status: "idle" | "running" | "done" | "failed" | "skipped" | "out-of-scope";
|
|
76
76
|
error?: string;
|
|
77
77
|
warning?: string;
|
|
78
78
|
outputs?: ArtifactSubset[];
|
|
@@ -293,7 +293,10 @@ export declare function waitFlow(runId: string, opts: {
|
|
|
293
293
|
}, deps: VideoDeps): Promise<FlowResult>;
|
|
294
294
|
export declare function statusFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
|
|
295
295
|
export declare function storyboardFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
|
|
296
|
-
export declare function approveFlow(runId: string,
|
|
296
|
+
export declare function approveFlow(runId: string, opts: {
|
|
297
|
+
json: boolean;
|
|
298
|
+
approveStaleCut: boolean;
|
|
299
|
+
}, deps: VideoDeps): Promise<FlowResult>;
|
|
297
300
|
export declare function flagFlow(runId: string, note: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
|
|
298
301
|
export declare function retryFrameFlow(runId: string, nodeId: string, sceneIndex: number, note: string | undefined, json: boolean, deps: VideoDeps): Promise<FlowResult>;
|
|
299
302
|
export type ClipRedoPlan = {
|
package/dist/commands/video.js
CHANGED
|
@@ -62,12 +62,14 @@ The whole loop, in order:
|
|
|
62
62
|
Attaches your cut to the run and prints the page to approve it on.
|
|
63
63
|
|
|
64
64
|
11. exodus video approve <runId>
|
|
65
|
-
Or click Approve on the page from step 10.
|
|
65
|
+
Or click Approve on the page from step 10. If you redid a clip after
|
|
66
|
+
uploading, this stops and says which scenes changed. Upload a fresh cut,
|
|
67
|
+
or add --approve-stale-cut to deliver the cut you already uploaded.
|
|
66
68
|
|
|
67
69
|
Usage:
|
|
68
70
|
exodus video status <runId> [--json]
|
|
69
71
|
exodus video storyboard <runId> [--json]
|
|
70
|
-
exodus video approve <runId> [--json]
|
|
72
|
+
exodus video approve <runId> [--approve-stale-cut] [--json]
|
|
71
73
|
exodus video retry-frame <runId> --node <nodeId> --scene <n> [--note "..."] [--json]
|
|
72
74
|
exodus video retry-clip <runId> --scene <n> [--node <nodeId>] [--note "..."] [--json]
|
|
73
75
|
exodus video revoice <runId> (--scene <n> | --all) [--node <nodeId>] [--json]
|
|
@@ -88,6 +90,9 @@ Options:
|
|
|
88
90
|
(retry-frame, retry-clip, revoice)
|
|
89
91
|
--all Every finished clip that kept the video model's voice
|
|
90
92
|
(revoice)
|
|
93
|
+
--approve-stale-cut Deliver the cut you uploaded even though a clip changed
|
|
94
|
+
after you uploaded it (approve). Without it, approving
|
|
95
|
+
stops and names the scenes that changed
|
|
91
96
|
--set <who>=<id> Give a character an ElevenLabs voice (voices). Name the
|
|
92
97
|
character by its ID or by the name your script uses.
|
|
93
98
|
Repeat it once per character
|
|
@@ -956,6 +961,11 @@ function itemWord(item) {
|
|
|
956
961
|
return "flagged";
|
|
957
962
|
return ITEM_STATUS_WORD[item.status] ?? item.status;
|
|
958
963
|
}
|
|
964
|
+
const CARRYING_ON_LINE = "Waiting for a worker to carry on. Everything finished so far is kept — the run picks up from there by itself.";
|
|
965
|
+
function carryingOn(run) {
|
|
966
|
+
return (run.status === "queued" &&
|
|
967
|
+
run.nodes.some((n) => n.status !== "idle" && n.status !== "out-of-scope"));
|
|
968
|
+
}
|
|
959
969
|
export async function statusFlow(runId, json, deps) {
|
|
960
970
|
const runRes = await deps.get(`${RUN_PATH}?runId=${encodeURIComponent(runId)}`);
|
|
961
971
|
if (!runRes.ok)
|
|
@@ -1007,6 +1017,7 @@ export async function statusFlow(runId, json, deps) {
|
|
|
1007
1017
|
: `Ad run ${runId} — ${displayRunStatus(run.status)}`;
|
|
1008
1018
|
const lines = [
|
|
1009
1019
|
headline,
|
|
1020
|
+
...(carryingOn(run) ? [CARRYING_ON_LINE] : []),
|
|
1010
1021
|
...guidance,
|
|
1011
1022
|
...(stop.at === "running" && run.pauseAhead ? [pauseAheadLine(run.pauseAhead)] : []),
|
|
1012
1023
|
...warnings.map((w) => `Heads-up (${w.step}): ${w.warning}`),
|
|
@@ -1136,14 +1147,30 @@ function sceneCardLines(scene) {
|
|
|
1136
1147
|
lines.push(` picture: ${scene.frame.status}`);
|
|
1137
1148
|
return lines;
|
|
1138
1149
|
}
|
|
1139
|
-
export async function approveFlow(runId,
|
|
1140
|
-
const res = await deps.post(APPROVE_PATH, {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1150
|
+
export async function approveFlow(runId, opts, deps) {
|
|
1151
|
+
const res = await deps.post(APPROVE_PATH, {
|
|
1152
|
+
runId,
|
|
1153
|
+
...(opts.approveStaleCut ? { approveStaleCut: true } : {}),
|
|
1154
|
+
});
|
|
1155
|
+
if (!res.ok) {
|
|
1156
|
+
const refusal = errorResult(res, opts.json);
|
|
1157
|
+
const staleCut = !opts.json && videoApiError(res).includes("approve this cut anyway");
|
|
1158
|
+
return staleCut
|
|
1159
|
+
? {
|
|
1160
|
+
...refusal,
|
|
1161
|
+
lines: [
|
|
1162
|
+
...refusal.lines,
|
|
1163
|
+
`To deliver it anyway: exodus video approve ${runId} --approve-stale-cut`,
|
|
1164
|
+
],
|
|
1165
|
+
}
|
|
1166
|
+
: refusal;
|
|
1167
|
+
}
|
|
1168
|
+
const data = res.data;
|
|
1169
|
+
const warnings = Array.isArray(data?.warnings) ? data.warnings : [];
|
|
1170
|
+
if (opts.json) {
|
|
1171
|
+
return { code: 0, lines: [JSON.stringify({ ok: true, runId, warnings, data: res.data })] };
|
|
1172
|
+
}
|
|
1173
|
+
const voices = data?.voices;
|
|
1147
1174
|
const summary = voices
|
|
1148
1175
|
? voiceSummaryLines(Array.isArray(voices.cast) ? voices.cast : [], Array.isArray(voices.notices) ? voices.notices : [])
|
|
1149
1176
|
: [];
|
|
@@ -1151,6 +1178,7 @@ export async function approveFlow(runId, json, deps) {
|
|
|
1151
1178
|
code: 0,
|
|
1152
1179
|
lines: [
|
|
1153
1180
|
"Approved.",
|
|
1181
|
+
...warnings,
|
|
1154
1182
|
...(summary.length > 0 ? ["", ...summary, ""] : []),
|
|
1155
1183
|
`See what happens next: exodus video status ${runId}`,
|
|
1156
1184
|
],
|
|
@@ -1208,8 +1236,9 @@ export async function retryFrameFlow(runId, nodeId, sceneIndex, note, json, deps
|
|
|
1208
1236
|
],
|
|
1209
1237
|
};
|
|
1210
1238
|
}
|
|
1211
|
-
const UPLOADED_CUT_WARNING = "The cut you already uploaded will not include the new clip.
|
|
1212
|
-
"re-cut
|
|
1239
|
+
const UPLOADED_CUT_WARNING = "The cut you already uploaded will not include the new clip. If this redo replaces the clip, " +
|
|
1240
|
+
"exodus video approve stops until you pull the pieces again, re-cut and upload again, " +
|
|
1241
|
+
"or pass --approve-stale-cut to deliver the cut you uploaded as it is.";
|
|
1213
1242
|
export function planClipRedo(run, items, target) {
|
|
1214
1243
|
const stop = classifyRun(run);
|
|
1215
1244
|
const found = findClipRow(run, items, target);
|
|
@@ -2028,8 +2057,9 @@ export async function run(flags, occurrences) {
|
|
|
2028
2057
|
return printResult(await statusFlow(runId, json, defaultDeps));
|
|
2029
2058
|
if (sub === "storyboard")
|
|
2030
2059
|
return printResult(await storyboardFlow(runId, json, defaultDeps));
|
|
2031
|
-
if (sub === "approve")
|
|
2032
|
-
return printResult(await approveFlow(runId, json, defaultDeps));
|
|
2060
|
+
if (sub === "approve") {
|
|
2061
|
+
return printResult(await approveFlow(runId, { json, approveStaleCut: flags["approve-stale-cut"] === true }, defaultDeps));
|
|
2062
|
+
}
|
|
2033
2063
|
if (sub === "flag") {
|
|
2034
2064
|
const note = flagString(flags, "note");
|
|
2035
2065
|
if (!note)
|
|
@@ -34,14 +34,60 @@ export interface WorkflowEdge {
|
|
|
34
34
|
targetHandle: string;
|
|
35
35
|
}
|
|
36
36
|
export type WorkflowTriggerEvent = "winner-promoted";
|
|
37
|
+
export interface ImageRigLineOverride {
|
|
38
|
+
count?: number;
|
|
39
|
+
aspects?: string[];
|
|
40
|
+
adType?: string;
|
|
41
|
+
style?: string;
|
|
42
|
+
memeFormat?: string;
|
|
43
|
+
steering?: string;
|
|
44
|
+
copyPin?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface ImageRigPlanOverride {
|
|
47
|
+
lines?: unknown[];
|
|
48
|
+
steering?: string;
|
|
49
|
+
defaultAspects?: string[];
|
|
50
|
+
defaultStyle?: string;
|
|
51
|
+
model?: string;
|
|
52
|
+
confirmLargeRun?: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface ImageRigNodeOverride {
|
|
55
|
+
plan?: ImageRigPlanOverride;
|
|
56
|
+
lines?: Record<string, ImageRigLineOverride>;
|
|
57
|
+
confirmLargeRun?: boolean;
|
|
58
|
+
model?: string;
|
|
59
|
+
}
|
|
60
|
+
export type ImageRigOverrides = Record<string, ImageRigNodeOverride>;
|
|
61
|
+
export interface VoiceRequest {
|
|
62
|
+
who: string;
|
|
63
|
+
choice: {
|
|
64
|
+
kind: "pin";
|
|
65
|
+
voiceId: string;
|
|
66
|
+
label?: string;
|
|
67
|
+
} | {
|
|
68
|
+
kind: "clear";
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export type TriggerVoiceMap = Record<string, string | {
|
|
72
|
+
voiceId: string;
|
|
73
|
+
label?: string;
|
|
74
|
+
} | null>;
|
|
37
75
|
export type WorkflowTrigger = {
|
|
38
76
|
type: "event";
|
|
39
77
|
event: WorkflowTriggerEvent;
|
|
40
78
|
enabled: boolean;
|
|
79
|
+
imageRigOverrides?: ImageRigOverrides;
|
|
80
|
+
voices?: VoiceRequest[];
|
|
41
81
|
} | {
|
|
42
82
|
type: "cron";
|
|
43
83
|
cron: string;
|
|
44
84
|
enabled: boolean;
|
|
85
|
+
imageRigOverrides?: ImageRigOverrides;
|
|
86
|
+
voices?: VoiceRequest[];
|
|
87
|
+
};
|
|
88
|
+
type WithoutVoices<T> = T extends unknown ? Omit<T, "voices"> : never;
|
|
89
|
+
export type WorkflowContractTrigger = WithoutVoices<WorkflowTrigger> & {
|
|
90
|
+
voices?: TriggerVoiceMap;
|
|
45
91
|
};
|
|
46
92
|
export interface WorkflowContractJson {
|
|
47
93
|
contract: "exodus-workflow";
|
|
@@ -51,7 +97,7 @@ export interface WorkflowContractJson {
|
|
|
51
97
|
name: string;
|
|
52
98
|
description?: string;
|
|
53
99
|
slots?: WorkflowSlot[];
|
|
54
|
-
triggers?:
|
|
100
|
+
triggers?: WorkflowContractTrigger[];
|
|
55
101
|
nodes: WorkflowNode[];
|
|
56
102
|
edges: WorkflowEdge[];
|
|
57
103
|
}
|
|
@@ -530,12 +576,13 @@ export declare function parseVersionFlag(flags: Record<string, string | boolean>
|
|
|
530
576
|
export declare function versionsFlow(workflowRef: string, opts: {
|
|
531
577
|
json: boolean;
|
|
532
578
|
}, deps: WorkflowRunDeps, channel?: Channel): Promise<FlowResult>;
|
|
533
|
-
|
|
579
|
+
type AnyTrigger = WorkflowTrigger | WorkflowContractTrigger;
|
|
580
|
+
export declare function triggerExpect(t: AnyTrigger): {
|
|
534
581
|
type: string;
|
|
535
582
|
event?: string;
|
|
536
583
|
cron?: string;
|
|
537
584
|
};
|
|
538
|
-
export declare function formatTriggers(triggers:
|
|
585
|
+
export declare function formatTriggers(triggers: AnyTrigger[]): string;
|
|
539
586
|
export declare function triggersListFlow(workflowRef: string, opts: {
|
|
540
587
|
json: boolean;
|
|
541
588
|
}, deps: WorkflowRunDeps): Promise<FlowResult>;
|
|
@@ -546,6 +593,7 @@ export declare function triggersFireFlow(workflowRef: string, opts: {
|
|
|
546
593
|
n?: number;
|
|
547
594
|
text?: string;
|
|
548
595
|
imageRigOverrides?: Record<string, unknown>;
|
|
596
|
+
voices?: VoiceMap;
|
|
549
597
|
wait: boolean;
|
|
550
598
|
json: boolean;
|
|
551
599
|
onProgressLine?: (line: string) => void;
|
|
@@ -32,7 +32,7 @@ Usage:
|
|
|
32
32
|
exodus workflow triggers <workflowId|name> [--json]
|
|
33
33
|
exodus workflow triggers <workflowId|name> enable <n> [--json]
|
|
34
34
|
exodus workflow triggers <workflowId|name> disable <n> [--json]
|
|
35
|
-
exodus workflow triggers <workflowId|name> fire [<n>] [--text "..."] [--rig-overrides <json|@file>] [--wait] [--json]
|
|
35
|
+
exodus workflow triggers <workflowId|name> fire [<n>] [--text "..."] [--rig-overrides <json|@file>] [--voices <json|@voices.json>] [--wait] [--json]
|
|
36
36
|
exodus workflow inbox [--json]
|
|
37
37
|
exodus workflow checkpoint <runId> [show] [--json]
|
|
38
38
|
exodus workflow checkpoint <runId> edit <n> [--text "..." | --file <path> | (stdin)] [--json]
|
|
@@ -84,9 +84,9 @@ Flags:
|
|
|
84
84
|
before anything runs, and the error names it. On
|
|
85
85
|
"triggers fire" it REPLACES the schedule's own
|
|
86
86
|
overrides for that one test fire.
|
|
87
|
-
--voices <json> (run) Give the ad's characters their
|
|
88
|
-
moment the run starts, instead of at the
|
|
89
|
-
review. Keyed by the names the SCRIPT uses for its
|
|
87
|
+
--voices <json> (run, triggers fire) Give the ad's characters their
|
|
88
|
+
voices at the moment the run starts, instead of at the
|
|
89
|
+
storyboard review. Keyed by the names the SCRIPT uses for its
|
|
90
90
|
speakers ("HOST 1") — the planner's character ids
|
|
91
91
|
(C1, C2) do not exist yet when a run starts. Takes a
|
|
92
92
|
JSON object, or @path to a .json file holding one:
|
|
@@ -103,6 +103,14 @@ Flags:
|
|
|
103
103
|
<run>" at the storyboard review instead. Voice ids
|
|
104
104
|
belong to the brand's own folder — never put them in a
|
|
105
105
|
shared workflow or template.
|
|
106
|
+
A schedule can carry the same map in its YAML
|
|
107
|
+
("voices:" beside the schedule), so a weekly ad arrives
|
|
108
|
+
already voiced. On "triggers fire" this flag REPLACES
|
|
109
|
+
the schedule's own voices for that one test fire. A
|
|
110
|
+
schedule whose voice has been deleted, or whose
|
|
111
|
+
ElevenLabs key is missing or refused, does not start at
|
|
112
|
+
all: it leaves a failed run saying which speaker and
|
|
113
|
+
what to fix, and spends nothing.
|
|
106
114
|
--voice-treatment <v> (run) Say HOW this run makes its voices. Two kinds of
|
|
107
115
|
answer. A name on its own picks a way of working that
|
|
108
116
|
uses voices you already have:
|
|
@@ -279,7 +287,9 @@ Notes:
|
|
|
279
287
|
panel in the app); anything you name that isn't there stops the launch and the
|
|
280
288
|
message says which key was wrong. A trigger can carry the same payload in its
|
|
281
289
|
YAML ("imageRigOverrides:" beside its schedule), so a Monday schedule and a
|
|
282
|
-
Friday one can fire the same workflow at different sizes.
|
|
290
|
+
Friday one can fire the same workflow at different sizes. A trigger can carry
|
|
291
|
+
"voices:" the same way — the same map "workflow run --voices" takes — so a run
|
|
292
|
+
that starts on a schedule arrives at the storyboard already voiced.
|
|
283
293
|
"workflow run --voices" hands a video run its voices at the start, so a brand
|
|
284
294
|
that always uses the same hosts stops choosing them by hand on every run. The
|
|
285
295
|
file is keyed by the names the script uses for its speakers, because that is
|
|
@@ -2450,6 +2460,7 @@ export async function triggersFireFlow(workflowRef, opts, deps) {
|
|
|
2450
2460
|
...(opts.imageRigOverrides
|
|
2451
2461
|
? { imageRigOverrides: opts.imageRigOverrides }
|
|
2452
2462
|
: {}),
|
|
2463
|
+
...(opts.voices ? { voices: opts.voices } : {}),
|
|
2453
2464
|
});
|
|
2454
2465
|
if (!res.ok)
|
|
2455
2466
|
return triggerErrorResult(res, verb, opts.json);
|
|
@@ -3313,8 +3324,10 @@ export async function run(flags, occurrences) {
|
|
|
3313
3324
|
}
|
|
3314
3325
|
}
|
|
3315
3326
|
let fireOverrides;
|
|
3327
|
+
let fireVoices;
|
|
3316
3328
|
try {
|
|
3317
3329
|
fireOverrides = parseRigOverridesFlag(occurrences, defaultDeps.readFile);
|
|
3330
|
+
fireVoices = parseVoicesFlag(occurrences, defaultDeps.readFile);
|
|
3318
3331
|
}
|
|
3319
3332
|
catch (e) {
|
|
3320
3333
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -3324,6 +3337,7 @@ export async function run(flags, occurrences) {
|
|
|
3324
3337
|
n,
|
|
3325
3338
|
text: flagString(flags, "text"),
|
|
3326
3339
|
imageRigOverrides: fireOverrides,
|
|
3340
|
+
voices: fireVoices,
|
|
3327
3341
|
wait: flags["wait"] === true,
|
|
3328
3342
|
json,
|
|
3329
3343
|
onProgressLine: (line) => console.log(line),
|