@aicopycoders/exodus 2026.7.200-beta.11 → 2026.7.200-beta.13

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/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # @aicopycoders/exodus
2
+
3
+ The Exodus CLI + Claude Code skills for the Copycoders ad pipeline. It drives the
4
+ Genesis creative bots, workflows, banks, and image/meme pipelines against your
5
+ brand — from your terminal and from Claude Code.
6
+
7
+ You don't install it globally. Run it with `npx` from your workspace folder.
8
+
9
+ ## Quickstart
10
+
11
+ ```bash
12
+ # 1. Scaffold (or refresh) the workspace in the current folder
13
+ npx -y @aicopycoders/exodus@beta init
14
+ ```
15
+
16
+ `init` creates the workspace (the `.claude/` skills, docs, and your brand
17
+ subfolder) and prints the next steps. The `@beta` tag is the dev/preview channel;
18
+ drop it (`@aicopycoders/exodus`) for the stable release channel.
19
+
20
+ ### Connect your account
21
+
22
+ 1. Open the Exodus dashboard → **Settings → Claude Code**.
23
+ 2. Copy the `.env` snippet it shows you (your `EXODUS_API_KEY` + dashboard URL)
24
+ and paste it into a `.env` file in your workspace folder.
25
+ 3. Pull your provider keys down so the local tools have them:
26
+
27
+ ```bash
28
+ npx -y @aicopycoders/exodus@beta keys pull
29
+ ```
30
+
31
+ This writes your dashboard provider + image keys into `.env` (it only upserts
32
+ those keys and preserves the rest of the file). Values are never printed.
33
+
34
+ ### Check everything is wired up
35
+
36
+ ```bash
37
+ npx -y @aicopycoders/exodus@beta doctor
38
+ ```
39
+
40
+ `doctor` diagnoses local and dashboard-side issues in one shot — keys, workspace
41
+ health, CLI currency — and tells you exactly what to fix.
42
+
43
+ ## Where to go next
44
+
45
+ - `npx -y @aicopycoders/exodus@beta --help` — the full command surface.
46
+ - `npx -y @aicopycoders/exodus@beta workflow --help` — run, build, and automate
47
+ saved multi-node workflows; resolve gate/repair parks from the inbox; read
48
+ banks and promote winners.
49
+
50
+ Inside Claude Code, just say **"exodus"** plus what you want
51
+ ("exodus, write me some ads") and the skills route the rest.
@@ -59,6 +59,7 @@ These are the **Exodus** pipelines — the automatic system that drives the Gene
59
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
60
  | **Template** | `exodus-template` | Finished ad copy → many static ads across 50 structured formats (testimonial, hero, UGC…). Otherwise start at **Image**. |
61
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
+ | **Workflow** | `exodus-workflow` | Run and build saved multi-node workflow automations from the CLI, resolve gate/repair parks from the inbox, continue bot sessions, fire triggers, and read banks or promote winners. |
62
63
  | **Browse** | `exodus-browse` | View history and retrieve past outputs |
63
64
  | **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
  | **Winners** | `exodus-winners` | Mine your own Meta ad account for winning ads (via the Meta Ads MCP) and import them into Exodus as generative fuel — interview, visual confirmation, `npx @aicopycoders/exodus winners import` |
@@ -14,6 +14,7 @@ In Claude Code you don't click buttons. You say **"exodus"** plus what you want
14
14
  | exodus-creative | Creative-Suite Engines (native / copy-derived / ref-match) — when you've already picked an engine |
15
15
  | exodus-template | Ad-Type Format Variations across 50 structured formats |
16
16
  | exodus-meme | Meme Ad Generator — recommend formats, one batched server-side run |
17
+ | exodus-workflow | Run/build saved multi-node workflows, resolve inbox gate/repair parks, continue sessions, fire triggers, read banks + promote winners |
17
18
  | exodus-browse | View History and Surface the Right Run |
18
19
  | exodus-drive | Google Drive, Docs, Sheets via the Dashboard's OAuth |
19
20
  | exodus-winners | Mine your own Meta ad account for winners (needs the Meta Ads MCP) and import them into Exodus |
@@ -171,6 +172,31 @@ Returns: runId + dashboard URL /creative-suite/runs/<runId> (results land in the
171
172
 
172
173
  ---
173
174
 
175
+ ## exodus-workflow
176
+
177
+ **What it does:** Run, chain, build, and edit saved multi-node Exodus Workflows from the CLI — the automations that wire Genesis bots, primers, briefs, and image nodes together on the dashboard canvas. Also resolves runs that park at a gate or repair step (from the inbox), continues bot chat sessions, enables/fires triggers, reads copy banks, and promotes a winner (which fires the Winner Flywheel).
178
+
179
+ ```operator-guide
180
+ Say something like:
181
+ "exodus, run my launch workflow"
182
+ "exodus, what does this workflow need"
183
+ "exodus, check my workflow inbox"
184
+ "exodus, review that gate" / "exodus, approve the run"
185
+ "exodus, continue that session"
186
+ "exodus, promote this winner"
187
+
188
+ Representative commands:
189
+ exodus workflow list
190
+ exodus workflow run <wf> --wait
191
+ exodus workflow inbox
192
+ exodus workflow gate <runId> pick 3,7 | edit 4 | push "punchier" | approve | reject
193
+ exodus session list | show | chat
194
+ exodus bank list | show <key> | promote <key>
195
+ Every workflow/session/bank verb takes --json — that output is the machine API.
196
+ ```
197
+
198
+ ---
199
+
174
200
  ## exodus-browse
175
201
 
176
202
  **What it does:** View past pipeline runs and pull outputs from previous sessions.
@@ -18,7 +18,10 @@ This skill ROUTES. Read what the user wants, name the destination skill, and han
18
18
  | Static image ads ("make images", "statics", "render this ad") | `exodus-image` (the image front door — it menus engines and confirms before firing) |
19
19
  | A specific image engine they already chose | `exodus-creative` (native / copy-derived / ref-match) or `exodus-template` (33 ad-type formats) |
20
20
  | Meme ads ("exodus, meme this offer", "make me meme ads") | `exodus-meme` |
21
- | Run / chain / build / edit a saved workflow ("run my workflow", "what does this workflow need", "build a workflow that…", "list my workflows") | `exodus-workflow` |
21
+ | Run / chain / build / edit a saved workflow, enable/fire its triggers, or roll back a version ("run my workflow", "what does this workflow need", "build a workflow that…", "list my workflows", "enable/fire the trigger", "roll back my workflow", "workflow versions") | `exodus-workflow` |
22
+ | Check the workflow inbox or review/resolve a parked run — gate or repair ("check my workflow inbox", "review that gate", "approve the run", "repair that run") | `exodus-workflow` |
23
+ | Continue a bot chat session ("continue that session", "chat with the bot session", "list my sessions") | `exodus-workflow` |
24
+ | Read copy banks or promote a winner ("show my hook bank", "list my banks", "promote this winner") | `exodus-workflow` |
22
25
  | Onboard a new brand / build the primer | `exodus-primer` (with winning ads) or `exodus-foundation` (no ads yet) |
23
26
  | List/switch/troubleshoot brands | `exodus-brand` |
24
27
  | See past runs / find an output | `exodus-browse` |
@@ -1,39 +1,77 @@
1
1
  ---
2
2
  name: exodus-workflow
3
- description: Run, chain, build, and edit saved Exodus Workflows — the multi-node automations that wire Genesis bots, primers, briefs, and image nodes together on the dashboard canvas — from the CLI. Use it to list a brand's workflows, describe what a workflow needs before running it (inputs, per-workspace primer prerequisites, outputs), run one and collect its outputs, chain one workflow's output into another's input, browse the bot catalog, and author or update a workflow contract via export → edit → import. Only invoke when the user has explicitly invoked Exodus: they said "exodus" in the request ("exodus, run my launch workflow", "exodus, what does this workflow need", "exodus, build a workflow that turns a swipe into hooks then ads", "list my exodus workflows", "exodus, edit that workflow"), named this skill or /exodus-workflow, ran an `npx @aicopycoders/exodus workflow` command, or the `exodus` hub skill routed here. Never claim generic "workflow"/"automation" requests ("automate this", "build me a workflow") without Exodus context — 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.
3
+ description: Run, chain, build, edit, and automate saved Exodus Workflows — the multi-node automations that wire Genesis bots, primers, briefs, transforms, and image nodes together on the dashboard canvas — from the CLI. Use it to list a brand's workflows, describe what a workflow needs before running it (inputs, per-workspace primer prerequisites, outputs), run one and collect its outputs, chain one workflow's output into another's input, browse the bot catalog, author or update a workflow contract via templates → edit → validate → import, resolve runs that park (review a gate from the terminal, check the workflow inbox, repair a stalled run, answer a nested workflow's slots), continue a bot session in chat, enable/disable/fire triggers, read the brand's copy banks or promote a winner, and list versions or roll one back. Only invoke when the user has explicitly invoked Exodus: they said "exodus" in the request ("exodus, run my launch workflow", "exodus, what does this workflow need", "exodus, build a workflow that turns a swipe into hooks then ads", "list my exodus workflows", "exodus, edit that workflow", "exodus, review this gate", "exodus, check my workflow inbox", "exodus, resolve that parked run", "exodus, continue that session", "exodus, enable/fire the trigger", "exodus, read my hooks bank", "exodus, promote this winner", "exodus, list the versions / roll it back"), named this skill or /exodus-workflow, ran an `npx @aicopycoders/exodus workflow` / `session` / `bank` command, or the `exodus` hub skill routed here. Never claim generic "workflow"/"automation"/"inbox"/"promote a winner"/"continue the session" requests ("automate this", "build me a workflow", "check my inbox") without Exodus context — 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.
4
4
  ---
5
5
 
6
6
  ```operator-guide
7
- Subcommands:
7
+ Core — discover & run:
8
8
  exodus workflow list [--json] List the brand's saved workflows
9
9
  exodus workflow describe <workflowId|name> [--json] Inputs, prerequisites, outputs
10
10
  exodus workflow bots [--category <cat>] [--slug <slug>] [--json] Bot catalog / one bot's spec
11
- exodus workflow run <workflowId|name> [--input key=value ...] [--wait] [--json] Run it
11
+ exodus workflow run <workflowId|name> [--input key=value ...] [--terminal <nodeId> ...] [--wait] [--json] Run it
12
12
  exodus workflow status [--id <runId>] [--json] Poll a run / read its outputs
13
- exodus workflow export <workflowId|name> [--out <file>] Dump the contract JSON
14
- exodus workflow import <file> [--update <workflowId>] [--dry-run] [--json] Author/edit
15
-
16
- Key flags:
17
- --input key=value Repeatable run input; the value may contain "="
18
- --input key=@path Load the value from a file (path resolved from the cwd);
19
- use key=@@text to keep a leading "@" as a literal character
20
- --wait Poll until the run reaches a terminal status, then print outputs
21
- --id <runId> Run id for `status` detail
22
- --category <cat> (bots) Filter the catalog to one category id
23
- --slug <slug> (bots) Show a single bot's full port + param spec
24
- --out <file> (export) Write JSON to a file instead of stdout
25
- --update <workflowId> (import) Update this workflow in place (optimistic-concurrency
26
- guarded by the export's updatedAt — a 409 means re-export first)
27
- --dry-run (import) Validate + resolve refs without writing; returns issues
13
+ exodus workflow export <workflowId|name> [--version <n>] [--out <file>] [--json] Dump the contract (YAML)
14
+
15
+ Authoring:
16
+ exodus workflow templates [list] [--json] List starter templates
17
+ exodus workflow templates export <key> [--out <file>] [--json] Write a template's YAML to edit
18
+ exodus workflow schema [--kind <kind>] [--face <face>] [--json] Live graph vocabulary
19
+ exodus workflow validate <file> [--update <workflowId>] [--json] Check a file (import --dry-run's door)
20
+ exodus workflow import <file> [--update <workflowId>] [--dry-run] [--json] Create/update a workflow
21
+
22
+ Parked runs:
23
+ exodus workflow inbox [--json] Every run parked waiting on you
24
+ exodus workflow gate <runId> [pick <n,..> | edit <n> | push "<msg>" | approve | reject [--reason "..."]] [--json] Resolve a gate
25
+ exodus workflow repair <runId> retry|skip|kill [--wait] [--json] Resolve a repair park
26
+ exodus workflow answer <runId> [--slot key=value ...] [--json] Answer a nested workflow's slots (no --slot → list them)
27
+
28
+ Sessions + triggers:
29
+ exodus session list [--json] Chat sessions on the active brand
30
+ exodus session show <sessionId> [--json] A session's turns
31
+ exodus session chat <sessionId> "message" [--json] Append a message, print the reply
32
+ exodus workflow triggers <workflowId|name> [enable <n> | disable <n> | fire [<n>] [--text "..."] [--wait]] [--json] List/flip/fire triggers
33
+
34
+ Banks + versions:
35
+ exodus bank list [--json] The brand's copy banks
36
+ exodus bank show <key> [--json] One bank's entries (newest-first)
37
+ exodus bank promote <key> [text] [--file <path>] [win/provenance flags] [--json] Land a winner (fires the flywheel)
38
+ exodus workflow versions <workflowId|name> [--json] A workflow's saved history
39
+
40
+ Flag notes that add agent-level guidance (everything else is in --help):
41
+ --input key=@path Load an input's value from a file (path resolved from cwd);
42
+ key=@@text keeps a leading "@" as a literal character.
43
+ --terminal <nodeId> (run) Repeatable. Scope the run to the upstream closure of
44
+ these end node(s); omit to run the whole graph.
45
+ --version <n> (export) A real 1-based id from `workflow versions`; a
46
+ version export carries NO triggers/description.
47
+
48
+ Every workflow/session/bank verb takes --json, and that output IS the machine API —
49
+ there is no separate agent command set.
28
50
 
29
51
  Auth: Bearer (EXODUS_API_KEY). The canvas lives on the dashboard at /workflows.
30
52
  ```
31
53
 
32
- # Workflow — Run, Chain, and Build Saved Automations
54
+ # Workflow — Run, Resolve, Build, and Automate Saved Workflows
33
55
 
34
- An Exodus **Workflow** is a saved graph the member builds on the dashboard canvas (`/workflows`): brief and primer sources feed Genesis bots, bots feed other bots or an image node, and an Output node collects the deliverables. This skill is the CLI operator for those graphs — discover and run them, chain one into the next, and author or edit the contract without opening a browser.
56
+ An Exodus **Workflow** is a saved graph the member builds on the dashboard canvas (`/workflows`): brief and primer sources feed Genesis bots, bots feed transforms, other bots, gates, or an image node, deposits land copy in banks, and an Output node collects the deliverables. This skill is the CLI operator for those graphs — a **peer door** to the same artifact the web canvas edits. Anything you can do to a run in the browser (review a gate, repair a stall, continue a session, promote a winner), you can do here.
35
57
 
36
- Three jobs, three sections below. Read the request and jump to the one that fits; don't walk all three when the user just wants a run.
58
+ **The `--json` contract (read this once).** Every `workflow`, `session`, and `bank` verb takes `--json`, and that structured output **is** the machine API — there is no separate agent command set and no MCP server. Read for humans without it; parse `--json` when you need to chain or branch on the result.
59
+
60
+ **Auth is one bearer key.** `EXODUS_API_KEY` from `.env` authorizes every verb; there are no scoped tokens. If a call 401s, run `npx @aicopycoders/exodus doctor`.
61
+
62
+ ## The agent operating loop
63
+
64
+ The end-to-end narrative, every step naming its verb. You rarely walk all of it — jump to the section you need — but this is the spine when you're driving a workflow from nothing to a promoted winner:
65
+
66
+ 1. **Author** — start from a template (`workflow templates`, then `templates export <key>`), edit the YAML, and pull live vocabulary with `workflow schema` as you go.
67
+ 2. **Validate** — `workflow validate <file>` until it comes back clean (it's `import --dry-run` under its own door).
68
+ 3. **Import** — `workflow import <file>` to create, or `import <file> --update <id>` to edit in place.
69
+ 4. **Describe** — `workflow describe` to confirm inputs, prerequisites (✓/✗ primers), and outputs before spending a run.
70
+ 5. **Run** — `workflow run … --wait` (streams progress, prints outputs) or omit `--wait` and poll `workflow status --id <runId>`.
71
+ 6. **Notice the park** — a run can stop and wait on you. There are no webhooks in v1; you notice a park one of three ways: `run --wait` prints a pause notice naming the verb to use (the command keeps waiting — resolve from another shell, or Ctrl-C and pick it up via `inbox`), `workflow inbox` lists it, or `workflow status` shows `awaiting-review`.
72
+ 7. **Resolve** — `workflow gate <runId> …` (taste review), `workflow repair <runId> …` (a stalled collector), or `workflow answer <runId> …` (a nested workflow's slots).
73
+ 8. **Harvest** — read deliverables with `workflow status --id <runId> --json`; read banked deposits with `bank show <key>`; keep thinking with a bot via `session chat`.
74
+ 9. **Promote** — `bank promote <key>` lands the winner **and** fires the Winner Flywheel; then check `workflow inbox` for any background run it kicked off.
37
75
 
38
76
  ## 1. Discover & run
39
77
 
@@ -75,95 +113,231 @@ npx @aicopycoders/exodus workflow run "Launch Flow" --input brief="new cortisol
75
113
  npx @aicopycoders/exodus workflow status --id <runId>
76
114
  ```
77
115
 
78
- Report the outputs plus a short take. Don't call a run "done" off the kickoff line alone — a run reaches `completed`, `partial`, or `failed`; a `partial` means some nodes were skipped or failed, so read the status before declaring victory.
116
+ To run only part of a graph, scope it with `--terminal <nodeId>` (repeatable): only nodes feeding the picked end node(s) execute; the rest are recorded out-of-scope.
79
117
 
80
- ## 2. Chain — feed one workflow's output into the next
118
+ Report the outputs plus a short take. Don't call a run "done" off the kickoff line alone — a run reaches `completed`, `partial`, `failed`, or **parks** (`awaiting-review`). A `partial` means some nodes were skipped or failed; a park means it's waiting on you (see the next section). Read `status` before declaring victory.
81
119
 
82
- The chaining surface is the run's flattened outputs. After a `--wait` run (or once `status` shows terminal), pull the machine-readable outputs:
120
+ ## 2. Runs that park
121
+
122
+ A run doesn't always finish on its own — it can **park** and wait for a human decision. There are three actionable park kinds:
123
+
124
+ - **gate** — a taste-review Gate node is holding candidates for your pick/edit/approval.
125
+ - **repair** — a require-all collector stalled on a dead input and needs a decision.
126
+ - **slots** — a nested sub-workflow is waiting on inputs (slot answers).
127
+
128
+ **No webhooks in v1 — nothing parks silently, but nothing pings you either.** You find parks three ways: `run --wait` prints a pause notice naming the verb to use (a park is not terminal — the command keeps polling so it can resume after you resolve; resolve from another shell, or Ctrl-C and pick it up via `inbox`); `workflow inbox` lists every parked run badged by kind (`gate`/`repair`/`slots`) and how it started (`bg`, `trig:<event>`); or `workflow status` shows `awaiting-review`. **`inbox` is the one place nothing hides** — including background runs a trigger fired or a promote's flywheel kicked off.
83
129
 
84
130
  ```bash
85
- npx @aicopycoders/exodus workflow status --id <runId> --json
131
+ npx @aicopycoders/exodus workflow inbox
86
132
  ```
87
133
 
88
- The response carries a top-level `outputs` array — each entry is `{ nodeId, botSlug?, type, label, text?, imageUrl?, imageId? }`. Extract the text you want (e.g. `.outputs[].text`) and pass it into the next workflow's input:
134
+ ### Gate parks — pick / edit / push / approve / reject
135
+
136
+ Candidates are numbered **1-based**. Show them, then act:
89
137
 
90
138
  ```bash
91
- # Inline, for short outputs
92
- npx @aicopycoders/exodus workflow run "Ad Writer" --input hooks="<text from run A>" --wait
139
+ npx @aicopycoders/exodus workflow gate <runId> # show the candidates
140
+ npx @aicopycoders/exodus workflow gate <runId> pick 1,3 # keep candidates 1 and 3
141
+ npx @aicopycoders/exodus workflow gate <runId> edit 2 --text "punchier hook" # rewrite one in place
142
+ npx @aicopycoders/exodus workflow gate <runId> push "make them shorter" # steer the upstream session
143
+ npx @aicopycoders/exodus workflow gate <runId> approve --wait # resume the run
144
+ npx @aicopycoders/exodus workflow gate <runId> reject --reason "off-brand" # cancel it
93
145
  ```
94
146
 
95
- **The file-as-persistence convention.** For anything reusable or long — a brief, a sales letter, a hook pool — write the text to a **markdown file in the project** and pass it with `@file`. This is the deliberate v1 alternative to a server-side store: the file *is* the persistence layer, versioned in the project alongside the brand's other artifacts.
147
+ - **`edit`** takes `--text`, `--file <path>`, or piped stdin for the replacement copy.
148
+ - **`push`** sends a steering message into the gate's live chat session and banks a **fresh** candidate from the bot's reply — use it when none of the current candidates are right but the direction is fixable by asking. It does not resume the run; approve when you're happy.
149
+ - **`reject`** records the reason on the cancel **identically to the web** — the reason is not cosmetic, it's the same audit trail.
150
+
151
+ ### Repair parks — retry / skip / kill
96
152
 
97
153
  ```bash
98
- # Save run A's text output to a project file, then feed it to workflow B
99
- npx @aicopycoders/exodus workflow status --id <runId> --json # copy .outputs[].text into state/hooks.md
100
- npx @aicopycoders/exodus workflow run "Ad Writer" --input hooks=@state/hooks.md --wait
154
+ npx @aicopycoders/exodus workflow repair <runId> retry # re-run the dead input
155
+ npx @aicopycoders/exodus workflow repair <runId> skip # drop the dead input, continue with the rest
156
+ npx @aicopycoders/exodus workflow repair <runId> kill # give up on the run
101
157
  ```
102
158
 
103
- `@path` is resolved from the current directory. If the literal value you need actually starts with `@`, escape it as `@@` (e.g. `--input handle=@@brand` passes the string `@brand`).
159
+ Add `--wait` to `retry`/`kill` to poll through to the next terminal state or park.
160
+
161
+ ### Slot parks — answer
162
+
163
+ A nested sub-workflow waiting on inputs is a **slots** park. Run `answer` with **no** `--slot` to list the pending slot ids, then supply them:
104
164
 
105
- Use this pattern to compose pipelines the canvas doesn't wire directly: run the research/hook workflow, persist its best output as a file, then run the writing workflow off that file — reproducibly, because the file stays.
165
+ ```bash
166
+ npx @aicopycoders/exodus workflow answer <runId> # list the slot ids it wants
167
+ npx @aicopycoders/exodus workflow answer <runId> --slot tone=casual --slot length=short
168
+ ```
169
+
170
+ ## 3. Sessions
106
171
 
107
- ## 3. Build & edit — author a workflow contract
172
+ Some bots run in **session-mode**: their run leaves behind a live chat session you can keep talking to. You don't create sessions here — they're born from runs — but you can list, read, and continue them:
173
+
174
+ ```bash
175
+ npx @aicopycoders/exodus session list # sessions on the active brand
176
+ npx @aicopycoders/exodus session show sess_123 # the session's turns
177
+ npx @aicopycoders/exodus session chat sess_123 "make the hook punchier" # one-shot: append + print reply
178
+ ```
108
179
 
109
- The documented way to learn the contract format is to **export an existing workflow and study it** — there is no separate schema doc. The loop is: learn the bots → learn the format → compose → dry-run until clean → import → review on the canvas.
180
+ `session chat` is a one-shot append-and-reply, not an interactive REPL — one message, one reply per call. **Replies can take minutes** (the chat route's budget is ~5 minutes), so don't treat a long wait as a hang. The `workflow status` output names the session tied to a run — that's your run→chat jump when you want to keep thinking past what a run produced.
110
181
 
111
- ### Step 1 — Learn the bot catalog
182
+ ## 4. Triggers
183
+
184
+ A **trigger** makes a workflow run itself — on a schedule (cron) or on a platform event (e.g. a promoted winner). Triggers live on the workflow and are addressed by **1-based position**, not by id:
185
+
186
+ ```bash
187
+ npx @aicopycoders/exodus workflow triggers "Winner Flywheel" # numbered list
188
+ npx @aicopycoders/exodus workflow triggers "Winner Flywheel" enable 1 # flip one switch on
189
+ npx @aicopycoders/exodus workflow triggers "Winner Flywheel" disable 1 # flip it off
190
+ npx @aicopycoders/exodus workflow triggers "Winner Flywheel" fire 1 --text "fake winner copy" --wait
191
+ ```
192
+
193
+ - **`enable`/`disable` flip exactly one switch** — never rewrite a trigger by re-importing the whole contract to toggle it. The CLI reads the live trigger list and sends a **fingerprint** of the trigger's fields as a guard, so if the workflow changed under you the flip **fails loud** rather than hitting the wrong trigger. If it fails, re-list and retry.
194
+ - **Add or remove triggers by editing the YAML export** (they live in the head export) and re-importing — enable/disable never adds or removes, only toggles.
195
+ - **`fire`** starts a **real** background run on the workflow owner's keys. An event trigger's `fire` needs `--text` (the input the event would carry); a cron trigger's `fire` rejects `--text`. Its gates park into the **inbox** like any other run.
196
+
197
+ **The ruled test flow:** author the workflow with the trigger **disabled** → `fire <n> --text "fake winner copy"` to dry-fire it and watch the run (gates land in the inbox) → `enable <n>` once you're happy with what it produces.
198
+
199
+ ## 5. Banks + the flywheel
200
+
201
+ **Banks** are the brand's copy libraries (hooks, body, headlines, …) that primers and workflows fold in. A fresh brand starts with six, all empty.
202
+
203
+ ```bash
204
+ npx @aicopycoders/exodus bank list # the brand's banks + entry counts
205
+ npx @aicopycoders/exodus bank show hooks # entries, newest-first, with source + win metrics
206
+ npx @aicopycoders/exodus bank promote hooks "Stop scrolling — your knees will thank you"
207
+ ```
208
+
209
+ `bank promote` lands a winning line **and fires the Winner Flywheel** — it emits the `winner-promoted` event with **exact web parity and no opt-out**. Any workflow with an enabled matching trigger then starts a background run. So **after a promote, check `workflow inbox`** for the background run it may have kicked off. Promote reads text from an argument, `--file <path>`, or stdin (one of the three), and takes optional flags for awareness tagging (`--awareness`, body bank), win metrics (`--spend`, `--roas`, `--ctr`, `--note`), and provenance (`--run`, `--node`) — see `bank promote --help`.
210
+
211
+ ## 6. Versions
212
+
213
+ Every import saves a **version**. You can list a workflow's history and export any past version:
214
+
215
+ ```bash
216
+ npx @aicopycoders/exodus workflow versions "Launch Flow" # saved history, newest-first
217
+ npx @aicopycoders/exodus workflow export "Launch Flow" --version 3 --out v3.yaml
218
+ ```
219
+
220
+ Version numbers are real **1-based ids** from `versions` (not offsets). Two patterns:
221
+
222
+ ```bash
223
+ # Diff two saved versions
224
+ npx @aicopycoders/exodus workflow export X --version 3 --out v3.yaml
225
+ npx @aicopycoders/exodus workflow export X --version 5 --out v5.yaml
226
+ diff v3.yaml v5.yaml
227
+
228
+ # Roll back to an earlier version
229
+ npx @aicopycoders/exodus workflow export X --version 3 --out v3.yaml
230
+ npx @aicopycoders/exodus workflow import v3.yaml --update <id>
231
+ ```
232
+
233
+ A version export **intentionally carries no triggers or description** — those aren't versioned, so a rollback leaves them unchanged; the head export (`export` with no `--version`) is the only place triggers appear. Exporting a backup before a destructive `--update` is still good habit, but rollback is a real, supported move now.
234
+
235
+ ## 7. Build & edit — author a workflow contract
236
+
237
+ Cold-start is a **template, not a blank file** — there is no `workflow new`. Learn the vocabulary live (`workflow schema` replaces any notion of a static schema doc), start from a template, and treat `validate` as your compiler loop. The loop: pick a starter → export it → edit → check the schema for vocabulary → `validate` until clean → `import` → review on the canvas.
238
+
239
+ ### Step 1 — Learn the bot catalog and the graph vocabulary
112
240
 
113
241
  ```bash
114
242
  npx @aicopycoders/exodus workflow bots # full catalog, grouped by category
115
- npx @aicopycoders/exodus workflow bots --category writing # one category
116
243
  npx @aicopycoders/exodus workflow bots --slug new-hook-bot # one bot's full port + param spec
117
- npx @aicopycoders/exodus workflow bots --json # raw catalog for machine use
244
+ npx @aicopycoders/exodus workflow schema # LIVE node kinds, ports, config rules, faces, gate/wiring rules
245
+ npx @aicopycoders/exodus workflow schema --kind transform # one node kind
246
+ npx @aicopycoders/exodus workflow schema --face splitter # one transform face's ports + spec
118
247
  ```
119
248
 
120
- Per bot you get its **input ports** (id, what it accepts — `text` / `primer` / `image`, whether it's required, and for primer ports which `primerKinds` gate it — `body` / `hook` / `headline` / `summary`), its **params** (select/text/toggle/number/multiselect, with options and defaults), and its output type. This is what tells you which node can wire into which — a port only accepts an edge whose source produces a type it lists in `accepts`.
249
+ Per bot you get its **input ports** (id, what it `accepts` — `text` / `primer` / `image`, whether it's required, and for primer ports which `primerKinds` gate it), its **params**, and its output type. `workflow schema` prints the **live** graph grammar from the backend you're deployed against — node kinds, port ids, config rules, transform faces, gate policies, wiring rules — so what you author matches what will validate. This is always current; pull it fresh rather than trusting memory.
121
250
 
122
- ### Step 2 — Learn the format by exporting
251
+ ### Step 2 — Start from a template
123
252
 
124
253
  ```bash
125
- npx @aicopycoders/exodus workflow export "Launch Flow" --out launch.json
254
+ npx @aicopycoders/exodus workflow templates # list the starters (incl. Winner Flywheel)
255
+ npx @aicopycoders/exodus workflow templates export complete-ad-set --out my.yaml
126
256
  ```
127
257
 
128
- Study `launch.json`: it's the `exodus-workflow` contract — `nodes` (each with `id`, `kind` = `brief`/`bot`/`primer`/`image`/`output`, a `config`, and an optional `position`) and `edges` (`source`/`sourceHandle` → `target`/`targetHandle`, the handles being port ids). An export also carries `workflowId` + `updatedAt` at the top — those are update anchors, not part of the graph.
258
+ `templates export` writes the server-rendered YAML verbatim — a real, valid graph to edit down, not a stub. You can also learn the format by exporting an existing workflow (`workflow export "Launch Flow" --out launch.yaml`); export writes **canonical YAML** by default (fixed key order, so equal workflows dump byte-identically and diffs stay clean). Pass `--json` on export for the legacy JSON contract; import reads either.
129
259
 
130
- ### Step 3 — Compose the contract
260
+ The contract is `nodes` (each with `id`, `kind`, a `config`, and optional `position`) and `edges` (`source`/`sourceHandle` → `target`/`targetHandle`, the handles being **port ids**). **Node positions are optional** — omit them and the server auto-layouts on import. An export also carries `workflowId` + `updatedAt` at the top — those are update anchors, not part of the graph.
131
261
 
132
- Author the JSON by hand or by adapting an export. **Node positions are optional** — leave them out and the server auto-layouts the graph on import; you don't need to compute coordinates.
262
+ ### Step 3 — Validate like a compiler loop
133
263
 
134
- ### Step 4 — Dry-run like a compiler loop
264
+ ```bash
265
+ npx @aicopycoders/exodus workflow validate my.yaml
266
+ ```
267
+
268
+ `validate` is the front door; `import --dry-run` is the same check under a different door. It **needs network + login** — there is no offline validation, ever. It returns compiler-style issues, each naming the **node id**, the **port id** where relevant, and a concrete **remedy**. Codes include `dangling-edge`, `unknown-port`, `type-mismatch`, `missing-required-input`, `duplicate-input`, `cycle`, `unknown-kind`, and `bad-config`. Fix, re-run `validate`, repeat until clean.
269
+
270
+ ### Step 4 — Import (create or update)
135
271
 
136
272
  ```bash
137
- npx @aicopycoders/exodus workflow import launch.json --dry-run
273
+ npx @aicopycoders/exodus workflow import my.yaml # create a new workflow
274
+ npx @aicopycoders/exodus workflow import my.yaml --update wf_123 # update in place
138
275
  ```
139
276
 
140
- `--dry-run` validates and resolves refs without writing. It returns compiler-style issues — each names the **node id**, the **port id** where relevant, and a concrete **remedy** (the exact edit that fixes it). Codes you'll see include `dangling-edge`, `unknown-port`, `type-mismatch`, `missing-required-input`, `duplicate-input`, `cycle`, `unknown-kind`, and `bad-config`. Fix, re-run `--dry-run`, repeat until it's clean — same loop as fixing type errors.
277
+ Then **tell the user to review it on the canvas at `/workflows`** — the CLI writes the graph; the human eye confirms it reads right.
278
+
279
+ **The 409 rule.** `--update` sends the export's `updatedAt` as an optimistic-concurrency guard. If the workflow moved on the dashboard since your export, the import 409s. Re-export the current workflow, reapply your edits onto the fresh contract, and import again — never force past a 409, or you'll clobber their change.
141
280
 
142
- ### Step 5 — Import (create or update)
281
+ ### Authoring gotchas
282
+
283
+ A hand-picked list of the traps that bite first. `workflow schema` is authoritative on any conflict with what's written here:
284
+
285
+ - **Edge handles are port ids** — `sourceHandle`/`targetHandle` name a **port**, not a node id or a label. An edge only connects if the target port `accepts` the source port's type.
286
+ - **Required inputs hard-block** — a run refuses to start until every required input is supplied; there's no "run anyway".
287
+ - **Transform faces are sealed** — each face has fixed, per-face **output port ids** you must wire from by name; `workflow schema --face <face>` prints them.
288
+ - **`session` outputs are single-consumer** — a session-typed output feeds exactly one downstream edge; a Push node's `last`/`all` output ports fan out to many consumers freely.
289
+ - **Deposit shapes are strict** — a deposit's config must match the target bank's expected shape exactly, or import rejects it.
290
+ - **Empty defaults are deliberately invalid** — `rig`, `call`, `transform`, and `bot` nodes ship with empty config placeholders that fail validation on purpose; you MUST fill them, not leave them.
291
+ - **Quote YAML that looks numeric or boolean** — `"true"`, `"3.0"`, `"01"` need quotes or the parser coerces them to the wrong type.
292
+ - **A 409 on `--update` means re-export first** — reapply your edits onto the fresh contract; don't force.
293
+
294
+ ## 8. Chain — feed one workflow's output into the next
295
+
296
+ The chaining surface is the run's flattened outputs. After a `--wait` run (or once `status` shows terminal), pull the machine-readable outputs:
143
297
 
144
298
  ```bash
145
- # Create a new workflow
146
- npx @aicopycoders/exodus workflow import launch.json
299
+ npx @aicopycoders/exodus workflow status --id <runId> --json
300
+ ```
147
301
 
148
- # Update an existing workflow in place
149
- npx @aicopycoders/exodus workflow import launch.json --update wf_123
302
+ The response carries a top-level `outputs` array — each entry is `{ nodeId, botSlug?, type, label, text?, imageUrl?, imageId? }`. Extract the text you want (e.g. `.outputs[].text`) and pass it into the next workflow's input:
303
+
304
+ ```bash
305
+ # Inline, for short outputs
306
+ npx @aicopycoders/exodus workflow run "Ad Writer" --input hooks="<text from run A>" --wait
150
307
  ```
151
308
 
152
- Then **tell the user to review it on the canvas at `/workflows`** — the CLI writes the graph; the human eye confirms it reads right.
309
+ **The file-as-persistence convention.** For anything reusable or long — a brief, a sales letter, a hook pool — write the text to a **markdown file in the project** and pass it with `@file`. This is the deliberate v1 alternative to a server-side store: the file *is* the persistence layer, versioned in the project alongside the brand's other artifacts.
310
+
311
+ ```bash
312
+ # Save run A's text output to a project file, then feed it to workflow B
313
+ npx @aicopycoders/exodus workflow status --id <runId> --json # copy .outputs[].text into state/hooks.md
314
+ npx @aicopycoders/exodus workflow run "Ad Writer" --input hooks=@state/hooks.md --wait
315
+ ```
316
+
317
+ `@path` is resolved from the current directory. If the literal value you need actually starts with `@`, escape it as `@@` (e.g. `--input handle=@@brand` passes the string `@brand`).
318
+
319
+ For persistence that lives *inside* Exodus rather than in a file, remember **banks**: a run can deposit copy into a bank, and you read it back with `bank show <key>` — that's the server-side equivalent of the file convention.
320
+
321
+ ## Direct HTTP (if you're scripting past the CLI)
322
+
323
+ The same contract is on the v2 API, so a script can skip the CLI:
153
324
 
154
- **Two rules for edits (`--update`):**
325
+ - **Export:** `GET /api/v2/workflows/export?id=<workflowId>&format=yaml` returns the canonical YAML (`text/yaml`); drop `format` (or `format=json`) for the legacy JSON body.
326
+ - **Import:** `POST /api/v2/workflows/import`. A JSON object body carries the contract *plus* the control fields inline (`dryRun`, `targetWorkflowId`, `expectedUpdatedAt`). A raw **YAML** body is the contract text alone — pass the control fields as **query params** instead (`?dryRun=true&targetWorkflowId=<id>&expectedUpdatedAt=<ts>`).
155
327
 
156
- - **Always export a backup first.** v1 has no version history — a destructive `--update` overwrites the live workflow with no undo. `export --out backup.json` before you import over it.
157
- - **A 409 means the canvas moved.** `--update` sends the export's `updatedAt` as an optimistic-concurrency guard. If someone edited the workflow on the dashboard since your export, the import 409s. Re-export the current workflow, reapply your edits onto the fresh contract, and import again — never force past a 409, or you'll clobber their change.
328
+ The gate/inbox/session/bank verbs have v2 routes too, but the CLI is the maintained path — reach for HTTP only when you're already scripting, and prefer the CLI's `--json` output over hand-rolling routes. Both are Bearer-auth'd (`EXODUS_API_KEY`) like every v2 route.
158
329
 
159
330
  ## Failure handling
160
331
 
161
332
  - **`describe` shows a ✗ prerequisite** — don't run yet. Fill the primer via `exodus-primer` / `exodus-foundation`, then re-check `describe`.
162
- - **Run comes back `partial` or `failed`** — read `status --id <runId>` (drop `--json` for a readable view) to see which node failed and why before re-running.
163
- - **`import --dry-run` issues** — fix the exact node/port each issue names using its remedy; don't guess.
164
- - **`import --update` 409** — re-export, reapply, re-import (see Step 5). This is a conflict, not a bug.
333
+ - **Run comes back `partial` or `failed`** — read `status --id <runId>` (drop `--json` for a readable view) to see which node failed and why **before re-running**. A partial still has usable outputs; harvest them first.
334
+ - **A verb refuses because the run isn't in that state** — the gate/repair/answer verbs preflight the run and name its **actual** state (e.g. "parked for repair" when you tried a gate verb). Read the state it reports and switch to the matching verb, or check `inbox` for what's actually pending.
335
+ - **Trigger fingerprint mismatch** — the workflow changed since you listed its triggers. Re-list (`workflow triggers <wf>`) and retry the enable/disable/fire against the fresh numbering.
336
+ - **`validate` / `import` errors** — fix the exact node/port each issue names using its remedy; don't guess. `validate` needs network + login — there is no offline check.
337
+ - **`import --update` 409** — re-export, reapply, re-import. This is a conflict, not a bug.
338
+ - **After a promote** — the success line includes the flywheel note; check `workflow inbox` for the background run it may have started.
165
339
  - **Auth / whoami failure** — run `npx @aicopycoders/exodus doctor` and confirm `EXODUS_API_KEY`.
166
- - **Wrong brand** — workflows are per-brand. `npx @aicopycoders/exodus brand current` to confirm you're on the brand you think you are; switch with the `exodus-brand` skill.
340
+ - **Wrong brand** — workflows, sessions, and banks are per-brand. `npx @aicopycoders/exodus brand current` to confirm you're on the brand you think you are; switch with the `exodus-brand` skill.
167
341
 
168
342
  ## Not the right skill for
169
343
 
@@ -20,6 +20,8 @@ const OTHER_COMMANDS = new Set([
20
20
  "migrate",
21
21
  "segment",
22
22
  "workflow",
23
+ "session",
24
+ "bank",
23
25
  ]);
24
26
  const HIDDEN_COMMANDS = new Set([
25
27
  "creative",
@@ -0,0 +1,88 @@
1
+ import { type ApiResponse } from "../lib/client.js";
2
+ import { type Channel } from "../lib/channel.js";
3
+ export declare const helpText: string;
4
+ export declare const AWARENESS_PAIRS: readonly ["unawareProblemAware", "solutionProductAware", "mostAware"];
5
+ export type AwarenessPair = (typeof AWARENESS_PAIRS)[number];
6
+ export interface BankSummary {
7
+ key: string;
8
+ name: string;
9
+ type: string;
10
+ awarenessPair?: AwarenessPair;
11
+ entryCount: number;
12
+ }
13
+ export interface BankWinMetrics {
14
+ spend?: number;
15
+ roas?: number;
16
+ ctr?: number;
17
+ note?: string;
18
+ }
19
+ export interface BankProvenance {
20
+ workflowRunId?: string;
21
+ genesisRunId?: string;
22
+ nodeId?: string;
23
+ variantIndex?: number;
24
+ artifactKind?: string;
25
+ }
26
+ export interface BankEntry {
27
+ _id: string;
28
+ text: string;
29
+ source: string;
30
+ awarenessPair?: AwarenessPair;
31
+ winMetrics?: BankWinMetrics;
32
+ humanEdited?: boolean;
33
+ provenance?: BankProvenance;
34
+ createdAt: number | string;
35
+ }
36
+ export interface BankShowResponse {
37
+ bank: {
38
+ key: string;
39
+ name: string;
40
+ type: string;
41
+ awarenessPair?: AwarenessPair;
42
+ };
43
+ entries: BankEntry[];
44
+ }
45
+ export interface BankPromoteBody {
46
+ bankKey: string;
47
+ text: string;
48
+ awarenessPair?: AwarenessPair;
49
+ winMetrics?: BankWinMetrics;
50
+ provenance?: BankProvenance;
51
+ }
52
+ export interface BankPromoteResponse {
53
+ entryId: string;
54
+ bankName: string;
55
+ event?: string;
56
+ }
57
+ export interface FlowResult {
58
+ code: number;
59
+ lines: string[];
60
+ }
61
+ export interface BankDeps {
62
+ get: (path: string) => Promise<ApiResponse<unknown>>;
63
+ post: (path: string, body: unknown) => Promise<ApiResponse<unknown>>;
64
+ readFile: (path: string) => string;
65
+ readStdin: () => string;
66
+ stdinIsTTY: () => boolean;
67
+ channel: Channel;
68
+ }
69
+ export declare function relativeAge(value: number | string, now?: number): string;
70
+ export declare function formatBankList(banks: BankSummary[]): string;
71
+ export declare function formatBankShow(res: BankShowResponse): string;
72
+ interface PromoteTextSources {
73
+ arg?: string;
74
+ file?: string;
75
+ readFile: (path: string) => string;
76
+ readStdin: () => string;
77
+ stdinIsTTY: () => boolean;
78
+ }
79
+ export declare function resolvePromoteText(opts: PromoteTextSources): string;
80
+ export declare function parseWinMetrics(flags: Record<string, string | boolean>): BankWinMetrics | undefined;
81
+ export declare function parseProvenance(flags: Record<string, string | boolean>): BankProvenance | undefined;
82
+ export declare function parseAwareness(flags: Record<string, string | boolean>): AwarenessPair | undefined;
83
+ export declare function buildPromoteBody(bankKey: string, text: string, flags: Record<string, string | boolean>): BankPromoteBody;
84
+ export declare function listFlow(json: boolean, deps: BankDeps): Promise<FlowResult>;
85
+ export declare function showFlow(key: string, json: boolean, deps: BankDeps): Promise<FlowResult>;
86
+ export declare function promoteFlow(key: string, arg: string | undefined, flags: Record<string, string | boolean>, json: boolean, deps: BankDeps): Promise<FlowResult>;
87
+ export declare function run(flags: Record<string, string | boolean>): Promise<void>;
88
+ export {};