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
- script
- pip
- uv
- npm
Quickstart
Create a RAM-backed workspace that needs no credentials: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 undermounts is a path in the workspace: vfs names the backend, and config
holds the fields its VFS guide lists.
workspace.yaml
mode: readmakes every mount read-only unless it sets its ownmode, as/scratchdoes.${NAME}is read from the environment when the config loads, so keys stay out of the file. A missing variable is an error.command_limitsbounds the commands that run on that mount; its"@all"entry’swalk_limitis 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.
--jsondecodes 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
2when the arguments orMIRAGE_WORKSPACEare 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
--dataor--value, else from stdin. - Piped, it prints the call’s JSON. In a terminal,
readprints 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=VALUEsets a plain field and can be repeated. A value that starts with{or[is read as JSON.--secret KEYreads a secret field from a hidden prompt, or from the next line of stdin when stdin is not a terminal.--secret KEY=@FILEreads a file, without its last newline. A secret value on the command line is refused, by--secret KEY=VALUEand by--setalike.--unset KEYremoves a field, plain or secret.-f FILEreads the entry from YAML or JSON:name,type,fields, andsecrets, a list of the secret fields to read as--secretreads them. The output ofget -o yamlreads back too.- In a terminal, a missing required field is prompted for. Otherwise the
command exits
2and names every missing field. - Piped, every command prints JSON.
-o yamlongetprints a file thatcreate -fandupdate -fread 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.
How login works
How login works
mirage loginasks 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 TOKENkeeps 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-proxyandcodex-execincluded, but only to the server it was made for.MIRAGE_TOKENwins over it, and so doesauth_tokenin thelocalcontext. - 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 loginsays so.
Set up login on a server
Set up login on a server
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:
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.