@aicopycoders/exodus 2026.9.2000-beta.9 → 2026.10.1200-beta.1

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.
@@ -15,7 +15,8 @@ workspace this key is bound to. Prints:
15
15
 
16
16
  Run this BEFORE any pipeline command to confirm you're pointed at the right
17
17
  brand. If "foundation: NOT READY" appears, ask an admin to complete the
18
- brand's foundation at /settings?tab=brands&brand=<slug>.
18
+ brand's foundation in the dashboard at Library → Materials → Primers
19
+ (/library/materials?section=primer).
19
20
  `.trim();
20
21
  export async function run() {
21
22
  const res = await apiGet("/api/v2/whoami");
@@ -409,7 +409,7 @@ export interface RunFormatProvenance {
409
409
  };
410
410
  specVersion: string | null;
411
411
  }
412
- 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";
413
413
  export type RunVoiceProvenance = {
414
414
  source: "show";
415
415
  path: VoicePath;
@@ -460,6 +460,7 @@ export interface WorkflowRun {
460
460
  pauseAhead?: {
461
461
  nodeId: string;
462
462
  after: "frames" | "storyboard";
463
+ selfApproves?: true;
463
464
  };
464
465
  }
465
466
  export declare function pauseAheadLine(ahead: NonNullable<WorkflowRun["pauseAhead"]>): string;
@@ -378,6 +378,9 @@ export function formatRulesLine(provenance) {
378
378
  return `Format rules: ${rigName}${version}${where}.${borrowed}`;
379
379
  }
380
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
+ }
381
384
  return ahead.after === "frames"
382
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."
383
386
  : "This run will pause for your approval once the storyboard is written. Nothing is spent on pictures, voices or clips until you approve.";
@@ -2088,6 +2091,7 @@ function videoParkStop(raw) {
2088
2091
  async function waitForRun(runId, opts, deps) {
2089
2092
  const seen = new Map();
2090
2093
  let pausedNotified = false;
2094
+ let autoRedoNotified;
2091
2095
  const landOnPark = opts.landOnPark;
2092
2096
  const isLandingPark = (raw) => (landOnPark !== undefined && raw["pauseReason"] === landOnPark.pauseReason) ||
2093
2097
  videoParkStop(raw) !== undefined;
@@ -2105,7 +2109,15 @@ async function waitForRun(runId, opts, deps) {
2105
2109
  return;
2106
2110
  const parked = isParkedSnapshot(raw);
2107
2111
  const isLanding = isLandingPark(raw);
2108
- if (parked && !pausedNotified && !isLanding) {
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) {
2109
2121
  pausedNotified = true;
2110
2122
  const dashboardUrl = deps.dashboardUrl ?? getDashboardUrl();
2111
2123
  const pauseReason = raw["pauseReason"];
@@ -2113,7 +2125,9 @@ async function waitForRun(runId, opts, deps) {
2113
2125
  opts.onProgressLine(line);
2114
2126
  }
2115
2127
  }
2116
- const parkedNodeId = gateParkedNodeId(raw["status"], raw["pauseReason"], raw["pausedNodeId"]);
2128
+ const parkedNodeId = autoRedo?.at === "running"
2129
+ ? undefined
2130
+ : gateParkedNodeId(raw["status"], raw["pauseReason"], raw["pausedNodeId"]);
2117
2131
  const nodes = Array.isArray(raw["nodes"]) ? raw["nodes"] : [];
2118
2132
  for (const node of nodes) {
2119
2133
  const isParked = parkedNodeId !== undefined && node.nodeId === parkedNodeId;
@@ -22,6 +22,7 @@ export interface AdWindowStats {
22
22
  spend?: number | null;
23
23
  spendDisplay?: string | null;
24
24
  impressions?: number | null;
25
+ reach?: number | null;
25
26
  clicks?: number | null;
26
27
  currency?: string | null;
27
28
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@aicopycoders/exodus",
3
- "version": "2026.9.2000-beta.9",
3
+ "version": "2026.10.1200-beta.1",
4
4
  "description": "CLI + Claude Code skills for the Copycoders Exodus ad pipeline",
5
5
  "repository": {
6
6
  "type": "git",
7
- "url": "git+https://github.com/aicopycoders/exodus.git"
7
+ "url": "git+https://github.com/aicopycoders/exodus-cli.git"
8
8
  },
9
- "homepage": "https://github.com/aicopycoders/exodus#readme",
9
+ "homepage": "https://github.com/aicopycoders/exodus-cli#readme",
10
10
  "type": "module",
11
11
  "bin": {
12
12
  "exodus": "dist/bin/exodus.js"