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

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,30 @@ 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
+ autoRedoScenes?: number[];
122
+ }
123
+ export interface MissingScene {
124
+ sceneIndex: number;
125
+ pictureRefused: boolean;
79
126
  }
80
127
  export type BuilderPauseReason = "taste" | "repair" | "slots" | "call" | "checkpoint";
81
128
  export interface PullCastLockMember {
@@ -103,6 +150,13 @@ export interface VideoRun {
103
150
  pauseAhead?: WorkflowRun["pauseAhead"];
104
151
  castLock?: PullCastLock | null;
105
152
  provenance?: RunProvenance;
153
+ musicBed?: boolean;
154
+ videoChoice?: RunVideoChoice;
155
+ storyboardAutoApproval?: {
156
+ outcome: "approved" | "held";
157
+ at: number;
158
+ reason?: string;
159
+ };
106
160
  workflowId?: string;
107
161
  moduleOwned?: boolean;
108
162
  }
@@ -121,6 +175,11 @@ export interface NodeItem {
121
175
  take: number;
122
176
  outcome: string;
123
177
  label: string;
178
+ reason?: string;
179
+ displacedHistory?: {
180
+ title: string;
181
+ lines: string[];
182
+ };
124
183
  };
125
184
  artifact?: ArtifactSubset;
126
185
  }
@@ -169,12 +228,17 @@ export declare function videoApiError(res: ApiResponse<unknown>): string;
169
228
  export type RunStop = {
170
229
  at: "running";
171
230
  stage: string;
231
+ autoRedoScenes?: number[];
172
232
  } | {
173
233
  at: "storyboard-gate";
174
234
  nodeId?: string;
175
235
  framesNodeId?: string;
236
+ showAd?: true;
237
+ heldReason?: string;
176
238
  } | {
177
239
  at: "final-watch";
240
+ missingScenes?: MissingScene[];
241
+ framesNodeId?: string;
178
242
  } | {
179
243
  at: "paused";
180
244
  nodeId?: string;
@@ -191,18 +255,24 @@ export type RunStop = {
191
255
  };
192
256
  export type ResolvedStop = Exclude<RunStop, {
193
257
  at: "final-watch";
194
- }> | {
258
+ }> | (Extract<RunStop, {
195
259
  at: "final-watch";
260
+ }> & {
196
261
  cutAttached: boolean | null;
197
- };
262
+ });
198
263
  export declare function classifyRun(run: VideoRun): RunStop;
199
264
  export declare function hasAttachedCut(items: NodeItem[]): boolean;
265
+ export declare function scenesBeingRedone(run: VideoRun, items: NodeItem[]): number[];
200
266
  export declare function resolveStop(stop: RunStop, cutAttached: boolean | null): ResolvedStop;
201
267
  export declare function resolveStopAtPark(stop: RunStop, runId: string, deps: Pick<VideoDeps, "get">): Promise<ResolvedStop>;
202
268
  export declare function stageWord(stage: string): string;
203
269
  export declare function stepName(kind: string | undefined): string;
204
- export declare function reviewUrl(dashboardUrl: string, run: Pick<VideoRun, "_id" | "workflowId" | "moduleOwned">): string;
270
+ export declare function isShowAd(run: Pick<VideoRun, "moduleOwned">): boolean;
271
+ export declare function reviewUrl(dashboardUrl: string, run: Pick<VideoRun, "_id" | "workflowId">): string;
205
272
  export declare function stopLines(stop: ResolvedStop, runId: string, runUrl: string): string[];
273
+ export declare function failedStoryboardNode(run: VideoRun): VideoRunNode | undefined;
274
+ export declare function errorEchoesReasons(error: string | undefined, record: PlanFailureRecord | undefined): boolean;
275
+ export declare function planFailureLines(node: VideoRunNode | undefined, runId: string): string[];
206
276
  export interface PullDownload {
207
277
  file: string;
208
278
  url: string;
@@ -224,6 +294,8 @@ export interface ManifestScene {
224
294
  revoiced: boolean | null;
225
295
  speechTrimmed: boolean | null;
226
296
  rawStorageId: string | null;
297
+ voiceMode: "voice-first" | null;
298
+ voiceFirstBlockedBy: string[] | null;
227
299
  clipStatus: string;
228
300
  error: string | null;
229
301
  flagged: boolean;
@@ -245,6 +317,14 @@ export interface ManifestCastRef {
245
317
  voiceLabel: string | null;
246
318
  voiceDescription: string | null;
247
319
  }
320
+ export type MusicBedState = "on" | "off" | "unknown";
321
+ export interface RunVideoChoice {
322
+ videoModel: string;
323
+ videoModelLabel: string;
324
+ voiceMode: string;
325
+ voiceModeLabel: string;
326
+ }
327
+ export declare const MUSIC_HEARD_CODE = "music-heard";
248
328
  export interface VideoManifest {
249
329
  runId: string;
250
330
  pulledAt: string;
@@ -252,6 +332,9 @@ export interface VideoManifest {
252
332
  storyboard: string | null;
253
333
  reference: string | null;
254
334
  music: string | null;
335
+ musicBed: MusicBedState;
336
+ musicHeardScenes: number[];
337
+ pendingRedo: number[];
255
338
  cast: ManifestCastRef[];
256
339
  narration: {
257
340
  file: string;
@@ -260,8 +343,13 @@ export interface VideoManifest {
260
343
  scenes: ManifestScene[];
261
344
  failed: PullFailure[];
262
345
  provenance?: RunProvenance;
346
+ videoChoice?: {
347
+ videoModel: string;
348
+ voiceMode: string;
349
+ };
263
350
  }
264
- export declare const CAST_LEDGER_BASE = 910000;
351
+ export declare const CAST_LEDGER_BASE: number;
352
+ export declare function musicBedState(run: VideoRun): MusicBedState;
265
353
  export interface PullPlan {
266
354
  downloads: PullDownload[];
267
355
  texts: PullTextFile[];
@@ -284,7 +372,40 @@ export interface StartOptions {
284
372
  wait: boolean;
285
373
  json: boolean;
286
374
  }
375
+ export interface ScriptStartOptions {
376
+ scriptFile: string;
377
+ conceit: (typeof CONCEIT_KEYS)[number];
378
+ style: string;
379
+ direction?: string;
380
+ voicePath?: string;
381
+ videoModel?: string;
382
+ voiceMode?: VoiceMode;
383
+ music?: boolean;
384
+ reviewStoryboard?: true;
385
+ wait: boolean;
386
+ json: boolean;
387
+ }
388
+ export type VideoStartPlan = {
389
+ kind: "usage";
390
+ line: string;
391
+ } | {
392
+ kind: "show";
393
+ opts: StartOptions;
394
+ } | {
395
+ kind: "script";
396
+ opts: ScriptStartOptions;
397
+ };
287
398
  export declare function startFlow(opts: StartOptions, deps: VideoDeps): Promise<FlowResult>;
399
+ export type MusicChoice = {
400
+ ok: true;
401
+ music: boolean | undefined;
402
+ } | {
403
+ ok: false;
404
+ line: string;
405
+ };
406
+ export declare function planMusicChoice(occurrences: FlagOccurrence[]): MusicChoice;
407
+ export declare function planVideoStart(flags: Record<string, string | boolean>, occurrences: FlagOccurrence[]): VideoStartPlan;
408
+ export declare function startScriptFlow(opts: ScriptStartOptions, deps: VideoDeps): Promise<FlowResult>;
288
409
  export declare function waitFlow(runId: string, opts: {
289
410
  json: boolean;
290
411
  url?: string;
@@ -292,8 +413,12 @@ export declare function waitFlow(runId: string, opts: {
292
413
  maxPolls?: number;
293
414
  }, deps: VideoDeps): Promise<FlowResult>;
294
415
  export declare function statusFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
416
+ export declare function rejectedDraftFlow(runId: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
295
417
  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>;
418
+ export declare function approveFlow(runId: string, opts: {
419
+ json: boolean;
420
+ approveStaleCut: boolean;
421
+ }, deps: VideoDeps): Promise<FlowResult>;
297
422
  export declare function flagFlow(runId: string, note: string, json: boolean, deps: VideoDeps): Promise<FlowResult>;
298
423
  export declare function retryFrameFlow(runId: string, nodeId: string, sceneIndex: number, note: string | undefined, json: boolean, deps: VideoDeps): Promise<FlowResult>;
299
424
  export type ClipRedoPlan = {
@@ -311,7 +436,11 @@ export interface ClipRedoTarget {
311
436
  nodeId?: string;
312
437
  }
313
438
  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>;
439
+ export interface ClipRedoOptions {
440
+ note?: string;
441
+ voiceFirst?: boolean;
442
+ }
443
+ export declare function retryClipFlow(runId: string, target: ClipRedoTarget, { note, voiceFirst }: ClipRedoOptions, json: boolean, deps: VideoDeps): Promise<FlowResult>;
315
444
  export declare const VOICE_PATHS_NEVER_HEARD: Set<string>;
316
445
  export declare function planClipRevoice(run: VideoRun, items: NodeItem[], target: ClipRedoTarget): ClipRedoPlan;
317
446
  export type RevoiceTarget = ClipRedoTarget | {
@@ -379,3 +508,4 @@ export declare function describeFetchFailure(err: unknown): string;
379
508
  export declare function uploadFlow(runId: string, filePath: string, durationFlag: string | undefined, json: boolean, deps: VideoDeps): Promise<FlowResult>;
380
509
  export declare function parsePositional(args?: string[]): string[];
381
510
  export declare function run(flags: Record<string, string | boolean>, occurrences: FlagOccurrence[]): Promise<void>;
511
+ export {};