@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.
- package/assets/docs/PIPELINES.md +2 -1
- 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 +201 -39
- package/assets/skills/exodus-video/scripts/first-cut.mjs +538 -38
- package/assets/skills/exodus-workflow/SKILL.md +1 -1
- package/dist/commands/ads.js +1 -1
- package/dist/commands/image.js +1 -1
- package/dist/commands/template.js +11 -4
- package/dist/commands/video.d.ts +137 -7
- package/dist/commands/video.js +778 -64
- package/dist/commands/workflow.d.ts +53 -4
- package/dist/commands/workflow.js +76 -10
- package/dist/lib/helperLedgerBase.d.ts +3 -0
- package/dist/lib/helperLedgerBase.js +3 -0
- package/dist/lib/meta-ads.d.ts +1 -0
- package/package.json +1 -1
|
@@ -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
|
}
|
|
@@ -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;
|
|
@@ -414,6 +460,7 @@ export interface WorkflowRun {
|
|
|
414
460
|
pauseAhead?: {
|
|
415
461
|
nodeId: string;
|
|
416
462
|
after: "frames" | "storyboard";
|
|
463
|
+
selfApproves?: true;
|
|
417
464
|
};
|
|
418
465
|
}
|
|
419
466
|
export declare function pauseAheadLine(ahead: NonNullable<WorkflowRun["pauseAhead"]>): string;
|
|
@@ -530,12 +577,13 @@ export declare function parseVersionFlag(flags: Record<string, string | boolean>
|
|
|
530
577
|
export declare function versionsFlow(workflowRef: string, opts: {
|
|
531
578
|
json: boolean;
|
|
532
579
|
}, deps: WorkflowRunDeps, channel?: Channel): Promise<FlowResult>;
|
|
533
|
-
|
|
580
|
+
type AnyTrigger = WorkflowTrigger | WorkflowContractTrigger;
|
|
581
|
+
export declare function triggerExpect(t: AnyTrigger): {
|
|
534
582
|
type: string;
|
|
535
583
|
event?: string;
|
|
536
584
|
cron?: string;
|
|
537
585
|
};
|
|
538
|
-
export declare function formatTriggers(triggers:
|
|
586
|
+
export declare function formatTriggers(triggers: AnyTrigger[]): string;
|
|
539
587
|
export declare function triggersListFlow(workflowRef: string, opts: {
|
|
540
588
|
json: boolean;
|
|
541
589
|
}, deps: WorkflowRunDeps): Promise<FlowResult>;
|
|
@@ -546,6 +594,7 @@ export declare function triggersFireFlow(workflowRef: string, opts: {
|
|
|
546
594
|
n?: number;
|
|
547
595
|
text?: string;
|
|
548
596
|
imageRigOverrides?: Record<string, unknown>;
|
|
597
|
+
voices?: VoiceMap;
|
|
549
598
|
wait: boolean;
|
|
550
599
|
json: boolean;
|
|
551
600
|
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
|
|
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
|
|
@@ -368,6 +378,9 @@ export function formatRulesLine(provenance) {
|
|
|
368
378
|
return `Format rules: ${rigName}${version}${where}.${borrowed}`;
|
|
369
379
|
}
|
|
370
380
|
export function pauseAheadLine(ahead) {
|
|
381
|
+
if (ahead.after === "frames" && ahead.selfApproves) {
|
|
382
|
+
return "When the frames are ready, the app checks the storyboard and approves it itself if the checks pass, then goes on to the voices and clips. It only stops for your approval if a check finds a problem. The reference, cast and scene pictures are made first (a few cents each).";
|
|
383
|
+
}
|
|
371
384
|
return ahead.after === "frames"
|
|
372
385
|
? "This run will pause for your approval once the frames are ready. The reference, cast and scene pictures are made first (a few cents each). Nothing bigger is spent until you approve."
|
|
373
386
|
: "This run will pause for your approval once the storyboard is written. Nothing is spent on pictures, voices or clips until you approve.";
|
|
@@ -1372,6 +1385,43 @@ function planArtifactFile(artifact) {
|
|
|
1372
1385
|
url,
|
|
1373
1386
|
};
|
|
1374
1387
|
}
|
|
1388
|
+
function artifactFileSuffixes(artifacts) {
|
|
1389
|
+
const sceneBearing = artifacts.some((a) => typeof a.sceneIndex === "number" || a.final === true);
|
|
1390
|
+
if (!sceneBearing) {
|
|
1391
|
+
return artifacts.map((_, i) => ({
|
|
1392
|
+
suffix: i === 0 ? "" : `-${i + 1}`,
|
|
1393
|
+
renamed: false,
|
|
1394
|
+
}));
|
|
1395
|
+
}
|
|
1396
|
+
const taken = new Set();
|
|
1397
|
+
let fullCount = 0;
|
|
1398
|
+
return artifacts.map((a, i) => {
|
|
1399
|
+
let suffix;
|
|
1400
|
+
if (typeof a.sceneIndex === "number") {
|
|
1401
|
+
suffix = `-${scenePrefix(a.sceneIndex)}`;
|
|
1402
|
+
}
|
|
1403
|
+
else if (a.final === true) {
|
|
1404
|
+
suffix = "-final";
|
|
1405
|
+
}
|
|
1406
|
+
else if (a.type === "audio") {
|
|
1407
|
+
fullCount += 1;
|
|
1408
|
+
suffix = fullCount === 1 ? "-full" : `-full-${fullCount}`;
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
suffix = i === 0 ? "" : `-${i + 1}`;
|
|
1412
|
+
}
|
|
1413
|
+
if (taken.has(suffix)) {
|
|
1414
|
+
let fallback = `${suffix}-${i + 1}`;
|
|
1415
|
+
while (taken.has(fallback))
|
|
1416
|
+
fallback = `${fallback}-${i + 1}`;
|
|
1417
|
+
suffix = fallback;
|
|
1418
|
+
taken.add(suffix);
|
|
1419
|
+
return { suffix, renamed: true };
|
|
1420
|
+
}
|
|
1421
|
+
taken.add(suffix);
|
|
1422
|
+
return { suffix, renamed: false };
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1375
1425
|
export async function saveDeliveries(run, dir, deps) {
|
|
1376
1426
|
const paths = [];
|
|
1377
1427
|
if (!run.isTerminal) {
|
|
@@ -1406,9 +1456,10 @@ export async function saveDeliveries(run, dir, deps) {
|
|
|
1406
1456
|
lines.push(` skipped ${delivery.label} (${delivery.key}) — ${why}`);
|
|
1407
1457
|
continue;
|
|
1408
1458
|
}
|
|
1459
|
+
const suffixes = artifactFileSuffixes(delivery.artifacts);
|
|
1409
1460
|
for (let i = 0; i < delivery.artifacts.length; i++) {
|
|
1410
1461
|
const plan = planArtifactFile(delivery.artifacts[i]);
|
|
1411
|
-
const suffix
|
|
1462
|
+
const { suffix, renamed } = suffixes[i];
|
|
1412
1463
|
if (plan.kind === "none") {
|
|
1413
1464
|
lines.push(` skipped ${delivery.label} (${delivery.key})${suffix} — ${plan.reason}`);
|
|
1414
1465
|
continue;
|
|
@@ -1419,7 +1470,7 @@ export async function saveDeliveries(run, dir, deps) {
|
|
|
1419
1470
|
deps.writeFile(file, plan.body);
|
|
1420
1471
|
else
|
|
1421
1472
|
await downloadToFile(plan.url, file);
|
|
1422
|
-
lines.push(` wrote ${file}`);
|
|
1473
|
+
lines.push(` wrote ${file}${renamed ? " — renamed to avoid a collision" : ""}`);
|
|
1423
1474
|
paths.push(file);
|
|
1424
1475
|
}
|
|
1425
1476
|
catch (e) {
|
|
@@ -2040,6 +2091,7 @@ function videoParkStop(raw) {
|
|
|
2040
2091
|
async function waitForRun(runId, opts, deps) {
|
|
2041
2092
|
const seen = new Map();
|
|
2042
2093
|
let pausedNotified = false;
|
|
2094
|
+
let autoRedoNotified;
|
|
2043
2095
|
const landOnPark = opts.landOnPark;
|
|
2044
2096
|
const isLandingPark = (raw) => (landOnPark !== undefined && raw["pauseReason"] === landOnPark.pauseReason) ||
|
|
2045
2097
|
videoParkStop(raw) !== undefined;
|
|
@@ -2057,7 +2109,15 @@ async function waitForRun(runId, opts, deps) {
|
|
|
2057
2109
|
return;
|
|
2058
2110
|
const parked = isParkedSnapshot(raw);
|
|
2059
2111
|
const isLanding = isLandingPark(raw);
|
|
2060
|
-
|
|
2112
|
+
const autoRedo = parked ? classifyRun(asVideoRun(raw)) : undefined;
|
|
2113
|
+
if (autoRedo?.at === "running" && autoRedo.autoRedoScenes) {
|
|
2114
|
+
const line = ` ${stopLines(autoRedo, runId, "")[0]}`;
|
|
2115
|
+
if (line !== autoRedoNotified) {
|
|
2116
|
+
autoRedoNotified = line;
|
|
2117
|
+
opts.onProgressLine(line);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
else if (parked && !pausedNotified && !isLanding) {
|
|
2061
2121
|
pausedNotified = true;
|
|
2062
2122
|
const dashboardUrl = deps.dashboardUrl ?? getDashboardUrl();
|
|
2063
2123
|
const pauseReason = raw["pauseReason"];
|
|
@@ -2065,7 +2125,9 @@ async function waitForRun(runId, opts, deps) {
|
|
|
2065
2125
|
opts.onProgressLine(line);
|
|
2066
2126
|
}
|
|
2067
2127
|
}
|
|
2068
|
-
const parkedNodeId =
|
|
2128
|
+
const parkedNodeId = autoRedo?.at === "running"
|
|
2129
|
+
? undefined
|
|
2130
|
+
: gateParkedNodeId(raw["status"], raw["pauseReason"], raw["pausedNodeId"]);
|
|
2069
2131
|
const nodes = Array.isArray(raw["nodes"]) ? raw["nodes"] : [];
|
|
2070
2132
|
for (const node of nodes) {
|
|
2071
2133
|
const isParked = parkedNodeId !== undefined && node.nodeId === parkedNodeId;
|
|
@@ -2450,6 +2512,7 @@ export async function triggersFireFlow(workflowRef, opts, deps) {
|
|
|
2450
2512
|
...(opts.imageRigOverrides
|
|
2451
2513
|
? { imageRigOverrides: opts.imageRigOverrides }
|
|
2452
2514
|
: {}),
|
|
2515
|
+
...(opts.voices ? { voices: opts.voices } : {}),
|
|
2453
2516
|
});
|
|
2454
2517
|
if (!res.ok)
|
|
2455
2518
|
return triggerErrorResult(res, verb, opts.json);
|
|
@@ -3313,8 +3376,10 @@ export async function run(flags, occurrences) {
|
|
|
3313
3376
|
}
|
|
3314
3377
|
}
|
|
3315
3378
|
let fireOverrides;
|
|
3379
|
+
let fireVoices;
|
|
3316
3380
|
try {
|
|
3317
3381
|
fireOverrides = parseRigOverridesFlag(occurrences, defaultDeps.readFile);
|
|
3382
|
+
fireVoices = parseVoicesFlag(occurrences, defaultDeps.readFile);
|
|
3318
3383
|
}
|
|
3319
3384
|
catch (e) {
|
|
3320
3385
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -3324,6 +3389,7 @@ export async function run(flags, occurrences) {
|
|
|
3324
3389
|
n,
|
|
3325
3390
|
text: flagString(flags, "text"),
|
|
3326
3391
|
imageRigOverrides: fireOverrides,
|
|
3392
|
+
voices: fireVoices,
|
|
3327
3393
|
wait: flags["wait"] === true,
|
|
3328
3394
|
json,
|
|
3329
3395
|
onProgressLine: (line) => console.log(line),
|
package/dist/lib/meta-ads.d.ts
CHANGED