# CLI

`mimeo` — pull your Mimeo's definitions into a git repo, edit them, and push them back through a diff, a validation pass and a rehearsal.

Two facts up front. **There is nothing to install** — the CLI is a single file at `bin/mimeo` in your [manager repo](/content/docs/manager-repo/index.html), running on the Ruby that ships with macOS and every mainstream Linux. And **Mimeo never touches** **git** — `pull` and `push` here move definitions between your working tree and your Mimeo over HTTPS; your Mimeo never reads your git repo, and no remote is required.

The CLI is small on purpose: `init`, `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 Mimeo, and prints what comes back. Serialization, diffing, validation and dry-running all happen on your Mimeo, so the repo format has exactly one implementation and it's always the one your Mimeo is actually running.

## Getting it

It ships inside the [manager repo template](https://github.com/buildermethods/mimeo-manager) as `bin/mimeo`. The easiest start is **Settings → Agents & API → Manager repo** on your Mimeo, which mints a token and hands you these commands with real values filled in:

```
git clone https://github.com/buildermethods/mimeo-manager.git my-mimeo
cd my-mimeo
git remote remove origin
./bin/mimeo init --host https://your-mimeo.com --token mm_your_token
```

The `remote remove` disconnects your clone from the template, so nothing ever pushes back to it. `init` is `auth` plus a first `pull`: it checks the connection, writes every definition into files, points the committed `.mcp.json` at your Mimeo's MCP server, and prints the first-snapshot commit to run. 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.

Every command below is written `./bin/mimeo` because that's what it is — the file in your repo. Nothing lands on your PATH.

## The loop

```
./bin/mimeo pull      # sync down first, always
                      # ... 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
./bin/mimeo pull      # after a structural push: pick up ids Mimeo minted
```

Pull before editing — someone may have changed something in the admin UI since your last sync, and pushing a stale file would quietly revert it. And pull again after a push that added steps or emails: Mimeo mints their ids, and the second pull is how your files catch up.

## Commands

| Command          | What it does |
|------------------|--------------|
| `mimeo init`     | First-time setup: `auth`, then a first `pull`, then your next steps printed. Takes `--host` and `--token`, or asks. |
| `mimeo auth`     | Point this repo at your Mimeo (and re-point `.mcp.json`). Takes `--host` and `--token`, or asks. |
| `mimeo pull`     | Write your Mimeo's definitions and media 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 your Mimeo but not in the repo. Off by default:<br> a stray `rm` or a bad merge shouldn't delete a flow. Absences<br> are always _reported_ either way. Emails are archived rather than<br> destroyed, and a flow people have run through refuses regardless — its<br> history is theirs. |
| `--force` | Apply despite the identity warnings (`node_id_freed`,<br> `node_id_repurposed`). Never gets past an actual error, and<br> 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:

1. **Diff.** What changes on your Mimeo, 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 uses `emails/0043-x.md`, shared with 3 other places") rather than as an anonymous id change, because it's the same consequential decision.

2. **Validate.** Every definition, not just the changed ones. Errors block. Warnings are shown. Definitions that were _already_ broken on your Mimeo are reported separately and don't block — you can't fix them by not pushing.

3. **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.

4. **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 Mimeo 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<br> free — what gives it away is that the step under it is a different _kind_<br> 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 Mimeo mint one; `--force` exists for the operator who knows those runs are gone.

## Environment

| Variable         | Effect |
|------------------|--------|
| `MIMEO_HOST`     | Your Mimeo's URL, overriding `.mimeo/config.yml`. |
| `MIMEO_TOKEN`    | An API token, overriding `.mimeo/token`. Read first, so a secret manager never needs a file on disk. |
| `NO_COLOR`       | Plain output. |

## Media syncs too

The media library rides along with every `pull` and `push`: the files themselves land under `media/`, and `media.yml` records what each one is on your Mimeo — its hosted URL, alt text and id. Emails reference a media item by that hosted `url`, never by its local path.

- **Add a file** by dropping it into `media/` — `push` uploads it and records the minted URL in `media.yml`.
- **Edit alt text** in `media.yml` and push it like any other change.
- **Deletes never travel.** A deleted upload 404s inside every email already sent, so a push never deletes remote media (`--prune` included) — a file missing locally is something `pull` restores. Deleting for real is an explicit act in the admin UI or over the [media API](/content/docs/api/media.html).

## What it touches

Only `flows/`, `segments/`, `guards/`, `sequences/`, `emails/`, `templates/`, `media/`, `fields.yml`, `products.yml`, `themes.yml`, `settings.yml` and `media.yml` — the [definition files reference](/content/docs/reference/definitions-repo.html). Your README, your agent skills, your notes and any dotfile inside those directories are yours and are never read or removed — as is any file in `media/` your Mimeo doesn't have yet, which is an upload waiting for a push.

Mimeo never touches git. Committing, branching and merging are yours; the CLI only ever talks to your Mimeo over HTTP.

See also: [your manager repo](/content/docs/manager-repo/index.html) · [the file layout](/content/docs/reference/definitions-repo.html) · [the API underneath](/content/docs/api/definitions.html) · [MCP server](/content/docs/mcp/index.html) · [files or MCP?](/content/docs/how-it-fits/index.html)
