CLI
mimeo — pull your instance's definitions into a git repo, edit
them, and push them back through a diff, a validation pass and a rehearsal.
The CLI is small on purpose: auth, pull,
diff, push. It carries no knowledge of the file
formats at all — it reads your repo into { path => contents },
posts that to your instance, and prints what comes back. Serialization,
diffing, validation and dry-running all happen on the instance, so the repo
format has exactly one implementation and it's always the one your instance is
actually running.
Getting it
It ships inside the
manager template repo
as bin/mimeo — a single file with no dependencies beyond the Ruby
that already ships with macOS and every Linux. There is nothing to install.
git clone <your copy of the template> my-email-operation
cd my-email-operation
./bin/mimeo auth
auth asks for your instance URL and an API token (create one under
Settings → API), then checks the connection. The URL lands in
.mimeo/config.yml, which is committed so everyone on the repo
points at the same place. The token lands in .mimeo/token, which is
gitignored — and the CLI adds that line to .gitignore itself if
it's missing, because a token in a commit is a token you have to rotate.
The loop
./bin/mimeo pull # bring the repo up to date with the instance
# ... edit files, or ask your agent to ...
./bin/mimeo diff # what a push would change — nothing is applied
./bin/mimeo push # diff → validate → rehearse → apply
Commands
| Command | What it does |
|---|---|
mimeo auth |
Point this repo at an instance. Takes --host and --token, or asks. |
mimeo pull |
Write the instance's definitions here, overwriting. Reports created, updated and removed files. |
mimeo diff |
What a push would change, with validation and a rehearsal of every changed flow. Changes nothing. |
mimeo status | The same thing, by its other name. |
mimeo push |
Show the diff, validate, dry-run, then ask before applying. |
Push options
| Flag | Effect |
|---|---|
--prune |
Also remove what's on the instance but not in the repo. Off by default:
a stray rm or a bad merge shouldn't delete a flow. Absences
are always reported either way. Emails are archived rather than
destroyed, and a flow people have run through refuses regardless — its
history is theirs.
|
--force |
Apply despite the identity warnings (node_id_freed,
node_id_repurposed). Never gets past an actual error, and
never past a file that couldn't be read.
|
--yes, -y | Don't ask before applying. |
What push actually does
Four steps, in this order, and it stops at the first that fails:
-
Diff. What changes on the instance, per file. Steps are named
by their handle (
10-send-pitch) — the same thing the diagram, the logs and the traces call them. A step pointed at a different email is described in the chooser's own terms ("now usesemails/0043-x.md, shared with 3 other places") rather than as an anonymous id change, because it's the same consequential decision. - Validate. Every definition, not just the changed ones. Errors block. Warnings are shown. Definitions that were already broken on the instance are reported separately and don't block — you can't fix them by not pushing.
- Rehearse. Every changed flow is dry-run against a real person, with event details that satisfy that flow's own trigger. Nothing is sent and nothing is written.
- Apply. All of it or none of it, in one transaction.
Applied changes appear live on any admin page you have open — no refresh.
The two things it refuses over
A step's id is what the runs of people standing on it point at. Give
a new step an id an old step had, and everyone parked there silently lands
somewhere they were never sent. Nothing in a flow file shows this — an id is
just a positive integer — so the instance compares against the definition being
replaced:
| Code | What happened |
|---|---|
node_id_freed |
A new step carries an id this flow handed out and gave up in an earlier save. |
node_id_repurposed |
The delete and the replacement are in the same file, so the id never looks free — what gives it away is that the step under it is a different kind of step than it was. |
Both are warnings in the validator and both block a push. The message says how
many people are parked on that id right now. The fix is almost always to remove
the id from the new step and let the instance mint one; --force
exists for the operator who knows those runs are gone.
Environment
| Variable | Effect |
|---|---|
MIMEO_HOST | The instance URL, overriding .mimeo/config.yml. |
MIMEO_TOKEN | An API token, overriding .mimeo/token. Read first, so CI never needs a file on disk. |
NO_COLOR | Plain output. |
What it touches
Only flows/, segments/, guards/,
sequences/, emails/, templates/,
fields.yml, products.yml and settings.yml
— the definitions repo layout.
Your README, your agent skills, your notes and any dotfile inside those
directories are yours and are never read or removed.
Mimeo never touches git. Committing, branching and merging are yours; the CLI only ever talks to your instance over HTTP.
See also: the file layout · the API underneath · MCP server