Skip to documentation
MMirageDocs

Documentation

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:

macOS · use the bundled CLI
alias mirage='/usr/local/bin/mirage'
mirage --version

The 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

Terminal · authorize account access
mirage auth login
mirage auth status

The 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.

Terminal · inspect the local host
mirage host status
mirage host doctor
mirage host diagnostics --lines 80

status 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

Terminal · inspect your accounts
mirage remote get
mirage remote audit

Private repositories selected for the GitHub App use local Macs automatically. Use a returned repository ID only when changing its optional Cloudflare routing:

Terminal · enable Cloudflare for a repository
mirage remote repository 67890 --cloudflare true

Remote 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:

Terminal · delete a machine
mirage remote delete-machine 11111111-2222-4333-8444-555555555555

The 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.

Shell script · list private repositories (requires jq)
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:

Shell script · inspect Mirage without installing the CLI
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:

Shell script · change a remote setting
curl -fsS -X PATCH \
  -H "Authorization: Bearer $MIRAGE_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"cloudflareEnabled":false}' \
  https://mirageci.com/v1/settings/repositories/67890

The 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:

Shell script · read and update a job allocation
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-allocations

For 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:

Shell script · inspect lifecycle diagnostics
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.