@aicopycoders/exodus 2026.6.2902 → 2026.7.200-beta.10

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.
@@ -0,0 +1,5 @@
1
+ export type Channel = "beta" | "latest";
2
+ export declare function channelOf(version: string): Channel;
3
+ export declare function getChannel(): Channel;
4
+ export declare function pkgRef(channel?: Channel): string;
5
+ export declare function stampChannel(content: string, channel?: Channel): string;
@@ -0,0 +1,15 @@
1
+ import { getVersion } from "./version.js";
2
+ export function channelOf(version) {
3
+ return version.includes("-beta") ? "beta" : "latest";
4
+ }
5
+ export function getChannel() {
6
+ return channelOf(getVersion());
7
+ }
8
+ export function pkgRef(channel = getChannel()) {
9
+ return `@aicopycoders/exodus@${channel}`;
10
+ }
11
+ export function stampChannel(content, channel = getChannel()) {
12
+ if (channel !== "beta")
13
+ return content;
14
+ return content.replace(/npx @aicopycoders\/exodus(@[A-Za-z0-9.-]+)?/g, `npx ${pkgRef(channel)}`);
15
+ }
@@ -0,0 +1,6 @@
1
+ export declare const BRAND_IMAGE_MAX_BYTES: number;
2
+ export interface SniffedImageType {
3
+ ext: "png" | "jpg" | "webp";
4
+ mime: "image/png" | "image/jpeg" | "image/webp";
5
+ }
6
+ export declare function sniffImageType(buf: Uint8Array): SniffedImageType | null;
@@ -0,0 +1,18 @@
1
+ export const BRAND_IMAGE_MAX_BYTES = 15 * 1024 * 1024;
2
+ export function sniffImageType(buf) {
3
+ if (buf.length >= 8) {
4
+ const png = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
5
+ if (png.every((b, i) => buf[i] === b)) {
6
+ return { ext: "png", mime: "image/png" };
7
+ }
8
+ }
9
+ if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) {
10
+ return { ext: "jpg", mime: "image/jpeg" };
11
+ }
12
+ if (buf.length >= 12 &&
13
+ buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
14
+ buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) {
15
+ return { ext: "webp", mime: "image/webp" };
16
+ }
17
+ return null;
18
+ }
@@ -8,7 +8,10 @@ export const PROVIDER_ENV_MAP = {
8
8
  elevenlabs: "ELEVENLABS_API_KEY",
9
9
  kie: "KIE_API_KEY",
10
10
  };
11
- export const SERVER_RESOLVED_PROVIDERS = new Set(["imgflip"]);
11
+ export const SERVER_RESOLVED_PROVIDERS = new Set([
12
+ "imgflip",
13
+ "scrapecreators",
14
+ ]);
12
15
  export async function fetchRemoteKeys() {
13
16
  const res = await apiGetDashboard("/api/settings/keys");
14
17
  if (!res.ok) {
@@ -1,9 +1,10 @@
1
+ import { type Channel } from "./channel.js";
1
2
  export declare const ENV_SCAFFOLD = "# Exodus config \u2014 paste your dashboard .env block below this line.\n# Get it from your Exodus dashboard -> Settings -> Claude Code -> \"Copy .env block\".\n# ONE key covers every brand you own; switch brands with brand subfolders or\n# `npx @aicopycoders/exodus brand use <slug>`, never by editing this file.\n";
2
- export declare function writeEnvScaffold(root: string): {
3
+ export declare function writeEnvScaffold(root: string, channel?: Channel): {
3
4
  created: boolean;
4
5
  };
5
6
  export declare function ensureGitignore(root: string): void;
6
- export declare function writeSkills(root: string, srcOverride?: string): string[];
7
- export declare function writeReferences(root: string, srcOverride?: string): void;
8
- export declare function writeDocs(root: string, srcOverride?: string): string[];
7
+ export declare function writeSkills(root: string, srcOverride?: string, channel?: Channel): string[];
8
+ export declare function writeReferences(root: string, srcOverride?: string, channel?: Channel): void;
9
+ export declare function writeDocs(root: string, srcOverride?: string, channel?: Channel): string[];
9
10
  export declare function ensureBaseDirs(root: string): void;
@@ -1,18 +1,36 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { skillsDir, referencesDir, docsDir } from "./assets.js";
4
+ import { getChannel, stampChannel } from "./channel.js";
4
5
  export const ENV_SCAFFOLD = `# Exodus config — paste your dashboard .env block below this line.
5
6
  # Get it from your Exodus dashboard -> Settings -> Claude Code -> "Copy .env block".
6
7
  # ONE key covers every brand you own; switch brands with brand subfolders or
7
8
  # \`npx @aicopycoders/exodus brand use <slug>\`, never by editing this file.
8
9
  `;
9
- export function writeEnvScaffold(root) {
10
+ export function writeEnvScaffold(root, channel = getChannel()) {
10
11
  const envPath = path.join(root, ".env");
11
12
  if (fs.existsSync(envPath))
12
13
  return { created: false };
13
- fs.writeFileSync(envPath, ENV_SCAFFOLD);
14
+ fs.writeFileSync(envPath, stampChannel(ENV_SCAFFOLD, channel));
14
15
  return { created: true };
15
16
  }
17
+ function stampTree(dir, channel) {
18
+ if (channel !== "beta")
19
+ return;
20
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
21
+ const p = path.join(dir, entry.name);
22
+ if (entry.isDirectory()) {
23
+ stampTree(p, channel);
24
+ continue;
25
+ }
26
+ if (!entry.isFile())
27
+ continue;
28
+ const raw = fs.readFileSync(p, "utf8");
29
+ const stamped = stampChannel(raw, channel);
30
+ if (stamped !== raw)
31
+ fs.writeFileSync(p, stamped);
32
+ }
33
+ }
16
34
  export function ensureGitignore(root) {
17
35
  const giPath = path.join(root, ".gitignore");
18
36
  const lines = fs.existsSync(giPath)
@@ -22,7 +40,7 @@ export function ensureGitignore(root) {
22
40
  lines.push(".env");
23
41
  fs.writeFileSync(giPath, lines.join("\n").replace(/\n+$/, "") + "\n");
24
42
  }
25
- export function writeSkills(root, srcOverride) {
43
+ export function writeSkills(root, srcOverride, channel = getChannel()) {
26
44
  const src = skillsDir(srcOverride);
27
45
  const dest = path.join(root, ".claude", "skills");
28
46
  fs.mkdirSync(dest, { recursive: true });
@@ -33,11 +51,12 @@ export function writeSkills(root, srcOverride) {
33
51
  const target = path.join(dest, entry.name);
34
52
  fs.rmSync(target, { recursive: true, force: true });
35
53
  fs.cpSync(path.join(src, entry.name), target, { recursive: true });
54
+ stampTree(target, channel);
36
55
  names.push(entry.name);
37
56
  }
38
57
  return names;
39
58
  }
40
- export function writeReferences(root, srcOverride) {
59
+ export function writeReferences(root, srcOverride, channel = getChannel()) {
41
60
  let src;
42
61
  try {
43
62
  src = referencesDir(srcOverride);
@@ -50,8 +69,9 @@ export function writeReferences(root, srcOverride) {
50
69
  const dest = path.join(root, "references");
51
70
  fs.rmSync(dest, { recursive: true, force: true });
52
71
  fs.cpSync(src, dest, { recursive: true });
72
+ stampTree(dest, channel);
53
73
  }
54
- export function writeDocs(root, srcOverride) {
74
+ export function writeDocs(root, srcOverride, channel = getChannel()) {
55
75
  let src;
56
76
  try {
57
77
  src = docsDir(srcOverride);
@@ -65,7 +85,8 @@ export function writeDocs(root, srcOverride) {
65
85
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
66
86
  if (!entry.isFile() || !entry.name.endsWith(".md"))
67
87
  continue;
68
- fs.copyFileSync(path.join(src, entry.name), path.join(root, entry.name));
88
+ const content = fs.readFileSync(path.join(src, entry.name), "utf8");
89
+ fs.writeFileSync(path.join(root, entry.name), stampChannel(content, channel));
69
90
  names.push(entry.name);
70
91
  }
71
92
  return names.sort();
@@ -1,4 +1,4 @@
1
- export declare const AD_TYPES: readonly ["before-after", "native-news", "testimonial", "bold", "meme", "screenshot", "hero", "holding-sign", "handwritten", "comment", "breaking-news", "lofi", "comparison", "infographic", "headline", "collage", "product-breakdown", "scientific", "animation", "founder-note", "carousel", "statistics", "post-it-notes", "happy-avatar", "problem-solution", "writing-on-body", "multi-testimonial", "receipt", "step-by-step", "sale-promotional-offer", "cost-of-inaction", "ugc", "quiz-interactive", "action-figure", "at-home-routine", "billboard", "black-and-white", "blurred", "choose", "cutout", "drawing", "facetime", "marker", "notes", "photo", "quadrant", "search", "thumbnail", "xray", "zoom-bubble"];
1
+ export declare const AD_TYPES: readonly ["before-after", "native-news", "testimonial", "bold", "meme", "screenshot", "hero", "holding-sign", "handwritten", "comment", "breaking-news", "lofi", "comparison", "infographic", "headline", "collage", "product-breakdown", "scientific", "animation", "founder-note", "carousel", "statistics", "post-it-notes", "happy-avatar", "problem-solution", "writing-on-body", "multi-testimonial", "receipt", "step-by-step", "sale-promotional-offer", "cost-of-inaction", "ugc", "quiz-interactive", "action-figure", "at-home-routine", "billboard", "black-and-white", "blurred", "choose", "cutout", "drawing", "facetime", "marker", "notes", "photo", "quadrant", "search", "thumbnail", "xray", "zoom-bubble", "advice-cacophony", "annotated-testimonial", "body-morph", "body-portal", "bundle-showcase", "clay-world", "colossus-product", "cost-receipt", "cultural-icon-dressup", "effect-field", "fine-art-intervention", "flavor-story", "giant-tiny", "half-face-reveal", "hero-vs-villain", "horror-illustration", "horror-object-interior", "illustrated-cutaway", "impossible-physics", "ingredient-warrior", "lifestyle-colorway-array", "material-state", "mirror-reveal", "object-launch", "organ-buddy", "polaroid-sticky", "pop-art-comic", "product-as-body", "product-as-hero", "retro-game-screen", "sequential-state-strip", "sketch-equation", "spotlight-hero", "stat-surround-flatlay", "stat-surround-hero", "street-find", "street-poster", "stuffed-container", "surrogate-character", "symbol-swap", "symptom-collage", "threat-map", "tiny-universe", "tiny-world", "toxic-assemblage", "toxic-emanation", "trophy-moment", "villain-pour", "vintage-reframe", "whiteboard", "zipper-reveal"];
2
2
  export type AdType = (typeof AD_TYPES)[number];
3
3
  export declare const AD_TYPE_NAMES: Record<AdType, string>;
4
4
  export declare const REPTILE_TRIGGERS: readonly ["ultra-real", "bizarre", "voyeur", "suffering", "gory", "sexual", "primal-fear", "odd-contrast", "inside-joke", "time-warp", "victory-lap", "selfie", "uncanny-objects"];
@@ -49,6 +49,57 @@ export const AD_TYPES = [
49
49
  "thumbnail",
50
50
  "xray",
51
51
  "zoom-bubble",
52
+ "advice-cacophony",
53
+ "annotated-testimonial",
54
+ "body-morph",
55
+ "body-portal",
56
+ "bundle-showcase",
57
+ "clay-world",
58
+ "colossus-product",
59
+ "cost-receipt",
60
+ "cultural-icon-dressup",
61
+ "effect-field",
62
+ "fine-art-intervention",
63
+ "flavor-story",
64
+ "giant-tiny",
65
+ "half-face-reveal",
66
+ "hero-vs-villain",
67
+ "horror-illustration",
68
+ "horror-object-interior",
69
+ "illustrated-cutaway",
70
+ "impossible-physics",
71
+ "ingredient-warrior",
72
+ "lifestyle-colorway-array",
73
+ "material-state",
74
+ "mirror-reveal",
75
+ "object-launch",
76
+ "organ-buddy",
77
+ "polaroid-sticky",
78
+ "pop-art-comic",
79
+ "product-as-body",
80
+ "product-as-hero",
81
+ "retro-game-screen",
82
+ "sequential-state-strip",
83
+ "sketch-equation",
84
+ "spotlight-hero",
85
+ "stat-surround-flatlay",
86
+ "stat-surround-hero",
87
+ "street-find",
88
+ "street-poster",
89
+ "stuffed-container",
90
+ "surrogate-character",
91
+ "symbol-swap",
92
+ "symptom-collage",
93
+ "threat-map",
94
+ "tiny-universe",
95
+ "tiny-world",
96
+ "toxic-assemblage",
97
+ "toxic-emanation",
98
+ "trophy-moment",
99
+ "villain-pour",
100
+ "vintage-reframe",
101
+ "whiteboard",
102
+ "zipper-reveal",
52
103
  ];
53
104
  export const AD_TYPE_NAMES = {
54
105
  "before-after": "Before / After",
@@ -101,6 +152,57 @@ export const AD_TYPE_NAMES = {
101
152
  thumbnail: "Thumbnail",
102
153
  xray: "X-ray",
103
154
  "zoom-bubble": "Zoom Bubble",
155
+ "advice-cacophony": "Advice Cacophony",
156
+ "annotated-testimonial": "Highlighted / Annotated Testimonial",
157
+ "body-morph": "Body Morph",
158
+ "body-portal": "Body Portal",
159
+ "bundle-showcase": "Bundle Showcase + Benefit Bar",
160
+ "clay-world": "Clay World",
161
+ "colossus-product": "Colossus Product",
162
+ "cost-receipt": "Cost Receipt",
163
+ "cultural-icon-dressup": "Cultural Icon Dress-Up",
164
+ "effect-field": "Effect Field",
165
+ "fine-art-intervention": "Fine Art Intervention",
166
+ "flavor-story": "Flavor Story / Tastes Like",
167
+ "giant-tiny": "Giant / Tiny",
168
+ "half-face-reveal": "Half-Face Reveal",
169
+ "hero-vs-villain": "Hero vs Villain",
170
+ "horror-illustration": "Horror Illustration",
171
+ "horror-object-interior": "Horror Object Interior",
172
+ "illustrated-cutaway": "Illustrated Cutaway",
173
+ "impossible-physics": "Impossible Physics",
174
+ "ingredient-warrior": "Ingredient Warrior",
175
+ "lifestyle-colorway-array": "Lifestyle Action + Product Colorway Array",
176
+ "material-state": "Material State",
177
+ "mirror-reveal": "Mirror Reveal",
178
+ "object-launch": "Object Launch",
179
+ "organ-buddy": "Organ Buddy",
180
+ "polaroid-sticky": "Polaroid + Sticky",
181
+ "pop-art-comic": "Pop Art / Comic",
182
+ "product-as-body": "Product as Body",
183
+ "product-as-hero": "Product as Hero",
184
+ "retro-game-screen": "Retro Game Screen",
185
+ "sequential-state-strip": "Sequential State Strip",
186
+ "sketch-equation": "Sketch Equation",
187
+ "spotlight-hero": "Spotlight Hero",
188
+ "stat-surround-flatlay": "Stat Surround / Callout Radial (Lifestyle Flatlay)",
189
+ "stat-surround-hero": "Stat Surround / Callout Radial (Product Hero)",
190
+ "street-find": "Street Find",
191
+ "street-poster": "Street Poster",
192
+ "stuffed-container": "Stuffed Container",
193
+ "surrogate-character": "Surrogate Character",
194
+ "symbol-swap": "Symbol Swap",
195
+ "symptom-collage": "Symptom Collage",
196
+ "threat-map": "Threat Map",
197
+ "tiny-universe": "Tiny Universe",
198
+ "tiny-world": "Tiny World",
199
+ "toxic-assemblage": "Toxic Assemblage",
200
+ "toxic-emanation": "Toxic Emanation",
201
+ "trophy-moment": "Trophy Moment",
202
+ "villain-pour": "Villain Pour",
203
+ "vintage-reframe": "Vintage Reframe",
204
+ "whiteboard": "Whiteboard",
205
+ "zipper-reveal": "Zipper Reveal",
104
206
  };
105
207
  export const REPTILE_TRIGGERS = [
106
208
  "ultra-real",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aicopycoders/exodus",
3
- "version": "2026.6.2902",
3
+ "version": "2026.7.200-beta.10",
4
4
  "description": "CLI + Claude Code skills for the Copycoders Exodus ad pipeline",
5
5
  "repository": {
6
6
  "type": "git",