@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.
@@ -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?: WorkflowTrigger[];
100
+ triggers?: WorkflowContractTrigger[];
55
101
  nodes: WorkflowNode[];
56
102
  edges: WorkflowEdge[];
57
103
  }
@@ -363,7 +409,7 @@ export interface RunFormatProvenance {
363
409
  };
364
410
  specVersion: string | null;
365
411
  }
366
- export type VoicePath = "sts-revoice" | "lipsync-retarget" | "native-prompt" | "omni-audio-ids" | "gemini-direct";
412
+ export type VoicePath = "sts-revoice" | "lipsync-retarget" | "native-prompt" | "omni-audio-ids" | "gemini-direct" | "voice-first";
367
413
  export type RunVoiceProvenance = {
368
414
  source: "show";
369
415
  path: VoicePath;
@@ -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
- export declare function triggerExpect(t: WorkflowTrigger): {
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: WorkflowTrigger[]): string;
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;
@@ -10,7 +10,7 @@ import { runDisplayName, runHeadingWord, runTypeWord } from "../lib/runNames.js"
10
10
  import { workflowToYaml, parseWorkflowText } from "../lib/workflowText.js";
11
11
  import { missingRouteLine } from "../lib/route-support.js";
12
12
  import { getChannel } from "../lib/channel.js";
13
- import { asVideoRun, classifyRun, resolveStopAtPark, reviewUrl, stopLines, } from "./video.js";
13
+ import { asVideoRun, classifyRun, resolveStopAtPark, reviewUrl, scenePrefix, stopLines, } from "./video.js";
14
14
  export const helpText = `
15
15
  exodus workflow — List, describe, run, inspect, import, and export saved workflows
16
16
 
@@ -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 voices at the
88
- moment the run starts, instead of at the storyboard
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
@@ -1372,6 +1382,43 @@ function planArtifactFile(artifact) {
1372
1382
  url,
1373
1383
  };
1374
1384
  }
1385
+ function artifactFileSuffixes(artifacts) {
1386
+ const sceneBearing = artifacts.some((a) => typeof a.sceneIndex === "number" || a.final === true);
1387
+ if (!sceneBearing) {
1388
+ return artifacts.map((_, i) => ({
1389
+ suffix: i === 0 ? "" : `-${i + 1}`,
1390
+ renamed: false,
1391
+ }));
1392
+ }
1393
+ const taken = new Set();
1394
+ let fullCount = 0;
1395
+ return artifacts.map((a, i) => {
1396
+ let suffix;
1397
+ if (typeof a.sceneIndex === "number") {
1398
+ suffix = `-${scenePrefix(a.sceneIndex)}`;
1399
+ }
1400
+ else if (a.final === true) {
1401
+ suffix = "-final";
1402
+ }
1403
+ else if (a.type === "audio") {
1404
+ fullCount += 1;
1405
+ suffix = fullCount === 1 ? "-full" : `-full-${fullCount}`;
1406
+ }
1407
+ else {
1408
+ suffix = i === 0 ? "" : `-${i + 1}`;
1409
+ }
1410
+ if (taken.has(suffix)) {
1411
+ let fallback = `${suffix}-${i + 1}`;
1412
+ while (taken.has(fallback))
1413
+ fallback = `${fallback}-${i + 1}`;
1414
+ suffix = fallback;
1415
+ taken.add(suffix);
1416
+ return { suffix, renamed: true };
1417
+ }
1418
+ taken.add(suffix);
1419
+ return { suffix, renamed: false };
1420
+ });
1421
+ }
1375
1422
  export async function saveDeliveries(run, dir, deps) {
1376
1423
  const paths = [];
1377
1424
  if (!run.isTerminal) {
@@ -1406,9 +1453,10 @@ export async function saveDeliveries(run, dir, deps) {
1406
1453
  lines.push(` skipped ${delivery.label} (${delivery.key}) — ${why}`);
1407
1454
  continue;
1408
1455
  }
1456
+ const suffixes = artifactFileSuffixes(delivery.artifacts);
1409
1457
  for (let i = 0; i < delivery.artifacts.length; i++) {
1410
1458
  const plan = planArtifactFile(delivery.artifacts[i]);
1411
- const suffix = i === 0 ? "" : `-${i + 1}`;
1459
+ const { suffix, renamed } = suffixes[i];
1412
1460
  if (plan.kind === "none") {
1413
1461
  lines.push(` skipped ${delivery.label} (${delivery.key})${suffix} — ${plan.reason}`);
1414
1462
  continue;
@@ -1419,7 +1467,7 @@ export async function saveDeliveries(run, dir, deps) {
1419
1467
  deps.writeFile(file, plan.body);
1420
1468
  else
1421
1469
  await downloadToFile(plan.url, file);
1422
- lines.push(` wrote ${file}`);
1470
+ lines.push(` wrote ${file}${renamed ? " — renamed to avoid a collision" : ""}`);
1423
1471
  paths.push(file);
1424
1472
  }
1425
1473
  catch (e) {
@@ -2450,6 +2498,7 @@ export async function triggersFireFlow(workflowRef, opts, deps) {
2450
2498
  ...(opts.imageRigOverrides
2451
2499
  ? { imageRigOverrides: opts.imageRigOverrides }
2452
2500
  : {}),
2501
+ ...(opts.voices ? { voices: opts.voices } : {}),
2453
2502
  });
2454
2503
  if (!res.ok)
2455
2504
  return triggerErrorResult(res, verb, opts.json);
@@ -3313,8 +3362,10 @@ export async function run(flags, occurrences) {
3313
3362
  }
3314
3363
  }
3315
3364
  let fireOverrides;
3365
+ let fireVoices;
3316
3366
  try {
3317
3367
  fireOverrides = parseRigOverridesFlag(occurrences, defaultDeps.readFile);
3368
+ fireVoices = parseVoicesFlag(occurrences, defaultDeps.readFile);
3318
3369
  }
3319
3370
  catch (e) {
3320
3371
  console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
@@ -3324,6 +3375,7 @@ export async function run(flags, occurrences) {
3324
3375
  n,
3325
3376
  text: flagString(flags, "text"),
3326
3377
  imageRigOverrides: fireOverrides,
3378
+ voices: fireVoices,
3327
3379
  wait: flags["wait"] === true,
3328
3380
  json,
3329
3381
  onProgressLine: (line) => console.log(line),
@@ -0,0 +1,3 @@
1
+ export declare const HELPER_LEDGER_BASE = 900000;
2
+ export declare const SET_OPTION_LEDGER_BASE = 920000;
3
+ export declare const CAST_OPTION_LEDGER_BASE = 960000;
@@ -0,0 +1,3 @@
1
+ export const HELPER_LEDGER_BASE = 900000;
2
+ export const SET_OPTION_LEDGER_BASE = 920000;
3
+ export const CAST_OPTION_LEDGER_BASE = 960000;
@@ -22,6 +22,7 @@ export interface AdWindowStats {
22
22
  spend?: number | null;
23
23
  spendDisplay?: string | null;
24
24
  impressions?: number | null;
25
+ reach?: number | null;
25
26
  clicks?: number | null;
26
27
  currency?: string | null;
27
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aicopycoders/exodus",
3
- "version": "2026.9.2000-beta.2",
3
+ "version": "2026.9.2000-beta.20",
4
4
  "description": "CLI + Claude Code skills for the Copycoders Exodus ad pipeline",
5
5
  "repository": {
6
6
  "type": "git",