Reference
Use the CLI
Manage routing settings, accounts, and connected machines from your terminal.
Get the CLI
The CLI manages Mirage settings on macOS and Linux. Commands beginning with
mirage host manage the host service on the Mac where you run them; mirage remote manages
your account through the hosted service.
The Mac bootstrap installation installs the CLI as
/usr/local/bin/mirage. It updates with the host service, including when the menu-bar app is
closed. If that directory is not on your PATH, use it in your current terminal with:
alias mirage='/usr/local/bin/mirage'
mirage --versionThe alias lasts for this shell session. In scripts, use /usr/local/bin/mirage
directly when it is not on your PATH.
Standalone x64 and ARM64 archives for Linux are distributed through Mirage’s
GitHub releases under cli-v tags. On macOS, use the CLI installed by the Mac bootstrap
command above; it ships with the app and host service.
Sign in
mirage auth login
mirage auth statusThe login command opens Mirage in your browser. Check that the displayed code matches the
terminal and approve access with your GitHub-linked Mirage account. Use mirage auth login --no-open to open the page yourself, or --read-only to request read-only settings access.
The default server is https://mirageci.com. Login credentials are stored in your operating
system’s credential store. On an SSH-only shell, the CLI uses a private credential file if
that store is unavailable. Run mirage auth logout to revoke the stored login and remove it
from this computer.
Browser sign-in authorizes remote settings. Pairing a Mac separately authorizes that machine to run jobs.
Manage this Mac
These commands require the installed Mac host service. They do not require a remote CLI login.
mirage host status
mirage host doctor
mirage host diagnostics --lines 80status reports the host’s current state. doctor checks prerequisites and runner profiles
and includes the reason an unavailable profile can’t run; the machine page shows the next
step. diagnostics returns the host service’s redacted log tail.
Use mirage host configure --help for pause and battery settings. Setup also uses it to
choose the first runner settings; after that, change them on the machine page. See
Set up a Mac for the installation and examples.
Manage account settings
mirage remote get
mirage remote auditPrivate repositories selected for the GitHub App use local Macs automatically. Use a returned repository ID only when changing its optional Cloudflare routing:
mirage remote repository 67890 --cloudflare trueRemote changes follow your GitHub organization role: organization owners administer the
Mirage account, and organization members can read it and offer machines. Manage people on
GitHub. Use mirage remote --help for machine access, scheduling, transfers, and Cloudflare
settings.
Owners can also create, connect to, stop, and resume retained macOS VMs through
mirage vm. Follow Agent VMs on a Mac for the commands.
To delete a machine from the fleet, use its ID from mirage remote get:
mirage remote delete-machine 11111111-2222-4333-8444-555555555555The machine's personal controller or an organization owner may delete it. Delete its retained Agent VM disks first and wait for active work on a connected machine to finish. Deletion revokes pairing and sharing access; jobs on an unreachable machine may be interrupted. Past run history is kept. The host software stays installed.
Use in scripts
Successful commands write one versioned JSON value to standard output and exit with status
zero. Prompts and JSON errors go to standard error; failures exit nonzero. Inspect the
response version when building an integration, and use a JSON parser such as jq instead of
matching display text.
mirage remote get | jq '.accounts[].installations[].repositories[] | select(.private == true)'Interactive login uses Keychain on macOS or Secret Service on Linux when available. If it
fails in an SSH-only shell, Mirage automatically
stores the refresh token in an owner-only file under ~/.config/mirage/credentials/.
Use mirage auth login --no-open --credential-file to select that file explicitly in
another headless environment. mirage auth logout revokes and removes the file.
For headless mirage remote commands, MIRAGE_ACCESS_TOKEN can instead hold a dedicated
API token or an existing short-lived OAuth token. The CLI does not store or refresh that
environment token; unset it to stop using it.
Use the HTTP API
Create a dedicated API token in Settings → Account → API tokens. Read-only access is the default; choose read/write only when an integration needs to change remote settings. Give the token a name and a 30, 90, or 365 day expiry, then copy it when Mirage shows it once. Store it in your agent's secret store. Tokens use your current account access, which follows your GitHub organization role; revoke a token from the same page when it is no longer needed.
These endpoints use Authorization: Bearer and return versioned JSON on success. Discover the
installation ID from settings, then read status for that installation:
curl -fsS -H "Authorization: Bearer $MIRAGE_API_TOKEN" \
https://mirageci.com/v1/settings
curl -fsS -H "Authorization: Bearer $MIRAGE_API_TOKEN" \
'https://mirageci.com/v1/status?installationId=12345'
curl -fsS -H "Authorization: Bearer $MIRAGE_API_TOKEN" \
'https://mirageci.com/v1/status/jobs?installationId=12345&state=active'
curl -fsS -H "Authorization: Bearer $MIRAGE_API_TOKEN" \
'https://mirageci.com/v1/status/runs/987654321?installationId=12345'The /v1/status snapshot includes generatedAt, queued and running counts, failures in the
last 24 hours, machine connection and capacity, profile availability, and Cloudflare mode and
health. upstream.github reports cached health for GitHub Actions, API Requests and Webhooks,
including state, freshness, component states, lastSuccessfulFetchAt, and official incident
links. Unknown means Mirage has no fresh, successful status check; it does not mean GitHub is
healthy. Mirage checks the public status feed once per minute and considers checks stale after
three minutes. sourceUpdatedAt is GitHub's last status transition, not the fetch time.
It requires runners:read; users without runs:read receive jobs: null. Job lists
and run lookup require runs:read; they return jobs with GitHub IDs and URLs, provider or
failure reasons, and observation times. Run lookup also returns attempts. Each job's
upstreamContext lists retained incidents that overlapped its queue or startup window, with
relationship: "possibly_related". An overlap does not establish the cause of a job failure
and never replaces its recorded status or reason. These endpoints read Mirage's cache and
incident history without making requests to GitHub Status.
For job lists, state accepts active, failed (all recorded failures), or recent
(updated in the last 24 hours).
Job and run responses return nextCursor for the next page. Pass it as cursor; limit
defaults to 20 and cannot exceed 100. Job pages use descending GitHub job IDs, and the
recent cutoff stays fixed across pages. Jobs can change state between requests, so start
a new page sequence for a fresh view. Timestamps are Unix milliseconds. Mirage reports what it
has observed; GitHub remains authoritative for check results. Machine activeSlots includes
occupancy from other accounts on a shared Mac. Status responses use Cache-Control: no-store;
poll every 30 seconds or less often.
A read/write token can call the existing remote settings endpoints, including Mac host policy and macOS image settings when its owner has permission. This example disables a repository's optional Cloudflare route:
curl -fsS -X PATCH \
-H "Authorization: Bearer $MIRAGE_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"cloudflareEnabled":false}' \
https://mirageci.com/v1/settings/repositories/67890The same endpoint accepts diskExhaustionAction, report or fallback: what Mirage does
when one of the repository's jobs runs out of disk.
Settings list it for each repository.
Job allocations are available with the same token. List the selected installation's jobs to
find the stable definitionId, then send the complete CPU, memory, and disk request:
curl -fsS -H "Authorization: Bearer $MIRAGE_API_TOKEN" \
'https://mirageci.com/v1/settings/job-allocations?installationId=12345'
curl -fsS -X POST \
-H "Authorization: Bearer $MIRAGE_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"definitionId":"11111111-2222-4333-8444-555555555555","resources":{"kind":"linux","cpuCount":2,"memoryBytes":8589934592,"rootDiskBytes":17179869184}}' \
https://mirageci.com/v1/settings/job-allocationsFor a job that already ran on a direct Mirage label but has no registered definition, send
workflowJobId instead of definitionId. Use the numeric job ID from its GitHub Actions URL.
Mirage reads the observed runner profile and stored workflow plan, and requires one static,
unambiguous job name. This registers only an allocation; it does not change the workflow,
create provider routes, or enable experiments. The response returns its new definitionId.
Dynamic job names and matrix children need a registered definition instead.
Reading requires settings:read, and updating requires settings:write. Both also require
repository-management access to that GitHub account. Set resources to null to restore
the profile default. Linux requests use rootDiskBytes; macOS requests use writableDiskBytes.
Changes apply to future capacity leases, so rerun a completed job after updating its request.
These routes support dedicated API tokens and CLI OAuth tokens, and don't require browser
cookies or an Origin header. POST /v1/dashboard/job-allocation also accepts a bearer token;
its cookie-authenticated browser requests still require the same origin.
Tokens cannot manage other tokens or control interactive VMs. Commands that act on the local
Mac still run on that host. Invalid query values return 400; a missing or invalid token
returns 401; insufficient token scope or GitHub role returns 403; an installation or run
outside your access returns 404.
Operational diagnostics
Read the selected GitHub account's queue and lifecycle stages after mirage auth login:
mirage diagnostics lifecycle --account ahoylabs
mirage diagnostics lifecycle --account ahoylabs --window-hours 48
mirage diagnostics lifecycle --account ahoylabs | jq '.layers[] | {label, steps}'
mirage diagnostics lifecycle --account ahoylabs | jq '.waiting[] | {job, code, reason}'--account is required; remote get lists accessible accounts. The reporting
window defaults to 24 hours and accepts 1–168 hours. A read-only login is sufficient.
The command uses GET /v1/diagnostics/lifecycle?account=ahoylabs&windowHours=24
with settings:read and the account's runs:read capability. Each successful read
creates an account-scoped CLI audit event.
The version 1 JSON includes accountLogin, installationId, generatedAt,
windowHours, metrics, layers, raw counts and timings, the oldest waiting
jobs with stable reason codes and display text, waitingTotal, blocked labels, sampling limits,
capacity.available and warnings.
Intake, Capacity, Runner, Cloudflare and Outcome count their own records, so a
job can appear in several layers. In-flight jobs count Intake only. Terminal
counts cover the reporting window; active counts describe now. Timings sample
up to 500 recent records per layer: active stages measure elapsed age and
terminal stages measure time to finish. P50 is the median and P95 the slow end;
missing samples remain null. The waiting list is limited to 12 jobs; its total
excludes unsupported labels, which are reported separately in blocked.
Use waiting[].code in scripts; reason is display text and may change. The
codes are cloudflare, unsupported_label, no_ready_machine, slots_busy,
awaiting_github, leasing_slot, awaiting_approval and capacity_unavailable.
Approval and unsupported-label codes remain explicit when capacity is unknown.
If live capacity cannot be read, the command still returns stored counts and
timings. capacity.available is false and warnings includes
capacity_unavailable; affected jobs say capacity details are unavailable
instead of claiming that no machines are ready. Infrastructure failures while
reading account access or stored diagnostics return an error.
The dashboard shows each queued job's waiting explanation and elapsed wait on Runs. Machine startup timing remains in Machines → Reliability.