Skip to main content
The mirage CLI is a thin httpx wrapper over the Mirage daemon. It auto-spawns the daemon on first workspace create, and the daemon auto-exits 30 seconds after the last workspace is deleted. Most users never type a daemon command directly. Output is structured JSON to stdout for every verb — pipe to jq, save to file, or read it directly.

Install

Verify:

Define a workspace in YAML

A workspace is a set of prefixed mounts plus some workspace-level settings. Save this as workspace.yaml:
${VAR} placeholders are interpolated from your shell environment at mirage workspace create time. Missing vars fail fast with the full list, not lazily on first use.

Walkthrough

A guided tour from creating a workspace to snapshotting it. Each step builds on the previous one; you can copy them in order. For a runnable end-to-end version against a real multi-mount workspace (/s3, /gdrive, /gmail, /slack, /discord), see examples/python/cross/README.md.

1. Source env and create a workspace

The YAML’s ${...} placeholders resolve from your shell at create time, so source your env first. The daemon auto-spawns on the first create.

2. Inspect

list is one line per workspace; get returns the full mount and session detail.

3. Run commands against your mounts

execute runs a shell command inside the workspace. Paths resolve through the mount registry (/s3/... hits S3, / hits the RAM backing, etc.).

4. Pipe stdin

When stdout isn’t a TTY, the CLI forwards stdin to the command automatically.

5. Dry-run with provision

provision returns a ProvisionResult (network bytes, cache hits, estimated cost) without running the command — handy for predicting spend before kicking off an expensive read.
Every factory-built backend estimates commands out of the box, by family: whole-file readers (cat, sort, md5, …) charge the byte total from stat, head/tail/file charge a bounded range, grep/rg charge a worst-case full read, and metadata commands (ls, find, stat, du, …) charge op counts only. Transforms (gzip, tar, split, …) keep the read total as a floor with precision=unknown output; cp brackets both read and write between 0 (server-side copy) and the source total; metadata writes (rm, mkdir, touch, …) are zero-byte op counts, with recursive rm degrading to a floor; pure commands (seq, date, bc, expr) and shell builtins (echo, cd, …) are zero-cost. Anything the planner cannot estimate honestly — mv (free rename or full cross-mount copy), tee (stdin size), arbitrary programs — reports precision=unknown with all totals as floors, never an error. Virtual files whose size cannot be resolved (for example a rendered chat.jsonl) degrade the estimate to precision=unknown while keeping the known byte total as a floor. Provision is optional when you register your own commands: leave it out and the planner reports precision=unknown. To opt in with one line, reuse the estimator helpers (make_file_read_provision, make_search_provision, metadata_provision, … in Python; makeFileReadProvision and friends in TypeScript), or pass provision_overrides={"grep": my_estimator} to the command factory. An explicit None/null override disables a default. Pipelines combine field-wise: |, ; and && sum the estimates, || brackets the branches (cheapest low, priciest high), and for loops multiply by the iteration count. A stage downstream of an unknown stage is also unknown, and totals under precision=unknown are floors.

6. Cache: network → hit after a real read

After a real cat, provision flips that path from a network read to a cache hit (cache_hits=1).

7. Command history

Every executed command is recorded by a hidden recorder (the Observer). The history builtin shows the calling session’s commands (GNU bash semantics, history -c clears only that session’s view), and /.bash_history renders the GNU histfile across all sessions, readable with the ordinary file commands.

8. Background jobs

Long-running commands take --background and return a job_id immediately. mirage job wait blocks until it’s done.

9. Snapshot to disk

10. Restore from snapshot

Snapshots redact cloud creds at save time, so loading needs fresh creds via a config file. The same workspace YAML used for create works.
A config file is required for any mount whose snapshot config contains redacted secrets (S3 with inline keys, GDrive, Slack, Discord, Redis, …). The snapshot stores "<REDACTED>" in place of those secrets at save time. Loading without the config 400s with the list of prefixes that need fresh creds. Local resources (RAM, Disk) restore as-is with no extra config needed.

11. Clean up

The daemon exits ~30s after the last workspace is deleted.

Versioning

Every workspace has its own git-backed history kept by the daemon (under ~/.mirage/repos/<workspace-id> by default; set MIRAGE_VERSION_ROOT to relocate). You commit the live state as a version, then log, diff, branch, and restore in place. The verbs follow git.

Commit and log

Diff

diff reports changed paths (added / modified / deleted), git-style. No refs compares live state to the branch HEAD; one ref compares live to that ref; two refs compare two versions.

Branch

branch forks at another branch’s current version; commit onto it with -b.

Checkout

checkout restores the live state to a past version or branch, in place. It overwrites uncommitted live state.

Clone from a version

clone --at makes a new workspace from one of the source’s past versions (omit --at to clone the live state).

Verbs at a glance

Per-mount safeguards

Per-mount command_safeguards are Python CLI only today. The TypeScript CLI config schema does not carry them yet.
Cap what a command may stream back per mount with command_safeguards, so a runaway cat/grep/rg can’t flood the agent or hang. Each entry sets max_lines / max_bytes (output cap) and/or timeout_seconds (deadline), with on_exceed: truncate (stop, exit 0, add a stderr notice) or on_exceed: error (stop, exit 1):
Caps fire on the terminal command of a pipeline only, so cat big.txt | head -n 30 still shows 30 lines. Truncation exits 0, error exits 1, and a timeout exits 124 — each with a stderr notice. Without a command_safeguards block, cat/grep/rg/head/tail still cap at 2000 lines by default. See Output Safeguards for the SDK form and the same fields.

Daemon control

Most users never run these directly — the daemon auto-spawns on first workspace create and auto-exits after the idle timer fires (default 30s after the last workspace is deleted). When you need to intervene — typically during development, when you want code changes to take effect:
When you change Mirage’s source code (commands, providers, etc.), the running daemon won’t see your changes — it loaded the old code at startup. Run mirage daemon restart to pick up new code. Same situation as dockerd after recompiling Docker.

Where the daemon lives

Most users never need to think about the daemon. If you do:
  • It listens on http://127.0.0.1:8765 by default.
  • Override via env vars or ~/.mirage/config.toml, edited with mirage config (the git config of Mirage):
  • Per key the precedence is: env var > config.toml > default. Because env vars silently beat the file, mirage config list (which shows only the file) can look right while the daemon uses something else. --resolved shows the value each key will actually get and which source won:
    Use it whenever a setting seems ignored: the origin column names the exact env var overriding you. Secrets are masked, and piped output is JSON ({"version_root": {"value": ..., "origin": ...}}) so it works with jq.
  • Allowed keys: url, socket, auth_token, auth_mode, allowed_hosts, idle_grace_seconds, port, pid_file, version_root, snapshot_root, and the jwt_* family (jwt_alg, jwt_issuer, jwt_audience, jwt_pubkey_file, jwt_clock_skew, jwt_authorized_parties). MIRAGE_HOME and raw secrets (MIRAGE_AUTH_TOKEN, MIRAGE_JWT_PUBKEY) stay env-only.
  • Like dockerd with a bad daemon.json, the daemon refuses to start on unknown keys or malformed TOML, naming the offender. mirage config unset <key> accepts unknown keys so you can repair the file; mirage config list warns about them.
  • Path settings (pid_file, version_root, snapshot_root, port) take effect on the next daemon start; there is no hot reload.
  • mirage config set chmods the file to 0600 since it may hold auth_token.
  • Logs go to ~/.mirage/daemon.log when the CLI auto-spawns it.
  • It exits 30 seconds after the workspace count hits zero (configurable).