@aicopycoders/exodus 2026.7.200-beta.11 → 2026.7.200-beta.12
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 +1 -0
- package/assets/docs/PIPELINES.md +26 -0
- package/assets/skills/exodus/SKILL.md +4 -1
- package/assets/skills/exodus-workflow/SKILL.md +235 -61
- package/dist/bin/exodus.js +2 -0
- package/dist/commands/bank.d.ts +87 -0
- package/dist/commands/bank.js +346 -0
- package/dist/commands/session.d.ts +55 -0
- package/dist/commands/session.js +202 -0
- package/dist/commands/workflow.d.ts +186 -4
- package/dist/commands/workflow.js +1355 -45
- package/dist/lib/client.d.ts +8 -1
- package/dist/lib/client.js +30 -6
- package/dist/lib/route-support.d.ts +4 -0
- package/dist/lib/route-support.js +22 -0
- package/dist/lib/workflowText.d.ts +3 -0
- package/dist/lib/workflowText.js +113 -0
- package/package.json +5 -2
package/assets/docs/CLAUDE.md
CHANGED
|
@@ -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` |
|
package/assets/docs/PIPELINES.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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>]
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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,
|
|
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 —
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
131
|
+
npx @aicopycoders/exodus workflow inbox
|
|
86
132
|
```
|
|
87
133
|
|
|
88
|
-
|
|
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
|
-
#
|
|
92
|
-
npx @aicopycoders/exodus workflow
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
npx @aicopycoders/exodus workflow
|
|
100
|
-
npx @aicopycoders/exodus workflow
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 —
|
|
251
|
+
### Step 2 — Start from a template
|
|
123
252
|
|
|
124
253
|
```bash
|
|
125
|
-
npx @aicopycoders/exodus workflow
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
262
|
+
### Step 3 — Validate like a compiler loop
|
|
133
263
|
|
|
134
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
146
|
-
|
|
299
|
+
npx @aicopycoders/exodus workflow status --id <runId> --json
|
|
300
|
+
```
|
|
147
301
|
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
-
|
|
164
|
-
-
|
|
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
|
|
package/dist/bin/exodus.js
CHANGED
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
}
|
|
56
|
+
export interface FlowResult {
|
|
57
|
+
code: number;
|
|
58
|
+
lines: string[];
|
|
59
|
+
}
|
|
60
|
+
export interface BankDeps {
|
|
61
|
+
get: (path: string) => Promise<ApiResponse<unknown>>;
|
|
62
|
+
post: (path: string, body: unknown) => Promise<ApiResponse<unknown>>;
|
|
63
|
+
readFile: (path: string) => string;
|
|
64
|
+
readStdin: () => string;
|
|
65
|
+
stdinIsTTY: () => boolean;
|
|
66
|
+
channel: Channel;
|
|
67
|
+
}
|
|
68
|
+
export declare function relativeAge(value: number | string, now?: number): string;
|
|
69
|
+
export declare function formatBankList(banks: BankSummary[]): string;
|
|
70
|
+
export declare function formatBankShow(res: BankShowResponse): string;
|
|
71
|
+
interface PromoteTextSources {
|
|
72
|
+
arg?: string;
|
|
73
|
+
file?: string;
|
|
74
|
+
readFile: (path: string) => string;
|
|
75
|
+
readStdin: () => string;
|
|
76
|
+
stdinIsTTY: () => boolean;
|
|
77
|
+
}
|
|
78
|
+
export declare function resolvePromoteText(opts: PromoteTextSources): string;
|
|
79
|
+
export declare function parseWinMetrics(flags: Record<string, string | boolean>): BankWinMetrics | undefined;
|
|
80
|
+
export declare function parseProvenance(flags: Record<string, string | boolean>): BankProvenance | undefined;
|
|
81
|
+
export declare function parseAwareness(flags: Record<string, string | boolean>): AwarenessPair | undefined;
|
|
82
|
+
export declare function buildPromoteBody(bankKey: string, text: string, flags: Record<string, string | boolean>): BankPromoteBody;
|
|
83
|
+
export declare function listFlow(json: boolean, deps: BankDeps): Promise<FlowResult>;
|
|
84
|
+
export declare function showFlow(key: string, json: boolean, deps: BankDeps): Promise<FlowResult>;
|
|
85
|
+
export declare function promoteFlow(key: string, arg: string | undefined, flags: Record<string, string | boolean>, json: boolean, deps: BankDeps): Promise<FlowResult>;
|
|
86
|
+
export declare function run(flags: Record<string, string | boolean>): Promise<void>;
|
|
87
|
+
export {};
|