@aicopycoders/exodus 2026.9.1100-beta.2 → 2026.9.1400-beta.0

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.
Files changed (44) hide show
  1. package/assets/docs/CLAUDE.md +22 -1
  2. package/assets/docs/PIPELINES.md +20 -0
  3. package/assets/skills/anatomy-of-ads/SKILL.md +138 -0
  4. package/assets/skills/anatomy-of-ads/references/beat-map.md +69 -0
  5. package/assets/skills/anatomy-of-ads/references/concept-angle.md +50 -0
  6. package/assets/skills/anatomy-of-ads/references/hook-and-structure.md +69 -0
  7. package/assets/skills/bot-builder/SKILL.md +147 -0
  8. package/assets/skills/bot-builder/references/exodus-prompt-node.md +113 -0
  9. package/assets/skills/bot-builder/references/prompt-blocks.md +169 -0
  10. package/assets/skills/copy-instincts/SKILL.md +154 -0
  11. package/assets/skills/copy-instincts/references/instincts-catalog.md +131 -0
  12. package/assets/skills/copy-instincts/references/locked-examples.md +104 -0
  13. package/assets/skills/copy-instincts/references/worked-runs.md +148 -0
  14. package/assets/skills/exodus/SKILL.md +10 -1
  15. package/assets/skills/exodus-video/SKILL.md +12 -4
  16. package/assets/skills/exodus-video/scripts/first-cut.mjs +129 -32
  17. package/assets/skills/find-competitors/SKILL.md +148 -0
  18. package/assets/skills/find-competitors/references/query-grid.md +78 -0
  19. package/assets/skills/find-competitors/scripts/__pycache__/adlib_sweep.cpython-314.pyc +0 -0
  20. package/assets/skills/find-competitors/scripts/__pycache__/competitor_signals.cpython-314.pyc +0 -0
  21. package/assets/skills/find-competitors/scripts/adlib_sweep.py +100 -0
  22. package/assets/skills/find-competitors/scripts/competitor_signals.py +168 -0
  23. package/assets/skills/handoff/SKILL.md +81 -0
  24. package/assets/skills/mechanism/SKILL.md +130 -0
  25. package/assets/skills/mechanism/references/bot-prompts.md +94 -0
  26. package/assets/skills/mechanism/references/competitor-mechanisms.md +39 -0
  27. package/assets/skills/odf/SKILL.md +167 -0
  28. package/assets/skills/patterns-and-gaps/SKILL.md +120 -0
  29. package/assets/skills/positioning-spiral/SKILL.md +166 -0
  30. package/assets/skills/remember-that/SKILL.md +56 -0
  31. package/assets/skills/segment-grid/README.md +45 -0
  32. package/assets/skills/segment-grid/SKILL.md +84 -0
  33. package/assets/skills/thinking-vs-build/SKILL.md +120 -0
  34. package/assets/templates/STANDARDS.md +49 -0
  35. package/dist/commands/init.d.ts +1 -0
  36. package/dist/commands/init.js +10 -5
  37. package/dist/commands/update.js +1 -1
  38. package/dist/commands/video.d.ts +6 -0
  39. package/dist/commands/video.js +30 -1
  40. package/dist/lib/assets.d.ts +1 -0
  41. package/dist/lib/assets.js +3 -0
  42. package/dist/lib/scaffold.d.ts +3 -0
  43. package/dist/lib/scaffold.js +18 -1
  44. package/package.json +1 -1
@@ -362,12 +362,19 @@ export function planPull(run, items, opts) {
362
362
  durationSec: artifact.durationSec ?? null,
363
363
  qc: artifact.qc ?? null,
364
364
  words: artifact.words ?? null,
365
+ revoiced: artifact.revoiced === true,
366
+ speechTrimmed: artifact.speechTrimmed === true,
365
367
  });
366
368
  }
367
369
  const clipItemByScene = new Map();
370
+ const frameItemByScene = new Map();
371
+ const sceneFrameNodeIds = new Set(run.nodes.filter((n) => n.kind === "scene-frames").map((n) => n.nodeId));
368
372
  for (const item of items) {
369
373
  if (item.itemKind === "clip")
370
374
  clipItemByScene.set(item.sceneIndex, item);
375
+ if (item.itemKind === "frame" && sceneFrameNodeIds.has(item.nodeId)) {
376
+ frameItemByScene.set(item.sceneIndex, item);
377
+ }
371
378
  }
372
379
  const sceneIndexes = [
373
380
  ...new Set([
@@ -375,12 +382,14 @@ export function planPull(run, items, opts) {
375
382
  ...voiceByScene.keys(),
376
383
  ...keyframeByScene.keys(),
377
384
  ...clipItemByScene.keys(),
385
+ ...frameItemByScene.keys(),
378
386
  ]),
379
387
  ].sort((a, b) => a - b);
380
388
  const scenes = sceneIndexes.map((sceneIndex) => {
381
389
  const clip = clipByScene.get(sceneIndex);
382
390
  const voice = voiceByScene.get(sceneIndex);
383
391
  const item = clipItemByScene.get(sceneIndex);
392
+ const frameItem = frameItemByScene.get(sceneIndex);
384
393
  let source = null;
385
394
  let wordsFrom = null;
386
395
  if (clip?.words && clip.words.length > 0) {
@@ -405,10 +414,13 @@ export function planPull(run, items, opts) {
405
414
  voice: voice?.download.file ?? null,
406
415
  keyframe: keyframeByScene.get(sceneIndex)?.file ?? null,
407
416
  qc: clip?.qc ?? null,
417
+ revoiced: clip ? clip.revoiced : null,
418
+ speechTrimmed: clip ? clip.speechTrimmed : null,
408
419
  clipStatus: item?.status ?? "missing",
409
420
  error: item?.error ?? null,
410
421
  flagged: item?.flagged === true,
411
422
  findings: item?.findings ?? [],
423
+ keyframeFindings: frameItem?.findings ?? [],
412
424
  };
413
425
  });
414
426
  const downloads = [
@@ -444,8 +456,11 @@ export function markPullFailure(manifest, failure) {
444
456
  for (const scene of manifest.scenes) {
445
457
  const lostWordsSource = (scene.clip === failure.file && scene.wordsFrom === "clip") ||
446
458
  (scene.voice === failure.file && scene.wordsFrom === "voice");
447
- if (scene.clip === failure.file)
459
+ if (scene.clip === failure.file) {
448
460
  scene.clip = null;
461
+ scene.revoiced = null;
462
+ scene.speechTrimmed = null;
463
+ }
449
464
  if (scene.voice === failure.file)
450
465
  scene.voice = null;
451
466
  if (scene.keyframe === failure.file)
@@ -648,11 +663,20 @@ export async function statusFlow(runId, json, deps) {
648
663
  if (json) {
649
664
  return { code: 0, lines: [JSON.stringify({ runId, status: run.status, stop, items, hasFinal })] };
650
665
  }
666
+ const revoicedByScene = new Set();
667
+ for (const artifact of outputsOfNodeKind(run, "video")) {
668
+ if (artifact.type === "video" && artifact.revoiced === true && typeof artifact.sceneIndex === "number") {
669
+ revoicedByScene.add(artifact.sceneIndex);
670
+ }
671
+ }
651
672
  const byScene = new Map();
673
+ const sceneFrameNodeIds = new Set(run.nodes.filter((n) => n.kind === "scene-frames").map((n) => n.nodeId));
652
674
  for (const item of items) {
653
675
  if (item.itemKind !== "clip" && item.itemKind !== "voiceover" && item.itemKind !== "frame") {
654
676
  continue;
655
677
  }
678
+ if (item.itemKind === "frame" && !sceneFrameNodeIds.has(item.nodeId))
679
+ continue;
656
680
  const row = byScene.get(item.sceneIndex) ?? {};
657
681
  row[item.itemKind] = item;
658
682
  byScene.set(item.sceneIndex, row);
@@ -674,6 +698,11 @@ export async function statusFlow(runId, json, deps) {
674
698
  }
675
699
  if (row.clip?.error)
676
700
  lines.push(` ${row.clip.error}`);
701
+ if (revoicedByScene.has(sceneIndex))
702
+ lines.push(" voice: cast voice applied");
703
+ for (const finding of row.frame?.findings ?? []) {
704
+ lines.push(` picture: ${finding.code} (${finding.severity}): ${finding.detail}`);
705
+ }
677
706
  }
678
707
  }
679
708
  lines.push("", hasFinal && stop.at === "finished"
@@ -2,3 +2,4 @@ export declare function assetsRoot(override?: string): string;
2
2
  export declare function skillsDir(override?: string): string;
3
3
  export declare function referencesDir(override?: string): string;
4
4
  export declare function docsDir(override?: string): string;
5
+ export declare function templatesDir(override?: string): string;
@@ -24,3 +24,6 @@ export function referencesDir(override) {
24
24
  export function docsDir(override) {
25
25
  return path.join(assetsRoot(override), "docs");
26
26
  }
27
+ export function templatesDir(override) {
28
+ return path.join(assetsRoot(override), "templates");
29
+ }
@@ -7,4 +7,7 @@ export declare function ensureGitignore(root: string): void;
7
7
  export declare function writeSkills(root: string, srcOverride?: string, channel?: Channel): string[];
8
8
  export declare function writeReferences(root: string, srcOverride?: string, channel?: Channel): void;
9
9
  export declare function writeDocs(root: string, srcOverride?: string, channel?: Channel): string[];
10
+ export declare function writeStandards(root: string, srcOverride?: string, channel?: Channel): {
11
+ created: boolean;
12
+ };
10
13
  export declare function ensureBaseDirs(root: string): void;
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { skillsDir, referencesDir, docsDir } from "./assets.js";
3
+ import { skillsDir, referencesDir, docsDir, templatesDir } from "./assets.js";
4
4
  import { getChannel, stampChannel } from "./channel.js";
5
5
  export const ENV_SCAFFOLD = `# Exodus config — paste your dashboard .env block below this line.
6
6
  # Get it from your Exodus dashboard -> Settings -> Claude Code -> "Copy .env block".
@@ -91,6 +91,23 @@ export function writeDocs(root, srcOverride, channel = getChannel()) {
91
91
  }
92
92
  return names.sort();
93
93
  }
94
+ export function writeStandards(root, srcOverride, channel = getChannel()) {
95
+ const dest = path.join(root, "STANDARDS.md");
96
+ if (fs.existsSync(dest))
97
+ return { created: false };
98
+ let src;
99
+ try {
100
+ src = templatesDir(srcOverride);
101
+ }
102
+ catch {
103
+ return { created: false };
104
+ }
105
+ const template = path.join(src, "STANDARDS.md");
106
+ if (!fs.existsSync(template))
107
+ return { created: false };
108
+ fs.writeFileSync(dest, stampChannel(fs.readFileSync(template, "utf8"), channel));
109
+ return { created: true };
110
+ }
94
111
  export function ensureBaseDirs(root) {
95
112
  const out = path.join(root, "output");
96
113
  fs.mkdirSync(out, { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aicopycoders/exodus",
3
- "version": "2026.9.1100-beta.2",
3
+ "version": "2026.9.1400-beta.0",
4
4
  "description": "CLI + Claude Code skills for the Copycoders Exodus ad pipeline",
5
5
  "repository": {
6
6
  "type": "git",