@aicopycoders/exodus 2026.7.200-beta.4 → 2026.7.200-beta.6
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/skills/exodus/SKILL.md +1 -0
- package/assets/skills/exodus-workflow/SKILL.md +173 -0
- package/dist/bin/exodus.js +1 -0
- package/dist/commands/brand.js +2 -1
- package/dist/commands/doctor.js +22 -16
- package/dist/commands/init.js +3 -2
- package/dist/commands/meme.js +5 -4
- package/dist/commands/migrate.js +7 -6
- package/dist/commands/workflow.d.ts +271 -0
- package/dist/commands/workflow.js +744 -0
- package/dist/lib/channel.d.ts +5 -0
- package/dist/lib/channel.js +15 -0
- package/dist/lib/scaffold.d.ts +5 -4
- package/dist/lib/scaffold.js +27 -6
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ 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
22
|
| Onboard a new brand / build the primer | `exodus-primer` (with winning ads) or `exodus-foundation` (no ads yet) |
|
|
22
23
|
| List/switch/troubleshoot brands | `exodus-brand` |
|
|
23
24
|
| See past runs / find an output | `exodus-browse` |
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
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.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
```operator-guide
|
|
7
|
+
Subcommands:
|
|
8
|
+
exodus workflow list [--json] List the brand's saved workflows
|
|
9
|
+
exodus workflow describe <workflowId|name> [--json] Inputs, prerequisites, outputs
|
|
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
|
|
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
|
|
28
|
+
|
|
29
|
+
Auth: Bearer (EXODUS_API_KEY). The canvas lives on the dashboard at /workflows.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
# Workflow — Run, Chain, and Build Saved Automations
|
|
33
|
+
|
|
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.
|
|
35
|
+
|
|
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.
|
|
37
|
+
|
|
38
|
+
## 1. Discover & run
|
|
39
|
+
|
|
40
|
+
The mistake to avoid is firing a run blind and wasting it on a missing input or an unmet prerequisite. Always **`describe` before `run`**.
|
|
41
|
+
|
|
42
|
+
### Step 1 — List
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx @aicopycoders/exodus workflow list
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Prints the brand's saved workflows (name, id, node/edge counts, timestamps). If the user named the workflow already, skip straight to `describe`.
|
|
49
|
+
|
|
50
|
+
### Step 2 — Describe (read inputs, prereqs, outputs)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx @aicopycoders/exodus workflow describe "Launch Flow"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Accepts a workflow id **or** a name. It reports three things:
|
|
57
|
+
|
|
58
|
+
- **Inputs** — each free input the run needs: field name, source (`text`, `swipe-ad`, `swipe-bundle`, `organic-url`, `ad-url`), whether it's required, and a description. These are the `--input key=value` pairs you'll pass.
|
|
59
|
+
- **Prerequisites** — the per-workspace primer markers the graph's primer nodes depend on, each shown **✓ stored** or **✗ missing** for *this brand*. **If any prerequisite is ✗, warn the user before running** — the run will not produce good output (or will fail) without it. Route them to the `exodus-primer` / `exodus-foundation` skill to fill the gap first, rather than burning a run.
|
|
60
|
+
- **Outputs** — the deliverables the Output node collects (text or image, with labels), so you know what to expect back.
|
|
61
|
+
|
|
62
|
+
### Step 3 — Gather inputs
|
|
63
|
+
|
|
64
|
+
Fill every required input. For a free-text brief you can compose a **bespoke brief from in-session research** — the winning angle you just worked out, the swipe you analyzed — and pass it inline (`--input brief="..."`) or, if it's long, write it to a project file and pass it with `@file` (see Chain, below). For URL/swipe sources, pass the value the source expects.
|
|
65
|
+
|
|
66
|
+
### Step 4 — Run and collect
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx @aicopycoders/exodus workflow run "Launch Flow" --input brief="new cortisol offer, problem-aware" --wait
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`--wait` polls to a terminal status and prints the run's collected outputs — text inline, image URLs. Without `--wait` the command returns the `runId` and you poll it yourself:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx @aicopycoders/exodus workflow status --id <runId>
|
|
76
|
+
```
|
|
77
|
+
|
|
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.
|
|
79
|
+
|
|
80
|
+
## 2. Chain — feed one workflow's output into the next
|
|
81
|
+
|
|
82
|
+
The chaining surface is the run's flattened outputs. After a `--wait` run (or once `status` shows terminal), pull the machine-readable outputs:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npx @aicopycoders/exodus workflow status --id <runId> --json
|
|
86
|
+
```
|
|
87
|
+
|
|
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:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Inline, for short outputs
|
|
92
|
+
npx @aicopycoders/exodus workflow run "Ad Writer" --input hooks="<text from run A>" --wait
|
|
93
|
+
```
|
|
94
|
+
|
|
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.
|
|
96
|
+
|
|
97
|
+
```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
|
|
101
|
+
```
|
|
102
|
+
|
|
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`).
|
|
104
|
+
|
|
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.
|
|
106
|
+
|
|
107
|
+
## 3. Build & edit — author a workflow contract
|
|
108
|
+
|
|
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.
|
|
110
|
+
|
|
111
|
+
### Step 1 — Learn the bot catalog
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npx @aicopycoders/exodus workflow bots # full catalog, grouped by category
|
|
115
|
+
npx @aicopycoders/exodus workflow bots --category writing # one category
|
|
116
|
+
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
|
|
118
|
+
```
|
|
119
|
+
|
|
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`.
|
|
121
|
+
|
|
122
|
+
### Step 2 — Learn the format by exporting
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx @aicopycoders/exodus workflow export "Launch Flow" --out launch.json
|
|
126
|
+
```
|
|
127
|
+
|
|
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.
|
|
129
|
+
|
|
130
|
+
### Step 3 — Compose the contract
|
|
131
|
+
|
|
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.
|
|
133
|
+
|
|
134
|
+
### Step 4 — Dry-run like a compiler loop
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npx @aicopycoders/exodus workflow import launch.json --dry-run
|
|
138
|
+
```
|
|
139
|
+
|
|
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.
|
|
141
|
+
|
|
142
|
+
### Step 5 — Import (create or update)
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Create a new workflow
|
|
146
|
+
npx @aicopycoders/exodus workflow import launch.json
|
|
147
|
+
|
|
148
|
+
# Update an existing workflow in place
|
|
149
|
+
npx @aicopycoders/exodus workflow import launch.json --update wf_123
|
|
150
|
+
```
|
|
151
|
+
|
|
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.
|
|
153
|
+
|
|
154
|
+
**Two rules for edits (`--update`):**
|
|
155
|
+
|
|
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.
|
|
158
|
+
|
|
159
|
+
## Failure handling
|
|
160
|
+
|
|
161
|
+
- **`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.
|
|
165
|
+
- **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.
|
|
167
|
+
|
|
168
|
+
## Not the right skill for
|
|
169
|
+
|
|
170
|
+
- **Running a single Genesis writing pass** (brief → copy in two voices) — that's `exodus-genesis`. Reach here when the member has a *saved multi-node workflow* to run or build.
|
|
171
|
+
- **One-off image renders** — `exodus-image` / `exodus-creative` / `exodus-template`.
|
|
172
|
+
- **Building the primer a workflow depends on** — `exodus-primer` (winning ads) or `exodus-foundation` (no ads).
|
|
173
|
+
- **Finding a past run's output doc** — `exodus-browse`.
|
package/dist/bin/exodus.js
CHANGED
package/dist/commands/brand.js
CHANGED
|
@@ -5,6 +5,7 @@ import { sniffImageType, BRAND_IMAGE_MAX_BYTES } from "../lib/image-sniff.js";
|
|
|
5
5
|
import { getActiveBrand, setActiveBrand, clearActiveBrand, findWorkspaceRoot, } from "../lib/state.js";
|
|
6
6
|
import { detectLayout, ensureBrandDir, resolveActiveBrand, brandStateDir, brandDirFor, } from "../lib/layout.js";
|
|
7
7
|
import { promptYesNo } from "../lib/prompts.js";
|
|
8
|
+
import { pkgRef } from "../lib/channel.js";
|
|
8
9
|
const AUTO_END_MARKER = "<!-- exodus:auto-section-end — manual brand notes below survive `brand use` refreshes -->";
|
|
9
10
|
export const helpText = `
|
|
10
11
|
exodus brand — list, switch, and inspect brands available to your CLI
|
|
@@ -180,7 +181,7 @@ async function runCreate(name) {
|
|
|
180
181
|
console.log(`✓ Wrote ${refresh.pathRel}.`);
|
|
181
182
|
}
|
|
182
183
|
console.log(`\nNext: set up the brand's primer to unlock the pipelines —\n` +
|
|
183
|
-
` say "exodus, set up my brand primer" (or run \`npx
|
|
184
|
+
` say "exodus, set up my brand primer" (or run \`npx ${pkgRef()} primer\`).`);
|
|
184
185
|
}
|
|
185
186
|
async function runDelete(slug, flags) {
|
|
186
187
|
if (!slug)
|
package/dist/commands/doctor.js
CHANGED
|
@@ -7,6 +7,7 @@ import { findWorkspaceRoot } from "../lib/state.js";
|
|
|
7
7
|
import { detectLayout, listBrandDirs, resolveActiveBrand, brandStateDir, } from "../lib/layout.js";
|
|
8
8
|
import { auth401Hint, isPlaceholderApiUrl } from "../lib/backend-hint.js";
|
|
9
9
|
import { compareVersions, readPackageVersion, } from "../lib/release.js";
|
|
10
|
+
import { channelOf, pkgRef, stampChannel } from "../lib/channel.js";
|
|
10
11
|
export const helpText = `
|
|
11
12
|
exodus doctor — Preflight checks for your local Claude Code setup
|
|
12
13
|
|
|
@@ -58,7 +59,7 @@ export function checkEnvFile() {
|
|
|
58
59
|
return {
|
|
59
60
|
ok: false,
|
|
60
61
|
label: ".env",
|
|
61
|
-
fix:
|
|
62
|
+
fix: `run \`npx ${pkgRef()} init\`, then paste your dashboard .env block (Settings → Claude Code → Copy .env block)`,
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
const content = fs.readFileSync(envPath, "utf8");
|
|
@@ -80,7 +81,7 @@ export function checkEnvFile() {
|
|
|
80
81
|
ok: false,
|
|
81
82
|
label: ".env",
|
|
82
83
|
detail: "missing EXODUS_API_URL or EXODUS_API_KEY",
|
|
83
|
-
fix:
|
|
84
|
+
fix: `run \`npx ${pkgRef()} init\`, then paste your dashboard .env block (Settings → Claude Code → Copy .env block)`,
|
|
84
85
|
};
|
|
85
86
|
}
|
|
86
87
|
export async function checkWhoami() {
|
|
@@ -124,7 +125,7 @@ export async function checkWhoami() {
|
|
|
124
125
|
todo: true,
|
|
125
126
|
label: "Brand primer",
|
|
126
127
|
detail: `${d.workspaceSlug} has no primer yet — set one up to unlock the pipelines`,
|
|
127
|
-
fix: `say "exodus, set up my brand primer" (or run \`npx
|
|
128
|
+
fix: `say "exodus, set up my brand primer" (or run \`npx ${pkgRef()} foundation\` from a source doc, or paste it at /settings?tab=brands&brand=${d.workspaceSlug})`,
|
|
128
129
|
};
|
|
129
130
|
}
|
|
130
131
|
return {
|
|
@@ -182,7 +183,7 @@ export async function checkActiveBrandMatch() {
|
|
|
182
183
|
ok: false,
|
|
183
184
|
label: "Active brand",
|
|
184
185
|
detail: `local active brand "${resolved.slug}" (${source}) is NOT in your accessible list — the server ignores it and every command silently targets the key's default brand. available: ${available}`,
|
|
185
|
-
fix: `run \`npx
|
|
186
|
+
fix: `run \`npx ${pkgRef()} brand use <slug>\` with one of your brands, or \`npx ${pkgRef()} brand clear\` to fall back to the key default`,
|
|
186
187
|
};
|
|
187
188
|
}
|
|
188
189
|
const FRESHNESS_SUBTREES = ["commands", "lib", "bin"];
|
|
@@ -251,7 +252,9 @@ export function checkExodusDistFreshness(pkgRootOverride) {
|
|
|
251
252
|
}
|
|
252
253
|
return { ok: true, label: "Exodus CLI build", detail: "dist up-to-date" };
|
|
253
254
|
}
|
|
254
|
-
|
|
255
|
+
function npmRegistryUrl(channel) {
|
|
256
|
+
return `https://registry.npmjs.org/@aicopycoders/exodus/${channel}`;
|
|
257
|
+
}
|
|
255
258
|
export async function checkVersionCurrency(pkgRootOverride, fetchImpl = fetch) {
|
|
256
259
|
let pkgRoot;
|
|
257
260
|
if (pkgRootOverride) {
|
|
@@ -265,28 +268,29 @@ export async function checkVersionCurrency(pkgRootOverride, fetchImpl = fetch) {
|
|
|
265
268
|
}
|
|
266
269
|
}
|
|
267
270
|
const local = readPackageVersion(pkgRoot);
|
|
271
|
+
const channel = channelOf(local);
|
|
268
272
|
try {
|
|
269
273
|
const ctrl = new AbortController();
|
|
270
274
|
const t = setTimeout(() => ctrl.abort(), 8_000);
|
|
271
275
|
let res;
|
|
272
276
|
try {
|
|
273
|
-
res = await fetchImpl(
|
|
277
|
+
res = await fetchImpl(npmRegistryUrl(channel), { signal: ctrl.signal });
|
|
274
278
|
}
|
|
275
279
|
finally {
|
|
276
280
|
clearTimeout(t);
|
|
277
281
|
}
|
|
278
282
|
if (!res.ok)
|
|
279
283
|
throw new Error(`HTTP ${res.status}`);
|
|
280
|
-
const
|
|
281
|
-
if (compareVersions(local,
|
|
284
|
+
const published = (await res.json()).version;
|
|
285
|
+
if (compareVersions(local, published) >= 0) {
|
|
282
286
|
return { ok: true, label: "Exodus version", detail: `${local} — up to date` };
|
|
283
287
|
}
|
|
284
288
|
return {
|
|
285
289
|
ok: true,
|
|
286
290
|
warn: true,
|
|
287
291
|
label: "Exodus version",
|
|
288
|
-
detail: `${local} installed, ${
|
|
289
|
-
fix:
|
|
292
|
+
detail: `${local} installed, ${published} available`,
|
|
293
|
+
fix: `run \`npx ${pkgRef(channel)} init\` to update (or \`npm install -g ${pkgRef(channel)}\`)`,
|
|
290
294
|
};
|
|
291
295
|
}
|
|
292
296
|
catch (err) {
|
|
@@ -326,7 +330,7 @@ export async function checkApiAndDrive() {
|
|
|
326
330
|
ok: false,
|
|
327
331
|
label: "Dashboard API key",
|
|
328
332
|
detail: `HTTP ${res.status}`,
|
|
329
|
-
fix:
|
|
333
|
+
fix: `regenerate at Settings → API Keys, then paste the new .env block (or re-run \`npx ${pkgRef()} init\`)`,
|
|
330
334
|
},
|
|
331
335
|
{
|
|
332
336
|
ok: false,
|
|
@@ -445,7 +449,9 @@ export async function checkDashboardAuth() {
|
|
|
445
449
|
ok: false,
|
|
446
450
|
label: "Dashboard auth (dashboard-routed pipelines)",
|
|
447
451
|
detail: res.data?.error ?? "401 from /api/doctor/dashboard-ping",
|
|
448
|
-
fix: hint
|
|
452
|
+
fix: hint
|
|
453
|
+
? stampChannel(hint)
|
|
454
|
+
: `run \`npx ${pkgRef()} init\` to update the CLI — older releases skipped the Bearer header on dashboard routes`,
|
|
449
455
|
};
|
|
450
456
|
}
|
|
451
457
|
return {
|
|
@@ -476,7 +482,7 @@ export function checkLayout() {
|
|
|
476
482
|
return {
|
|
477
483
|
ok: true,
|
|
478
484
|
label: "Layout",
|
|
479
|
-
detail:
|
|
485
|
+
detail: `single-brand (legacy) — run \`npx ${pkgRef()} migrate\` to switch to the multi-brand layout`,
|
|
480
486
|
};
|
|
481
487
|
}
|
|
482
488
|
const brands = listBrandDirs(root);
|
|
@@ -490,7 +496,7 @@ export function checkLayout() {
|
|
|
490
496
|
warn: true,
|
|
491
497
|
label: "Layout",
|
|
492
498
|
detail: `multi-brand — no brand folders yet; ${active}`,
|
|
493
|
-
fix:
|
|
499
|
+
fix: `run \`npx ${pkgRef()} init\` (or \`npx ${pkgRef()} brand use <slug>\`) to create your brand folders`,
|
|
494
500
|
};
|
|
495
501
|
}
|
|
496
502
|
const missingProfiles = brands.filter((b) => !fs.existsSync(path.join(b.dir, "state", "brand-profile.md")));
|
|
@@ -500,7 +506,7 @@ export function checkLayout() {
|
|
|
500
506
|
warn: true,
|
|
501
507
|
label: "Layout",
|
|
502
508
|
detail: `multi-brand, ${brands.length} brand folder(s); ${active}; missing brand profile in: ${missingProfiles.map((b) => b.slug).join(", ")}`,
|
|
503
|
-
fix:
|
|
509
|
+
fix: `run \`npx ${pkgRef()} init\` to refresh every brand folder's profile`,
|
|
504
510
|
};
|
|
505
511
|
}
|
|
506
512
|
return {
|
|
@@ -518,7 +524,7 @@ export function checkBrandProfileGenesisDepth() {
|
|
|
518
524
|
ok: false,
|
|
519
525
|
label: "brand-profile",
|
|
520
526
|
detail: `${rel} is missing`,
|
|
521
|
-
fix:
|
|
527
|
+
fix: `run \`npx ${pkgRef()} brand use <slug>\` to generate it`,
|
|
522
528
|
};
|
|
523
529
|
}
|
|
524
530
|
const contents = fs.readFileSync(filePath, "utf-8");
|
package/dist/commands/init.js
CHANGED
|
@@ -3,12 +3,13 @@ import path from "node:path";
|
|
|
3
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
|
+
import { pkgRef } from "../lib/channel.js";
|
|
6
7
|
export const helpText = `
|
|
7
8
|
exodus init — Set up (or refresh) an Exodus workspace in the current folder
|
|
8
9
|
|
|
9
10
|
Usage:
|
|
10
|
-
npx
|
|
11
|
-
npx
|
|
11
|
+
npx ${pkgRef()} init Scaffold the workspace + install skills
|
|
12
|
+
npx ${pkgRef()} init --root <dir> Target a specific folder
|
|
12
13
|
|
|
13
14
|
Run this in a fresh, empty folder. It creates the workspace layout, writes the
|
|
14
15
|
Exodus + Genesis skills into .claude/skills/, scaffolds a comment-only .env, and
|
package/dist/commands/meme.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { apiPostDashboard, getDashboardUrl } from "../lib/client.js";
|
|
2
2
|
import { formatError } from "../lib/format.js";
|
|
3
|
+
import { pkgRef } from "../lib/channel.js";
|
|
3
4
|
import { readFileSync } from "node:fs";
|
|
4
5
|
export const helpText = `
|
|
5
6
|
exodus meme — Meme generator: brief → recommended formats → one batched run
|
|
@@ -26,7 +27,7 @@ Flow:
|
|
|
26
27
|
|
|
27
28
|
Keys (strict BYOK — checked server-side before anything starts):
|
|
28
29
|
classic (layer 1) memes need your Imgflip login; AI (layer 2/3) memes need
|
|
29
|
-
your Kie.ai key; captions always need your LLM key. Run \`npx
|
|
30
|
+
your Kie.ai key; captions always need your LLM key. Run \`npx ${pkgRef()} doctor\`.
|
|
30
31
|
|
|
31
32
|
Examples:
|
|
32
33
|
exodus meme recommend --brief "grounding sheets reduce inflammation"
|
|
@@ -114,7 +115,7 @@ async function runRecommend(flags) {
|
|
|
114
115
|
if (!res.ok) {
|
|
115
116
|
console.error(formatError(res));
|
|
116
117
|
if (res.status === 400) {
|
|
117
|
-
console.error(
|
|
118
|
+
console.error(`Hint: run \`npx ${pkgRef()} doctor\` to check your keys.`);
|
|
118
119
|
}
|
|
119
120
|
process.exit(1);
|
|
120
121
|
return;
|
|
@@ -171,7 +172,7 @@ async function runRun(flags) {
|
|
|
171
172
|
if (!res.ok) {
|
|
172
173
|
console.error(formatError(res));
|
|
173
174
|
if (res.status === 400) {
|
|
174
|
-
console.error(
|
|
175
|
+
console.error(`Hint: run \`npx ${pkgRef()} doctor\` to check your keys.`);
|
|
175
176
|
}
|
|
176
177
|
process.exit(1);
|
|
177
178
|
return;
|
|
@@ -233,7 +234,7 @@ async function runRegenerate(flags) {
|
|
|
233
234
|
if (!res.ok) {
|
|
234
235
|
console.error(formatError(res));
|
|
235
236
|
if (res.status === 400) {
|
|
236
|
-
console.error(
|
|
237
|
+
console.error(`Hint: run \`npx ${pkgRef()} doctor\` to check your keys.`);
|
|
237
238
|
}
|
|
238
239
|
process.exit(1);
|
|
239
240
|
return;
|
package/dist/commands/migrate.js
CHANGED
|
@@ -4,6 +4,7 @@ import { execSync } from "node:child_process";
|
|
|
4
4
|
import { apiGet, apiGetDashboard } from "../lib/client.js";
|
|
5
5
|
import { getActiveBrand, setLayoutVersion, findWorkspaceRoot } from "../lib/state.js";
|
|
6
6
|
import { detectLayout, brandDirFor, ensureBrandDir, BRAND_MARKER_FILE, } from "../lib/layout.js";
|
|
7
|
+
import { pkgRef } from "../lib/channel.js";
|
|
7
8
|
export const helpText = `
|
|
8
9
|
exodus migrate — convert a single-brand install to the multi-brand layout
|
|
9
10
|
|
|
@@ -15,7 +16,7 @@ What it does (one-time, opt-in — updates never force this):
|
|
|
15
16
|
falling back to the key's bound brand).
|
|
16
17
|
2. Creates a subfolder for it and MOVES your brand-specific files in:
|
|
17
18
|
state/ and output/ → <brand>/state/ and <brand>/output/
|
|
18
|
-
3. Marks the install as multi-brand. From then on, \`npx
|
|
19
|
+
3. Marks the install as multi-brand. From then on, \`npx ${pkgRef()} init\`
|
|
19
20
|
creates a subfolder for every brand you own, and running a command
|
|
20
21
|
from inside a brand's folder targets that brand automatically.
|
|
21
22
|
|
|
@@ -94,7 +95,7 @@ async function resolveBrand() {
|
|
|
94
95
|
}
|
|
95
96
|
if (!slug) {
|
|
96
97
|
fail("could not determine which brand this install belongs to.\n" +
|
|
97
|
-
|
|
98
|
+
` Run \`npx ${pkgRef()} brand use <slug>\` first (or check your network/.env), then re-run \`npx ${pkgRef()} migrate\`.`);
|
|
98
99
|
}
|
|
99
100
|
if (!name) {
|
|
100
101
|
try {
|
|
@@ -115,7 +116,7 @@ export async function run(flags) {
|
|
|
115
116
|
const root = findWorkspaceRoot();
|
|
116
117
|
if (detectLayout(root) === "v2") {
|
|
117
118
|
console.log("✓ Already on the multi-brand layout — nothing to migrate.");
|
|
118
|
-
console.log(
|
|
119
|
+
console.log(` Run \`npx ${pkgRef()} init\` to sync brand folders.`);
|
|
119
120
|
return;
|
|
120
121
|
}
|
|
121
122
|
const brand = await resolveBrand();
|
|
@@ -128,7 +129,7 @@ export async function run(flags) {
|
|
|
128
129
|
if (conflicts.length > 0) {
|
|
129
130
|
fail(`"${brandDirRel}/" already contains different versions of:\n` +
|
|
130
131
|
conflicts.map((f) => ` ${f}`).join("\n") +
|
|
131
|
-
|
|
132
|
+
`\n Reconcile or remove them, then re-run \`npx ${pkgRef()} migrate\`. Nothing was changed.`);
|
|
132
133
|
}
|
|
133
134
|
console.log(`Migrating this install to the multi-brand layout...`);
|
|
134
135
|
console.log(` brand: ${brand.slug} (${brand.name})`);
|
|
@@ -151,8 +152,8 @@ export async function run(flags) {
|
|
|
151
152
|
Shared files (.env, exodus/, .claude/skills/, references/) did not move.
|
|
152
153
|
|
|
153
154
|
Next steps:
|
|
154
|
-
• Run \`npx
|
|
155
|
+
• Run \`npx ${pkgRef()} init\` to pull folders for your other brands.
|
|
155
156
|
• Run commands from inside ${brandDirRel}/ to target it automatically,
|
|
156
|
-
or keep using \`npx
|
|
157
|
+
or keep using \`npx ${pkgRef()} brand use <slug>\` from anywhere.
|
|
157
158
|
`.trimEnd());
|
|
158
159
|
}
|