@aicopycoders/exodus 2026.6.2700-beta.2 → 2026.6.2900-beta.4
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/docs/CLAUDE.md +125 -0
- package/assets/docs/PIPELINES.md +202 -0
- package/assets/skills/exodus-genesis/SKILL.md +76 -25
- package/assets/skills/exodus-write/SKILL.md +18 -12
- package/dist/commands/genesis.d.ts +12 -0
- package/dist/commands/genesis.js +131 -5
- package/dist/commands/idea.d.ts +2 -0
- package/dist/commands/idea.js +29 -3
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +6 -2
- package/dist/lib/ad-library-write.d.ts +1 -0
- package/dist/lib/ad-library-write.js +1 -1
- package/dist/lib/assets.d.ts +1 -0
- package/dist/lib/assets.js +3 -0
- package/dist/lib/reel-write.d.ts +1 -0
- package/dist/lib/reel-write.js +1 -1
- package/dist/lib/scaffold.d.ts +1 -0
- package/dist/lib/scaffold.js +20 -1
- package/package.json +1 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Creative Strategy Workspace
|
|
2
|
+
|
|
3
|
+
> **New here?** Your onboarding home is the Exodus dashboard (Settings → Claude Code) — it walks you through install and keys. If anything's broken at runtime, run `npx @aicopycoders/exodus doctor` from this directory — it diagnoses local and dashboard-side issues in one shot.
|
|
4
|
+
|
|
5
|
+
This is a creative-strategy workspace operated by the **exodus** CLI. It is brand-agnostic infrastructure serving **all of the account's brands**: each brand has its own subfolder here (marked by a `.exodus-brand.json` file) holding that brand's `state/` and `output/`. Which brand you're working on resolves **folder first, pointer second**: commands run from inside a brand's subfolder target that brand automatically; otherwise the `npx @aicopycoders/exodus brand use <slug>` pointer applies. (Older single-brand installs keep `state/` and `output/` at this root — same rules, one brand.)
|
|
6
|
+
|
|
7
|
+
**How to engage the system:** every skill here is namespaced `exodus-*`, and a front-door `exodus` hub skill routes generic asks. Members say "exodus" to engage ("exodus, write me some ads") or invoke a specific skill with `/exodus-<name>` (e.g. `/exodus-write`). This namespacing is deliberate: it lets these skills coexist with other skill packs (like the workshop pack) installed in the same folder, and it keeps the bare word "Genesis" unambiguous — on its own, "Genesis" always means the member's own Genesis API key/recipes, never Exodus. CLI commands are unchanged (`npx @aicopycoders/exodus genesis`, `npx @aicopycoders/exodus brand use`, etc.).
|
|
8
|
+
|
|
9
|
+
The strategist persona, the Luke Mills / Genesis frameworks, the operating rules and creative principles, and the operating discipline all live in **one skill** plus its references:
|
|
10
|
+
|
|
11
|
+
- `.claude/skills/exodus-strategist/SKILL.md` — the single always-on creative-strategy skill: persona, operating rules, creative principles, red flags, active-brand resolution, the quality bars, and a **Framework Index** that routes to the depth below
|
|
12
|
+
- `references/` — the framework depth, consulted from the skill when needed: `creative-strategy.md` (the encyclopedia + Genesis bot catalog), `segments.md`, `copy-blocks.md` (P3C2), `cast-video.md` (CAST), `iterations.md` (CASHED), `editing-rules.md`, `scrawls.md`, `hook-quality-checklist.md`, `awareness-framework.md`
|
|
13
|
+
|
|
14
|
+
If the persona isn't firing for you, check that the skill loaded. It is not optional reading.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Two systems in this folder: Exodus (automatic) + Genesis (manual)
|
|
19
|
+
|
|
20
|
+
This folder usually holds **two related products that share the same Genesis engine** — keep them straight. Think of it as a car: same engine underneath, two ways to drive it.
|
|
21
|
+
|
|
22
|
+
- **Exodus — the automatic transmission.** This workspace: the `exodus-*` skills and the `npx @aicopycoders/exodus` CLI. Pre-built, done-for-you pipelines (write, image, idea bank, brand onboarding) that drive the Genesis bots *for* you against your brand. You engage it by saying **"exodus"** ("exodus, write me some ads"). Its keys are `EXODUS_API_KEY` / `EXODUS_API_URL` in `.env`; Genesis access is internal — there's nothing to configure or debug about Genesis for Exodus to work.
|
|
23
|
+
|
|
24
|
+
- **Genesis (standalone) — the manual transmission.** A separate skill at `.claude/skills/genesis-bots` that calls the *same* Genesis copywriting / research / strategy bots **directly** with the member's own `gen_` key — you pick a bot and prompt it yourself. It reads `GENESIS_API_KEY` plus a provider key (`ANTHROPIC_API_KEY` or `OPENROUTER_API_KEY`) from `.env`. This is the member's own workflow, **not** owned by the `exodus-*` skills. (It may not be installed in every folder — check for the `genesis-bots` skill.)
|
|
25
|
+
|
|
26
|
+
**Routing rule:** "exodus …" → the Exodus pipelines below. Bare **"genesis"**, "run the bots", or a named bot slug → the `genesis-bots` skill (the member's direct workflow — never route it into Exodus). If someone clearly just wants ads and doesn't know which system they have, ask one question — "drive the Genesis bots directly, or run an Exodus pipeline?" — rather than guessing.
|
|
27
|
+
|
|
28
|
+
**Provider key:** the manual Genesis bots need a provider key in `.env`. `npx @aicopycoders/exodus keys pull` syncs your dashboard provider + image keys down for you (the install runs it; safe to re-run anytime keys change). Exodus's own pipelines don't need it locally — they run server-side.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Multi-Brand Operating Notes
|
|
33
|
+
|
|
34
|
+
Any user can create and own unlimited brands — `npx @aicopycoders/exodus brand create "<name>"` (or the dashboard); it is NOT admin-only, and brand names are scoped per user (two users can each have a "Matt Beard"). One account key then switches across every brand the user owns (admins: every brand). The only exception is a brand someone ELSE invited the user into — they don't own it, so their key stays pinned to just that one. Never tell a user they can't make brands or are "locked to one brand" just because their key shows `role: member`. The active brand resolves **folder > pointer > key default**: being inside a brand's subfolder wins, then the `brand use` pointer, then the key's default brand.
|
|
35
|
+
|
|
36
|
+
- **Identify the active brand:** `npx @aicopycoders/exodus brand current` (also says whether it came from the folder or the pointer)
|
|
37
|
+
- **List accessible brands:** `npx @aicopycoders/exodus brand list`
|
|
38
|
+
- **Create a new brand:** `npx @aicopycoders/exodus brand create "<name>"` — creates it server-side, switches you in, and sets up its local subfolder
|
|
39
|
+
- **Switch brand:** `npx @aicopycoders/exodus brand use <slug>` — or just work from inside that brand's subfolder
|
|
40
|
+
- **New brand created on the dashboard?** `npx @aicopycoders/exodus@latest init` creates its subfolder and pulls its profile
|
|
41
|
+
- **Brand voice / ICP / offer:** the active brand's `state/brand-profile.md` (inside its subfolder; at this root on older single-brand installs) — read this at session start and after any switch. If it doesn't match the active brand, the exodus-strategist skill will prompt to refresh.
|
|
42
|
+
|
|
43
|
+
Don't carry voice or proven angles across brands — each brand is its own creative universe.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Available Pipelines
|
|
48
|
+
|
|
49
|
+
### Exodus pipelines (automatic)
|
|
50
|
+
|
|
51
|
+
These are the **Exodus** pipelines — the automatic system that drives the Genesis bots for you. Use them to generate and analyze creative. Each has its own skill directory in `.claude/skills/`. Invoke them by saying "exodus" plus what you want, or directly with `/exodus-<name>`.
|
|
52
|
+
|
|
53
|
+
| Pipeline | Skill | When to Use |
|
|
54
|
+
|----------|-----------|-------------|
|
|
55
|
+
| **Write** | `exodus-write` | **Start here for copywriting.** Say what you want ("exodus, write me some ads"); it routes new-brand → foundation → brief → genesis → Doc and sequences the whole flow. |
|
|
56
|
+
| **Genesis** | `exodus-genesis` | Brief → ad copy in two voices (Mario + Infeed). Default 1 pass = 2 variants; scale with `--passes` (pass 2 adds the Top-Ads-biased primer). Also writes from a pasted reel (`--reel`). |
|
|
57
|
+
| **Idea** | `exodus-idea` | The Idea Bank — capture reels/brain-dumps/swipes into formed ideas, curate them, then write the keepers through Genesis. Use when collecting and curating ideas rather than writing one ad on the spot. |
|
|
58
|
+
| **Image** | `exodus-image` | **Start here for static images.** Say what you want; it reads the request and routes to the right engine — `exodus-creative` (renders from copy) or `exodus-template` (33 ad-type formats). |
|
|
59
|
+
| **Creative** | `exodus-creative` | A specific creative-suite engine (native / copy-derived renders from copy, or ref-match to a reference image) when you've already picked one. Otherwise start at **Image**. |
|
|
60
|
+
| **Template** | `exodus-template` | Finished ad copy → many static ads across 33 structured formats (testimonial, hero, UGC…). Otherwise start at **Image**. |
|
|
61
|
+
| **Meme** | `exodus-meme` | Meme ads — recommends formats for a brief, then one batched run renders AI-image and classic Imgflip-template memes server-side. |
|
|
62
|
+
| **Browse** | `exodus-browse` | View history and retrieve past outputs |
|
|
63
|
+
| **Drive** | `exodus-drive` | Read/write Google Docs, Sheets, and Drive files via `npx @aicopycoders/exodus drive` (uses the dashboard's Google OAuth — no local CLI) |
|
|
64
|
+
|
|
65
|
+
For the operator-facing quick reference on how to invoke these in Claude Code, see `PIPELINES.md` in the workspace root.
|
|
66
|
+
|
|
67
|
+
### Genesis bots (manual)
|
|
68
|
+
|
|
69
|
+
The same folder usually also carries **`genesis-bots`** — the *manual* way to drive the same Genesis bots. You pick a bot and prompt it yourself with the member's own `gen_` key, chaining bots by hand (buyer profile → creative brief → ad hooks → ads). It is **not** an `exodus-*` skill and is **never** invoked by saying "exodus".
|
|
70
|
+
|
|
71
|
+
| Skill | When to Use |
|
|
72
|
+
|-------|-------------|
|
|
73
|
+
| `genesis-bots` | The user says bare **"genesis"**, "run the bots", "openclaw", or names a bot slug, and wants to call a Genesis copywriting / research / strategy / image-prompt bot directly. Needs `GENESIS_API_KEY` plus a provider key (`ANTHROPIC_API_KEY` or `OPENROUTER_API_KEY`) in `.env` — see **Dashboard** below. May not be installed in every folder — check for the skill directory before assuming it's there. |
|
|
74
|
+
|
|
75
|
+
Route a bare-"genesis" ask to this skill and an "exodus …" ask to the pipelines above — never cross the two. If someone clearly just wants ads and doesn't know which system they have, ask one question ("drive the Genesis bots directly, or run an Exodus pipeline?") rather than guessing.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Awareness Level Routing
|
|
80
|
+
|
|
81
|
+
Every ad targets an audience at a specific awareness level. Getting this right is one of the highest-leverage decisions in creative strategy.
|
|
82
|
+
|
|
83
|
+
> `--awareness` sets the audience awareness level for a Genesis run (unaware / problem-aware / solution-aware / product-aware). Pick the level the idea is actually written for.
|
|
84
|
+
|
|
85
|
+
| Level | Audience State | Ad Approach |
|
|
86
|
+
|-------|---------------|-------------|
|
|
87
|
+
| **Unaware** | Doesn't know they have a problem | Shock, pattern interrupt, reveal |
|
|
88
|
+
| **Problem-Aware** | Knows the problem, not the solution | Educate, validate, agitate |
|
|
89
|
+
| **Solution-Aware** | Knows solutions exist, not ours | Compare, differentiate, position |
|
|
90
|
+
| **Product-Aware** | Knows the brand, hasn't bought yet | Offer, urgency, social proof |
|
|
91
|
+
|
|
92
|
+
Routing detail and signal patterns live in `.claude/skills/exodus-strategist/SKILL.md` and `references/creative-strategy.md`.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Dashboard
|
|
97
|
+
|
|
98
|
+
This workspace connects to a live dashboard for:
|
|
99
|
+
- Settings management (defaults, account connections)
|
|
100
|
+
- **Pipeline keys** — your provider LLM key (Anthropic or OpenRouter) plus Kie.ai, entered once in Settings → Pipeline Keys
|
|
101
|
+
- Google Drive credentials (where outputs are saved)
|
|
102
|
+
- Run history and output retrieval
|
|
103
|
+
- Ad account connections (e.g. the Genesis top-ads-biased track)
|
|
104
|
+
- Brand admin (switching, profile management)
|
|
105
|
+
|
|
106
|
+
The dashboard URL and your `EXODUS_API_KEY` live in `.env`.
|
|
107
|
+
|
|
108
|
+
**Keys & setup.** Exodus's own pipelines run server-side and need nothing beyond `EXODUS_API_KEY` — there's no provider key to configure for them. The **manual `genesis-bots` path** (and other local tools) *do* need a provider key in `.env`. Enter your keys once in the dashboard, then pull them down:
|
|
109
|
+
|
|
110
|
+
- `npx @aicopycoders/exodus keys pull` — writes your dashboard provider + Kie.ai keys into `.env` (the install runs this for you; safe to re-run anytime a key changes). It only upserts the keys and preserves the rest of the file.
|
|
111
|
+
- `npx @aicopycoders/exodus keys status` — shows which keys are set in the dashboard vs locally. Values are never printed.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Reviewing Run Output
|
|
116
|
+
|
|
117
|
+
When you need to review a run's creative output, always read it via `npx @aicopycoders/exodus read-doc <runId>`. This walks every tab of the Google Doc and gives you the full output as markdown.
|
|
118
|
+
|
|
119
|
+
Do **not** use the Drive MCP as your first choice — it only sees tab 1 and will misreport the run as empty. If `npx @aicopycoders/exodus read-doc` exits with code 2 (`unavailable`), fall back to the Drive MCP using the URL printed by the command, and warn the user in your summary that you only saw tab 1.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Tool Feedback
|
|
124
|
+
|
|
125
|
+
This workspace is also a testbed for the CLI and pipelines. When you run a pipeline and hit a gap — a missing input, missing output, missing filter, awkward flow — log it per the **Tool Feedback Protocol** in `.claude/skills/exodus-strategist/SKILL.md`. Your feedback drives the next round of pipeline improvements.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Pipelines Cheat Sheet
|
|
2
|
+
|
|
3
|
+
In Claude Code you don't click buttons. You say **"exodus"** plus what you want — "exodus, write me some ads" — and the `exodus` hub skill routes you to the right pipeline. You can also invoke a specific skill directly with a slash command: `/exodus-write`, `/exodus-image`, etc.
|
|
4
|
+
|
|
5
|
+
> This doc is the CLI/skill invocation reference for the active brand workspace. For the dashboard's clickable pipeline list, see the dashboard's Creative Suite.
|
|
6
|
+
|
|
7
|
+
## All skills at a glance
|
|
8
|
+
|
|
9
|
+
| Skill | What it does |
|
|
10
|
+
|-------|--------------|
|
|
11
|
+
| exodus-write | The front door — routes brand → foundation → brief → Genesis → Doc. Start here. |
|
|
12
|
+
| exodus-genesis | Brief → Ad Copy (two voices; 1 pass = 2 variants, scalable via --passes) |
|
|
13
|
+
| exodus-image | The front door for static images — routes to exodus-creative or exodus-template. Start here for "make me images / statics". |
|
|
14
|
+
| exodus-creative | Creative-Suite Engines (native / copy-derived / ref-match) — when you've already picked an engine |
|
|
15
|
+
| exodus-template | Ad-Type Format Variations across 33 structured formats |
|
|
16
|
+
| exodus-meme | Meme Ad Generator — recommend formats, one batched server-side run |
|
|
17
|
+
| exodus-browse | View History and Surface the Right Run |
|
|
18
|
+
| exodus-drive | Google Drive, Docs, Sheets via the Dashboard's OAuth |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## exodus-write (start here)
|
|
23
|
+
|
|
24
|
+
**What it does:** The guided front door for copywriting. You don't need to know which command to run — say "exodus" plus what you want and the exodus-write flow figures out where you are, helps shape the brief, sets up your foundation if needed, runs Genesis, and hands back the Google Doc.
|
|
25
|
+
|
|
26
|
+
```operator-guide
|
|
27
|
+
No command — say "exodus" plus what you want (or /exodus-write). It sequences
|
|
28
|
+
the existing commands (foundation, genesis, browse, read-doc) for you.
|
|
29
|
+
|
|
30
|
+
Say things like:
|
|
31
|
+
"exodus, I want to write some ads: <your idea>"
|
|
32
|
+
"exodus, turn this winning ad into copy: <paste>"
|
|
33
|
+
"exodus, set up a new brand and write my first ad"
|
|
34
|
+
"exodus, make a variant of that last run"
|
|
35
|
+
|
|
36
|
+
Routing:
|
|
37
|
+
new brand / no foundation → onboarding (foundation, optional primer prompt)
|
|
38
|
+
brief or idea in hand → straight to the Genesis writing pass
|
|
39
|
+
rerun / tweak → browse past runs + the editing menu
|
|
40
|
+
Works without a primer (defaults); sharper once your foundation has winning examples.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## exodus-genesis
|
|
46
|
+
|
|
47
|
+
**What it does:** Take a brief through Luke's full Genesis writing process — two voices (Mario × Brand + Infeed × Brand). Default is 1 pass per bot = 2 variants; add `--passes` to widen coverage (pass 2 brings in the Top-Ads-biased primer). Also accepts per-run seeds extracted from a source ad.
|
|
48
|
+
|
|
49
|
+
```operator-guide
|
|
50
|
+
Inputs:
|
|
51
|
+
--brief <file|"text"> (required) — path to a text file OR an inline brief string
|
|
52
|
+
Optional:
|
|
53
|
+
--seeds <file|"text"> — per-run creative seeds (file: one per line or JSON array; inline: one string)
|
|
54
|
+
--awareness — unaware | problem-aware (default) | solution-aware | product-aware
|
|
55
|
+
--passes <n> — writing passes per bot (1–5, default 1). 1 pass = 2 variants
|
|
56
|
+
(1 Mario×Brand + 1 Infeed×Brand); pass 2 adds the Top-Ads-biased primer
|
|
57
|
+
--variants <n> — advanced: raw total count (1–10) that overrides --passes
|
|
58
|
+
--ad-account <id> — Meta ad account for the top-ads-biased track (defaults to first active account)
|
|
59
|
+
--no-wait — return immediately with the run ID
|
|
60
|
+
Returns: Google Doc URL printed to stdout when the run completes
|
|
61
|
+
Examples:
|
|
62
|
+
exodus genesis run --brief brief.txt --seeds seeds.txt
|
|
63
|
+
exodus genesis run --brief "Grounding sheets help joint pain" --awareness problem-aware --passes 2
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## exodus-image (start here for statics)
|
|
69
|
+
|
|
70
|
+
**What it does:** The front door for static image ads. Just say "exodus" plus what
|
|
71
|
+
you want — "exodus, make me some statics from this ad", "exodus, render AD-001" —
|
|
72
|
+
and Claude reads the request and runs the right engine: **exodus-creative**
|
|
73
|
+
(renders straight from copy, or matches a reference image) or **exodus-template**
|
|
74
|
+
(one piece of copy spread across 33 ad-type formats). You don't pick the engine;
|
|
75
|
+
the request decides it. Use the engines below directly only when you already know
|
|
76
|
+
exactly which one you want.
|
|
77
|
+
|
|
78
|
+
**Say something like:** "exodus, make me some image ads from this copy" · "exodus,
|
|
79
|
+
render this ad as statics" · "exodus, spread AD-001 across ad formats" · "exodus,
|
|
80
|
+
make ads that look like this library image".
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## exodus-creative
|
|
85
|
+
|
|
86
|
+
**What it does:** Generate static creative images through the creative-suite engines — native (render image ads straight from ad copy), copy-derived (renders derived from copy variations), and ref-match (match the look of one or more reference images you've uploaded to the library).
|
|
87
|
+
|
|
88
|
+
```operator-guide
|
|
89
|
+
Subcommands:
|
|
90
|
+
exodus creative native --ad "<text>" [options] Render image ads from ad copy
|
|
91
|
+
exodus creative copy-derived --ad "<text>" [options] Renders derived from copy variations
|
|
92
|
+
exodus creative ref-match --refs <id,id,...> [options] Match the look of reference image(s)
|
|
93
|
+
exodus creative status --id <runId> Poll a run
|
|
94
|
+
|
|
95
|
+
Shared options (kickoffs):
|
|
96
|
+
--variations N Renders to generate (default: engine-specific)
|
|
97
|
+
--aspect 1:1|4:5|9:16 Image aspect ratio (default: 1:1)
|
|
98
|
+
--name "<label>" Custom run name (otherwise auto-generated)
|
|
99
|
+
--ad-group <id> Attach to an existing ad-group
|
|
100
|
+
|
|
101
|
+
ref-match additional options:
|
|
102
|
+
--refs <id,id,...> REQUIRED. Comma-separated creativeSuiteImages ids
|
|
103
|
+
(the images must already be in the dashboard library)
|
|
104
|
+
--subject "<text>" Optional subject hint
|
|
105
|
+
--objects <id,id,...> Optional object-overlay image ids
|
|
106
|
+
|
|
107
|
+
Returns: runId + dashboard URL (/creative-suite/runs/<runId>).
|
|
108
|
+
Status: `exodus creative status --id <runId>` → engine, status, completed/total, isTerminal.
|
|
109
|
+
Auth: Bearer (kickoff via Convex HTTP; status via dashboard route).
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## exodus-template
|
|
115
|
+
|
|
116
|
+
**What it does:** Turn finished ad copy into many static ad images spread across structured ad-type formats (testimonial, hero, UGC, logo, infographic, and 28 more). Runs Fernando's 5-stage Template pipeline — give it one brief or a numbered list of ads and it generates format variations, then renders them.
|
|
117
|
+
|
|
118
|
+
```operator-guide
|
|
119
|
+
Subcommands:
|
|
120
|
+
exodus template run --input "<text>" [options] Kick off a run
|
|
121
|
+
exodus template resume --id <runId> Finalize an orphaned run (#56)
|
|
122
|
+
exodus template ad-types Print the 33 AD_TYPES
|
|
123
|
+
exodus template reptile-triggers Print the 13 reptile triggers
|
|
124
|
+
|
|
125
|
+
run options:
|
|
126
|
+
--input "<text>" REQUIRED. One brief, or a numbered list of finished ads
|
|
127
|
+
("1. <ad copy> 2. <ad copy> ..."). Min 3 chars.
|
|
128
|
+
--mode auto|manual|hybrid Generation mode (default: auto)
|
|
129
|
+
--render-mode images|prompts prompts = skip render, return LLM prompts only (default: images)
|
|
130
|
+
--aspect 1:1|9:16 Image aspect ratio (default: 1:1)
|
|
131
|
+
--model gpt-image-2|nano-banana-pro Kie.ai model (default: gpt-image-2)
|
|
132
|
+
--realism off|realistic Realism enforcement (default: off)
|
|
133
|
+
--quantities "slug:N,slug:N" manual-mode per-type counts (e.g. "testimonial:3,hero:2")
|
|
134
|
+
--pairings '<JSON>' hybrid-mode host/modifier pairs: '[{"host":"testimonial","modifier":"hero"}]'
|
|
135
|
+
--requested-count N auto-mode total render target (optional)
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
runId + dashboard URL. POST-only — there is NO CLI status endpoint yet.
|
|
139
|
+
Live progress renders in the dashboard at /creative-suite/template/sessions/<runId>.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## exodus-meme
|
|
145
|
+
|
|
146
|
+
**What it does:** Generate meme-style ad creative for the active brand — recommends meme formats for a brief, then renders the whole batch server-side in one run (AI image memes via Kie.ai, classic Imgflip-template memes on the member's Imgflip login).
|
|
147
|
+
|
|
148
|
+
```operator-guide
|
|
149
|
+
Subcommands:
|
|
150
|
+
exodus meme recommend --brief "<text>" [--avatar "<text>"]
|
|
151
|
+
exodus meme run --brief "<text>" --formats '<json>' [--avatar "<text>"] [--name "<label>"]
|
|
152
|
+
exodus meme regenerate --brief "<text>" --layer 1|2|3 <format fields> [--hint "<text>"] [--id <runId>]
|
|
153
|
+
|
|
154
|
+
Typical flow:
|
|
155
|
+
1. recommend → { recommendations: [...] } — 15 picks (5 per layer) with
|
|
156
|
+
name, layer, reasoning, ad_angle. Layer 1 = classic Imgflip
|
|
157
|
+
templates; layers 2/3 = AI-image formats.
|
|
158
|
+
2. run → pass the picked recommendation objects straight into
|
|
159
|
+
--formats (JSON array, 1–50). One enqueue; the batch renders
|
|
160
|
+
server-side via Trigger.dev — closing the session won't kill it.
|
|
161
|
+
3. poll → exodus status --id <runId> --type creative
|
|
162
|
+
(terminal: complete | partial-error | error)
|
|
163
|
+
4. regenerate → re-render a single miss (layer 1: --template-id
|
|
164
|
+
--template-name --boxes; layer 2/3: --format [--hint]).
|
|
165
|
+
|
|
166
|
+
Keys (strict BYOK, preflighted server-side): classic memes need the member's
|
|
167
|
+
Imgflip login; AI memes need their Kie.ai key; captions always need an LLM key.
|
|
168
|
+
Returns: runId + dashboard URL /creative-suite/runs/<runId> (results land in the library).
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## exodus-browse
|
|
174
|
+
|
|
175
|
+
**What it does:** View past pipeline runs and pull outputs from previous sessions.
|
|
176
|
+
|
|
177
|
+
```operator-guide
|
|
178
|
+
Inputs:
|
|
179
|
+
none required — defaults to recent runs across all pipelines
|
|
180
|
+
Flags:
|
|
181
|
+
--limit (default: 20) — max runs to list
|
|
182
|
+
--agent — filter by agent: genesis | creative | template | meme
|
|
183
|
+
Returns: terminal table of recent runs with timestamps, agents, statuses, and Doc URLs
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## exodus-drive
|
|
189
|
+
|
|
190
|
+
**What it does:** Read and write Google Drive / Docs / Sheets via the dashboard's existing OAuth, using `npx @aicopycoders/exodus drive` subcommands.
|
|
191
|
+
|
|
192
|
+
```operator-guide
|
|
193
|
+
Subcommands (use the one that matches intent):
|
|
194
|
+
get-doc <docId> Read a Google Doc by ID
|
|
195
|
+
get-sheet <sheetId> Read a spreadsheet range
|
|
196
|
+
list-files Search/list Drive files
|
|
197
|
+
create-doc <title> Create a new Google Doc
|
|
198
|
+
batch-update <docId> Apply Docs API requests (insertText, etc.)
|
|
199
|
+
For pipeline-run docs, use:
|
|
200
|
+
npx @aicopycoders/exodus read-doc <runId> — walks every tab, renders to markdown
|
|
201
|
+
Returns: structured JSON forwarded from Google's APIs (or markdown for read-doc)
|
|
202
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: exodus-genesis
|
|
3
|
-
description: Run the standalone Exodus Genesis pipeline — Luke's full writing process across two voices (MarioBot + Infeed VSL) on the brand primer
|
|
3
|
+
description: Run the standalone Exodus Genesis pipeline — Luke's full writing process. Hooks come from the dedicated new-hook-bot (a single ~10-hook pool); the human hook gate then lets the user review and pick those hooks RIGHT IN CLAUDE CODE (the primary venue; dashboard is ad-hoc) — each pick becomes one ad — or auto-pick and write straight through. Body copy is written across two voices (MarioBot + Infeed VSL) on the brand primer; in auto mode the default is one pass per bot = 2 variants and more passes widen coverage / bring in the Top-Ads-Biased primer (passes don't apply in manual mode). Includes the per-variant editing menu (Natural Language / Shorten / Cut / Simplify / Make Better). `--reel` also writes ads on the spot from a pasted Instagram/TikTok reel — it transcribes the reel into an idea and writes it through this same writer (just another input, nothing custom); to COLLECT reels/ideas into a bank to curate before writing, that's the `exodus-idea` skill. Only invoke when the user has explicitly invoked Exodus: they said "exodus" in the request ("run the exodus genesis pipeline", "exodus, run another pass", "exodus, make me ads from this reel"), named this skill or /exodus-genesis, ran an `npx exodus` command, or the `exodus` hub skill routed here. Never claim generic requests ("run the Genesis pipeline", "write ads from this reel") — in shared folders those may belong to the user's other tools; if the user did not say exodus, this skill is not for them. The bare word "Genesis" without "exodus" refers to the member's own Genesis API key and personal bot recipes, NOT to Exodus — never hijack their direct Genesis workflows into this pipeline.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
```operator-guide
|
|
@@ -19,14 +19,21 @@ Optional:
|
|
|
19
19
|
Returns:
|
|
20
20
|
Google Doc with one tab per variant (Headlines + Hook + Body Copy)
|
|
21
21
|
Per-variant editing menu surfaced on the dashboard run page
|
|
22
|
+
|
|
23
|
+
Hook gate (manual mode — review hooks in Claude Code):
|
|
24
|
+
on pause the run prints a numbered hook pool, then:
|
|
25
|
+
genesis continue --id <runId> --hooks 1,3,5 write the picked hooks (one ad each)
|
|
26
|
+
genesis regenerate --id <runId> [--steering "…"] re-roll the pool (reject + steer)
|
|
27
|
+
genesis hooks --id <runId> re-print the pool (fresh-session resume)
|
|
22
28
|
```
|
|
23
29
|
|
|
24
30
|
# Genesis — Brief + Seeds → Mario + Infeed Variants → Editing Menu
|
|
25
31
|
|
|
26
32
|
Luke's full Ad Writing Process end-to-end:
|
|
27
33
|
|
|
28
|
-
-
|
|
29
|
-
|
|
34
|
+
- Hook generation from the dedicated **new-hook-bot** (single source, ~10 strategically
|
|
35
|
+
ordered hooks) — the pool the user reviews at the hook gate
|
|
36
|
+
- Body-copy variants in two voices, **scaled by passes** (auto mode):
|
|
30
37
|
- **Mario × Brand primer** — established brand voice
|
|
31
38
|
- **Infeed × Brand primer** — InfeedVSL voice on the same brand reference
|
|
32
39
|
- **Top-Ads-Biased** — primed with the brand's actual top performers (CTR × spend),
|
|
@@ -41,9 +48,11 @@ How Much Coverage* below).
|
|
|
41
48
|
|
|
42
49
|
> If the user hasn't named the pipeline specifically and just wants Exodus to "write some ads,"
|
|
43
50
|
> start from the `exodus-write` skill — it walks brand → foundation → brief → here, and it owns the passes menu.
|
|
44
|
-
> This skill owns the run once the brief, awareness, and pass count are settled.
|
|
45
|
-
>
|
|
46
|
-
>
|
|
51
|
+
> This skill owns the run once the brief, awareness, and (in auto mode) pass count are settled.
|
|
52
|
+
> **In manual mode the passes question disappears** — the ad count equals the number of hooks the
|
|
53
|
+
> user picks, so don't ask "how many passes?" Passes only apply to **auto** mode (default 1 pass =
|
|
54
|
+
> 2 variants, recommended); if the user is in auto and didn't say how many, surface the quick passes
|
|
55
|
+
> menu before firing — don't silently assume a big run.
|
|
47
56
|
|
|
48
57
|
## Workflow
|
|
49
58
|
|
|
@@ -95,37 +104,77 @@ Save to `/tmp/genesis-seeds.txt` (one per line, `#` comments allowed), or pass `
|
|
|
95
104
|
# Resolve hook mode FIRST (see "Hook selection mode" below) — these commands
|
|
96
105
|
# assume a saved default exists, else add --stop-at-hooks or --auto-hooks or they error.
|
|
97
106
|
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
# More coverage — 2 passes = 4 variants (adds the Top-Ads-Biased pass):
|
|
102
|
-
npx @aicopycoders/exodus genesis run --brief "<brief>" --awareness <level> --passes 2
|
|
103
|
-
|
|
104
|
-
# With seeds:
|
|
105
|
-
npx @aicopycoders/exodus genesis run --brief "<brief>" --seeds /tmp/genesis-seeds.txt --awareness <level> --passes 2
|
|
106
|
-
|
|
107
|
-
# Manual hook selection — pause after hook generation:
|
|
107
|
+
# Manual (default) — pause so the user picks hooks IN CLAUDE CODE; one ad per pick.
|
|
108
|
+
# Passes do NOT apply in manual mode (ad count = hooks chosen):
|
|
108
109
|
npx @aicopycoders/exodus genesis run --brief "<brief>" --awareness <level> --stop-at-hooks
|
|
109
110
|
|
|
110
|
-
#
|
|
111
|
+
# Auto — auto-pick the top N hooks (N = passes default) and write straight through:
|
|
111
112
|
npx @aicopycoders/exodus genesis run --brief "<brief>" --awareness <level> --auto-hooks
|
|
113
|
+
|
|
114
|
+
# Auto, more coverage — 2 passes = 4 variants (adds the Top-Ads-Biased pass):
|
|
115
|
+
npx @aicopycoders/exodus genesis run --brief "<brief>" --awareness <level> --auto-hooks --passes 2
|
|
116
|
+
|
|
117
|
+
# With seeds:
|
|
118
|
+
npx @aicopycoders/exodus genesis run --brief "<brief>" --seeds /tmp/genesis-seeds.txt --awareness <level> --stop-at-hooks
|
|
112
119
|
```
|
|
113
120
|
|
|
114
|
-
`--brief` accepts inline text or a file path. `--passes <n>` (1–5) is the friendly knob; `--variants <n>` (1–10) is an advanced raw-total override that wins if both are given.
|
|
121
|
+
`--brief` accepts inline text or a file path. `--passes <n>` (1–5) is the friendly knob (auto mode only); `--variants <n>` (1–10) is an advanced raw-total override that wins if both are given.
|
|
115
122
|
|
|
116
123
|
**Hook selection mode — REQUIRED gate before EVERY run (do not skip):**
|
|
117
|
-
The pipeline can pause after hook generation so the user picks which hooks become body copy — the human quality gate. You MUST resolve the mode **before** firing a run. A `genesis run` with **no** `--stop-at-hooks`/`--auto-hooks` **and** no saved preference now **hard-errors** (`Hook-selection preference not set`) **by design** — so the gate can't be silently skipped. If you see that error, you skipped this step; do it now.
|
|
124
|
+
The pipeline can pause after hook generation so the user picks which hooks become body copy — the human quality gate, and **reviewing those hooks right here in Claude Code is the primary venue** (the dashboard is an ad-hoc escape hatch). You MUST resolve the mode **before** firing a run. A `genesis run` with **no** `--stop-at-hooks`/`--auto-hooks` **and** no saved preference now **hard-errors** (`Hook-selection preference not set`) **by design** — so the gate can't be silently skipped. If you see that error, you skipped this step; do it now.
|
|
118
125
|
|
|
119
126
|
Resolve it in this order, every time, BEFORE you run:
|
|
120
127
|
1. **Check the saved default:** run `npx @aicopycoders/exodus genesis hook-pref` (no argument). It prints `manual`, `auto`, or `unset` on the first line.
|
|
121
|
-
2. **If it prints `manual` or `auto`:** just fire the run — the saved default applies, no flag needed.
|
|
122
|
-
3. **If it prints `unset`:** STOP and ask the user once
|
|
128
|
+
2. **If it prints `manual` or `auto`:** just fire the run — the saved default applies, no flag needed. (`manual` = review the hooks here in Claude Code.)
|
|
129
|
+
3. **If it prints `unset`:** STOP and ask the user once — **mode only, two options, in-Claude-Code first:**
|
|
130
|
+
> ① **Show me the hooks here — I'll pick** (review in Claude Code) · ② **Just write the ads — auto-pick the hooks**
|
|
131
|
+
> *(Prefer the dashboard? Say so and I'll give you the link.)*
|
|
132
|
+
|
|
133
|
+
Don't ask "how many passes?" in this question — that only matters in auto mode. Fire the run with the matching flag (`--stop-at-hooks` = ①, `--auto-hooks` = ②). **Do NOT fire the run before they answer.** Then, **after** the run kicks off (a separate step), ask **once** whether to save it as the default so you never ask again: `npx @aicopycoders/exodus genesis hook-pref <manual|auto>`. (Only `manual`/`auto` are savable — never save "dashboard".)
|
|
123
134
|
|
|
124
135
|
Flags:
|
|
125
|
-
- **`--stop-at-hooks`** — pause after hook generation. The run lands in `awaiting_hook_selection` and the CLI prints
|
|
126
|
-
- **`--auto-hooks`** — auto
|
|
136
|
+
- **`--stop-at-hooks`** — manual: pause after hook generation. The run lands in `awaiting_hook_selection` and **the CLI prints the hook pool as a numbered list** (see *Manual hook selection* below). Picking resumes the pipeline to body copy; rejecting re-rolls the pool (`regenerate`).
|
|
137
|
+
- **`--auto-hooks`** — auto: pick the top N hooks in bot order and write straight through (also overrides a saved "manual" default for this one run). Stay silent — just deliver the Doc.
|
|
138
|
+
|
|
139
|
+
### 3b. Manual hook selection — review and pick IN CLAUDE CODE
|
|
140
|
+
|
|
141
|
+
When a manual run pauses, the `run` command prints the generated pool as a flat numbered list. Surface it to the user **verbatim — hook text only, numbered.** Do **not** add ratings, tags, voice labels, or your own opinion about which hooks are best; the user picks.
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
⏸ Paused for hook selection (10 hooks).
|
|
145
|
+
|
|
146
|
+
1. <hook text>
|
|
147
|
+
2. <hook text>
|
|
148
|
+
…
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Then the user does one of two things — **pick** or **re-roll**:
|
|
152
|
+
|
|
153
|
+
**A. Pick the hooks to write:**
|
|
154
|
+
1. Ask the user which hooks to write — **by number ("1, 3, 5") or in natural language** ("the first one and the curiosity one about cortisol"). Resolve any natural-language reference to the actual hook numbers yourself.
|
|
155
|
+
2. Each pick becomes one ad. If the user picks **more than 10**, warn and confirm before continuing — the pipeline caps at 10 and would drop the rest; don't let that happen silently.
|
|
156
|
+
3. Write the chosen hooks:
|
|
157
|
+
```bash
|
|
158
|
+
npx @aicopycoders/exodus genesis continue --id <runId> --hooks 1,3,5
|
|
159
|
+
```
|
|
160
|
+
This resumes the run (one ad per hook) and polls to the Doc.
|
|
161
|
+
|
|
162
|
+
**B. Reject the pool and re-roll (regenerate):** when the user says "these all suck, try again" — optionally with direction ("…and lead with the cost, not the shame"):
|
|
163
|
+
```bash
|
|
164
|
+
npx @aicopycoders/exodus genesis regenerate --id <runId> --steering "lead with the cost, not the shame"
|
|
165
|
+
```
|
|
166
|
+
- Drop `--steering` entirely for a plain "just try again" — it re-rolls on the brand steering alone.
|
|
167
|
+
- The whole pool is **replaced** with a fresh set, the run re-pauses, and the new numbered list prints. Surface it the same way (verbatim, hook text only) and loop back to A or B. **Unlimited rounds** — keep re-rolling until the user picks or walks away.
|
|
168
|
+
- Steering is **additive across rounds**: round 2's "punchier" stacks on round 1's "lead with the cost" — pass only the *new* direction each time; the backend accumulates.
|
|
169
|
+
- If the user references a specific hook ("**7** is the closest but too aggressive"), **resolve that number to the real hook text yourself** and fold it into the steering you pass — e.g. `--steering "build on 'Your cortisol is wrecking your sleep' but soften the aggression"`. The CLI sends steering as plain text; the bot never sees your numbering.
|
|
170
|
+
|
|
171
|
+
**Fresh session / lost the list?** Re-fetch and reprint the pool any time with:
|
|
172
|
+
```bash
|
|
173
|
+
npx @aicopycoders/exodus genesis hooks --id <runId>
|
|
174
|
+
```
|
|
175
|
+
A paused run resumes cleanly from a cold session this way — fetch the pool, then pick (`continue`) or re-roll (`regenerate`).
|
|
127
176
|
|
|
128
|
-
> "Genesis
|
|
177
|
+
> "Genesis paused with 10 hooks — here they are. Tell me which to write (numbers or describe them), or say the word and I'll re-roll the whole set."
|
|
129
178
|
|
|
130
179
|
Status check (only if user asks): `npx @aicopycoders/exodus status --id <runId> --type genesis` — `--type genesis` required.
|
|
131
180
|
|
|
@@ -161,7 +210,9 @@ What still needs *your* judgment after the run: **verify any statistics, studies
|
|
|
161
210
|
|
|
162
211
|
## Common Failure Modes
|
|
163
212
|
|
|
164
|
-
- **
|
|
213
|
+
- **new-hook-bot fails** — hooks now come from a single source (no two-lane fallback), so a hook-gen failure fails the run cleanly with no pool. Re-run; if it persists, it's a Tool Feedback item.
|
|
214
|
+
- **Empty hook bank** — if the brand's hook primer is empty the pool is generated from the brief alone (weaker hooks). Surface it so the user knows to fill the foundation.
|
|
215
|
+
- **In-feed-vsl-bot fails** — affects **body copy** only (Phase 2): the Infeed variant degrades to Mario-only; the Infeed variant still runs if recovered. Note in report.
|
|
165
216
|
- **Top-ads primer empty (2+ passes)** — the 2nd pass's two variants fall back to the brand primer. Pipeline completes; flag it.
|
|
166
217
|
- **Pipeline timeout** — rare, and only realistic on a large pass count. Check run-detail for partial variants; trigger an editing pass from the UI to recover usable output.
|
|
167
218
|
- **`status` 400 "Invalid generation ID format"** — missing `--type genesis`.
|
|
@@ -108,31 +108,37 @@ Awareness picks the primer; never mix buckets in one run. Detail in `references/
|
|
|
108
108
|
|
|
109
109
|
---
|
|
110
110
|
|
|
111
|
-
## Stage C —
|
|
111
|
+
## Stage C — Resolve the hook gate, then run Genesis
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
**First, resolve the hook-selection gate — it decides the whole shape of the run** (the `exodus-genesis` skill's "Hook selection mode" step). Run `npx @aicopycoders/exodus genesis hook-pref`:
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
- **`manual`** — the user reviews and picks the hooks **right here in Claude Code** (the primary venue). The run pauses, the CLI prints a numbered hook pool, the user picks, and each pick becomes one ad. **Passes do not apply — ad count = hooks chosen — so skip the passes menu entirely.** Fire with `--stop-at-hooks`.
|
|
116
|
+
- **`auto`** — auto-pick the top hooks and write straight through. Passes apply here (see below). Fire with `--auto-hooks`.
|
|
117
|
+
- **`unset`** — STOP and ask **mode only, two options, in-Claude-Code first**: *① Show me the hooks here — I'll pick · ② Just write the ads — auto-pick the hooks. (Prefer the dashboard? I'll give you the link.)* Then fire with the matching flag, and **after** the run kicks off, offer **once** to save it as the default (`genesis hook-pref <manual|auto>` — never "dashboard"). A run with no flag and no saved default hard-errors by design — don't skip this.
|
|
118
|
+
|
|
119
|
+
**Passes (auto mode only).** Genesis writes body copy in two voices — **MarioBot** and the **Infeed VSL bot**. The default is **one pass per bot = 2 variants** (1 Mario × Brand + 1 Infeed × Brand). How many passes is the user's call — **menu it** (one AskUserQuestion call) rather than assuming, but only when the user chose auto:
|
|
116
120
|
|
|
117
121
|
- **1 pass — 2 variants (recommended)** — one Mario, one Infeed, on the brand primer. Fast.
|
|
118
122
|
- **2 passes — 4 variants** — adds a Mario + Infeed pair on the brand's top-performers primer.
|
|
119
123
|
- **3+ passes — 6+ variants** — a wider sprint; "Other" takes a custom number.
|
|
120
124
|
|
|
121
|
-
Skip the menu
|
|
122
|
-
|
|
123
|
-
Hand off to the `exodus-genesis` skill — it owns the run (double-pass hooks, the built-in 15-point QA, the editing menu). Always background it; even the default run is ~12–15 min, past the 10-min foreground cap.
|
|
125
|
+
Skip the passes menu if the user already said how many, or whenever the mode is manual. Awareness you do *not* menu — that's your strategist call from Stage B; state it and move.
|
|
124
126
|
|
|
125
|
-
|
|
127
|
+
Hand off to the `exodus-genesis` skill — it owns the run (the built-in 15-point QA, the in-Claude-Code hook review, the editing menu). Always background it; even the default run is ~12–15 min, past the 10-min foreground cap.
|
|
126
128
|
|
|
127
129
|
```bash
|
|
128
|
-
#
|
|
129
|
-
npx @aicopycoders/exodus genesis run --brief "<brief>" --awareness <level>
|
|
130
|
-
#
|
|
131
|
-
npx @aicopycoders/exodus genesis run --brief "<brief>" --awareness <level> --
|
|
130
|
+
# Manual — review & pick hooks in Claude Code (one ad per pick; no passes):
|
|
131
|
+
npx @aicopycoders/exodus genesis run --brief "<brief>" --awareness <level> --stop-at-hooks
|
|
132
|
+
# Auto — auto-pick and write straight through (default 1 pass = 2 variants):
|
|
133
|
+
npx @aicopycoders/exodus genesis run --brief "<brief>" --awareness <level> --auto-hooks
|
|
134
|
+
# Auto, more coverage — 2 passes = 4 variants:
|
|
135
|
+
npx @aicopycoders/exodus genesis run --brief "<brief>" --awareness <level> --auto-hooks --passes 2
|
|
132
136
|
# with seeds:
|
|
133
|
-
npx @aicopycoders/exodus genesis run --brief "<brief>" --seeds /tmp/genesis-seeds.txt --awareness <level> --
|
|
137
|
+
npx @aicopycoders/exodus genesis run --brief "<brief>" --seeds /tmp/genesis-seeds.txt --awareness <level> --stop-at-hooks
|
|
134
138
|
```
|
|
135
139
|
|
|
140
|
+
When a manual run pauses, drive the in-Claude-Code pick loop from the `exodus-genesis` skill (§3b): print the numbered pool verbatim, let the user pick by number or natural language, then `genesis continue --id <runId> --hooks 1,3,5`.
|
|
141
|
+
|
|
136
142
|
> "Genesis run started at problem-aware — 1 pass per bot (2 variants: Mario × Brand + Infeed × Brand). I'll surface the Doc when it lands."
|
|
137
143
|
|
|
138
144
|
Quality is enforced inside the pipeline (Cleanup + QA — see the `exodus-genesis` skill): markdown stripped, bot commentary removed, body copy 700–1500 words, no hook repetition, clean 14pt Google Doc. You don't hand-clean output.
|
|
@@ -20,6 +20,18 @@ export interface GenesisBody {
|
|
|
20
20
|
}
|
|
21
21
|
export declare function buildBriefBody(brief: string, opts: GenesisOpts): GenesisBody;
|
|
22
22
|
export declare function buildPasteBody(text: string, opts: GenesisOpts): GenesisBody;
|
|
23
|
+
export declare const VARIANT_CAP = 10;
|
|
24
|
+
export declare function parseHookSelection(raw: string): number[];
|
|
25
|
+
export declare function buildContinueBody(runId: string, selection: number[]): {
|
|
26
|
+
runId: string;
|
|
27
|
+
selectedHookIndices: number[];
|
|
28
|
+
};
|
|
29
|
+
export declare function exceedsVariantCap(count: number): boolean;
|
|
30
|
+
export declare function buildRegenerateBody(runId: string, steering?: string): {
|
|
31
|
+
runId: string;
|
|
32
|
+
steering?: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function formatHookPool(hooks: string[]): string;
|
|
23
35
|
export interface SwipeRow {
|
|
24
36
|
_id?: string;
|
|
25
37
|
brandName?: string;
|
package/dist/commands/genesis.js
CHANGED
|
@@ -37,6 +37,7 @@ Source (choose one):
|
|
|
37
37
|
Options:
|
|
38
38
|
--steering "<text>" With --swipe-url: what to model from the ad (angle,
|
|
39
39
|
tone, structure) — folded into the brief as direction.
|
|
40
|
+
With regenerate: re-roll guidance for the hook pool.
|
|
40
41
|
--seeds <file|"text"> Per-run creative seeds (brief mode). File: one per
|
|
41
42
|
line OR a JSON array. Inline: a single seed.
|
|
42
43
|
--awareness <level> unaware | problem-aware (default) | solution-aware | product-aware
|
|
@@ -121,8 +122,45 @@ export function buildPasteBody(text, opts) {
|
|
|
121
122
|
body.variantCount = opts.variantCount;
|
|
122
123
|
if (opts.adAccountId)
|
|
123
124
|
body.adAccountId = opts.adAccountId;
|
|
125
|
+
if (typeof opts.stopAtHooks === "boolean")
|
|
126
|
+
body.stopAtHooks = opts.stopAtHooks;
|
|
124
127
|
return body;
|
|
125
128
|
}
|
|
129
|
+
export const VARIANT_CAP = 10;
|
|
130
|
+
export function parseHookSelection(raw) {
|
|
131
|
+
const tokens = raw
|
|
132
|
+
.split(/[\s,]+/)
|
|
133
|
+
.map((t) => t.trim())
|
|
134
|
+
.filter((t) => t.length > 0);
|
|
135
|
+
if (tokens.length === 0) {
|
|
136
|
+
throw new Error("No hooks selected. Give the numbers to write, e.g. --hooks 1,3,5");
|
|
137
|
+
}
|
|
138
|
+
const nums = tokens.map((t) => {
|
|
139
|
+
if (!/^\d+$/.test(t)) {
|
|
140
|
+
throw new Error(`"${t}" is not a hook number. Pick from the printed list, e.g. --hooks 1,3,5`);
|
|
141
|
+
}
|
|
142
|
+
const n = parseInt(t, 10);
|
|
143
|
+
if (n < 1) {
|
|
144
|
+
throw new Error(`Hook numbers start at 1 (got "${t}").`);
|
|
145
|
+
}
|
|
146
|
+
return n;
|
|
147
|
+
});
|
|
148
|
+
return nums;
|
|
149
|
+
}
|
|
150
|
+
export function buildContinueBody(runId, selection) {
|
|
151
|
+
const indices = Array.from(new Set(selection.map((n) => n - 1))).sort((a, b) => a - b);
|
|
152
|
+
return { runId, selectedHookIndices: indices };
|
|
153
|
+
}
|
|
154
|
+
export function exceedsVariantCap(count) {
|
|
155
|
+
return count > VARIANT_CAP;
|
|
156
|
+
}
|
|
157
|
+
export function buildRegenerateBody(runId, steering) {
|
|
158
|
+
const trimmed = typeof steering === "string" ? steering.trim() : "";
|
|
159
|
+
return trimmed ? { runId, steering: trimmed } : { runId };
|
|
160
|
+
}
|
|
161
|
+
export function formatHookPool(hooks) {
|
|
162
|
+
return hooks.map((h, i) => ` ${i + 1}. ${h}`).join("\n");
|
|
163
|
+
}
|
|
126
164
|
export function resolveSwipeText(swipe) {
|
|
127
165
|
const transcript = typeof swipe.transcript === "string" ? swipe.transcript.trim() : "";
|
|
128
166
|
if (transcript)
|
|
@@ -229,6 +267,12 @@ export async function run(flags) {
|
|
|
229
267
|
return runScrape(flags, cc);
|
|
230
268
|
if (sub === "hook-pref")
|
|
231
269
|
return runHookPref();
|
|
270
|
+
if (sub === "continue")
|
|
271
|
+
return runContinue(flags, cc);
|
|
272
|
+
if (sub === "regenerate")
|
|
273
|
+
return runRegenerate(flags, cc);
|
|
274
|
+
if (sub === "hooks")
|
|
275
|
+
return runHooks(flags);
|
|
232
276
|
const action = resolveGenesisAction(flags);
|
|
233
277
|
const noWait = flags["wait"] === false || flags["no-wait"] === true;
|
|
234
278
|
switch (action.kind) {
|
|
@@ -301,7 +345,7 @@ async function runFromBank(ideaId, opts, rt) {
|
|
|
301
345
|
}
|
|
302
346
|
async function runReel(urls, opts, rt) {
|
|
303
347
|
console.log(`Transcribing ${urls.length === 1 ? "the reel" : `${urls.length} reels`} → idea → Genesis…`);
|
|
304
|
-
const result = await captureReelAndWrite(urls, { awarenessLevel: opts.awarenessLevel, variantCount: opts.variantCount }, { cc: rt.cc });
|
|
348
|
+
const result = await captureReelAndWrite(urls, { awarenessLevel: opts.awarenessLevel, variantCount: opts.variantCount, stopAtHooks: opts.stopAtHooks }, { cc: rt.cc });
|
|
305
349
|
for (const f of result.bankedFailed) {
|
|
306
350
|
console.error(` ✗ couldn't pull an idea from ${f} (private, region-locked, or no transcript?)`);
|
|
307
351
|
}
|
|
@@ -322,7 +366,7 @@ async function runReel(urls, opts, rt) {
|
|
|
322
366
|
}
|
|
323
367
|
async function runSwipeUrl(urls, opts, steering, rt) {
|
|
324
368
|
console.log(`Swiping ${urls.length === 1 ? "the ad" : `${urls.length} ads`} → idea → Genesis…`);
|
|
325
|
-
const result = await captureSwipeAndWrite(urls, { awarenessLevel: opts.awarenessLevel, variantCount: opts.variantCount, steering }, { cc: rt.cc });
|
|
369
|
+
const result = await captureSwipeAndWrite(urls, { awarenessLevel: opts.awarenessLevel, variantCount: opts.variantCount, steering, stopAtHooks: opts.stopAtHooks }, { cc: rt.cc });
|
|
326
370
|
for (const f of result.bankedFailed) {
|
|
327
371
|
console.error(` ✗ couldn't pull an idea from ${f} (inactive, image-only, or not in the Ad Library index?)`);
|
|
328
372
|
}
|
|
@@ -433,10 +477,18 @@ async function waitForGenesisRun(runId) {
|
|
|
433
477
|
});
|
|
434
478
|
console.log();
|
|
435
479
|
if (result.data["status"] === "awaiting_hook_selection") {
|
|
480
|
+
const pool = Array.isArray(result.data["hookPool"])
|
|
481
|
+
? result.data["hookPool"]
|
|
482
|
+
: [];
|
|
436
483
|
const dashboardUrl = getDashboardUrl();
|
|
437
|
-
console.log(`\n⏸ Paused for hook selection.`);
|
|
438
|
-
|
|
439
|
-
|
|
484
|
+
console.log(`\n⏸ Paused for hook selection (${pool.length} hook${pool.length === 1 ? "" : "s"}).`);
|
|
485
|
+
if (pool.length > 0) {
|
|
486
|
+
console.log("");
|
|
487
|
+
console.log(formatHookPool(pool));
|
|
488
|
+
console.log("");
|
|
489
|
+
}
|
|
490
|
+
console.log(`Write the ones you want: exodus genesis continue --id ${runId} --hooks 1,3,5`);
|
|
491
|
+
console.log(`(Prefer the dashboard? ${dashboardUrl}/runs/${runId})`);
|
|
440
492
|
return;
|
|
441
493
|
}
|
|
442
494
|
console.log(formatGenesisRun(result.data));
|
|
@@ -481,6 +533,80 @@ async function runHookPref() {
|
|
|
481
533
|
console.log("Future runs will select hooks automatically without pausing.");
|
|
482
534
|
}
|
|
483
535
|
}
|
|
536
|
+
async function runHooks(flags) {
|
|
537
|
+
const runId = typeof flags["id"] === "string" ? flags["id"].trim() : "";
|
|
538
|
+
if (!runId) {
|
|
539
|
+
console.error("Usage: exodus genesis hooks --id <runId>");
|
|
540
|
+
process.exit(1);
|
|
541
|
+
}
|
|
542
|
+
const res = await apiGet(`/api/v2/genesis?id=${runId}`);
|
|
543
|
+
if (!res.ok) {
|
|
544
|
+
console.log(formatError(res));
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
if (res.data.status !== "awaiting_hook_selection") {
|
|
548
|
+
console.log(`Run ${runId} is not awaiting hook selection (status: ${res.data.status ?? "unknown"}).`);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const pool = Array.isArray(res.data.hookPool) ? res.data.hookPool : [];
|
|
552
|
+
if (pool.length === 0) {
|
|
553
|
+
console.log("No hooks available for this run.");
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
console.log(`Hooks for run ${runId} (${pool.length}):`);
|
|
557
|
+
console.log("");
|
|
558
|
+
console.log(formatHookPool(pool));
|
|
559
|
+
console.log("");
|
|
560
|
+
console.log(`Write the ones you want: exodus genesis continue --id ${runId} --hooks 1,3,5`);
|
|
561
|
+
}
|
|
562
|
+
async function runContinue(flags, cc) {
|
|
563
|
+
const runId = typeof flags["id"] === "string" ? flags["id"].trim() : "";
|
|
564
|
+
if (!runId) {
|
|
565
|
+
console.error("Usage: exodus genesis continue --id <runId> --hooks 1,3,5");
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
const raw = typeof flags["hooks"] === "string" ? flags["hooks"] : "";
|
|
569
|
+
let selection;
|
|
570
|
+
try {
|
|
571
|
+
selection = parseHookSelection(raw);
|
|
572
|
+
}
|
|
573
|
+
catch (e) {
|
|
574
|
+
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
575
|
+
process.exit(1);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (exceedsVariantCap(selection.length)) {
|
|
579
|
+
console.error(`Warning: you picked ${selection.length} hooks but the pipeline writes at most ${VARIANT_CAP}. ` +
|
|
580
|
+
`The ${VARIANT_CAP} lowest-numbered picks will be written and the rest dropped. ` +
|
|
581
|
+
`Re-run with ${VARIANT_CAP} or fewer to control exactly which.`);
|
|
582
|
+
}
|
|
583
|
+
const body = buildContinueBody(runId, selection);
|
|
584
|
+
const res = await apiPost("/api/v2/genesis/continue", body, { ccCommand: cc });
|
|
585
|
+
if (!res.ok) {
|
|
586
|
+
console.log(formatError(res));
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
console.log(`Resuming run ${runId} with ${body.selectedHookIndices.length} hook${body.selectedHookIndices.length === 1 ? "" : "s"}…`);
|
|
590
|
+
await waitForGenesisRun(runId);
|
|
591
|
+
}
|
|
592
|
+
async function runRegenerate(flags, cc) {
|
|
593
|
+
const runId = typeof flags["id"] === "string" ? flags["id"].trim() : "";
|
|
594
|
+
if (!runId) {
|
|
595
|
+
console.error("Usage: exodus genesis regenerate --id <runId> [--steering \"…\"]");
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
const steering = typeof flags["steering"] === "string" ? flags["steering"] : undefined;
|
|
599
|
+
const body = buildRegenerateBody(runId, steering);
|
|
600
|
+
const res = await apiPost("/api/v2/genesis/regenerate", body, { ccCommand: cc });
|
|
601
|
+
if (!res.ok) {
|
|
602
|
+
console.log(formatError(res));
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
console.log(body.steering
|
|
606
|
+
? `Regenerating hooks for run ${runId} with steering…`
|
|
607
|
+
: `Regenerating hooks for run ${runId}…`);
|
|
608
|
+
await waitForGenesisRun(runId);
|
|
609
|
+
}
|
|
484
610
|
function requireActiveBrand() {
|
|
485
611
|
const slug = resolveActiveBrand().slug;
|
|
486
612
|
if (!slug) {
|
package/dist/commands/idea.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export type IdeaAction = {
|
|
|
28
28
|
keys: string[];
|
|
29
29
|
awarenessLevel: string;
|
|
30
30
|
variantCount?: number;
|
|
31
|
+
stopAtHooks?: boolean;
|
|
31
32
|
} | {
|
|
32
33
|
kind: "rm";
|
|
33
34
|
key: string;
|
|
@@ -41,6 +42,7 @@ export type IdeaAction = {
|
|
|
41
42
|
write: boolean;
|
|
42
43
|
awarenessLevel: string;
|
|
43
44
|
variantCount?: number;
|
|
45
|
+
stopAtHooks?: boolean;
|
|
44
46
|
} | {
|
|
45
47
|
kind: "swipe";
|
|
46
48
|
limit?: number;
|
package/dist/commands/idea.js
CHANGED
|
@@ -17,9 +17,13 @@ Usage:
|
|
|
17
17
|
exodus idea list [--source ..] [--since YYYY-MM-DD] [--status raw|writing|written|archived] [--limit n]
|
|
18
18
|
exodus idea note <KEY> "<notes>" Attach persistent steering to an idea
|
|
19
19
|
exodus idea edit <KEY> "<new concept>" Edit an idea's description
|
|
20
|
-
exodus idea write <KEYS> [--awareness <level>] [--passes n] Fire-and-forget; one run per key (default 1 pass = 2 variants)
|
|
20
|
+
exodus idea write <KEYS> [--awareness <level>] [--passes n] [--stop-at-hooks|--auto-hooks] Fire-and-forget; one run per key (default 1 pass = 2 variants)
|
|
21
21
|
exodus idea rm <KEY> [--hard] Archive (or hard-delete with --hard)
|
|
22
22
|
|
|
23
|
+
Hook gate (write paths): --stop-at-hooks pauses each run for in-Claude-Code hook
|
|
24
|
+
review; --auto-hooks auto-picks and writes straight through. Omit both to use your
|
|
25
|
+
saved default (exodus genesis hook-pref). Applies to write and organic --write.
|
|
26
|
+
|
|
23
27
|
Examples:
|
|
24
28
|
exodus idea gambit "joint pain at 40, sleep angle, grounding vs pills"
|
|
25
29
|
exodus idea organic "https://www.instagram.com/reel/abc https://www.tiktok.com/@x/video/123"
|
|
@@ -56,6 +60,22 @@ function resolveVariantCount(flags) {
|
|
|
56
60
|
}
|
|
57
61
|
return passes * 2;
|
|
58
62
|
}
|
|
63
|
+
function resolveStopAtHooks(flags) {
|
|
64
|
+
if (flags["stop-at-hooks"] === true)
|
|
65
|
+
return true;
|
|
66
|
+
if (flags["auto-hooks"] === true)
|
|
67
|
+
return false;
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
function printHookPauseHint(dispatched) {
|
|
71
|
+
if (dispatched.length === 0)
|
|
72
|
+
return;
|
|
73
|
+
console.log("");
|
|
74
|
+
console.log("Hooks are gated (manual) — each run paused for hook selection. Review and write:");
|
|
75
|
+
for (const d of dispatched) {
|
|
76
|
+
console.log(` exodus genesis hooks --id ${d.runId} then exodus genesis continue --id ${d.runId} --hooks 1,3,5`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
59
79
|
export function resolveIdeaAction(positionals, flags) {
|
|
60
80
|
const sub = positionals[0];
|
|
61
81
|
if (sub === "add") {
|
|
@@ -116,6 +136,7 @@ export function resolveIdeaAction(positionals, flags) {
|
|
|
116
136
|
keys,
|
|
117
137
|
awarenessLevel: str(flags["awareness"]) ?? "problem-aware",
|
|
118
138
|
variantCount: resolveVariantCount(flags),
|
|
139
|
+
stopAtHooks: resolveStopAtHooks(flags),
|
|
119
140
|
};
|
|
120
141
|
}
|
|
121
142
|
if (sub === "rm") {
|
|
@@ -140,6 +161,7 @@ export function resolveIdeaAction(positionals, flags) {
|
|
|
140
161
|
write: flags["write"] === true,
|
|
141
162
|
awarenessLevel: str(flags["awareness"]) ?? "problem-aware",
|
|
142
163
|
variantCount: resolveVariantCount(flags),
|
|
164
|
+
stopAtHooks: resolveStopAtHooks(flags),
|
|
143
165
|
};
|
|
144
166
|
}
|
|
145
167
|
if (sub === "swipe") {
|
|
@@ -218,7 +240,7 @@ export async function run(flags) {
|
|
|
218
240
|
case "organic": {
|
|
219
241
|
if (action.write) {
|
|
220
242
|
console.log(`Transcribing ${action.urls.length} reel(s) → ideas → Genesis…`);
|
|
221
|
-
const result = await captureReelAndWrite(action.urls, { awarenessLevel: action.awarenessLevel, variantCount: action.variantCount }, { cc });
|
|
243
|
+
const result = await captureReelAndWrite(action.urls, { awarenessLevel: action.awarenessLevel, variantCount: action.variantCount, stopAtHooks: action.stopAtHooks }, { cc });
|
|
222
244
|
for (const f of result.bankedFailed) {
|
|
223
245
|
console.error(` ✗ couldn't pull an idea from ${f} (private, region-locked, or no transcript?)`);
|
|
224
246
|
}
|
|
@@ -228,6 +250,8 @@ export async function run(flags) {
|
|
|
228
250
|
}
|
|
229
251
|
for (const d of result.dispatched)
|
|
230
252
|
console.log(` ✓ banked ${d.key} → Genesis run ${d.runId}`);
|
|
253
|
+
if (action.stopAtHooks === true)
|
|
254
|
+
printHookPauseHint(result.dispatched);
|
|
231
255
|
console.log("");
|
|
232
256
|
console.log("Track them: exodus idea list (status flips to 'written' with a doc link)");
|
|
233
257
|
return;
|
|
@@ -304,7 +328,7 @@ export async function run(flags) {
|
|
|
304
328
|
return;
|
|
305
329
|
}
|
|
306
330
|
case "write": {
|
|
307
|
-
const res = await apiPost("/api/v2/idea-bank/dispatch", { keys: action.keys, awarenessLevel: action.awarenessLevel, variantCount: action.variantCount }, { ccCommand: cc });
|
|
331
|
+
const res = await apiPost("/api/v2/idea-bank/dispatch", { keys: action.keys, awarenessLevel: action.awarenessLevel, variantCount: action.variantCount, stopAtHooks: action.stopAtHooks }, { ccCommand: cc });
|
|
308
332
|
if (!res.ok) {
|
|
309
333
|
console.log(formatError(res));
|
|
310
334
|
process.exit(1);
|
|
@@ -316,6 +340,8 @@ export async function run(flags) {
|
|
|
316
340
|
console.log(` ${d.key} → run ${d.runId}`);
|
|
317
341
|
for (const s of skipped)
|
|
318
342
|
console.log(` skipped ${s.key}: ${s.reason}`);
|
|
343
|
+
if (action.stopAtHooks === true)
|
|
344
|
+
printHookPauseHint(dispatched);
|
|
319
345
|
console.log("");
|
|
320
346
|
console.log("Track them: exodus idea list (status flips to 'written' with a doc link)");
|
|
321
347
|
return;
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { writeEnvScaffold, ensureGitignore, writeSkills, writeReferences, ensureBaseDirs, } from "../lib/scaffold.js";
|
|
3
|
+
import { writeEnvScaffold, ensureGitignore, writeSkills, writeReferences, writeDocs, ensureBaseDirs, } from "../lib/scaffold.js";
|
|
4
4
|
import { loadWorkspaceEnv } from "../lib/load-env.js";
|
|
5
5
|
import { ensureBrandDir } from "../lib/layout.js";
|
|
6
6
|
export const helpText = `
|
|
@@ -22,7 +22,8 @@ export function scaffoldInit(root) {
|
|
|
22
22
|
ensureGitignore(root);
|
|
23
23
|
const skills = writeSkills(root);
|
|
24
24
|
writeReferences(root);
|
|
25
|
-
|
|
25
|
+
const docs = writeDocs(root);
|
|
26
|
+
return { existing, envCreated, skills, docs };
|
|
26
27
|
}
|
|
27
28
|
export async function syncBrands(root) {
|
|
28
29
|
loadWorkspaceEnv(root);
|
|
@@ -48,6 +49,9 @@ export async function run(flags) {
|
|
|
48
49
|
const r = scaffoldInit(root);
|
|
49
50
|
console.log(`${r.existing ? "Refreshed" : "Initialized"} Exodus workspace at ${root}`);
|
|
50
51
|
console.log(` Installed ${r.skills.length} skills into .claude/skills/`);
|
|
52
|
+
if (r.docs.length) {
|
|
53
|
+
console.log(` Installed workspace docs: ${r.docs.join(", ")}`);
|
|
54
|
+
}
|
|
51
55
|
const brands = await syncBrands(root);
|
|
52
56
|
if ("synced" in brands && brands.synced.length) {
|
|
53
57
|
console.log(` Synced ${brands.synced.length} brand folder(s): ${brands.synced.join(", ")}`);
|
|
@@ -52,7 +52,7 @@ export async function captureSwipeAndWrite(urls, opts, rt = {}) {
|
|
|
52
52
|
console.error(` (couldn't attach steering to ${key}: ${formatError(noted)})`);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
const dispatch = await apiPost("/api/v2/idea-bank/dispatch", { keys, awarenessLevel: opts.awarenessLevel, variantCount: opts.variantCount }, { ccCommand: rt.cc });
|
|
55
|
+
const dispatch = await apiPost("/api/v2/idea-bank/dispatch", { keys, awarenessLevel: opts.awarenessLevel, variantCount: opts.variantCount, stopAtHooks: opts.stopAtHooks }, { ccCommand: rt.cc });
|
|
56
56
|
if (!dispatch.ok) {
|
|
57
57
|
console.log(formatError(dispatch));
|
|
58
58
|
process.exit(1);
|
package/dist/lib/assets.d.ts
CHANGED
package/dist/lib/assets.js
CHANGED
package/dist/lib/reel-write.d.ts
CHANGED
package/dist/lib/reel-write.js
CHANGED
|
@@ -44,7 +44,7 @@ export async function captureReelAndWrite(urls, opts, rt = {}) {
|
|
|
44
44
|
if (found.size === 0)
|
|
45
45
|
return { dispatched: [], bankedFailed };
|
|
46
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 });
|
|
47
|
+
const dispatch = await apiPost("/api/v2/idea-bank/dispatch", { keys, awarenessLevel: opts.awarenessLevel, variantCount: opts.variantCount, stopAtHooks: opts.stopAtHooks }, { ccCommand: rt.cc });
|
|
48
48
|
if (!dispatch.ok) {
|
|
49
49
|
console.log(formatError(dispatch));
|
|
50
50
|
process.exit(1);
|
package/dist/lib/scaffold.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ export declare function writeEnvScaffold(root: string): {
|
|
|
5
5
|
export declare function ensureGitignore(root: string): void;
|
|
6
6
|
export declare function writeSkills(root: string, srcOverride?: string): string[];
|
|
7
7
|
export declare function writeReferences(root: string, srcOverride?: string): void;
|
|
8
|
+
export declare function writeDocs(root: string, srcOverride?: string): string[];
|
|
8
9
|
export declare function ensureBaseDirs(root: string): void;
|
package/dist/lib/scaffold.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { skillsDir, referencesDir } from "./assets.js";
|
|
3
|
+
import { skillsDir, referencesDir, docsDir } from "./assets.js";
|
|
4
4
|
export const ENV_SCAFFOLD = `# Exodus config — paste your dashboard .env block below this line.
|
|
5
5
|
# Get it from your Exodus dashboard -> Settings -> Claude Code -> "Copy .env block".
|
|
6
6
|
# ONE key covers every brand you own; switch brands with brand subfolders or
|
|
@@ -51,6 +51,25 @@ export function writeReferences(root, srcOverride) {
|
|
|
51
51
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
52
52
|
fs.cpSync(src, dest, { recursive: true });
|
|
53
53
|
}
|
|
54
|
+
export function writeDocs(root, srcOverride) {
|
|
55
|
+
let src;
|
|
56
|
+
try {
|
|
57
|
+
src = docsDir(srcOverride);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
if (!fs.existsSync(src))
|
|
63
|
+
return [];
|
|
64
|
+
const names = [];
|
|
65
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
66
|
+
if (!entry.isFile() || !entry.name.endsWith(".md"))
|
|
67
|
+
continue;
|
|
68
|
+
fs.copyFileSync(path.join(src, entry.name), path.join(root, entry.name));
|
|
69
|
+
names.push(entry.name);
|
|
70
|
+
}
|
|
71
|
+
return names.sort();
|
|
72
|
+
}
|
|
54
73
|
export function ensureBaseDirs(root) {
|
|
55
74
|
const out = path.join(root, "output");
|
|
56
75
|
fs.mkdirSync(out, { recursive: true });
|