@aicopycoders/exodus 2026.9.2000-beta.3 → 2026.9.2000-beta.4
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/dist/commands/image.js +1 -1
- package/dist/commands/template.js +11 -4
- package/dist/commands/video.d.ts +2 -0
- package/dist/commands/video.js +16 -3
- package/dist/commands/workflow.js +41 -3
- package/package.json +1 -1
package/assets/docs/PIPELINES.md
CHANGED
|
@@ -152,7 +152,8 @@ run options:
|
|
|
152
152
|
--mode auto|manual|hybrid Generation mode (default: auto)
|
|
153
153
|
--render-mode images|prompts prompts = skip render, return LLM prompts only (default: images)
|
|
154
154
|
--aspect 1:1|9:16 Image aspect ratio (default: 1:1)
|
|
155
|
-
--model gpt-image-2|
|
|
155
|
+
--model gpt-image-2.5|gpt-image-2.5-sunburst|gpt-image-2|nano-banana-pro
|
|
156
|
+
Kie.ai model (default: gpt-image-2.5)
|
|
156
157
|
--realism off|realistic Realism enforcement (default: off)
|
|
157
158
|
--quantities "slug:N,slug:N" manual-mode per-type counts (e.g. "testimonial:3,hero:2")
|
|
158
159
|
--pairings '<JSON>' hybrid-mode host/modifier pairs: '[{"host":"testimonial","modifier":"hero"}]'
|
|
@@ -244,6 +244,7 @@ internals into chat.
|
|
|
244
244
|
- The rig's child image runs are still `creativeSuiteRuns` with engine `meme`,
|
|
245
245
|
so finished memes appear in the creative-suite library alongside other engines
|
|
246
246
|
and `exodus browse --agent meme` still covers them.
|
|
247
|
-
- `run` accepts only the
|
|
248
|
-
`nano-banana-pro`,
|
|
249
|
-
|
|
247
|
+
- `run` accepts only the models the rig renders (`gpt-image-2.5`, the house
|
|
248
|
+
default, plus `gpt-image-2.5-sunburst`, `gpt-image-2` and `nano-banana-pro`,
|
|
249
|
+
or their Kie slugs); anything else is a 400 listing the accepted values. It
|
|
250
|
+
used to accept any value and silently ignore it.
|
|
@@ -17,7 +17,8 @@ run options:
|
|
|
17
17
|
--mode auto|manual|hybrid Generation mode (default: auto)
|
|
18
18
|
--render-mode images|prompts prompts = skip render, return LLM prompts only (default: images)
|
|
19
19
|
--aspect 1:1|9:16 Image aspect ratio (default: 1:1)
|
|
20
|
-
--model 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
|
--pairings '<JSON>' hybrid-mode host/modifier pairs: '[{"host":"testimonial","modifier":"hero"}]'
|
package/dist/commands/image.js
CHANGED
|
@@ -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|
|
|
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
|
|
107
|
-
|
|
108
|
-
|
|
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
|
}
|
package/dist/commands/video.d.ts
CHANGED
|
@@ -173,6 +173,7 @@ export type RunStop = {
|
|
|
173
173
|
at: "storyboard-gate";
|
|
174
174
|
nodeId?: string;
|
|
175
175
|
framesNodeId?: string;
|
|
176
|
+
showAd?: true;
|
|
176
177
|
} | {
|
|
177
178
|
at: "final-watch";
|
|
178
179
|
} | {
|
|
@@ -201,6 +202,7 @@ export declare function resolveStop(stop: RunStop, cutAttached: boolean | null):
|
|
|
201
202
|
export declare function resolveStopAtPark(stop: RunStop, runId: string, deps: Pick<VideoDeps, "get">): Promise<ResolvedStop>;
|
|
202
203
|
export declare function stageWord(stage: string): string;
|
|
203
204
|
export declare function stepName(kind: string | undefined): string;
|
|
205
|
+
export declare function isShowAd(run: Pick<VideoRun, "moduleOwned">): boolean;
|
|
204
206
|
export declare function reviewUrl(dashboardUrl: string, run: Pick<VideoRun, "_id" | "workflowId" | "moduleOwned">): string;
|
|
205
207
|
export declare function stopLines(stop: ResolvedStop, runId: string, runUrl: string): string[];
|
|
206
208
|
export interface PullDownload {
|
package/dist/commands/video.js
CHANGED
|
@@ -250,6 +250,7 @@ export function classifyRun(run) {
|
|
|
250
250
|
...(pausedNode.kind === "scene-frames"
|
|
251
251
|
? { framesNodeId: pausedNode.nodeId }
|
|
252
252
|
: {}),
|
|
253
|
+
...(isShowAd(run) ? { showAd: true } : {}),
|
|
253
254
|
};
|
|
254
255
|
}
|
|
255
256
|
if (parkedAtFinalWatch(run))
|
|
@@ -300,8 +301,11 @@ export function stepName(kind) {
|
|
|
300
301
|
return "A step in this run";
|
|
301
302
|
return STEP_NAMES[kind] ?? `The ${kind} step`;
|
|
302
303
|
}
|
|
304
|
+
export function isShowAd(run) {
|
|
305
|
+
return run.moduleOwned === true;
|
|
306
|
+
}
|
|
303
307
|
export function reviewUrl(dashboardUrl, run) {
|
|
304
|
-
if (run
|
|
308
|
+
if (isShowAd(run))
|
|
305
309
|
return `${dashboardUrl}/video?ad=${run._id}`;
|
|
306
310
|
if (run.workflowId)
|
|
307
311
|
return `${dashboardUrl}/workflows/${run.workflowId}/runs/${run._id}`;
|
|
@@ -313,8 +317,10 @@ export function stopLines(stop, runId, runUrl) {
|
|
|
313
317
|
"Parked: the storyboard is waiting for your yes.",
|
|
314
318
|
`Read it: exodus video storyboard ${runId}`,
|
|
315
319
|
`Approve it: exodus video approve ${runId}`,
|
|
316
|
-
`Send back: exodus video flag ${runId} --note "what's wrong"`,
|
|
317
320
|
];
|
|
321
|
+
if (stop.showAd) {
|
|
322
|
+
lines.push(`Send back: exodus video flag ${runId} --note "what's wrong"`);
|
|
323
|
+
}
|
|
318
324
|
if (stop.framesNodeId) {
|
|
319
325
|
lines.push(`Redo one frame: exodus video retry-frame ${runId} --node ${stop.framesNodeId} --scene <n>`);
|
|
320
326
|
}
|
|
@@ -1119,7 +1125,14 @@ export async function storyboardFlow(runId, json, deps) {
|
|
|
1119
1125
|
}
|
|
1120
1126
|
}
|
|
1121
1127
|
}
|
|
1122
|
-
|
|
1128
|
+
const runRes = await deps
|
|
1129
|
+
.get(`${RUN_PATH}?runId=${encodeURIComponent(runId)}`)
|
|
1130
|
+
.catch(() => null);
|
|
1131
|
+
const showAd = runRes?.ok === true && isShowAd(asVideoRun(runRes.data));
|
|
1132
|
+
lines.push("", `approve with: exodus video approve ${runId}`);
|
|
1133
|
+
if (showAd) {
|
|
1134
|
+
lines.push(`send it back: exodus video flag ${runId} --note "what's wrong"`);
|
|
1135
|
+
}
|
|
1123
1136
|
if (cards.framesNodeId) {
|
|
1124
1137
|
lines.push(`redo one frame: exodus video retry-frame ${runId} --node ${cards.framesNodeId} --scene <n>`);
|
|
1125
1138
|
}
|
|
@@ -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
|
|
|
@@ -1382,6 +1382,43 @@ function planArtifactFile(artifact) {
|
|
|
1382
1382
|
url,
|
|
1383
1383
|
};
|
|
1384
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
|
+
}
|
|
1385
1422
|
export async function saveDeliveries(run, dir, deps) {
|
|
1386
1423
|
const paths = [];
|
|
1387
1424
|
if (!run.isTerminal) {
|
|
@@ -1416,9 +1453,10 @@ export async function saveDeliveries(run, dir, deps) {
|
|
|
1416
1453
|
lines.push(` skipped ${delivery.label} (${delivery.key}) — ${why}`);
|
|
1417
1454
|
continue;
|
|
1418
1455
|
}
|
|
1456
|
+
const suffixes = artifactFileSuffixes(delivery.artifacts);
|
|
1419
1457
|
for (let i = 0; i < delivery.artifacts.length; i++) {
|
|
1420
1458
|
const plan = planArtifactFile(delivery.artifacts[i]);
|
|
1421
|
-
const suffix
|
|
1459
|
+
const { suffix, renamed } = suffixes[i];
|
|
1422
1460
|
if (plan.kind === "none") {
|
|
1423
1461
|
lines.push(` skipped ${delivery.label} (${delivery.key})${suffix} — ${plan.reason}`);
|
|
1424
1462
|
continue;
|
|
@@ -1429,7 +1467,7 @@ export async function saveDeliveries(run, dir, deps) {
|
|
|
1429
1467
|
deps.writeFile(file, plan.body);
|
|
1430
1468
|
else
|
|
1431
1469
|
await downloadToFile(plan.url, file);
|
|
1432
|
-
lines.push(` wrote ${file}`);
|
|
1470
|
+
lines.push(` wrote ${file}${renamed ? " — renamed to avoid a collision" : ""}`);
|
|
1433
1471
|
paths.push(file);
|
|
1434
1472
|
}
|
|
1435
1473
|
catch (e) {
|