@aicopycoders/exodus 2026.9.1900-beta.0 → 2026.9.1900-beta.2
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 +12 -0
- package/assets/skills/exodus-video/SKILL.md +189 -13
- package/assets/skills/exodus-workflow/SKILL.md +33 -1
- package/dist/bin/exodus.js +3 -36
- package/dist/commands/video.d.ts +20 -2
- package/dist/commands/video.js +263 -62
- package/dist/commands/workflow.d.ts +52 -8
- package/dist/commands/workflow.js +188 -96
- package/dist/lib/args.d.ts +10 -0
- package/dist/lib/args.js +39 -0
- package/package.json +1 -1
|
@@ -20,7 +20,7 @@ Usage:
|
|
|
20
20
|
exodus workflow templates [list] [--json]
|
|
21
21
|
exodus workflow templates export <key> [--out <file>] [--json]
|
|
22
22
|
exodus workflow schema [--kind <kind>] [--face <face>] [--json]
|
|
23
|
-
exodus workflow run <workflowId|name> [--fill <name>] [--input key=value ...] [--input <fileField>=<path> ...] [--rig-overrides <json|@file>] [--voices <json|@voices.json>] [--auto-approve] [--wait] [--out <dir>] [--json]
|
|
23
|
+
exodus workflow run <workflowId|name> [--fill <name>] [--input key=value ...] [--input <fileField>=<path> ...] [--rig-overrides <json|@file>] [--voices <json|@voices.json>] [--voice-treatment <name|json|@file>] [--auto-approve] [--wait] [--out <dir>] [--json]
|
|
24
24
|
exodus workflow status [--json]
|
|
25
25
|
exodus workflow status --id <runId> [--out <dir>] [--json]
|
|
26
26
|
exodus workflow cancel <runId> [--reason "..."] [--json]
|
|
@@ -102,6 +102,29 @@ Flags:
|
|
|
102
102
|
<run>" at the storyboard review instead. Voice ids
|
|
103
103
|
belong to the brand's own folder — never put them in a
|
|
104
104
|
shared workflow or template.
|
|
105
|
+
--voice-treatment <v> (run) Say HOW this run makes its voices. Two kinds of
|
|
106
|
+
answer. A name on its own picks a way of working that
|
|
107
|
+
uses voices you already have:
|
|
108
|
+
--voice-treatment sts-revoice
|
|
109
|
+
--voice-treatment lipsync-retarget --voices @voices.json
|
|
110
|
+
The other kind has the video model speak the lines
|
|
111
|
+
itself, in a voice you WRITE instead of one you pick.
|
|
112
|
+
Those need a description for every speaker in your
|
|
113
|
+
script, so they take a JSON object, or @path to a .json
|
|
114
|
+
file holding one:
|
|
115
|
+
--voice-treatment @voice-treatment.json
|
|
116
|
+
--voice-treatment '{"path":"native-prompt","describe":{"HOST 1":"Dry, tired baritone."}}'
|
|
117
|
+
Written voices and --voices are never used together:
|
|
118
|
+
the video model owns the voice on those runs, so
|
|
119
|
+
ElevenLabs voices would be paid for and never heard,
|
|
120
|
+
and asking for both stops the launch. Anything the run
|
|
121
|
+
could not actually do — a treatment this stack has not
|
|
122
|
+
switched on, a Video step on a model that cannot do it,
|
|
123
|
+
a missing key, a rig that shows every line as on-screen
|
|
124
|
+
text — stops the launch before the run is created, and
|
|
125
|
+
the message says which. Descriptions are chosen HERE,
|
|
126
|
+
at the start: "exodus video voices <run>" shows them at
|
|
127
|
+
the review but cannot change them.
|
|
105
128
|
--auto-approve (run) Deliberately unattended launch: every Checkpoint
|
|
106
129
|
box this run stops at is approved automatically, with
|
|
107
130
|
whatever it was holding left exactly as it is, and the
|
|
@@ -194,6 +217,7 @@ Examples:
|
|
|
194
217
|
exodus workflow run "Product Shots" --rig-overrides '{"rig_1":{"lines":{"line_1":{"count":3}}}}'
|
|
195
218
|
exodus workflow run "Product Shots" --rig-overrides @rig.json --wait
|
|
196
219
|
exodus workflow run "Podcast Ad" --input script=@script.txt --voices @voices.json --wait
|
|
220
|
+
exodus workflow run "Podcast Ad" --input script=@script.txt --voice-treatment @voice-treatment.json --wait
|
|
197
221
|
exodus workflow run "Launch Flow" --wait --out ./deliverables
|
|
198
222
|
exodus workflow status # recent runs + their ids
|
|
199
223
|
exodus workflow status --id wr_123
|
|
@@ -268,6 +292,20 @@ Notes:
|
|
|
268
292
|
no speaker names to key on at the start, so it is refused with a pointer at
|
|
269
293
|
the review-time command. Keep voice ids in the brand's own folder, never in a
|
|
270
294
|
shared workflow or template. (#1846)
|
|
295
|
+
"workflow run --voice-treatment" says HOW a run makes its voices. Some ways of
|
|
296
|
+
working play a voice you picked (that is today's behaviour, and it still takes
|
|
297
|
+
--voices); others have the video model speak the CAST's lines itself, in voices
|
|
298
|
+
you describe in words. A described voice needs one description per speaker,
|
|
299
|
+
because a speaker nobody described gets a voice the model invents afresh on
|
|
300
|
+
every clip, and the drift only shows up once the clips are paid for. Written
|
|
301
|
+
voices and --voices are refused together, since the ElevenLabs voices would
|
|
302
|
+
never be heard. Everything the run could not do — a way of working this stack
|
|
303
|
+
has not switched on, a Video step whose model cannot do it, a missing key, a
|
|
304
|
+
rig that shows every line as on-screen text — stops the launch before the run
|
|
305
|
+
is created. The ways of working beyond the two shipped ones are switched on
|
|
306
|
+
only on the test stack while they are being tried out, so a live run refuses
|
|
307
|
+
them by name. Both the launch receipt and "workflow status" say which way a
|
|
308
|
+
run took, and "exodus video voices <run>" shows each written voice. (#1869)
|
|
271
309
|
workflow triggers are addressed by 1-based position — a trigger carries no id,
|
|
272
310
|
so the CLI reads the live list from the export contract and sends a fingerprint
|
|
273
311
|
of the trigger's fields as the guard; a concurrent edit fails loud rather than
|
|
@@ -310,6 +348,24 @@ Notes:
|
|
|
310
348
|
ids it wants). The "gate" verbs retired in 2.0 along with the Gate node — the
|
|
311
349
|
command now prints a pointer at "checkpoint" and exits 1.
|
|
312
350
|
`.trim();
|
|
351
|
+
export function formatRulesLine(provenance) {
|
|
352
|
+
const format = provenance?.format;
|
|
353
|
+
if (!format || typeof format !== "object")
|
|
354
|
+
return undefined;
|
|
355
|
+
const { rigName, specVersion, nodeId, rulesFrom } = format;
|
|
356
|
+
if (typeof rigName !== "string" || rigName === "")
|
|
357
|
+
return undefined;
|
|
358
|
+
const version = typeof specVersion === "string" && specVersion !== ""
|
|
359
|
+
? `, version ${specVersion}`
|
|
360
|
+
: "";
|
|
361
|
+
const where = typeof nodeId === "string" && nodeId !== ""
|
|
362
|
+
? ` (from the "${nodeId}" Rig box)`
|
|
363
|
+
: " (from the Show's rig)";
|
|
364
|
+
const borrowed = rulesFrom && typeof rulesFrom.rigName === "string"
|
|
365
|
+
? ` The rules come from "${rulesFrom.rigName}".`
|
|
366
|
+
: "";
|
|
367
|
+
return `Format rules: ${rigName}${version}${where}.${borrowed}`;
|
|
368
|
+
}
|
|
313
369
|
const LIST_PATH = "/api/v2/workflows";
|
|
314
370
|
const RUN_PATH = "/api/v2/workflows/run";
|
|
315
371
|
const STATUS_PATH = "/api/v2/workflow";
|
|
@@ -354,6 +410,7 @@ const VALUE_FLAGS = new Set([
|
|
|
354
410
|
"reject",
|
|
355
411
|
"rig-overrides",
|
|
356
412
|
"voices",
|
|
413
|
+
"voice-treatment",
|
|
357
414
|
"note",
|
|
358
415
|
]);
|
|
359
416
|
function defaultMkdirp(dir) {
|
|
@@ -552,35 +609,25 @@ function normalizeMultiValue(value) {
|
|
|
552
609
|
.filter((p) => p.length > 0)
|
|
553
610
|
.join(", ");
|
|
554
611
|
}
|
|
555
|
-
export function parseRawInputFlags(
|
|
612
|
+
export function parseRawInputFlags(occurrences) {
|
|
556
613
|
const inputs = {};
|
|
557
|
-
for (
|
|
558
|
-
|
|
559
|
-
let raw;
|
|
560
|
-
if (arg === "--input") {
|
|
561
|
-
raw = args[i + 1];
|
|
562
|
-
i++;
|
|
563
|
-
}
|
|
564
|
-
else if (arg.startsWith("--input=")) {
|
|
565
|
-
raw = arg.slice("--input=".length);
|
|
566
|
-
}
|
|
567
|
-
else {
|
|
614
|
+
for (const { flag, value } of occurrences) {
|
|
615
|
+
if (flag !== "input")
|
|
568
616
|
continue;
|
|
569
|
-
|
|
570
|
-
if (!raw)
|
|
617
|
+
if (!value)
|
|
571
618
|
throw new Error("--input requires key=value");
|
|
572
|
-
const eq =
|
|
619
|
+
const eq = value.indexOf("=");
|
|
573
620
|
if (eq <= 0)
|
|
574
|
-
throw new Error(`--input must be key=value (got "${
|
|
575
|
-
const key =
|
|
621
|
+
throw new Error(`--input must be key=value (got "${value}")`);
|
|
622
|
+
const key = value.slice(0, eq).trim();
|
|
576
623
|
if (!key)
|
|
577
|
-
throw new Error(`--input must include a key (got "${
|
|
578
|
-
inputs[key] =
|
|
624
|
+
throw new Error(`--input must include a key (got "${value}")`);
|
|
625
|
+
inputs[key] = value.slice(eq + 1);
|
|
579
626
|
}
|
|
580
627
|
return inputs;
|
|
581
628
|
}
|
|
582
|
-
export function parseInputFlags(
|
|
583
|
-
const inputs = parseRawInputFlags(
|
|
629
|
+
export function parseInputFlags(occurrences, readFile) {
|
|
630
|
+
const inputs = parseRawInputFlags(occurrences);
|
|
584
631
|
for (const [key, value] of Object.entries(inputs)) {
|
|
585
632
|
inputs[key] = expandInputValue(key, value, readFile);
|
|
586
633
|
}
|
|
@@ -876,12 +923,10 @@ async function prepareRunInputs(workflowId, raw, deps, note) {
|
|
|
876
923
|
}
|
|
877
924
|
return { inputs: prepared, warnings };
|
|
878
925
|
}
|
|
879
|
-
export function rejectTerminalFlag(
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
"workflow or it doesn't start. Re-run without --terminal.");
|
|
884
|
-
}
|
|
926
|
+
export function rejectTerminalFlag(occurrences) {
|
|
927
|
+
if (occurrences.some((o) => o.flag === "terminal")) {
|
|
928
|
+
throw new Error("--terminal is no longer supported: a run now executes the whole " +
|
|
929
|
+
"workflow or it doesn't start. Re-run without --terminal.");
|
|
885
930
|
}
|
|
886
931
|
}
|
|
887
932
|
export function parseAutoApproveFlag(args) {
|
|
@@ -894,21 +939,11 @@ export function parseAutoApproveFlag(args) {
|
|
|
894
939
|
}
|
|
895
940
|
return false;
|
|
896
941
|
}
|
|
897
|
-
export function parseRigOverridesFlag(
|
|
942
|
+
export function parseRigOverridesFlag(occurrences, readFile) {
|
|
898
943
|
let raw;
|
|
899
|
-
for (
|
|
900
|
-
|
|
901
|
-
let value;
|
|
902
|
-
if (arg === "--rig-overrides") {
|
|
903
|
-
value = args[i + 1];
|
|
904
|
-
i++;
|
|
905
|
-
}
|
|
906
|
-
else if (arg.startsWith("--rig-overrides=")) {
|
|
907
|
-
value = arg.slice("--rig-overrides=".length);
|
|
908
|
-
}
|
|
909
|
-
else {
|
|
944
|
+
for (const { flag, value } of occurrences) {
|
|
945
|
+
if (flag !== "rig-overrides")
|
|
910
946
|
continue;
|
|
911
|
-
}
|
|
912
947
|
if (value === undefined || value.startsWith("--")) {
|
|
913
948
|
throw new Error("--rig-overrides requires JSON or @path/to/file.json");
|
|
914
949
|
}
|
|
@@ -951,21 +986,11 @@ export function parseRigOverridesFlag(args, readFile) {
|
|
|
951
986
|
}
|
|
952
987
|
return parsed;
|
|
953
988
|
}
|
|
954
|
-
export function parseVoicesFlag(
|
|
989
|
+
export function parseVoicesFlag(occurrences, readFile) {
|
|
955
990
|
let raw;
|
|
956
|
-
for (
|
|
957
|
-
|
|
958
|
-
let value;
|
|
959
|
-
if (arg === "--voices") {
|
|
960
|
-
value = args[i + 1];
|
|
961
|
-
i++;
|
|
962
|
-
}
|
|
963
|
-
else if (arg.startsWith("--voices=")) {
|
|
964
|
-
value = arg.slice("--voices=".length);
|
|
965
|
-
}
|
|
966
|
-
else {
|
|
991
|
+
for (const { flag, value } of occurrences) {
|
|
992
|
+
if (flag !== "voices")
|
|
967
993
|
continue;
|
|
968
|
-
}
|
|
969
994
|
if (value === undefined || value.startsWith("--")) {
|
|
970
995
|
throw new Error("--voices requires JSON or @path/to/voices.json");
|
|
971
996
|
}
|
|
@@ -1008,25 +1033,66 @@ export function parseVoicesFlag(args, readFile) {
|
|
|
1008
1033
|
}
|
|
1009
1034
|
return parsed;
|
|
1010
1035
|
}
|
|
1011
|
-
export function
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1036
|
+
export function parseVoiceTreatmentFlag(occurrences, readFile) {
|
|
1037
|
+
const usage = "--voice-treatment requires a treatment name, JSON, or @path/to/voice-treatment.json";
|
|
1038
|
+
let raw;
|
|
1039
|
+
for (const { flag, value } of occurrences) {
|
|
1040
|
+
if (flag !== "voice-treatment")
|
|
1041
|
+
continue;
|
|
1042
|
+
if (value === undefined || value.startsWith("--"))
|
|
1043
|
+
throw new Error(usage);
|
|
1044
|
+
raw = value;
|
|
1045
|
+
}
|
|
1046
|
+
if (raw === undefined)
|
|
1047
|
+
return undefined;
|
|
1048
|
+
let text = raw.trim();
|
|
1049
|
+
if (!text)
|
|
1050
|
+
throw new Error(usage);
|
|
1051
|
+
let fromFile = false;
|
|
1052
|
+
if (text.startsWith("@@")) {
|
|
1053
|
+
text = text.slice(1);
|
|
1054
|
+
}
|
|
1055
|
+
else if (text.startsWith("@")) {
|
|
1056
|
+
const filePath = text.slice(1);
|
|
1057
|
+
if (!filePath) {
|
|
1058
|
+
throw new Error("--voice-treatment @<file> needs a file path after \"@\"");
|
|
1019
1059
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1060
|
+
if (!readFile) {
|
|
1061
|
+
throw new Error(`--voice-treatment: cannot load @${filePath} here (no file access)`);
|
|
1022
1062
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1063
|
+
try {
|
|
1064
|
+
text = readFile(filePath);
|
|
1065
|
+
}
|
|
1066
|
+
catch (e) {
|
|
1067
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1068
|
+
throw new Error(`--voice-treatment: could not read file "${filePath}": ${msg}`);
|
|
1025
1069
|
}
|
|
1026
|
-
|
|
1070
|
+
fromFile = true;
|
|
1071
|
+
}
|
|
1072
|
+
if (!fromFile && !text.startsWith("{"))
|
|
1073
|
+
return { path: text };
|
|
1074
|
+
let parsed;
|
|
1075
|
+
try {
|
|
1076
|
+
parsed = JSON.parse(text);
|
|
1077
|
+
}
|
|
1078
|
+
catch (e) {
|
|
1079
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1080
|
+
throw new Error(`--voice-treatment is not valid JSON: ${msg}`);
|
|
1081
|
+
}
|
|
1082
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1083
|
+
throw new Error('--voice-treatment must be a treatment name, or a JSON object like {"path":"native-prompt","describe":{"HOST 1":"..."}}');
|
|
1084
|
+
}
|
|
1085
|
+
return parsed;
|
|
1086
|
+
}
|
|
1087
|
+
export function parseFillFlag(occurrences) {
|
|
1088
|
+
let name;
|
|
1089
|
+
for (const { flag, value } of occurrences) {
|
|
1090
|
+
if (flag !== "fill")
|
|
1091
|
+
continue;
|
|
1092
|
+
if (value === undefined || value.startsWith("--")) {
|
|
1027
1093
|
throw new Error("--fill requires a saved fill's name");
|
|
1028
1094
|
}
|
|
1029
|
-
const trimmed =
|
|
1095
|
+
const trimmed = value.trim();
|
|
1030
1096
|
if (!trimmed)
|
|
1031
1097
|
throw new Error("--fill requires a saved fill's name");
|
|
1032
1098
|
name = trimmed;
|
|
@@ -1137,6 +1203,12 @@ export function formatWorkflowRun(run) {
|
|
|
1137
1203
|
lines.push(`status: ${runVerdict(run)}${counts ? ` (${counts})` : ""}`);
|
|
1138
1204
|
if (run.isTerminal)
|
|
1139
1205
|
lines.push("terminal: yes");
|
|
1206
|
+
const rulesLine = formatRulesLine(run.provenance);
|
|
1207
|
+
if (rulesLine)
|
|
1208
|
+
lines.push(rulesLine);
|
|
1209
|
+
const treatmentLine = voiceTreatmentLine(run.provenance);
|
|
1210
|
+
if (treatmentLine)
|
|
1211
|
+
lines.push(treatmentLine);
|
|
1140
1212
|
if (run.error)
|
|
1141
1213
|
lines.push(`error: ${run.error}`);
|
|
1142
1214
|
if (Object.keys(run.inputs ?? {}).length > 0) {
|
|
@@ -1828,6 +1900,23 @@ function launchVoicesLine(attached) {
|
|
|
1828
1900
|
}
|
|
1829
1901
|
return said.length > 0 ? `Voices: ${said.join(", ")}` : undefined;
|
|
1830
1902
|
}
|
|
1903
|
+
export function voiceTreatmentLine(provenance) {
|
|
1904
|
+
const voice = provenance?.voice;
|
|
1905
|
+
if (!voice || typeof voice !== "object")
|
|
1906
|
+
return undefined;
|
|
1907
|
+
if (voice.source === "unreadable") {
|
|
1908
|
+
return ("Voice treatment: the setting saved when this run started cannot be read, so no " +
|
|
1909
|
+
"clip will be made. Start the run again.");
|
|
1910
|
+
}
|
|
1911
|
+
const path = voice.path;
|
|
1912
|
+
if (typeof path !== "string" || path === "")
|
|
1913
|
+
return undefined;
|
|
1914
|
+
const where = voice.source === "show" ? "from its Show" : "chosen at launch";
|
|
1915
|
+
const described = voice.source === "launch" && Array.isArray(voice.descriptions) && voice.descriptions.length > 0
|
|
1916
|
+
? `. Written voices: ${voice.descriptions.map((d) => d.speaker).join(", ")}`
|
|
1917
|
+
: "";
|
|
1918
|
+
return `Voice treatment: ${path} (${where})${described}`;
|
|
1919
|
+
}
|
|
1831
1920
|
export async function runFlow(workflowRef, opts, deps) {
|
|
1832
1921
|
let workflowId;
|
|
1833
1922
|
try {
|
|
@@ -1871,6 +1960,7 @@ export async function runFlow(workflowRef, opts, deps) {
|
|
|
1871
1960
|
...(opts.autoApprove === true ? { autoApprove: true } : {}),
|
|
1872
1961
|
...(opts.imageRigOverrides ? { imageRigOverrides: opts.imageRigOverrides } : {}),
|
|
1873
1962
|
...(opts.voices ? { voices: opts.voices } : {}),
|
|
1963
|
+
...(opts.voiceTreatment ? { voiceTreatment: opts.voiceTreatment } : {}),
|
|
1874
1964
|
launchedVia: "cli",
|
|
1875
1965
|
};
|
|
1876
1966
|
const start = await deps.post(RUN_PATH, body);
|
|
@@ -1885,6 +1975,12 @@ export async function runFlow(workflowRef, opts, deps) {
|
|
|
1885
1975
|
if (opts.json && !opts.wait)
|
|
1886
1976
|
return { code: 0, lines: [JSON.stringify(base)] };
|
|
1887
1977
|
const voicesLine = launchVoicesLine(data?.launchVoices);
|
|
1978
|
+
const provenance = { format: data?.format ?? null, voice: data?.voice ?? null };
|
|
1979
|
+
const rulesLine = formatRulesLine(provenance);
|
|
1980
|
+
const treatmentLine = voiceTreatmentLine(provenance);
|
|
1981
|
+
const advisories = Array.isArray(data?.advisories)
|
|
1982
|
+
? data.advisories.filter((line) => typeof line === "string")
|
|
1983
|
+
: [];
|
|
1888
1984
|
const lines = opts.json
|
|
1889
1985
|
? []
|
|
1890
1986
|
: [
|
|
@@ -1892,7 +1988,10 @@ export async function runFlow(workflowRef, opts, deps) {
|
|
|
1892
1988
|
"Workflow run started.",
|
|
1893
1989
|
`runId: ${data.runId}`,
|
|
1894
1990
|
`triggerRunId: ${data.triggerRunId}`,
|
|
1991
|
+
...(rulesLine ? [rulesLine] : []),
|
|
1992
|
+
...advisories,
|
|
1895
1993
|
...(voicesLine ? [voicesLine] : []),
|
|
1994
|
+
...(treatmentLine ? [treatmentLine] : []),
|
|
1896
1995
|
`Poll: exodus workflow status --id ${data.runId}`,
|
|
1897
1996
|
];
|
|
1898
1997
|
if (!opts.wait) {
|
|
@@ -2858,30 +2957,20 @@ async function resumeAndMaybeWait(runId, triggerRunId, headline, opts, deps, lan
|
|
|
2858
2957
|
const prefix = opts.onProgressLine ? [] : startLines;
|
|
2859
2958
|
return { code: waited.code, lines: [...prefix, ...waited.lines] };
|
|
2860
2959
|
}
|
|
2861
|
-
export function parseSlotFlags(
|
|
2960
|
+
export function parseSlotFlags(occurrences) {
|
|
2862
2961
|
const values = {};
|
|
2863
|
-
for (
|
|
2864
|
-
|
|
2865
|
-
let raw;
|
|
2866
|
-
if (arg === "--slot") {
|
|
2867
|
-
raw = args[i + 1];
|
|
2868
|
-
i++;
|
|
2869
|
-
}
|
|
2870
|
-
else if (arg.startsWith("--slot=")) {
|
|
2871
|
-
raw = arg.slice("--slot=".length);
|
|
2872
|
-
}
|
|
2873
|
-
else {
|
|
2962
|
+
for (const { flag, value } of occurrences) {
|
|
2963
|
+
if (flag !== "slot")
|
|
2874
2964
|
continue;
|
|
2875
|
-
|
|
2876
|
-
if (!raw)
|
|
2965
|
+
if (!value)
|
|
2877
2966
|
throw new Error("--slot requires key=value");
|
|
2878
|
-
const eq =
|
|
2967
|
+
const eq = value.indexOf("=");
|
|
2879
2968
|
if (eq <= 0)
|
|
2880
|
-
throw new Error(`--slot must be key=value (got "${
|
|
2881
|
-
const key =
|
|
2969
|
+
throw new Error(`--slot must be key=value (got "${value}")`);
|
|
2970
|
+
const key = value.slice(0, eq).trim();
|
|
2882
2971
|
if (!key)
|
|
2883
|
-
throw new Error(`--slot must include a key (got "${
|
|
2884
|
-
values[key] =
|
|
2972
|
+
throw new Error(`--slot must include a key (got "${value}")`);
|
|
2973
|
+
values[key] = value.slice(eq + 1);
|
|
2885
2974
|
}
|
|
2886
2975
|
return values;
|
|
2887
2976
|
}
|
|
@@ -2953,7 +3042,7 @@ async function maybeReadStdin(flags) {
|
|
|
2953
3042
|
return undefined;
|
|
2954
3043
|
return raw.endsWith("\n") ? raw.slice(0, -1) : raw;
|
|
2955
3044
|
}
|
|
2956
|
-
export async function run(flags) {
|
|
3045
|
+
export async function run(flags, occurrences) {
|
|
2957
3046
|
const positional = parsePositional();
|
|
2958
3047
|
const [sub, ...rest] = positional;
|
|
2959
3048
|
const json = !!flags["json"];
|
|
@@ -3009,7 +3098,7 @@ export async function run(flags) {
|
|
|
3009
3098
|
const workflowRef = rest[0];
|
|
3010
3099
|
if (!workflowRef) {
|
|
3011
3100
|
console.error("Error: workflow run requires <workflowId|name>.");
|
|
3012
|
-
console.log("Usage: exodus workflow run <workflowId|name> [--fill <name>] [--input key=value ...] [--input <fileField>=./path/to/file ...] [--rig-overrides <json|@file>] [--voices <json|@voices.json>] [--auto-approve] [--wait] [--json]");
|
|
3101
|
+
console.log("Usage: exodus workflow run <workflowId|name> [--fill <name>] [--input key=value ...] [--input <fileField>=./path/to/file ...] [--rig-overrides <json|@file>] [--voices <json|@voices.json>] [--voice-treatment <name|json|@file>] [--auto-approve] [--wait] [--json]");
|
|
3013
3102
|
process.exit(1);
|
|
3014
3103
|
}
|
|
3015
3104
|
let inputs;
|
|
@@ -3017,13 +3106,15 @@ export async function run(flags) {
|
|
|
3017
3106
|
let autoApprove;
|
|
3018
3107
|
let imageRigOverrides;
|
|
3019
3108
|
let voices;
|
|
3109
|
+
let voiceTreatment;
|
|
3020
3110
|
try {
|
|
3021
|
-
inputs = parseRawInputFlags(
|
|
3022
|
-
rejectTerminalFlag(
|
|
3023
|
-
fill = parseFillFlag(
|
|
3111
|
+
inputs = parseRawInputFlags(occurrences);
|
|
3112
|
+
rejectTerminalFlag(occurrences);
|
|
3113
|
+
fill = parseFillFlag(occurrences);
|
|
3024
3114
|
autoApprove = parseAutoApproveFlag(process.argv.slice(3));
|
|
3025
|
-
imageRigOverrides = parseRigOverridesFlag(
|
|
3026
|
-
voices = parseVoicesFlag(
|
|
3115
|
+
imageRigOverrides = parseRigOverridesFlag(occurrences, defaultDeps.readFile);
|
|
3116
|
+
voices = parseVoicesFlag(occurrences, defaultDeps.readFile);
|
|
3117
|
+
voiceTreatment = parseVoiceTreatmentFlag(occurrences, defaultDeps.readFile);
|
|
3027
3118
|
}
|
|
3028
3119
|
catch (e) {
|
|
3029
3120
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -3035,6 +3126,7 @@ export async function run(flags) {
|
|
|
3035
3126
|
autoApprove,
|
|
3036
3127
|
imageRigOverrides,
|
|
3037
3128
|
voices,
|
|
3129
|
+
voiceTreatment,
|
|
3038
3130
|
wait: flags["wait"] === true,
|
|
3039
3131
|
json,
|
|
3040
3132
|
out: flagString(flags, "out"),
|
|
@@ -3167,7 +3259,7 @@ export async function run(flags) {
|
|
|
3167
3259
|
}
|
|
3168
3260
|
let values;
|
|
3169
3261
|
try {
|
|
3170
|
-
values = parseSlotFlags(
|
|
3262
|
+
values = parseSlotFlags(occurrences);
|
|
3171
3263
|
}
|
|
3172
3264
|
catch (e) {
|
|
3173
3265
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -3205,7 +3297,7 @@ export async function run(flags) {
|
|
|
3205
3297
|
}
|
|
3206
3298
|
let fireOverrides;
|
|
3207
3299
|
try {
|
|
3208
|
-
fireOverrides = parseRigOverridesFlag(
|
|
3300
|
+
fireOverrides = parseRigOverridesFlag(occurrences, defaultDeps.readFile);
|
|
3209
3301
|
}
|
|
3210
3302
|
catch (e) {
|
|
3211
3303
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface FlagOccurrence {
|
|
2
|
+
flag: string;
|
|
3
|
+
value: string | undefined;
|
|
4
|
+
}
|
|
5
|
+
export interface ParsedArgs {
|
|
6
|
+
command: string;
|
|
7
|
+
flags: Record<string, string | boolean>;
|
|
8
|
+
occurrences: FlagOccurrence[];
|
|
9
|
+
}
|
|
10
|
+
export declare function parseArgs(argv: string[]): ParsedArgs;
|
package/dist/lib/args.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function parseArgs(argv) {
|
|
2
|
+
const [, , rawCommand = "help", ...rest] = argv;
|
|
3
|
+
const command = ["--help", "-h", "-help"].includes(rawCommand) ? "help" : rawCommand;
|
|
4
|
+
const flags = {};
|
|
5
|
+
const occurrences = [];
|
|
6
|
+
let i = 0;
|
|
7
|
+
while (i < rest.length) {
|
|
8
|
+
const arg = rest[i];
|
|
9
|
+
if (arg === "--help" || arg === "-h") {
|
|
10
|
+
flags["help"] = true;
|
|
11
|
+
i++;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (arg.startsWith("--")) {
|
|
15
|
+
const key = arg.slice(2);
|
|
16
|
+
const next = rest[i + 1];
|
|
17
|
+
const eq = key.indexOf("=");
|
|
18
|
+
occurrences.push(eq === -1
|
|
19
|
+
? { flag: key, value: next }
|
|
20
|
+
: { flag: key.slice(0, eq), value: key.slice(eq + 1) });
|
|
21
|
+
if (key.startsWith("no-")) {
|
|
22
|
+
flags[key.slice(3)] = false;
|
|
23
|
+
i++;
|
|
24
|
+
}
|
|
25
|
+
else if (next !== undefined && !next.startsWith("--")) {
|
|
26
|
+
flags[key] = next;
|
|
27
|
+
i += 2;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
flags[key] = true;
|
|
31
|
+
i++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { command, flags, occurrences };
|
|
39
|
+
}
|