@aicopycoders/exodus 2026.9.2000-beta.1 → 2026.9.2000-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.
|
@@ -104,7 +104,10 @@ under it:
|
|
|
104
104
|
- storyboard gate: needs `approve`. When the stop block prints a `Redo one
|
|
105
105
|
frame:` line with a node id, one still can be redone first with `retry-frame`;
|
|
106
106
|
a gate parked before the stills are drawn has nothing to redraw.
|
|
107
|
-
- final watch: every piece is made; needs `pull`, a cut, and `upload`.
|
|
107
|
+
- final watch: every piece is made; needs `pull`, a cut, and `upload`. Once a
|
|
108
|
+
cut is uploaded the stop block leads with `approve` instead, and `status
|
|
109
|
+
--json` says which state it is in (`stop.cutAttached`, plus the same next-step
|
|
110
|
+
lines under `guidance`). It is also
|
|
108
111
|
the one park where a finished clip can be redone, with `retry-clip`. A run
|
|
109
112
|
only reaches this park if its video node sets `finalWatch: true` — otherwise
|
|
110
113
|
it skips straight to finished.
|
|
@@ -677,8 +680,11 @@ them. If the words are wrong, that is a new run.
|
|
|
677
680
|
**A redo whose take fails the checks keeps the original clip.** On a finished
|
|
678
681
|
clip the row stays `done` with the ORIGINAL take and only its findings are
|
|
679
682
|
refreshed, because a worse take must never replace a delivered one. So never
|
|
680
|
-
report that the clip changed
|
|
681
|
-
|
|
683
|
+
report that the clip changed without reading `status` again: under the scene it
|
|
684
|
+
prints what the redo did, `new take accepted (take N)` or `new take rejected,
|
|
685
|
+
original kept (take N)`, and `status --json` carries the same sentence on the
|
|
686
|
+
row as `lastRedo.label`. A row with no `lastRedo` was redone before outcomes
|
|
687
|
+
were recorded; for those, pull and compare the file before and after.
|
|
682
688
|
|
|
683
689
|
**Re-cut afterwards.** A cut you already uploaded still holds the old clip, and
|
|
684
690
|
the CLI says so when that is the case. Pull again, re-cut and upload again before
|
package/dist/commands/genesis.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import { createInterface } from "node:readline/promises";
|
|
4
3
|
import { apiGet, apiPost, getDashboardUrl } from "../lib/client.js";
|
|
5
4
|
import { pollUntilDone } from "../lib/poll.js";
|
|
6
5
|
import { displayRunStatus, formatGenesisRun, formatError, tickerRunStatus } from "../lib/format.js";
|
|
@@ -278,52 +277,13 @@ export async function run(flags) {
|
|
|
278
277
|
return submitGenesis(action.body, { noWait, cc });
|
|
279
278
|
}
|
|
280
279
|
}
|
|
281
|
-
async function listBank(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
console.log(formatError(res));
|
|
285
|
-
process.exit(1);
|
|
286
|
-
}
|
|
287
|
-
const ideas = Array.isArray(res.data.ideas) ? res.data.ideas : [];
|
|
288
|
-
if (ideas.length === 0) {
|
|
289
|
-
console.log("Bank is empty.");
|
|
290
|
-
console.log('Fill it with: exodus genesis scrape (or paste a reel: exodus genesis --reel "<url>")');
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
console.log(`## Idea Bank (${ideas.length} reel${ideas.length === 1 ? "" : "s"})`);
|
|
294
|
-
console.log("");
|
|
295
|
-
for (const i of ideas) {
|
|
296
|
-
const hook = i.hook ?? "(no hook)";
|
|
297
|
-
const user = typeof i.sourceUsername === "string" && i.sourceUsername.length > 0
|
|
298
|
-
? ` @${i.sourceUsername.replace(/^@+/, "")}`
|
|
299
|
-
: "";
|
|
300
|
-
const score = typeof i.relevanceScore === "number" ? ` score=${i.relevanceScore.toFixed(2)}` : "";
|
|
301
|
-
const used = typeof i.useCount === "number" && i.useCount > 0 ? " · used" : "";
|
|
302
|
-
console.log(` • ${hook}${user}${score}`);
|
|
303
|
-
console.log(` id: ${i._id ?? "?"} [${i.status ?? "new"}${used}]`);
|
|
304
|
-
if (i.sourceUrl)
|
|
305
|
-
console.log(` ${i.sourceUrl}`);
|
|
306
|
-
}
|
|
307
|
-
console.log("");
|
|
308
|
-
console.log("Write from one: exodus genesis --from-bank --idea <id>");
|
|
280
|
+
async function listBank(_limit) {
|
|
281
|
+
console.error(`This command has been retired. View the idea bank in the dashboard: ${getDashboardUrl()}/idea-bank`);
|
|
282
|
+
process.exit(1);
|
|
309
283
|
}
|
|
310
|
-
async function runFromBank(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
console.log(formatError(res));
|
|
314
|
-
process.exit(1);
|
|
315
|
-
}
|
|
316
|
-
const idea = (res.data.ideas ?? [])[0];
|
|
317
|
-
if (!idea) {
|
|
318
|
-
console.error(`No banked idea found for id "${ideaId}". List the bank: exodus genesis --from-bank`);
|
|
319
|
-
process.exit(1);
|
|
320
|
-
}
|
|
321
|
-
if (!idea.sourceUrl) {
|
|
322
|
-
console.error(`Banked idea "${ideaId}" has no source reel URL to write from.`);
|
|
323
|
-
process.exit(1);
|
|
324
|
-
}
|
|
325
|
-
console.log(`Writing from banked reel: ${idea.sourceUrl}`);
|
|
326
|
-
return runReel([idea.sourceUrl], opts, rt);
|
|
284
|
+
async function runFromBank(_ideaId, _opts, _rt) {
|
|
285
|
+
console.error(`This command has been retired. Write from banked ideas in the dashboard: ${getDashboardUrl()}/idea-bank`);
|
|
286
|
+
process.exit(1);
|
|
327
287
|
}
|
|
328
288
|
async function runReel(urls, opts, rt) {
|
|
329
289
|
console.log(`Transcribing ${urls.length === 1 ? "the reel" : `${urls.length} reels`} → idea → Genesis…`);
|
|
@@ -613,92 +573,11 @@ function requireActiveBrand() {
|
|
|
613
573
|
}
|
|
614
574
|
return slug;
|
|
615
575
|
}
|
|
616
|
-
async function runConnectInstagram(
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
if (!start.ok || !start.data.liveViewUrl) {
|
|
620
|
-
console.log(formatError(start));
|
|
621
|
-
process.exit(1);
|
|
622
|
-
}
|
|
623
|
-
console.log(`\nConnecting an Instagram account to brand "${client}".`);
|
|
624
|
-
console.log("\n1. Open this link in your browser:\n");
|
|
625
|
-
console.log(` ${start.data.liveViewUrl}\n`);
|
|
626
|
-
console.log('2. Go to instagram.com and log into the account for THIS brand.');
|
|
627
|
-
console.log("3. Scroll your feed for a bit so the algorithm learns the niche.");
|
|
628
|
-
console.log("4. Come back here and press Enter.\n");
|
|
629
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
630
|
-
try {
|
|
631
|
-
await rl.question("Press Enter once you're logged in and done grooming... ");
|
|
632
|
-
let username = typeof flags["username"] === "string" ? flags["username"].trim() : "";
|
|
633
|
-
while (!username) {
|
|
634
|
-
username = (await rl.question("Instagram handle you just logged in as (e.g. mybrand): ")).trim();
|
|
635
|
-
}
|
|
636
|
-
const finish = await apiPost("/api/v2/genesis/instagram/connect/finish", { client, igUsername: username }, { ccCommand: cc });
|
|
637
|
-
if (!finish.ok) {
|
|
638
|
-
console.log(formatError(finish));
|
|
639
|
-
process.exit(1);
|
|
640
|
-
}
|
|
641
|
-
console.log(`\n✓ Instagram @${username.replace(/^@+/, "")} connected to "${client}".`);
|
|
642
|
-
console.log("Fill the idea bank now: exodus genesis scrape --organic");
|
|
643
|
-
}
|
|
644
|
-
finally {
|
|
645
|
-
rl.close();
|
|
646
|
-
}
|
|
576
|
+
async function runConnectInstagram(_flags, _cc) {
|
|
577
|
+
console.error(`This command has been retired. Connect Instagram accounts in the dashboard: ${getDashboardUrl()}/settings`);
|
|
578
|
+
process.exit(1);
|
|
647
579
|
}
|
|
648
|
-
async function runScrape(
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (flags["organic"] === true) {
|
|
652
|
-
const smoke = flags["smoke"] === true;
|
|
653
|
-
const res = await apiPost("/api/v2/scout/organic", { client, ...(smoke ? { smoke: true } : {}) }, { ccCommand: cc });
|
|
654
|
-
if (!res.ok || !res.data.runId) {
|
|
655
|
-
console.log(formatError(res));
|
|
656
|
-
process.exit(1);
|
|
657
|
-
}
|
|
658
|
-
console.log(`Organic scrape started for "${client}": ${res.data.runId}${smoke ? " (smoke)" : ""}`);
|
|
659
|
-
return pollScrape(res.data.runId, noWait, 1_800_000);
|
|
660
|
-
}
|
|
661
|
-
const term = typeof flags["term"] === "string" ? flags["term"] : undefined;
|
|
662
|
-
const body = { sourceMode: "fresh", client };
|
|
663
|
-
if (term)
|
|
664
|
-
body.term = term;
|
|
665
|
-
const res = await apiPost("/api/v2/scout", body, { ccCommand: cc });
|
|
666
|
-
if (!res.ok || !res.data.runId) {
|
|
667
|
-
console.log(formatError(res));
|
|
668
|
-
process.exit(1);
|
|
669
|
-
}
|
|
670
|
-
console.log(`Discovery scrape started for "${client}": ${res.data.runId}`);
|
|
671
|
-
return pollScrape(res.data.runId, noWait, 1_200_000);
|
|
672
|
-
}
|
|
673
|
-
async function pollScrape(runId, noWait, timeoutMs) {
|
|
674
|
-
if (noWait) {
|
|
675
|
-
console.log(`Run ID: ${runId}`);
|
|
676
|
-
console.log(`Check status: exodus status --type scout --id ${runId}`);
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
console.log("Polling for completion...");
|
|
680
|
-
const result = await pollUntilDone({
|
|
681
|
-
path: `/api/v2/scout?runId=${runId}`,
|
|
682
|
-
intervalMs: 15_000,
|
|
683
|
-
timeoutMs,
|
|
684
|
-
onProgress: (data) => {
|
|
685
|
-
const status = data["status"];
|
|
686
|
-
const captured = data["organicCaptured"];
|
|
687
|
-
const qualified = data["organicQualified"];
|
|
688
|
-
const counts = captured !== undefined || qualified !== undefined
|
|
689
|
-
? ` captured=${captured ?? 0} qualified=${qualified ?? 0}`
|
|
690
|
-
: "";
|
|
691
|
-
if (status)
|
|
692
|
-
process.stdout.write(`\r status: ${tickerRunStatus(status)}${counts} `);
|
|
693
|
-
},
|
|
694
|
-
});
|
|
695
|
-
console.log();
|
|
696
|
-
if (result.timedOut) {
|
|
697
|
-
console.log(`Timed out waiting. Check later: exodus status --type scout --id ${runId}`);
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
console.log(`Done. Banked reels are ready to write: exodus genesis --from-bank`);
|
|
701
|
-
}
|
|
702
|
-
if (!result.ok && !result.timedOut)
|
|
703
|
-
process.exit(1);
|
|
580
|
+
async function runScrape(_flags, _cc) {
|
|
581
|
+
console.error(`This command has been retired. Fill the idea bank from the Mining page in the dashboard: ${getDashboardUrl()}/mining`);
|
|
582
|
+
process.exit(1);
|
|
704
583
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { apiGet } from "../lib/client.js";
|
|
1
|
+
import { apiGet, getDashboardUrl } from "../lib/client.js";
|
|
2
2
|
import { formatGeneration, formatGenesisRun, formatError, displayRunStatus } from "../lib/format.js";
|
|
3
|
-
import { hydrateScoutIdeasCount } from "../lib/scout-hydrate.js";
|
|
4
3
|
import { formatIntelResult as formatIntelResultRich } from "../lib/intel-format.js";
|
|
5
4
|
export const helpText = `
|
|
6
5
|
exodus status — Check the status of a pipeline run
|
|
@@ -12,19 +11,13 @@ Required:
|
|
|
12
11
|
--id <runId> The generation or run ID returned by a prior exodus command
|
|
13
12
|
|
|
14
13
|
Options:
|
|
15
|
-
--type <pipeline> Pipeline type: generation (default) | genesis | intel | pulse
|
|
14
|
+
--type <pipeline> Pipeline type: generation (default) | genesis | intel | pulse
|
|
16
15
|
| creative | template
|
|
17
16
|
Spark, Viral, Mirror, Remix runs use --type generation (default).
|
|
18
|
-
Intel, Pulse
|
|
17
|
+
Intel, Pulse require --type intel|pulse.
|
|
19
18
|
Creative-suite (native/copy-derived/ref-match/meme)
|
|
20
19
|
use --type creative; Template uses --type template.
|
|
21
20
|
`.trim();
|
|
22
|
-
async function fetchScoutIdeasCount(runId) {
|
|
23
|
-
const res = await apiGet(`/api/v2/scout/ideas?runId=${runId}&limit=1`);
|
|
24
|
-
if (res.ok && typeof res.data.count === "number")
|
|
25
|
-
return res.data.count;
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
21
|
export function formatIntelResult(data) {
|
|
29
22
|
return formatIntelResultRich(data).replace("## Intel Analysis Result", "## Intel Status");
|
|
30
23
|
}
|
|
@@ -157,18 +150,21 @@ export async function run(flags) {
|
|
|
157
150
|
process.exit(1);
|
|
158
151
|
}
|
|
159
152
|
const type = flags["type"] ?? "generation";
|
|
153
|
+
if (type === "scout") {
|
|
154
|
+
console.error(`--type scout has been retired. Check run status in the dashboard: ${getDashboardUrl()}/runs`);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
160
157
|
const pathMap = {
|
|
161
158
|
generation: `/api/v2/generations?id=${id}`,
|
|
162
159
|
genesis: `/api/v2/genesis?id=${id}`,
|
|
163
160
|
intel: `/api/v2/intel?id=${id}`,
|
|
164
161
|
pulse: `/api/v2/pulse?id=${id}`,
|
|
165
|
-
scout: `/api/v2/scout?runId=${id}`,
|
|
166
162
|
creative: `/api/v2/creative?runId=${id}`,
|
|
167
163
|
template: `/api/v2/template?runId=${id}`,
|
|
168
164
|
};
|
|
169
165
|
const path = pathMap[type];
|
|
170
166
|
if (!path) {
|
|
171
|
-
console.error(`Unknown type: "${type}". Valid types: generation, genesis, intel, pulse,
|
|
167
|
+
console.error(`Unknown type: "${type}". Valid types: generation, genesis, intel, pulse, creative, template`);
|
|
172
168
|
process.exit(1);
|
|
173
169
|
}
|
|
174
170
|
const res = await apiGet(path);
|
|
@@ -176,9 +172,6 @@ export async function run(flags) {
|
|
|
176
172
|
console.log(formatError(res));
|
|
177
173
|
process.exit(1);
|
|
178
174
|
}
|
|
179
|
-
if (type === "scout") {
|
|
180
|
-
await hydrateScoutIdeasCount(id, res.data, fetchScoutIdeasCount);
|
|
181
|
-
}
|
|
182
175
|
switch (type) {
|
|
183
176
|
case "generation":
|
|
184
177
|
console.log(formatGeneration(res.data));
|
|
@@ -192,9 +185,6 @@ export async function run(flags) {
|
|
|
192
185
|
case "pulse":
|
|
193
186
|
console.log(formatPulseResult(res.data));
|
|
194
187
|
break;
|
|
195
|
-
case "scout":
|
|
196
|
-
console.log(formatScoutResult(res.data));
|
|
197
|
-
break;
|
|
198
188
|
case "creative":
|
|
199
189
|
console.log(formatCreativeSuiteStatus("Creative Suite Status", res.data));
|
|
200
190
|
break;
|
package/dist/commands/video.d.ts
CHANGED
|
@@ -117,6 +117,11 @@ export interface NodeItem {
|
|
|
117
117
|
flagged?: boolean;
|
|
118
118
|
findings?: ClipFinding[];
|
|
119
119
|
staleClaim?: boolean;
|
|
120
|
+
lastRedo?: {
|
|
121
|
+
take: number;
|
|
122
|
+
outcome: string;
|
|
123
|
+
label: string;
|
|
124
|
+
};
|
|
120
125
|
artifact?: ArtifactSubset;
|
|
121
126
|
}
|
|
122
127
|
export interface ShowRow {
|
|
@@ -184,11 +189,20 @@ export type RunStop = {
|
|
|
184
189
|
at: "finished";
|
|
185
190
|
status: string;
|
|
186
191
|
};
|
|
192
|
+
export type ResolvedStop = Exclude<RunStop, {
|
|
193
|
+
at: "final-watch";
|
|
194
|
+
}> | {
|
|
195
|
+
at: "final-watch";
|
|
196
|
+
cutAttached: boolean | null;
|
|
197
|
+
};
|
|
187
198
|
export declare function classifyRun(run: VideoRun): RunStop;
|
|
199
|
+
export declare function hasAttachedCut(items: NodeItem[]): boolean;
|
|
200
|
+
export declare function resolveStop(stop: RunStop, cutAttached: boolean | null): ResolvedStop;
|
|
201
|
+
export declare function resolveStopAtPark(stop: RunStop, runId: string, deps: Pick<VideoDeps, "get">): Promise<ResolvedStop>;
|
|
188
202
|
export declare function stageWord(stage: string): string;
|
|
189
203
|
export declare function stepName(kind: string | undefined): string;
|
|
190
204
|
export declare function reviewUrl(dashboardUrl: string, run: Pick<VideoRun, "_id" | "workflowId" | "moduleOwned">): string;
|
|
191
|
-
export declare function stopLines(stop:
|
|
205
|
+
export declare function stopLines(stop: ResolvedStop, runId: string, runUrl: string): string[];
|
|
192
206
|
export interface PullDownload {
|
|
193
207
|
file: string;
|
|
194
208
|
url: string;
|
package/dist/commands/video.js
CHANGED
|
@@ -254,6 +254,22 @@ export function classifyRun(run) {
|
|
|
254
254
|
const active = run.nodes.find((n) => n.status === "running");
|
|
255
255
|
return { at: "running", stage: active?.kind ?? "starting" };
|
|
256
256
|
}
|
|
257
|
+
export function hasAttachedCut(items) {
|
|
258
|
+
return items.some((i) => i.itemKind === "final" && i.status === "done");
|
|
259
|
+
}
|
|
260
|
+
export function resolveStop(stop, cutAttached) {
|
|
261
|
+
if (stop.at !== "final-watch")
|
|
262
|
+
return stop;
|
|
263
|
+
return { at: "final-watch", cutAttached };
|
|
264
|
+
}
|
|
265
|
+
export async function resolveStopAtPark(stop, runId, deps) {
|
|
266
|
+
if (stop.at !== "final-watch")
|
|
267
|
+
return stop;
|
|
268
|
+
const res = await deps.get(`${ITEMS_PATH}?runId=${encodeURIComponent(runId)}`);
|
|
269
|
+
if (!res.ok)
|
|
270
|
+
return resolveStop(stop, null);
|
|
271
|
+
return resolveStop(stop, hasAttachedCut(res.data.items ?? []));
|
|
272
|
+
}
|
|
257
273
|
const STAGE_WORDS = {
|
|
258
274
|
brief: "reading the script",
|
|
259
275
|
storyboard: "writing the storyboard",
|
|
@@ -300,6 +316,21 @@ export function stopLines(stop, runId, runUrl) {
|
|
|
300
316
|
return lines;
|
|
301
317
|
}
|
|
302
318
|
if (stop.at === "final-watch") {
|
|
319
|
+
if (stop.cutAttached === null) {
|
|
320
|
+
return [
|
|
321
|
+
"Parked: every piece is made.",
|
|
322
|
+
`Couldn't check whether a cut is already uploaded. See where it stands: exodus video status ${runId}`,
|
|
323
|
+
`Watch it here: ${runUrl}`,
|
|
324
|
+
];
|
|
325
|
+
}
|
|
326
|
+
if (stop.cutAttached) {
|
|
327
|
+
return [
|
|
328
|
+
"Parked: your cut is uploaded and waiting for your approval.",
|
|
329
|
+
`Approve it: exodus video approve ${runId}`,
|
|
330
|
+
`Watch it here: ${runUrl}`,
|
|
331
|
+
"Changed your mind? You can still redo a clip and upload a new cut.",
|
|
332
|
+
];
|
|
333
|
+
}
|
|
303
334
|
return [
|
|
304
335
|
"Parked: every piece is made and the run is waiting for a cut.",
|
|
305
336
|
`Pull the pieces: exodus video pull ${runId} --out ./ad-${runId}`,
|
|
@@ -891,15 +922,16 @@ export async function waitFlow(runId, opts, deps) {
|
|
|
891
922
|
}
|
|
892
923
|
continue;
|
|
893
924
|
}
|
|
925
|
+
const resolved = await resolveStopAtPark(stop, runId, deps);
|
|
894
926
|
if (opts.json) {
|
|
895
927
|
return {
|
|
896
928
|
code: stop.at === "failed" ? 1 : 0,
|
|
897
|
-
lines: [JSON.stringify({ runId, stop, status: run.status, url: opts.url })],
|
|
929
|
+
lines: [JSON.stringify({ runId, stop: resolved, status: run.status, url: opts.url })],
|
|
898
930
|
};
|
|
899
931
|
}
|
|
900
932
|
return {
|
|
901
933
|
code: stop.at === "failed" ? 1 : 0,
|
|
902
|
-
lines: [...lines, ...stopLines(
|
|
934
|
+
lines: [...lines, ...stopLines(resolved, runId, reviewUrl(deps.dashboardUrl, run))],
|
|
903
935
|
};
|
|
904
936
|
}
|
|
905
937
|
const timeoutLine = `Still running after ${Math.round((maxPolls * interval) / 60000)} minutes. Check in with: exodus video status ${runId}`;
|
|
@@ -933,15 +965,18 @@ export async function statusFlow(runId, json, deps) {
|
|
|
933
965
|
if (!itemsRes.ok)
|
|
934
966
|
return errorResult(itemsRes, json);
|
|
935
967
|
const items = itemsRes.data.items ?? [];
|
|
936
|
-
const
|
|
937
|
-
const
|
|
968
|
+
const hasFinal = hasAttachedCut(items);
|
|
969
|
+
const stop = resolveStop(classifyRun(run), hasFinal);
|
|
970
|
+
const guidance = stopLines(stop, runId, reviewUrl(deps.dashboardUrl, run));
|
|
938
971
|
const warnings = run.nodes
|
|
939
972
|
.filter((n) => n.warning)
|
|
940
973
|
.map((n) => ({ nodeId: n.nodeId, step: n.kind, warning: n.warning }));
|
|
941
974
|
if (json) {
|
|
942
975
|
return {
|
|
943
976
|
code: 0,
|
|
944
|
-
lines: [
|
|
977
|
+
lines: [
|
|
978
|
+
JSON.stringify({ runId, status: run.status, stop, items, hasFinal, warnings, guidance }),
|
|
979
|
+
],
|
|
945
980
|
};
|
|
946
981
|
}
|
|
947
982
|
const revoicedByScene = new Set();
|
|
@@ -972,7 +1007,7 @@ export async function statusFlow(runId, json, deps) {
|
|
|
972
1007
|
: `Ad run ${runId} — ${displayRunStatus(run.status)}`;
|
|
973
1008
|
const lines = [
|
|
974
1009
|
headline,
|
|
975
|
-
...
|
|
1010
|
+
...guidance,
|
|
976
1011
|
...(stop.at === "running" && run.pauseAhead ? [pauseAheadLine(run.pauseAhead)] : []),
|
|
977
1012
|
...warnings.map((w) => `Heads-up (${w.step}): ${w.warning}`),
|
|
978
1013
|
];
|
|
@@ -984,6 +1019,12 @@ export async function statusFlow(runId, json, deps) {
|
|
|
984
1019
|
for (const sceneIndex of [...byScene.keys()].sort((a, b) => a - b)) {
|
|
985
1020
|
const row = byScene.get(sceneIndex);
|
|
986
1021
|
lines.push(`${String(sceneIndex).padEnd(5)} ${itemWord(row.clip).padEnd(8)} ${itemWord(row.voiceover).padEnd(8)} ${itemWord(row.frame)}`);
|
|
1022
|
+
if (row.clip?.lastRedo)
|
|
1023
|
+
lines.push(` clip: ${row.clip.lastRedo.label}`);
|
|
1024
|
+
if (row.voiceover?.lastRedo)
|
|
1025
|
+
lines.push(` voice: ${row.voiceover.lastRedo.label}`);
|
|
1026
|
+
if (row.frame?.lastRedo)
|
|
1027
|
+
lines.push(` picture: ${row.frame.lastRedo.label}`);
|
|
987
1028
|
for (const finding of row.clip?.findings ?? []) {
|
|
988
1029
|
lines.push(` ${finding.code} (${finding.severity}): ${finding.detail}`);
|
|
989
1030
|
const judgeSeverity = finding.judgeSeverity ?? finding.severity;
|
|
@@ -1201,7 +1242,7 @@ export function planClipRedo(run, items, target) {
|
|
|
1201
1242
|
};
|
|
1202
1243
|
}
|
|
1203
1244
|
function findClipRow(run, items, target) {
|
|
1204
|
-
const uploadedCut = items
|
|
1245
|
+
const uploadedCut = hasAttachedCut(items);
|
|
1205
1246
|
const rows = items.filter((i) => i.itemKind === "clip" &&
|
|
1206
1247
|
i.sceneIndex === target.sceneIndex &&
|
|
1207
1248
|
(target.nodeId === undefined || i.nodeId === target.nodeId));
|
|
@@ -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, stopLines, } from "./video.js";
|
|
14
14
|
export const helpText = `
|
|
15
15
|
exodus workflow — List, describe, run, inspect, import, and export saved workflows
|
|
16
16
|
|
|
@@ -2085,9 +2085,10 @@ async function waitForRun(runId, opts, deps) {
|
|
|
2085
2085
|
const saved = opts.out !== undefined && terminalRun
|
|
2086
2086
|
? await saveDeliveries(terminalRun, opts.out, deps)
|
|
2087
2087
|
: undefined;
|
|
2088
|
-
const
|
|
2088
|
+
const parkStop = !pollResult.timedOut && isRecord(pollResult.data)
|
|
2089
2089
|
? videoParkStop(pollResult.data)
|
|
2090
2090
|
: undefined;
|
|
2091
|
+
const videoStop = parkStop ? await resolveStopAtPark(parkStop, runId, deps) : undefined;
|
|
2091
2092
|
if (opts.json) {
|
|
2092
2093
|
return {
|
|
2093
2094
|
code: pollResult.ok ? 0 : 1,
|