@aicopycoders/exodus 2026.6.2400
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/references/awareness-framework.md +156 -0
- package/assets/references/cast-video.md +165 -0
- package/assets/references/copy-blocks.md +138 -0
- package/assets/references/creative-strategy.md +990 -0
- package/assets/references/editing-rules.md +81 -0
- package/assets/references/hook-quality-checklist.md +153 -0
- package/assets/references/iterations.md +73 -0
- package/assets/references/scrawls.md +483 -0
- package/assets/references/segments.md +79 -0
- package/assets/skills/exodus/SKILL.md +50 -0
- package/assets/skills/exodus-brand/SKILL.md +117 -0
- package/assets/skills/exodus-browse/SKILL.md +96 -0
- package/assets/skills/exodus-creative/SKILL.md +94 -0
- package/assets/skills/exodus-drive/SKILL.md +118 -0
- package/assets/skills/exodus-foundation/SKILL.md +170 -0
- package/assets/skills/exodus-genesis/SKILL.md +159 -0
- package/assets/skills/exodus-idea/SKILL.md +78 -0
- package/assets/skills/exodus-image/SKILL.md +224 -0
- package/assets/skills/exodus-meme/SKILL.md +183 -0
- package/assets/skills/exodus-primer/SKILL.md +145 -0
- package/assets/skills/exodus-strategist/SKILL.md +217 -0
- package/assets/skills/exodus-swipe/SKILL.md +85 -0
- package/assets/skills/exodus-template/SKILL.md +106 -0
- package/assets/skills/exodus-write/SKILL.md +157 -0
- package/assets/skills/genesis-bots/.env.example +12 -0
- package/assets/skills/genesis-bots/README.txt +29 -0
- package/assets/skills/genesis-bots/SKILL.md +219 -0
- package/assets/skills/genesis-bots/scripts/genesis-stream.mjs +307 -0
- package/assets/skills/genesis-bots/scripts/genesis-stream.py +220 -0
- package/dist/bin/exodus.d.ts +2 -0
- package/dist/bin/exodus.js +177 -0
- package/dist/commands/__tests__/genesis.hookgate.test.d.ts +1 -0
- package/dist/commands/__tests__/genesis.hookgate.test.js +12 -0
- package/dist/commands/brand.d.ts +24 -0
- package/dist/commands/brand.js +292 -0
- package/dist/commands/browse.d.ts +9 -0
- package/dist/commands/browse.js +132 -0
- package/dist/commands/creative.d.ts +2 -0
- package/dist/commands/creative.js +223 -0
- package/dist/commands/doctor.d.ts +21 -0
- package/dist/commands/doctor.js +590 -0
- package/dist/commands/drive.d.ts +2 -0
- package/dist/commands/drive.js +154 -0
- package/dist/commands/foundation.d.ts +2 -0
- package/dist/commands/foundation.js +468 -0
- package/dist/commands/genesis.d.ts +64 -0
- package/dist/commands/genesis.js +564 -0
- package/dist/commands/idea.d.ts +53 -0
- package/dist/commands/idea.js +333 -0
- package/dist/commands/image.d.ts +2 -0
- package/dist/commands/image.js +249 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.js +70 -0
- package/dist/commands/keys.d.ts +2 -0
- package/dist/commands/keys.js +101 -0
- package/dist/commands/meme.d.ts +12 -0
- package/dist/commands/meme.js +272 -0
- package/dist/commands/migrate.d.ts +3 -0
- package/dist/commands/migrate.js +158 -0
- package/dist/commands/primer.d.ts +2 -0
- package/dist/commands/primer.js +540 -0
- package/dist/commands/read-doc.d.ts +3 -0
- package/dist/commands/read-doc.js +62 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.js +205 -0
- package/dist/commands/swipe.d.ts +2 -0
- package/dist/commands/swipe.js +398 -0
- package/dist/commands/template.d.ts +2 -0
- package/dist/commands/template.js +225 -0
- package/dist/commands/whoami.d.ts +2 -0
- package/dist/commands/whoami.js +51 -0
- package/dist/lib/ad-library-write.d.ts +15 -0
- package/dist/lib/ad-library-write.js +67 -0
- package/dist/lib/assets.d.ts +3 -0
- package/dist/lib/assets.js +23 -0
- package/dist/lib/backend-hint.d.ts +2 -0
- package/dist/lib/backend-hint.js +22 -0
- package/dist/lib/cc-command.d.ts +1 -0
- package/dist/lib/cc-command.js +13 -0
- package/dist/lib/client.d.ts +23 -0
- package/dist/lib/client.js +213 -0
- package/dist/lib/format.d.ts +8 -0
- package/dist/lib/format.js +172 -0
- package/dist/lib/intel-format.d.ts +1 -0
- package/dist/lib/intel-format.js +38 -0
- package/dist/lib/keys-sync.d.ts +16 -0
- package/dist/lib/keys-sync.js +88 -0
- package/dist/lib/layout.d.ts +38 -0
- package/dist/lib/layout.js +153 -0
- package/dist/lib/load-env.d.ts +1 -0
- package/dist/lib/load-env.js +47 -0
- package/dist/lib/pixar-orchestrator-types.d.ts +54 -0
- package/dist/lib/pixar-orchestrator-types.js +1 -0
- package/dist/lib/poll.d.ts +14 -0
- package/dist/lib/poll.js +32 -0
- package/dist/lib/preflight.d.ts +18 -0
- package/dist/lib/preflight.js +56 -0
- package/dist/lib/prompts.d.ts +9 -0
- package/dist/lib/prompts.js +99 -0
- package/dist/lib/reel-write.d.ts +14 -0
- package/dist/lib/reel-write.js +59 -0
- package/dist/lib/release.d.ts +4 -0
- package/dist/lib/release.js +81 -0
- package/dist/lib/scaffold.d.ts +8 -0
- package/dist/lib/scaffold.js +67 -0
- package/dist/lib/scout-hydrate.d.ts +1 -0
- package/dist/lib/scout-hydrate.js +10 -0
- package/dist/lib/state.d.ts +6 -0
- package/dist/lib/state.js +49 -0
- package/dist/lib/template-constants.d.ts +4 -0
- package/dist/lib/template-constants.js +85 -0
- package/dist/lib/version.d.ts +1 -0
- package/dist/lib/version.js +19 -0
- package/package.json +30 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { fetchRemoteKeys, mapKeysToEnvVars, resolveEnvFilePath, upsertEnvVars, } from "./keys-sync.js";
|
|
3
|
+
export function hasBinary(name) {
|
|
4
|
+
try {
|
|
5
|
+
const res = spawnSync(name, ["-version"], { stdio: "ignore" });
|
|
6
|
+
return res.error == null;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function syncKeysToEnv() {
|
|
13
|
+
let remote;
|
|
14
|
+
try {
|
|
15
|
+
remote = await fetchRemoteKeys();
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
return { pulled: [], failed: [], error: e.message };
|
|
19
|
+
}
|
|
20
|
+
const { vars } = mapKeysToEnvVars(remote.keys);
|
|
21
|
+
upsertEnvVars(resolveEnvFilePath(), vars);
|
|
22
|
+
for (const [name, value] of Object.entries(vars)) {
|
|
23
|
+
process.env[name] = value;
|
|
24
|
+
}
|
|
25
|
+
return { pulled: Object.keys(vars), failed: remote.failed };
|
|
26
|
+
}
|
|
27
|
+
export const PIXAR_REQUIRED_KEYS = [
|
|
28
|
+
{ label: "Genesis", anyOf: ["GENESIS_API_KEY"] },
|
|
29
|
+
{
|
|
30
|
+
label: "LLM (Anthropic or OpenRouter)",
|
|
31
|
+
anyOf: ["ANTHROPIC_API_KEY", "OPENROUTER_API_KEY"],
|
|
32
|
+
},
|
|
33
|
+
{ label: "ElevenLabs", anyOf: ["ELEVENLABS_API_KEY"] },
|
|
34
|
+
{ label: "Kie.ai", anyOf: ["KIE_API_KEY"] },
|
|
35
|
+
];
|
|
36
|
+
export const PIXAR_MUX_ENV_VARS = ["MUX_TOKEN_ID", "MUX_TOKEN_SECRET"];
|
|
37
|
+
function isSet(env, name) {
|
|
38
|
+
const v = env[name];
|
|
39
|
+
return typeof v === "string" && v.trim().length > 0;
|
|
40
|
+
}
|
|
41
|
+
export function missingRequiredKeys(env, required = PIXAR_REQUIRED_KEYS) {
|
|
42
|
+
return required
|
|
43
|
+
.filter((r) => !r.anyOf.some((name) => isSet(env, name)))
|
|
44
|
+
.map((r) => r.label);
|
|
45
|
+
}
|
|
46
|
+
export function missingRequiredEnvVars(env, required = PIXAR_REQUIRED_KEYS) {
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const r of required) {
|
|
49
|
+
if (!r.anyOf.some((name) => isSet(env, name)))
|
|
50
|
+
out.push(...r.anyOf);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
export function muxConfigured(env) {
|
|
55
|
+
return PIXAR_MUX_ENV_VARS.every((name) => isSet(env, name));
|
|
56
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function promptYesNo(question: string, defaultValue?: boolean): Promise<boolean>;
|
|
2
|
+
export interface ChoiceOption {
|
|
3
|
+
key: string;
|
|
4
|
+
label: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function promptChoice(question: string, options: ChoiceOption[], defaultKey?: string): Promise<string>;
|
|
7
|
+
export declare function promptMultiline(prompt: string): Promise<string>;
|
|
8
|
+
export declare function openInEditor(initial: string, fileSuffix?: string): string | null;
|
|
9
|
+
export declare function promptText(question: string): Promise<string>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
export async function promptYesNo(question, defaultValue = false) {
|
|
7
|
+
const suffix = defaultValue ? "(Y/n)" : "(y/N)";
|
|
8
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
9
|
+
try {
|
|
10
|
+
const answer = await new Promise((resolve) => {
|
|
11
|
+
rl.question(`${question} ${suffix} `, resolve);
|
|
12
|
+
});
|
|
13
|
+
const trimmed = answer.trim();
|
|
14
|
+
if (!trimmed)
|
|
15
|
+
return defaultValue;
|
|
16
|
+
return /^y(es)?$/i.test(trimmed);
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
rl.close();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function promptChoice(question, options, defaultKey) {
|
|
23
|
+
const keys = options.map((o) => o.key.toLowerCase());
|
|
24
|
+
const labelLine = options
|
|
25
|
+
.map((o) => o.key === defaultKey ? `[${o.key.toUpperCase()}] ${o.label}` : `[${o.key}] ${o.label}`)
|
|
26
|
+
.join(" ");
|
|
27
|
+
for (;;) {
|
|
28
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
29
|
+
const answer = await new Promise((resolve) => {
|
|
30
|
+
rl.question(`${question}\n${labelLine}\n> `, resolve);
|
|
31
|
+
});
|
|
32
|
+
rl.close();
|
|
33
|
+
const trimmed = answer.trim().toLowerCase();
|
|
34
|
+
if (!trimmed && defaultKey)
|
|
35
|
+
return defaultKey;
|
|
36
|
+
if (keys.includes(trimmed))
|
|
37
|
+
return trimmed;
|
|
38
|
+
process.stdout.write(`Please enter one of: ${keys.join(", ")}\n`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function promptMultiline(prompt) {
|
|
42
|
+
process.stdout.write(`${prompt}\n(end with Ctrl-D on its own line)\n`);
|
|
43
|
+
return await new Promise((resolve, reject) => {
|
|
44
|
+
let buf = "";
|
|
45
|
+
process.stdin.setEncoding("utf-8");
|
|
46
|
+
const onData = (chunk) => {
|
|
47
|
+
buf += chunk;
|
|
48
|
+
};
|
|
49
|
+
const onEnd = () => {
|
|
50
|
+
cleanup();
|
|
51
|
+
resolve(buf);
|
|
52
|
+
};
|
|
53
|
+
const onError = (err) => {
|
|
54
|
+
cleanup();
|
|
55
|
+
reject(err);
|
|
56
|
+
};
|
|
57
|
+
const cleanup = () => {
|
|
58
|
+
process.stdin.off("data", onData);
|
|
59
|
+
process.stdin.off("end", onEnd);
|
|
60
|
+
process.stdin.off("error", onError);
|
|
61
|
+
};
|
|
62
|
+
process.stdin.on("data", onData);
|
|
63
|
+
process.stdin.on("end", onEnd);
|
|
64
|
+
process.stdin.on("error", onError);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export function openInEditor(initial, fileSuffix = ".md") {
|
|
68
|
+
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
69
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "exodus-edit-"));
|
|
70
|
+
const file = path.join(dir, `edit${fileSuffix}`);
|
|
71
|
+
try {
|
|
72
|
+
fs.writeFileSync(file, initial, "utf-8");
|
|
73
|
+
const result = spawnSync(editor, [file], { stdio: "inherit" });
|
|
74
|
+
if (result.status !== 0) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const out = fs.readFileSync(file, "utf-8");
|
|
78
|
+
return out.replace(/\n+$/, "");
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
try {
|
|
82
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export async function promptText(question) {
|
|
89
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
90
|
+
try {
|
|
91
|
+
const answer = await new Promise((resolve) => {
|
|
92
|
+
rl.question(`${question} `, resolve);
|
|
93
|
+
});
|
|
94
|
+
return answer.trim();
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
rl.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ReelWriteResult {
|
|
2
|
+
dispatched: Array<{
|
|
3
|
+
url: string;
|
|
4
|
+
key: string;
|
|
5
|
+
runId: string;
|
|
6
|
+
}>;
|
|
7
|
+
bankedFailed: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function captureReelAndWrite(urls: string[], opts: {
|
|
10
|
+
awarenessLevel: string;
|
|
11
|
+
variantCount?: number;
|
|
12
|
+
}, rt?: {
|
|
13
|
+
cc?: string;
|
|
14
|
+
}): Promise<ReelWriteResult>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { apiGet, apiPost } from "./client.js";
|
|
2
|
+
import { formatError } from "./format.js";
|
|
3
|
+
function normalizeUrl(u) {
|
|
4
|
+
return u.trim().replace(/\/+$/, "").toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
7
|
+
async function listOrganic() {
|
|
8
|
+
const res = await apiGet("/api/v2/idea-bank?source=organic&limit=200");
|
|
9
|
+
if (!res.ok)
|
|
10
|
+
return [];
|
|
11
|
+
return Array.isArray(res.data.ideas) ? res.data.ideas : [];
|
|
12
|
+
}
|
|
13
|
+
export async function captureReelAndWrite(urls, opts, rt = {}) {
|
|
14
|
+
const wanted = urls.map((u) => u.trim()).filter(Boolean);
|
|
15
|
+
if (wanted.length === 0)
|
|
16
|
+
return { dispatched: [], bankedFailed: [] };
|
|
17
|
+
const wantedNorm = new Map(wanted.map((u) => [normalizeUrl(u), u]));
|
|
18
|
+
const before = new Set((await listOrganic()).map((r) => r.key).filter(Boolean));
|
|
19
|
+
const ideate = await apiPost("/api/v2/idea-bank/ideate", { source: "organic", urls: wanted }, { ccCommand: rt.cc });
|
|
20
|
+
if (!ideate.ok) {
|
|
21
|
+
console.log(formatError(ideate));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const TIMEOUT_MS = 5 * 60 * 1000;
|
|
25
|
+
const INTERVAL_MS = 4_000;
|
|
26
|
+
const start = Date.now();
|
|
27
|
+
const found = new Map();
|
|
28
|
+
process.stdout.write(` transcribing ${wanted.length} reel${wanted.length === 1 ? "" : "s"} into ideas`);
|
|
29
|
+
while (found.size < wantedNorm.size && Date.now() - start < TIMEOUT_MS) {
|
|
30
|
+
await sleep(INTERVAL_MS);
|
|
31
|
+
process.stdout.write(".");
|
|
32
|
+
for (const row of await listOrganic()) {
|
|
33
|
+
if (!row.key || before.has(row.key) || found.has(normalizeUrl(row.sourceRef ?? "")))
|
|
34
|
+
continue;
|
|
35
|
+
const ref = row.sourceRef ? normalizeUrl(row.sourceRef) : "";
|
|
36
|
+
if (ref && wantedNorm.has(ref))
|
|
37
|
+
found.set(ref, row.key);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
process.stdout.write("\n");
|
|
41
|
+
const bankedFailed = [...wantedNorm.entries()]
|
|
42
|
+
.filter(([norm]) => !found.has(norm))
|
|
43
|
+
.map(([, original]) => original);
|
|
44
|
+
if (found.size === 0)
|
|
45
|
+
return { dispatched: [], bankedFailed };
|
|
46
|
+
const keys = [...found.values()];
|
|
47
|
+
const dispatch = await apiPost("/api/v2/idea-bank/dispatch", { keys, awarenessLevel: opts.awarenessLevel, variantCount: opts.variantCount }, { ccCommand: rt.cc });
|
|
48
|
+
if (!dispatch.ok) {
|
|
49
|
+
console.log(formatError(dispatch));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const keyToUrl = new Map([...found.entries()].map(([norm, key]) => [key, wantedNorm.get(norm)]));
|
|
53
|
+
const dispatched = (dispatch.data.dispatched ?? []).map((d) => ({
|
|
54
|
+
url: keyToUrl.get(d.key) ?? "",
|
|
55
|
+
key: d.key,
|
|
56
|
+
runId: d.runId,
|
|
57
|
+
}));
|
|
58
|
+
return { dispatched, bankedFailed };
|
|
59
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
2
|
+
export declare function readPackageVersion(exodusDir: string): string;
|
|
3
|
+
export declare function readOwnedCommands(exodusDir: string): string[];
|
|
4
|
+
export declare function missingInstalledCommands(exodusDir: string, ownedCommands: string[]): string[];
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function compareVersions(a, b) {
|
|
4
|
+
const parse = (v) => {
|
|
5
|
+
const clean = v.replace(/^v/, "");
|
|
6
|
+
const dash = clean.indexOf("-");
|
|
7
|
+
const main = dash === -1 ? clean : clean.slice(0, dash);
|
|
8
|
+
const pre = dash === -1 ? null : clean.slice(dash + 1);
|
|
9
|
+
return { nums: main.split(".").map((x) => parseInt(x, 10) || 0), pre };
|
|
10
|
+
};
|
|
11
|
+
const pa = parse(a);
|
|
12
|
+
const pb = parse(b);
|
|
13
|
+
const len = Math.max(pa.nums.length, pb.nums.length);
|
|
14
|
+
for (let i = 0; i < len; i++) {
|
|
15
|
+
const av = pa.nums[i] ?? 0;
|
|
16
|
+
const bv = pb.nums[i] ?? 0;
|
|
17
|
+
if (av !== bv)
|
|
18
|
+
return av < bv ? -1 : 1;
|
|
19
|
+
}
|
|
20
|
+
if (pa.pre === null && pb.pre === null)
|
|
21
|
+
return 0;
|
|
22
|
+
if (pa.pre === null)
|
|
23
|
+
return 1;
|
|
24
|
+
if (pb.pre === null)
|
|
25
|
+
return -1;
|
|
26
|
+
const sa = pa.pre.split(".");
|
|
27
|
+
const sb = pb.pre.split(".");
|
|
28
|
+
const slen = Math.max(sa.length, sb.length);
|
|
29
|
+
for (let i = 0; i < slen; i++) {
|
|
30
|
+
const x = sa[i];
|
|
31
|
+
const y = sb[i];
|
|
32
|
+
if (x === undefined)
|
|
33
|
+
return -1;
|
|
34
|
+
if (y === undefined)
|
|
35
|
+
return 1;
|
|
36
|
+
const xn = parseInt(x, 10);
|
|
37
|
+
const yn = parseInt(y, 10);
|
|
38
|
+
const bothNumeric = !Number.isNaN(xn) && !Number.isNaN(yn);
|
|
39
|
+
if (bothNumeric) {
|
|
40
|
+
if (xn !== yn)
|
|
41
|
+
return xn < yn ? -1 : 1;
|
|
42
|
+
}
|
|
43
|
+
else if (x !== y) {
|
|
44
|
+
return x < y ? -1 : 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
function readJson(filePath) {
|
|
50
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
51
|
+
}
|
|
52
|
+
export function readPackageVersion(exodusDir) {
|
|
53
|
+
try {
|
|
54
|
+
return readJson(path.join(exodusDir, "package.json")).version ?? "0.0.0";
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return "0.0.0";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function readOwnedCommands(exodusDir) {
|
|
61
|
+
const out = new Set();
|
|
62
|
+
for (const tier of ["base", "custom"]) {
|
|
63
|
+
const p = path.join(exodusDir, `.overlay-${tier}.json`);
|
|
64
|
+
if (!fs.existsSync(p))
|
|
65
|
+
continue;
|
|
66
|
+
try {
|
|
67
|
+
const m = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
68
|
+
if (Array.isArray(m.ownedCommands)) {
|
|
69
|
+
for (const c of m.ownedCommands)
|
|
70
|
+
if (typeof c === "string")
|
|
71
|
+
out.add(c);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return [...out];
|
|
78
|
+
}
|
|
79
|
+
export function missingInstalledCommands(exodusDir, ownedCommands) {
|
|
80
|
+
return ownedCommands.filter((c) => !fs.existsSync(path.join(exodusDir, "dist", "commands", `${c}.js`)));
|
|
81
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
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
|
+
created: boolean;
|
|
4
|
+
};
|
|
5
|
+
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 ensureBaseDirs(root: string): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { skillsDir, referencesDir } from "./assets.js";
|
|
4
|
+
export const ENV_SCAFFOLD = `# Exodus config — paste your dashboard .env block below this line.
|
|
5
|
+
# Get it from your Exodus dashboard -> Settings -> Claude Code -> "Copy .env block".
|
|
6
|
+
# ONE key covers every brand you own; switch brands with brand subfolders or
|
|
7
|
+
# \`npx @aicopycoders/exodus brand use <slug>\`, never by editing this file.
|
|
8
|
+
`;
|
|
9
|
+
export function writeEnvScaffold(root) {
|
|
10
|
+
const envPath = path.join(root, ".env");
|
|
11
|
+
if (fs.existsSync(envPath))
|
|
12
|
+
return { created: false };
|
|
13
|
+
fs.writeFileSync(envPath, ENV_SCAFFOLD);
|
|
14
|
+
return { created: true };
|
|
15
|
+
}
|
|
16
|
+
export function ensureGitignore(root) {
|
|
17
|
+
const giPath = path.join(root, ".gitignore");
|
|
18
|
+
const lines = fs.existsSync(giPath)
|
|
19
|
+
? fs.readFileSync(giPath, "utf8").split("\n")
|
|
20
|
+
: ["node_modules/", "output/*", "!output/.gitkeep"];
|
|
21
|
+
if (!lines.some((l) => l.trim() === ".env"))
|
|
22
|
+
lines.push(".env");
|
|
23
|
+
fs.writeFileSync(giPath, lines.join("\n").replace(/\n+$/, "") + "\n");
|
|
24
|
+
}
|
|
25
|
+
export function writeSkills(root, srcOverride) {
|
|
26
|
+
const src = skillsDir(srcOverride);
|
|
27
|
+
const dest = path.join(root, ".claude", "skills");
|
|
28
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
29
|
+
const names = [];
|
|
30
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
31
|
+
if (!entry.isDirectory())
|
|
32
|
+
continue;
|
|
33
|
+
const target = path.join(dest, entry.name);
|
|
34
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
35
|
+
fs.cpSync(path.join(src, entry.name), target, { recursive: true });
|
|
36
|
+
names.push(entry.name);
|
|
37
|
+
}
|
|
38
|
+
return names;
|
|
39
|
+
}
|
|
40
|
+
export function writeReferences(root, srcOverride) {
|
|
41
|
+
let src;
|
|
42
|
+
try {
|
|
43
|
+
src = referencesDir(srcOverride);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!fs.existsSync(src))
|
|
49
|
+
return;
|
|
50
|
+
const dest = path.join(root, "references");
|
|
51
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
52
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
export function ensureBaseDirs(root) {
|
|
55
|
+
const out = path.join(root, "output");
|
|
56
|
+
fs.mkdirSync(out, { recursive: true });
|
|
57
|
+
const keep = path.join(out, ".gitkeep");
|
|
58
|
+
if (!fs.existsSync(keep))
|
|
59
|
+
fs.writeFileSync(keep, "");
|
|
60
|
+
fs.mkdirSync(path.join(root, "state"), { recursive: true });
|
|
61
|
+
const exodusDir = path.join(root, ".exodus");
|
|
62
|
+
fs.mkdirSync(exodusDir, { recursive: true });
|
|
63
|
+
const statePath = path.join(exodusDir, "state.json");
|
|
64
|
+
if (!fs.existsSync(statePath)) {
|
|
65
|
+
fs.writeFileSync(statePath, JSON.stringify({ layoutVersion: 2 }, null, 2) + "\n");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function hydrateScoutIdeasCount<T extends Record<string, unknown>>(runId: string, data: T, fetchCount: (runId: string) => Promise<number | null>): Promise<T>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export async function hydrateScoutIdeasCount(runId, data, fetchCount) {
|
|
2
|
+
const generated = data["ideasGenerated"];
|
|
3
|
+
if (generated === 0 || generated === undefined) {
|
|
4
|
+
const count = await fetchCount(runId);
|
|
5
|
+
if (count !== null && typeof count === "number") {
|
|
6
|
+
data["ideasGenerated"] = count;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return data;
|
|
10
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function findWorkspaceRoot(): string;
|
|
2
|
+
export declare function getActiveBrand(): string | null;
|
|
3
|
+
export declare function setActiveBrand(slug: string): void;
|
|
4
|
+
export declare function clearActiveBrand(): void;
|
|
5
|
+
export declare function getLayoutVersion(): number | null;
|
|
6
|
+
export declare function setLayoutVersion(version: number): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { findParentRoot } from "./layout.js";
|
|
4
|
+
export function findWorkspaceRoot() {
|
|
5
|
+
return findParentRoot();
|
|
6
|
+
}
|
|
7
|
+
function findStateDir() {
|
|
8
|
+
return path.join(findWorkspaceRoot(), ".exodus");
|
|
9
|
+
}
|
|
10
|
+
function statePath() {
|
|
11
|
+
return path.join(findStateDir(), "state.json");
|
|
12
|
+
}
|
|
13
|
+
function readState() {
|
|
14
|
+
const p = statePath();
|
|
15
|
+
if (!fs.existsSync(p))
|
|
16
|
+
return {};
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function writeState(next) {
|
|
25
|
+
const dir = findStateDir();
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
fs.writeFileSync(statePath(), JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
28
|
+
}
|
|
29
|
+
export function getActiveBrand() {
|
|
30
|
+
return readState().activeBrand ?? null;
|
|
31
|
+
}
|
|
32
|
+
export function setActiveBrand(slug) {
|
|
33
|
+
const s = readState();
|
|
34
|
+
s.activeBrand = slug;
|
|
35
|
+
writeState(s);
|
|
36
|
+
}
|
|
37
|
+
export function clearActiveBrand() {
|
|
38
|
+
const s = readState();
|
|
39
|
+
delete s.activeBrand;
|
|
40
|
+
writeState(s);
|
|
41
|
+
}
|
|
42
|
+
export function getLayoutVersion() {
|
|
43
|
+
return readState().layoutVersion ?? null;
|
|
44
|
+
}
|
|
45
|
+
export function setLayoutVersion(version) {
|
|
46
|
+
const s = readState();
|
|
47
|
+
s.layoutVersion = version;
|
|
48
|
+
writeState(s);
|
|
49
|
+
}
|
|
@@ -0,0 +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"];
|
|
2
|
+
export type AdType = (typeof AD_TYPES)[number];
|
|
3
|
+
export declare const AD_TYPE_NAMES: Record<AdType, string>;
|
|
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"];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const AD_TYPES = [
|
|
2
|
+
"before-after",
|
|
3
|
+
"native-news",
|
|
4
|
+
"testimonial",
|
|
5
|
+
"bold",
|
|
6
|
+
"meme",
|
|
7
|
+
"screenshot",
|
|
8
|
+
"hero",
|
|
9
|
+
"holding-sign",
|
|
10
|
+
"handwritten",
|
|
11
|
+
"comment",
|
|
12
|
+
"breaking-news",
|
|
13
|
+
"lofi",
|
|
14
|
+
"comparison",
|
|
15
|
+
"infographic",
|
|
16
|
+
"headline",
|
|
17
|
+
"collage",
|
|
18
|
+
"product-breakdown",
|
|
19
|
+
"scientific",
|
|
20
|
+
"animation",
|
|
21
|
+
"founder-note",
|
|
22
|
+
"carousel",
|
|
23
|
+
"statistics",
|
|
24
|
+
"post-it-notes",
|
|
25
|
+
"happy-avatar",
|
|
26
|
+
"problem-solution",
|
|
27
|
+
"writing-on-body",
|
|
28
|
+
"multi-testimonial",
|
|
29
|
+
"receipt",
|
|
30
|
+
"step-by-step",
|
|
31
|
+
"sale-promotional-offer",
|
|
32
|
+
"cost-of-inaction",
|
|
33
|
+
"ugc",
|
|
34
|
+
"quiz-interactive",
|
|
35
|
+
];
|
|
36
|
+
export const AD_TYPE_NAMES = {
|
|
37
|
+
"before-after": "Before / After",
|
|
38
|
+
"native-news": "Native News",
|
|
39
|
+
testimonial: "Testimonial",
|
|
40
|
+
bold: "Bold Typography",
|
|
41
|
+
meme: "Meme",
|
|
42
|
+
screenshot: "Screenshot",
|
|
43
|
+
hero: "Hero",
|
|
44
|
+
"holding-sign": "Holding Sign",
|
|
45
|
+
handwritten: "Handwritten Note",
|
|
46
|
+
comment: "Comment / Review",
|
|
47
|
+
"breaking-news": "Breaking News",
|
|
48
|
+
lofi: "Lo-Fi",
|
|
49
|
+
comparison: "Comparison",
|
|
50
|
+
infographic: "Infographic",
|
|
51
|
+
headline: "Headline + Image",
|
|
52
|
+
collage: "Curiosity Collage",
|
|
53
|
+
"product-breakdown": "Product Breakdown",
|
|
54
|
+
scientific: "Scientific Study",
|
|
55
|
+
animation: "Animation",
|
|
56
|
+
"founder-note": "Founder Note",
|
|
57
|
+
carousel: "Carousel",
|
|
58
|
+
statistics: "Statistics",
|
|
59
|
+
"post-it-notes": "Post-It Notes",
|
|
60
|
+
"happy-avatar": "Happy Avatar",
|
|
61
|
+
"problem-solution": "Problem-Solution",
|
|
62
|
+
"writing-on-body": "Writing On Body",
|
|
63
|
+
"multi-testimonial": "Multi-Testimonial",
|
|
64
|
+
receipt: "Receipt",
|
|
65
|
+
"step-by-step": "Step by Step",
|
|
66
|
+
"sale-promotional-offer": "Sale / Promotional Offer",
|
|
67
|
+
"cost-of-inaction": "Cost of Inaction",
|
|
68
|
+
ugc: "UGC",
|
|
69
|
+
"quiz-interactive": "Quiz / Interactive Self-Selection",
|
|
70
|
+
};
|
|
71
|
+
export const REPTILE_TRIGGERS = [
|
|
72
|
+
"ultra-real",
|
|
73
|
+
"bizarre",
|
|
74
|
+
"voyeur",
|
|
75
|
+
"suffering",
|
|
76
|
+
"gory",
|
|
77
|
+
"sexual",
|
|
78
|
+
"primal-fear",
|
|
79
|
+
"odd-contrast",
|
|
80
|
+
"inside-joke",
|
|
81
|
+
"time-warp",
|
|
82
|
+
"victory-lap",
|
|
83
|
+
"selfie",
|
|
84
|
+
"uncanny-objects",
|
|
85
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getVersion(): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname, join, parse } from "node:path";
|
|
4
|
+
export function getVersion() {
|
|
5
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const { root } = parse(dir);
|
|
7
|
+
for (;;) {
|
|
8
|
+
try {
|
|
9
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
10
|
+
if (typeof pkg.version === "string")
|
|
11
|
+
return pkg.version;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
}
|
|
15
|
+
if (dir === root)
|
|
16
|
+
return "unknown";
|
|
17
|
+
dir = dirname(dir);
|
|
18
|
+
}
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aicopycoders/exodus",
|
|
3
|
+
"version": "2026.6.2400",
|
|
4
|
+
"description": "CLI + Claude Code skills for the Copycoders Exodus ad pipeline",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"exodus": "./dist/bin/exodus.js"
|
|
8
|
+
},
|
|
9
|
+
"files": ["dist", "assets"],
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public",
|
|
15
|
+
"provenance": true
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "rm -rf dist && tsc",
|
|
19
|
+
"bundle-assets": "node scripts/bundle-assets.mjs",
|
|
20
|
+
"prepack": "npm run build && npm run bundle-assets",
|
|
21
|
+
"dev": "tsx bin/exodus.ts"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^20.0.0",
|
|
26
|
+
"esbuild": "^0.25.0",
|
|
27
|
+
"tsx": "^4.7.0",
|
|
28
|
+
"typescript": "^5.4.0"
|
|
29
|
+
}
|
|
30
|
+
}
|