- name
- drive-mimo
- description
- Use when developing or testing the MiMoCode repository and you need to programmatically drive another MiMoCode (mimo) process. Supports headless `mimo run` with JSON events and interactive TUI via tmux for behavior, integration, and visual regression testing. Covers an installed `mimo` binary or a dev build launched from source with `bun dev`; do not use it for ordinary MiMoCode task execution.
# Drive MiMo
## Overview
Two **interfaces** for driving a separate mimo process:
| Interface | Command | Use when |
|------|---------|----------|
| **Headless** | `mimo run --format json` | Scripted tasks, CI, event validation |
| **TUI** | `mimo` in tmux | Interactive flow, permission dialogs, keybindings, visual regression |
**Core principle:** Every run produces parseable evidence. No eyeballing.
Always drive an *isolated* instance: give each run a fresh `MIMOCODE_HOME` and a throwaway workspace so it never touches your own config, memory, or session DB.
### Two ways to launch — orthogonal to interface
The interface (headless vs TUI, above) is *what you drive*. How the process is
**launched** is a separate axis — either can be launched either way:
| Launcher | Command | Use when |
|----------|---------|----------|
| **Installed binary** | `mimo …` | Testing a released/installed build |
| **Dev (from source)** | `bun dev …` | Debugging mimocode itself — runs `src/index.ts` directly, no build step, picks up local code changes |
Everything below uses `mimo` for brevity. To drive a **dev build** instead,
substitute `bun dev` for `mimo` and run from the repo root — every flag,
JSON event, and tmux technique is identical. See the Dev Mode section.
## Prerequisites
```bash
# mimo binary must be on PATH (installed-binary launcher)
which mimo || echo "mimo not found on PATH"
# OR: dev launcher — run from the mimocode repo root, needs bun
which bun || echo "bun not found — needed for dev mode"
# tmux (for TUI interface)
which tmux || echo "tmux not found — install it for TUI mode"
```
> **Running the `wait-for-text.sh` helper:** invoke it as
> `./scripts/wait-for-text.sh …` from this skill's directory. The repository copy
> is executable and can be called directly.
---
## Dev Mode (debugging mimocode itself)
When the goal is to debug **mimocode's own code**, launch from source with
`bun dev` instead of the installed `mimo` binary. It runs
`packages/cli/src/index.ts` directly — no build step — so local edits take
effect on the next launch.
**Key facts:**
- Run from the **repo root**. `bun dev` == `bun run dev`.
- It is the dev equivalent of the `mimo` command: same CLI, same subcommands
and flags. `bun dev --help`, `bun dev run …`, `bun dev serve`, etc.
- Args pass straight through, so **both interfaces work under dev**:
- Headless: `bun dev run --format json --dangerously-skip-permissions …`
- TUI: `bun dev <workspace>` (positional workspace arg, as with `mimo`)
- If `MIMOCODE_HOME` is not set, dev defaults it to a repo-local `.dev-home`
dir. For an isolated driven run, set `MIMOCODE_HOME=$(mktemp -d)` explicitly
just like with the binary.
**Substitution rule:** anywhere Part 1 / Part 2 / Part 3 below say `mimo`,
replace it with `bun dev` (invoked from the repo root) to drive a dev build.
```bash
# Headless, dev build (from repo root)
REPO=/path/to/mimocode/checkout # your local mimocode repo root
MIMOCODE_HOME=$(mktemp -d) bun --cwd "$REPO" dev run \
--format json --dangerously-skip-permissions --dir "$WORKSPACE" \
< "$PROMPT" > /tmp/mimo-dev.jsonl 2>&1
# TUI, dev build in tmux (from repo root)
tmux new-session -d -s "$SESSION" -x 120 -y 30 \
"cd $REPO && MIMOCODE_HOME=$(mktemp -d) MIMOCODE_PURE=true bun dev $WORKSPACE; sleep 999"
```
---
## Part 1: Headless Mode (`mimo run`)
### Launch
```bash
PROMPT=$(mktemp -t mimo-drive.XXXXXX)
cat >"$PROMPT" <<'EOF'
Your task here.
EOF
MIMOCODE_HOME=$(mktemp -d) mimo run \
--format json \
--dangerously-skip-permissions \
--dir "$WORKSPACE" \
< "$PROMPT" > /tmp/mimo-out.jsonl 2>&1
EXIT=$?
```
### Key flags
| Flag | Purpose |
|------|---------|
| `--format json` | Structured JSONL events to stdout |
| `--dangerously-skip-permissions` | Auto-approve all permissions |
| `--model provider/model` | Override model |
| `--agent compose` | Use compose agent |
| `--session SID` | Continue existing session |
| `--continue` | Continue last session |
| `--file path` | Attach file to message |
| `--dir path` | Working directory |
### JSON event types
`--format json` writes one JSON object per line. **Every** event has the shape
`{"type": ..., "timestamp": <ms>, "sessionID": "ses_...", ...payload}` — the
`sessionID` is a field on each event, not a standalone event. The payload for
most events is nested under `part`.
Emitted event types (from the run event stream):
```
{"type":"step_start","timestamp":...,"sessionID":"ses_abc","part":{...}}
{"type":"text","timestamp":...,"sessionID":"ses_abc","part":{"type":"text","text":"I'll create the file...","time":{...}}}
{"type":"reasoning","timestamp":...,"sessionID":"ses_abc","part":{"type":"reasoning","text":"The user wants..."}}
{"type":"tool_use","timestamp":...,"sessionID":"ses_abc","part":{"type":"tool","tool":"write","state":{"status":"completed",...}}}
{"type":"step_finish","timestamp":...,"sessionID":"ses_abc","part":{...}}
{"type":"error","timestamp":...,"sessionID":"ses_abc","error":{...}}
```
Notes that matter for parsing:
- **No `session.id`, no `tool_result`, no `session.status` event.** A `tool_use`
is emitted **once** per tool part when it reaches `completed` or `error` — the
result/output is inside `part.state`, there is no separate result event.
- **`tool_use` identifies the tool via `part.tool`** (a bare string, e.g.
`"tool":"write"`) — there is no `.tool.name` and no top-level `.name`.
- **`text` / `reasoning` text lives at `part.text`**, not top-level `.text`.
- **`reasoning` is only emitted when `--thinking` is passed.** Without it, no
reasoning events appear.
- **Completion is not a stream event.** The process finishes when the run
completes; the reliable completion signal is **process exit** (exit code 0),
not any line in the JSONL.
### Validation patterns
```bash
# Completion — the real signal is the exit code, not a stream event
[ $EXIT -eq 0 ] || echo "FAIL: exit $EXIT"
# No errors
grep -q '"type":"error"' /tmp/mimo-out.jsonl && echo "FAIL: errors found"
# A specific tool was used (match part.tool, the bare string)
grep -q '"tool":"write"' /tmp/mimo-out.jsonl || echo "FAIL: write tool not called"
# Text output contains expected string (text is at .part.text)
grep '"type":"text"' /tmp/mimo-out.jsonl | jq -r '.part.text' | grep -q "expected"
# Robust tool check via jq (works regardless of key ordering)
jq -e 'select(.type=="tool_use") | .part.tool=="write"' /tmp/mimo-out.jsonl >/dev/null \
|| echo "FAIL: write tool not called"
```
### Timeout
```bash
timeout 120 mimo run --format json --dangerously-skip-permissions < "$PROMPT"
[ $? -eq 124 ] && echo "FAIL: timed out"
```
---
## Part 2: TUI Mode (tmux)
### Isolation variables
| Variable | Purpose |
|---|---|
| `MIMOCODE_HOME` | **Required.** Fresh `mktemp -d` per run. Sandboxes DB, config, cache. |
| `MIMOCODE_PURE=true` | Disable external plugins. |
| `MIMOCODE_DISABLE_GIT=true` | Skip git ops if workspace isn't a real repo. |
### Launch TUI in tmux
```bash
# Create isolated environment
MHOME=$(mktemp -d)
WORKSPACE=$(mktemp -d)
SESSION="mimo-drive-$$"
# Launch mimo TUI in tmux (workspace is a positional arg, NOT --dir)
tmux new-session -d -s "$SESSION" -x 120 -y 30 \
"MIMOCODE_HOME=$MHOME MIMOCODE_PURE=true mimo $WORKSPACE; sleep 999"
# Wait for the TUI to render its input prompt
bash scripts/wait-for-text.sh -t "$SESSION:0.0" -p "$PROMPT_RE" -T 15
```
Where `PROMPT_RE` is a language-neutral pattern for the input prompt. The
prompt placeholder is localized, so match on the stable markers rather than a
localized string:
```bash
# Matches the prompt line regardless of UI language:
# the ">" input caret, the "Ask" English placeholder, or a "/" command hint
PROMPT_RE='>|Ask|/[a-z]'
```
If a run's UI language is known and fixed, you may match its literal
placeholder instead — but prefer the neutral pattern for portability.
### Send input
```bash
# Type a message (literal text, then Enter)
tmux send-keys -t "$SESSION:0.0" -l -- "Create a file called hello.txt with content 'world'"
tmux send-keys -t "$SESSION:0.0" Enter
# Special keys
tmux send-keys -t "$SESSION:0.0" C-c # Cancel
tmux send-keys -t "$SESSION:0.0" C-d # EOF
tmux send-keys -t "$SESSION:0.0" Escape # Escape
tmux send-keys -t "$SESSION:0.0" Tab # Tab completion
tmux send-keys -t "$SESSION:0.0" Up # History up
tmux send-keys -t "$SESSION:0.0" Down # History down
tmux send-keys -t "$SESSION:0.0" Enter # Submit
```
### Capture output
```bash
# Current screen
tmux capture-pane -t "$SESSION:0.0" -p
# Full scrollback
tmux capture-pane -t "$SESSION:0.0" -p -S -
# Last 50 lines
tmux capture-pane -t "$SESSION:0.0" -p -S -50
```
### Wait for state changes
```bash
# Wait for agent to start processing
bash scripts/wait-for-text.sh -t "$SESSION:0.0" -p "thinking\|reading\|writing" -T 30
# Wait for tool permission prompt
bash scripts/wait-for-text.sh -t "$SESSION:0.0" -p "Allow\|Deny\|permission\|approve" -T 30
# Wait for completion
bash scripts/wait-for-text.sh -t "$SESSION:0.0" -p "completed\|done\|finished\|idle" -T 120
# Wait for error
bash scripts/wait-for-text.sh -t "$SESSION:0.0" -p "error\|failed\|Error" -T 30
# Custom regex
bash scripts/wait-for-text.sh -t "$SESSION:0.0" -p "hello\.txt.*world" -T 30
```
### Handle permission dialogs
```bash
# Wait for permission prompt, then approve
bash scripts/wait-for-text.sh -t "$SESSION:0.0" -p "Allow\|approve" -T 30
tmux send-keys -t "$SESSION:0.0" -l -- "y"
tmux send-keys -t "$SESSION:0.0" Enter
# Or deny
tmux send-keys -t "$SESSION:0.0" -l -- "n"
tmux send-keys -t "$SESSION:0.0" Enter
```
### Multi-turn interaction
```bash
# Turn 1: send initial message
tmux send-keys -t "$SESSION:0.0" -l -- "Create a TypeScript file that adds two numbers"
tmux send-keys -t "$SESSION:0.0" Enter
bash scripts/wait-for-text.sh -t "$SESSION:0.0" -p "completed\|done" -T 120
# Turn 2: follow-up
tmux send-keys -t "$SESSION:0.0" -l -- "Now add a test for it"
tmux send-keys -t "$SESSION:0.0" Enter
GitHubで見る