@aicopycoders/exodus 2026.9.2000-beta.2 → 2026.9.2000-beta.20

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.
@@ -161,7 +161,7 @@ Accepts a workflow id **or** a name. It reports three things:
161
161
 
162
162
  Fill every required input. For a free-text brief you can compose a **bespoke brief from in-session research** — the winning angle you just worked out, the swipe you analyzed — and pass it inline (`--input brief="..."`) or, if it's long, write it to a project file and pass it with `@file` (see Chain, below). For URL/swipe sources, pass the value the source expects.
163
163
 
164
- **A brief wired straight into a Storyboard is a script, not a prompt.** On a video workflow whose Storyboard takes its `script` port from a Brief node, the text you pass for that brief boards VERBATIM, the same way a Show ad's pasted script does: one speaker per scene, every line rendered once in order, long lines split across clips, narrated lines rendered silent under the voice track. So it must be in the script form the `exodus-video` skill documents (an optional `CAST:` block of `NAME — description` lines, then `NAME:` turns, then a `CTA:` block; `NARRATOR:` lines only for Narrated Story and Explainer). A brief that is not a script, or one that does not fit the wired rig's family, is refused at launch with the parser's own message and nothing is spent. Write the script to a file and pass it with `--input script=@script.txt`. A Storyboard fed by a bot is unaffected.
164
+ **A brief wired straight into a Storyboard is a script, not a prompt.** On a video workflow whose Storyboard takes its `script` port from a Brief node, the text you pass for that brief boards VERBATIM, the same way a Show ad's pasted script does: one speaker per scene, every line rendered once in order, long lines split across clips, narrated lines rendered silent under the voice track. So it must be in the script form the `exodus-video` skill documents (an optional `DIRECTION:` block with no colon in its prose, then an optional `CAST:` block of `NAME — description` lines, then `NAME:` turns, then a `CTA:` block; `NARRATOR:` lines only for Narrated Story and Explainer). A brief that is not a script, or one that does not fit the wired rig's family, is refused at launch with the parser's own message and nothing is spent. Write the script to a file and pass it with `--input script=@script.txt`. A Storyboard fed by a bot is unaffected.
165
165
 
166
166
  **A saved rig can bring a rulebook with it.** Some saved rigs carry saved format rules: how many speakers the script may have, which checks run on the clips, how the cast is built. Point a Rig box at one of those rigs and the run follows its rules — an ensemble rig seats more speakers than the usual three, a talking-head rig seats one. The start receipt says which rulebook ran:
167
167
 
@@ -268,7 +268,7 @@ function windowBlock(label, stats) {
268
268
  ` spend: ${spendCell(stats)}`,
269
269
  ` ctr: ${ctrCell(stats)}`,
270
270
  ` cpc: ${cpcCell(stats)}`,
271
- ` impressions: ${count(stats.impressions)} · clicks ${count(stats.clicks)}`,
271
+ ` impressions: ${count(stats.impressions)} · reach ${count(stats.reach)} · clicks ${count(stats.clicks)}`,
272
272
  ];
273
273
  }
274
274
  function copyBlock(label, text) {
@@ -184,7 +184,7 @@ async function runTemplate(text, flags, who) {
184
184
  renderMode: "images",
185
185
  inputAds: text,
186
186
  aspectRatio: aspect,
187
- model: "gpt-image-2",
187
+ model: "gpt-image-2.5",
188
188
  realismMode: realisticFlag ? "realistic" : "off",
189
189
  ...(steering ? { steering } : {}),
190
190
  ...(requestedImageCount !== undefined ? { requestedImageCount } : {}),
@@ -17,7 +17,8 @@ Run options:
17
17
  --mode auto|manual Generation mode (default: auto)
18
18
  --render-mode images|prompts Skip render and return prompts only (default: images)
19
19
  --aspect 1:1|9:16 Image aspect ratio (default: 1:1)
20
- --model gpt-image-2|nano-banana-pro Kie.ai model (default: gpt-image-2)
20
+ --model gpt-image-2.5|gpt-image-2.5-sunburst|gpt-image-2|nano-banana-pro
21
+ Kie.ai model (default: gpt-image-2.5)
21
22
  --realism off|realistic Realism enforcement (default: off)
22
23
  --quantities <slug:N,slug:N> Manual-mode per-type counts (e.g. "testimonial:3,hero:2")
23
24
  --requested-count N Auto-mode total render target (optional)
@@ -103,9 +104,15 @@ async function runTemplate(flags) {
103
104
  process.exit(1);
104
105
  return;
105
106
  }
106
- const model = typeof flags["model"] === "string" ? flags["model"] : "gpt-image-2";
107
- if (model !== "gpt-image-2" && model !== "nano-banana-pro") {
108
- console.error(`Error: --model must be gpt-image-2 | nano-banana-pro (got "${model}").`);
107
+ const TEMPLATE_MODELS = [
108
+ "gpt-image-2.5",
109
+ "gpt-image-2.5-sunburst",
110
+ "gpt-image-2",
111
+ "nano-banana-pro",
112
+ ];
113
+ const model = typeof flags["model"] === "string" ? flags["model"] : "gpt-image-2.5";
114
+ if (!TEMPLATE_MODELS.includes(model)) {
115
+ console.error(`Error: --model must be one of ${TEMPLATE_MODELS.join(" | ")} (got "${model}").`);
109
116
  process.exit(1);
110
117
  return;
111
118
  }
@@ -1,6 +1,9 @@
1
1
  import { type ApiResponse } from "../lib/client.js";
2
2
  import type { FlagOccurrence } from "../lib/args.js";
3
3
  import { type RunProvenance, type WorkflowRun } from "./workflow.js";
4
+ declare const CONCEIT_KEYS: readonly ["podcast", "ugc", "stage", "street", "personification"];
5
+ declare const VOICE_MODES: readonly ["native", "voice-first"];
6
+ type VoiceMode = (typeof VOICE_MODES)[number];
4
7
  export declare const helpText: string;
5
8
  export declare const VOICES_PATH = "/api/v2/video/voices";
6
9
  export interface ClipWord {
@@ -8,10 +11,32 @@ export interface ClipWord {
8
11
  s: number;
9
12
  e: number;
10
13
  }
14
+ export type ClipQcTakeNeighbours = {
15
+ state: "not-applicable";
16
+ } | {
17
+ state: "not-attached";
18
+ } | {
19
+ state: "none-accepted";
20
+ } | {
21
+ state: "attached";
22
+ scenes: number[];
23
+ };
24
+ export interface ClipQcTake {
25
+ kind: "take" | "soft-retry";
26
+ failCodes: string[];
27
+ warnCodes: string[];
28
+ neighbours: ClipQcTakeNeighbours;
29
+ judgeWording?: {
30
+ code: string;
31
+ wording: string;
32
+ }[];
33
+ heardText?: string;
34
+ }
11
35
  export interface ClipQc {
12
36
  verdict: "pass" | "fail";
13
37
  attempts: number;
14
38
  neighbours?: number[];
39
+ takes?: ClipQcTake[];
15
40
  }
16
41
  export interface ClipFinding {
17
42
  check: string;
@@ -21,6 +46,9 @@ export interface ClipFinding {
21
46
  judgeDetail?: string;
22
47
  judgeSeverity?: "fail" | "warn";
23
48
  }
49
+ export declare function qcWithoutJudgeWording(qc: ClipQc): ClipQc;
50
+ export declare const NO_TAKE_HISTORY_LINE = "no history recorded for this clip";
51
+ export declare function renderQcTakeHistory(takes: ClipQcTake[] | undefined): string[];
24
52
  export type ArtifactSubset = {
25
53
  type: "storyboard";
26
54
  storyboard?: unknown;
@@ -47,6 +75,8 @@ export type ArtifactSubset = {
47
75
  speechTrimmed?: boolean;
48
76
  tailTrimmed?: boolean;
49
77
  rawStorageId?: string;
78
+ voiceMode?: "voice-first";
79
+ voiceFirstBlockedBy?: string[];
50
80
  } | {
51
81
  type: "audio";
52
82
  sceneIndex?: number;
@@ -69,13 +99,29 @@ export type ArtifactSubset = {
69
99
  } | {
70
100
  type: "text" | "primer" | "session" | "document";
71
101
  };
102
+ export interface PlanFailureRecord {
103
+ kind: string;
104
+ partsTotal?: number;
105
+ acceptedParts?: number;
106
+ acceptedScenes?: number;
107
+ failedPart?: number;
108
+ reasons?: string[];
109
+ lines: string[];
110
+ }
72
111
  export interface VideoRunNode {
73
112
  nodeId: string;
74
113
  kind: string;
75
- status: "idle" | "running" | "done" | "failed" | "skipped";
114
+ status: "idle" | "running" | "done" | "failed" | "skipped" | "out-of-scope";
76
115
  error?: string;
77
116
  warning?: string;
117
+ planFailure?: PlanFailureRecord;
118
+ hasRejectedDraft?: boolean;
78
119
  outputs?: ArtifactSubset[];
120
+ missingScenes?: MissingScene[];
121
+ }
122
+ export interface MissingScene {
123
+ sceneIndex: number;
124
+ pictureRefused: boolean;
79
125
  }
80
126
  export type BuilderPauseReason = "taste" | "repair" | "slots" | "call" | "checkpoint";
81
127
  export interface PullCastLockMember {
@@ -103,6 +149,8 @@ export interface VideoRun {
103
149
  pauseAhead?: WorkflowRun["pauseAhead"];
104
150
  castLock?: PullCastLock | null;
105
151
  provenance?: RunProvenance;
152
+ musicBed?: boolean;
153
+ videoChoice?: RunVideoChoice;
106
154
  workflowId?: string;
107
155
  moduleOwned?: boolean;
108
156
  }
@@ -121,6 +169,11 @@ export interface NodeItem {
121
169
  take: number;
122
170
  outcome: string;
123
171
  label: string;
172
+ reason?: string;
173
+ displacedHistory?: {
174
+ title: string;
175
+ lines: string[];
176
+ };
124
177
  };
125
178
  artifact?: ArtifactSubset;
126
179
  }
@@ -173,8 +226,11 @@ export type RunStop = {
173
226
  at: "storyboard-gate";
174
227
  nodeId?: string;
175
228
  framesNodeId?: string;
229
+ showAd?: true;
176
230
  } | {
177
231
  at: "final-watch";
232
+ missingScenes?: MissingScene[];
233
+ framesNodeId?: string;
178
234
  } | {
179
235
  at: "paused";
180
236
  nodeId?: string;
@@ -191,18 +247,23 @@ export type RunStop = {
191
247
  };
192
248
  export type ResolvedStop = Exclude<RunStop, {
193
249
  at: "final-watch";
194
- }> | {
250
+ }> | (Extract<RunStop, {
195
251
  at: "final-watch";
252
+ }> & {
196
253
  cutAttached: boolean | null;
197
- };
254
+ });
198
255
  export declare function classifyRun(run: VideoRun): RunStop;
199
256
  export declare function hasAttachedCut(items: NodeItem[]): boolean;
200
257
  export declare function resolveStop(stop: RunStop, cutAttached: boolean | null): ResolvedStop;
201
258
  export declare function resolveStopAtPark(stop: RunStop, runId: string, deps: Pick<VideoDeps, "get">): Promise<ResolvedStop>;
202
259
  export declare function stageWord(stage: string): string;
203
260
  export declare function stepName(kind: string | undefined): string;
204
- export declare function reviewUrl(dashboardUrl: string, run: Pick<VideoRun, "_id" | "workflowId" | "moduleOwned">): string;
261
+ export declare function isShowAd(run: Pick<VideoRun, "moduleOwned">): boolean;
262
+ export declare function reviewUrl(dashboardUrl: string, run: Pick<VideoRun, "_id" | "workflowId">): string;
205
263
  export declare function stopLines(stop: ResolvedStop, runId: string, runUrl: string): string[];
264
+ export declare function failedStoryboardNode(run: VideoRun): VideoRunNode | undefined;
265
+ export declare function errorEchoesReasons(error: string | undefined, record: PlanFailureRecord | undefined): boolean;
266
+ export declare function planFailureLines(node: VideoRunNode | undefined, runId: string): string[];
206
267
  export interface PullDownload {
207
268
  file: string;
208
269
  url: string;
@@ -224,6 +285,8 @@ export interface ManifestScene {
224
285
  revoiced: boolean | null;
225
286
  speechTrimmed: boolean | null;
226
287
  rawStorageId: string | null;
288
+ voiceMode: "voice-first" | null;
289
+ voiceFirstBlockedBy: string[] | null;
227
290
  clipStatus: string;
228
291
  error: string | null;
229
292
  flagged: boolean;
@@ -245,6 +308,14 @@ export interface ManifestCastRef {
245
308
  voiceLabel: string | null;
246
309
  voiceDescription: string | null;
247
310
  }
311
+ export type MusicBedState = "on" | "off" | "unknown";
312
+ export interface RunVideoChoice {
313
+ videoModel: string;
314
+ videoModelLabel: string;
315
+ voiceMode: string;
316
+ voiceModeLabel: string;
317
+ }
318
+ export declare const MUSIC_HEARD_CODE = "music-heard";
248
319
  export interface VideoManifest {
249
320
  runId: string;
250
321
  pulledAt: string;
@@ -252,6 +323,8 @@ export interface VideoManifest {
252
323
  storyboard: string | null;
253
324
  reference: string | null;
254
325
  music: string | null;
326
+ musicBed: MusicBedState;
327
+ musicHeardScenes: number[];
255
328
  cast: ManifestCastRef[];
256
329
  narration: {
257
330
  file: string;
@@ -260,8 +333,13 @@ export interface VideoManifest {
260
333
  scenes: ManifestScene[];
261
334
  failed: PullFailure[];
262
335
  provenance?: RunProvenance;
336
+ videoChoice?: {
337
+ videoModel: string;
338
+ voiceMode: string;
339
+ };
263
340
  }
264
- export declare const CAST_LEDGER_BASE = 910000;
341
+ export declare const CAST_LEDGER_BASE: number;
342
+ export declare function musicBedState(run: VideoRun): MusicBedState;
265
343
  export interface PullPlan {
266
344
  downloads: PullDownload[];
267
345
  texts: PullTextFile[];
@@ -284,7 +362,39 @@ export interface StartOptions {
284
362
  wait: boolean;
285
363
  json: boolean;
286
364
  }
365
+ export interface ScriptStartOptions {
366
+ scriptFile: string;
367
+ conceit: (typeof CONCEIT_KEYS)[number];
368
+ style: string;
369
+ direction?: string;
370
+ voicePath?: string;
371
+ videoModel?: string;
372
+ voiceMode?: VoiceMode;
373
+ music?: boolean;
374
+ wait: boolean;
375
+ json: boolean;
376
+ }
377
+ export type VideoStartPlan = {
378
+ kind: "usage";
379
+ line: string;
380
+ } | {
381
+ kind: "show";
382
+ opts: StartOptions;
383
+ } | {
384
+ kind: "script";
385
+ opts: ScriptStartOptions;
386
+ };
287
387
  export declare function startFlow(opts: StartOptions, deps: VideoDeps): Promise<FlowResult>;
388
+ export type MusicChoice = {
389
+ ok: true;
390
+ music: boolean | undefined;
391
+ } | {
392
+ ok: false;
393
+ line: string;
394
+ };
395
+ export declare function planMusicChoice(occurrences: FlagOccurrence[]): MusicChoice;
396
+ export declare function planVideoStart(flags: Record<string, string | boolean>, occurrences: FlagOccurrence[]): VideoStartPlan;
397
+ export declare function startScriptFlow(opts: ScriptStartOptions, deps: VideoDeps): Promise<FlowResult>;
288
398
  export declare function waitFlow(runId: string, opts: {
289
399
  json: boolean;
290
400
  url?: string;
@@ -292,8 +402,12 @@ export declare function waitFlow(runId: string, opts: {
292
402
  maxPolls?: number;
293
403
  }, deps: VideoDeps): Promise<FlowResult>;
294
404
  export declare function statusFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
405
+ export declare function rejectedDraftFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
295
406
  export declare function storyboardFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
296
- export declare function approveFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
407
+ export declare function approveFlow(runId: string, opts: {
408
+ json: boolean;
409
+ approveStaleCut: boolean;
410
+ }, deps: VideoDeps): Promise<FlowResult>;
297
411
  export declare function flagFlow(runId: string, note: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
298
412
  export declare function retryFrameFlow(runId: string, nodeId: string, sceneIndex: number, note: string | undefined, json: boolean, deps: VideoDeps): Promise<FlowResult>;
299
413
  export type ClipRedoPlan = {
@@ -311,7 +425,11 @@ export interface ClipRedoTarget {
311
425
  nodeId?: string;
312
426
  }
313
427
  export declare function planClipRedo(run: VideoRun, items: NodeItem[], target: ClipRedoTarget): ClipRedoPlan;
314
- export declare function retryClipFlow(runId: string, target: ClipRedoTarget, note: string | undefined, json: boolean, deps: VideoDeps): Promise<FlowResult>;
428
+ export interface ClipRedoOptions {
429
+ note?: string;
430
+ voiceFirst?: boolean;
431
+ }
432
+ export declare function retryClipFlow(runId: string, target: ClipRedoTarget, { note, voiceFirst }: ClipRedoOptions, json: boolean, deps: VideoDeps): Promise<FlowResult>;
315
433
  export declare const VOICE_PATHS_NEVER_HEARD: Set<string>;
316
434
  export declare function planClipRevoice(run: VideoRun, items: NodeItem[], target: ClipRedoTarget): ClipRedoPlan;
317
435
  export type RevoiceTarget = ClipRedoTarget | {
@@ -379,3 +497,4 @@ export declare function describeFetchFailure(err: unknown): string;
379
497
  export declare function uploadFlow(runId: string, filePath: string, durationFlag: string | undefined, json: boolean, deps: VideoDeps): Promise<FlowResult>;
380
498
  export declare function parsePositional(args?: string[]): string[];
381
499
  export declare function run(flags: Record<string, string | boolean>, occurrences: FlagOccurrence[]): Promise<void>;
500
+ export {};