@aicopycoders/exodus 2026.9.2000-beta.0 → 2026.9.2000-beta.10
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/CLAUDE.md +1 -1
- package/assets/docs/PIPELINES.md +7 -10
- 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 +215 -163
- package/assets/skills/exodus-video/scripts/first-cut.mjs +17 -30
- package/assets/skills/exodus-workflow/SKILL.md +1 -1
- package/dist/commands/genesis.js +12 -133
- package/dist/commands/image.js +1 -1
- package/dist/commands/status.js +8 -18
- package/dist/commands/template.js +11 -4
- package/dist/commands/video.d.ts +119 -4
- package/dist/commands/video.js +639 -77
- package/dist/commands/workflow.d.ts +51 -3
- package/dist/commands/workflow.js +62 -9
- package/dist/lib/helperLedgerBase.d.ts +3 -0
- package/dist/lib/helperLedgerBase.js +3 -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
|
}
|
|
@@ -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;
|
|
@@ -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, 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
|
|
@@ -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
|
|
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) {
|
|
@@ -2085,9 +2133,10 @@ async function waitForRun(runId, opts, deps) {
|
|
|
2085
2133
|
const saved = opts.out !== undefined && terminalRun
|
|
2086
2134
|
? await saveDeliveries(terminalRun, opts.out, deps)
|
|
2087
2135
|
: undefined;
|
|
2088
|
-
const
|
|
2136
|
+
const parkStop = !pollResult.timedOut && isRecord(pollResult.data)
|
|
2089
2137
|
? videoParkStop(pollResult.data)
|
|
2090
2138
|
: undefined;
|
|
2139
|
+
const videoStop = parkStop ? await resolveStopAtPark(parkStop, runId, deps) : undefined;
|
|
2091
2140
|
if (opts.json) {
|
|
2092
2141
|
return {
|
|
2093
2142
|
code: pollResult.ok ? 0 : 1,
|
|
@@ -2449,6 +2498,7 @@ export async function triggersFireFlow(workflowRef, opts, deps) {
|
|
|
2449
2498
|
...(opts.imageRigOverrides
|
|
2450
2499
|
? { imageRigOverrides: opts.imageRigOverrides }
|
|
2451
2500
|
: {}),
|
|
2501
|
+
...(opts.voices ? { voices: opts.voices } : {}),
|
|
2452
2502
|
});
|
|
2453
2503
|
if (!res.ok)
|
|
2454
2504
|
return triggerErrorResult(res, verb, opts.json);
|
|
@@ -3312,8 +3362,10 @@ export async function run(flags, occurrences) {
|
|
|
3312
3362
|
}
|
|
3313
3363
|
}
|
|
3314
3364
|
let fireOverrides;
|
|
3365
|
+
let fireVoices;
|
|
3315
3366
|
try {
|
|
3316
3367
|
fireOverrides = parseRigOverridesFlag(occurrences, defaultDeps.readFile);
|
|
3368
|
+
fireVoices = parseVoicesFlag(occurrences, defaultDeps.readFile);
|
|
3317
3369
|
}
|
|
3318
3370
|
catch (e) {
|
|
3319
3371
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -3323,6 +3375,7 @@ export async function run(flags, occurrences) {
|
|
|
3323
3375
|
n,
|
|
3324
3376
|
text: flagString(flags, "text"),
|
|
3325
3377
|
imageRigOverrides: fireOverrides,
|
|
3378
|
+
voices: fireVoices,
|
|
3326
3379
|
wait: flags["wait"] === true,
|
|
3327
3380
|
json,
|
|
3328
3381
|
onProgressLine: (line) => console.log(line),
|