Skip to main content
mirage is a client of the server’s HTTP routes, with the same commands in Python and TypeScript. When its url is on this machine, workspace create, workspace load, mcp, rpc, ssh-proxy and codex-exec start the server if it is not running; other commands need it running. A remote server is never started.

Install

Quickstart

Create a RAM-backed workspace that needs no credentials:
This prints 1:hello mirage. Keep demo for the MCP or RPC examples, then remove it with mirage workspace delete demo. See Workspace configuration for YAML settings and defaults.

Write a config

A config is a YAML file listing what the workspace mounts. Each key under mounts is a path in the workspace: vfs names the backend, and config holds the fields its VFS guide lists.
workspace.yaml
  • mode: read makes every mount read-only unless it sets its own mode, as /scratch does.
  • ${NAME} is read from the environment when the config loads, so keys stay out of the file. A missing variable is an error.
  • command_limits bounds the commands that run on that mount; its "@all" entry’s walk_limit is the most files one command walks there.
clis, runtimes, profiles and the other top-level keys are in Workspace configuration.

Exit codes

Run lines, VFS calls and tools

The workspace is -w, else MIRAGE_WORKSPACE, else the current one that mirage workspace use ID set for the context. With none, the command exits 2. Each takes -s SESSION, the default session when absent. shell and vfs take --explain, which says what the line or call would do and runs nothing; see Explain. mirage shell is a short form of mirage workspace shell, with the same options.

mirage workspace shell

  • Output streams as raw bytes as it arrives, and piped stdin streams in, so a line can answer before its input ends.
  • The CLI exits with the line’s code. Ctrl-C cancels the line.
  • --json decodes stdout and stderr as UTF-8, replacing invalid bytes.

mirage-bash

mirage-bash -c LINE runs a line as mirage workspace shell, for a coding agent that takes a shell path, such as Claude Code. Both packages install it beside mirage.
  • The line runs in the process’s working directory, which must be a path in the workspace.
  • It takes only -c LINE; other bash options are read and dropped.
  • It exits with the line’s code, and with 2 when the arguments or MIRAGE_WORKSPACE are missing.

mirage workspace vfs

  • Calls are spelled with dashes: mirage workspace vfs is-dir /data -w demo.
  • Required arguments come in order, the rest as --name VALUE.
  • Bytes come from --data or --value, else from stdin.
  • Piped, it prints the call’s JSON. In a terminal, read prints the text and a list prints one entry per line. A call that returns nothing prints nothing.

Relays

With -w, mcp and rpc serve a workspace the server already holds. Without it, they create one from CONFIG, then MIRAGE_MCP_CONFIG (MIRAGE_RPC_CONFIG) or MIRAGE_CONFIG, then a workspace.yaml found walking up from the working directory. They delete it when the client disconnects, unless the config names a workspace_id.

Workspaces

From a template

A template is a saved workspace config. -f takes a workspace config, or a file with name, an optional description, and config, as mirage template get ID -o yaml prints one:
The file is saved as written: ${NAME} is not read from the environment. The server checks the config when it saves the template, as a create would. A workspace gets a copy of the template’s config, so a later change to the template leaves it as it is, and a template’s config cannot set workspace_id: -w names the workspace. Give a config file or --template, not both. The local daemon keeps templates under ~/.mirage/resources, as resource_store sets. get, delete and restore take the template’s ID or its name: a name is unique, so it works as an ID. On the local daemon a template’s ID is its name.

Saved policies

mirage policy keeps Python programs by name on the daemon, or on the hosted API, through the /v1/policies routes. A name ends in .py. --source @FILE reads a file; any other value is the program itself. get, update and delete take the policy’s ID or its name, which works as an ID; on the local daemon the two are the same.

Asks

Saved connections

mirage vfs, mirage cli and mirage runtime keep connections on the server: a type, its settings and its credentials, saved once under a name. They are the /v1/vfs, /v1/clis and /v1/runtimes routes, and work the same against the local daemon and the hosted API. ID is an entry’s ID or its name: a name is unique, so it works as an ID. On the local daemon an entry’s ID is its name. mirage cli and mirage runtime take the same commands.
  • --set KEY=VALUE sets a plain field and can be repeated. A value that starts with { or [ is read as JSON.
  • --secret KEY reads a secret field from a hidden prompt, or from the next line of stdin when stdin is not a terminal. --secret KEY=@FILE reads a file, without its last newline. A secret value on the command line is refused, by --secret KEY=VALUE and by --set alike.
  • --unset KEY removes a field, plain or secret.
  • -f FILE reads the entry from YAML or JSON: name, type, fields, and secrets, a list of the secret fields to read as --secret reads them. The output of get -o yaml reads back too.
  • In a terminal, a missing required field is prompted for. Otherwise the command exits 2 and names every missing field.
  • Piped, every command prints JSON. -o yaml on get prints a file that create -f and update -f read back.
github.yaml
update keeps a secret field you leave out, so --secret token rotates a token while the entry stays in use. delete is refused while a saved template uses the entry. The local daemon keeps entries under ~/.mirage/resources, their secrets in a separate file only you can read.

Sessions

Each takes -w ID, else the current workspace, as shell and vfs do.

Daemon

The server the CLI starts. It exits 30 seconds after its last workspace is deleted, and logs to ~/.mirage/daemon.log.

Log in

A context is a server the CLI talks to, with its login, team and current workspace. local is the daemon on this machine, which needs no login and is always there. mirage login adds a context and makes it current:
--context NAME before a command, or MIRAGE_CONTEXT=NAME, runs one command in another context. MIRAGE_DAEMON_URL points any context at another URL. On a terminal, a command that changes something names its context first, as strukto · team acme · created demo.
  • mirage login asks the server where to sign in, opens your browser there, and waits. If you are already signed in, the browser comes straight back.
  • The CLI keeps each context in ~/.mirage/contexts/NAME.json, its tokens in ~/.mirage/logins/NAME.json, and the current context’s name in ~/.mirage/current_context, all readable only by you.
  • On a machine with no browser, mirage login --token TOKEN keeps a token you paste once the server accepts it. --token - reads it from stdin, so it stays out of your shell history.
  • Every command sends the login’s token, ssh-proxy and codex-exec included, but only to the server it was made for. MIRAGE_TOKEN wins over it, and so does auth_token in the local context.
  • The token refreshes on its own; you log in again 30 days after you signed in.
  • A server that publishes no login, such as the one the CLI starts on your machine, needs none, and mirage login says so.
A server publishes a login when it runs in jwt mode with an issuer and MIRAGE_LOGIN_CLIENT_ID set to an OAuth client that MIRAGE_JWT_AUDIENCE lists:
mirage login then reads the issuer and client from the server’s /.well-known/oauth-protected-resource, signs in through the browser, and exchanges the code for tokens with PKCE.In Clerk, create an OAuth application with no client secret and PKCE enabled. Add the redirect http://127.0.0.1/callback (the CLI picks a free port), allow the offline_access scope for a refresh token, and turn the consent screen off so a signed-in user comes straight back to the CLI.

Call any route

mirage api calls a route of the current context’s server with the CLI’s login or token, as gh api does, so a script needs no token of its own:
Every option after the route is a field, except these: A field is --key value or --key=value. A value that starts with { or [ is JSON, @FILE is the file’s text (@- reads stdin), anything else is a string, and a bare --flag is true. Fields make a JSON body, or the query string of a GET. The answer prints as JSON, a 204 prints nothing, and an error exits 2 with its detail. api takes only a route, never a URL, so the token goes nowhere but its own server.

Daemon config

mirage daemon config set|get|unset|list edits ~/.mirage/config.toml. An environment variable wins over the file. Changes apply on the next start. MIRAGE_HOME moves everything under ~/.mirage.