Skip to content

CLI reference

Ported from rex help, which is the source of truth. If this page and the binary ever disagree, the binary is right — run rex help and open an issue.

All commands accept --json for raw, machine-readable output. Exit codes are 0 ok, 1 command failed, 2 rexenv isn’t running — or rex refused the command line before it ran anything.

Every flag-taking command refuses a flag it does not know, naming it, and exits 2 without doing the work:

rex: unknown flag `--dry-runn` for `wp search-replace`

An unrecognised flag used to be dropped, so the command ran with the default that flag existed to override. That is why this is a refusal and not a warning: on site create it built the wrong site, and on wp search-replace a typo’d --dry-runn --yes turns a rehearsal into a real replace across the database with nothing left to ask you — the typo removes the dry run and the --yes removes the question. Each command checks its own list, kept in the same place that reads the flags, so the two cannot drift apart.

rex status Services + DNS state (the app's ownership-and-liveness truth)
rex open Bring the rexenv window to the front (it has no dock icon)
rex start Start the shared stack (same as the app's Start all)
rex stop Stop the shared stack (same as Stop all)
rex restart stop, then start
rex doctor Diagnose: DNS + resolver takeovers, edge wire identity, ports, CLI link
rex version App + CLI versions (needs the app; -v/--version works without)

rex never starts the app. When it is not running, every command says so and prints the command that does — open -a rexenv — rather than spawning a headless copy that would then own your services. rex open is the way to the window once the app is up: rexenv is a menu-bar app, and a window that has been closed is hidden, not gone.

rex status prints one extra line, and only when there is something to say: update rexenv <version> can be installed from Settings → About. --json and the MCP stack_status tool carry the same field. All three read an in-process snapshot — a status call verifies no signature and touches no network — and there is deliberately no rex update: installing one is a click in the app and nowhere else. See Updating.

rex doctor gained two findings after 0.4.0. TLDs in use names every TLD your sites answer on that this Mac cannot resolve, with the cause and the fix — rex tld --repair <tld> when the resolver file is simply missing, or the plain statement that another tool owns the file when it does. PHP curl names the PHP minors whose bundled curl uses a resolver that cannot see .rex hosts, with the count of sites on them: WordPress is patched, a plugin’s raw curl_init() and non-WordPress PHP are not. Since rexenv’s 8.1–8.5 were rebuilt with a curl that resolves .rex, the only minor it names is 8.0 — the one PHP rexenv does not build.

Runtime, Windows only, names a missing or too-old Microsoft Visual C++ Redistributable (x64) before it can fail a database or PHP start — see Requirements.

WP core names each WordPress site missing long-named core or default-theme files — the damage rexenv 0.4.0–0.7.1’s WordPress download did — and the fix, rexenv → <domain> → Repair core files. A site whose file list could not be fetched from wordpress.org is reported as not checked, never as clean. See WordPress.

rex --version answers without the app running, and names the commit the binary was built from — rex <version> (<commit>). rex version adds the running app’s half, labelled as the app’s: a rex from a mounted dmg reports its own build, not whatever is running on the machine.

rex site list All sites + whether each is actually serving
rex site info <domain> Full detail: config, serving state, cert, resources, WP
rex site open <domain> Open https://<domain> in the browser
rex site login <domain> Open a logged-in wp-admin (magic link; --print to not open)
rex site create <domain> [--name N] [--type wordpress|php|laravel] [--php 8.3]
[--server nginx|frankenphp|apache|openlitespeed] [--db mysql|mariadb|postgres|none]
[--blueprint <name>] [--multisite subdomain|subdirectory]
[--path <folder>]
rex site delete <domain> [--yes] Drops its database and docroot (asks first)
rex site logs <domain> [--source K] [--lines N] [--follow]
rex site stop <domain> | --all Stop serving THIS site, or every site — rexenv's services keep running
rex site start <domain> | --all Serve this site (or every site, --all) again
rex site restart <domain> [--pool] Restart the site's own backend (--pool also bounces its shared PHP pool)
rex site domains <domain> [--add N | --remove N] Extra hostnames the site answers on
rex site rename <domain> <name> Display name only (domain unchanged)
rex site domain <domain> <new-domain> Change the domain (URL rewrite; asks first)
rex site move <domain> <dest-parent> Move the docroot under a new parent folder
rex site relink <domain> <path> Re-point a LINKED site at a folder you moved (records only)
rex site retry <domain> Finish a site whose setup stopped part-way
rex site env <domain> [set K=V | unset K]
rex site cert <domain> [--regenerate] Certificate info / fresh leaf
rex blueprints Saved blueprints (for site create --blueprint)

site list’s STATE column has four words, because they have four fixes: serving; stopped — you stopped it, rex site start serves it again; incomplete — its setup never finished, so nothing serves it, Retry or Delete it in the app; and down — the stack, or this site’s backend, is not up. site info says the same thing as one line with the fix in it, and adds a fifth case: blocked — another server answers :443, not rexenv’s edge (rex doctor). Through 0.8.10 the last three were all down, and a half-made site was even serving, because both commands reported what the stack believed rather than what answers.

Defaults mirror the app’s New Site dialog — rex site create shop.rex names the site shop unless --name says otherwise — and WordPress sites get the one-click install. --starter-db on a blank PHP site (--type php) seeds a database, a starter_items table and a db.php; on any other shape it is refused rather than silently ignored. --db postgres is for --type laravel and --type php on a PHP whose build has a working pdo_pgsql — anything else comes back with the app’s own refusal (see PostgreSQL). A misspelt flag is refused too — through 0.4.0 site create ignored a flag it did not recognise and built the wrong site, and a forgotten value after --name could name a site --starter-db.

site stop removes one site from the serving surface and touches no shared process; site restart restarts only the site’s own backend unless you pass --pool, and reports how many sites the pool covers either way. Both are explained on Sites, and site domains on the same page under More than one hostname. Every site verb finds a site by any name it answers on.

rex php list Pinned versions: installed, default, pool port
rex php default <minor> Default version for new sites
rex php install <minor>
rex php uninstall <minor>
rex php settings <minor> [set K=V] Whitelisted ini settings (set restarts the pool)
rex site php <domain> <minor> Switch a site's PHP version
rex site xdebug <domain> on|off Toggle the site's Xdebug debug pool
rex site server <domain> nginx|frankenphp|apache|openlitespeed

php list prints a NOTE column carrying the two facts that can differ from the pinned patch: serving 8.3.30 when the running pool has not moved onto it yet, and 8.3.33 exists when php.net lists a newer patch. A CLI that printed only the pin would be the same silent lie the app’s Settings row exists to prevent. --json has carried both fields all along.

rex db export <domain> Dump to ~/Downloads (prints the path)
rex db import <domain> <file.sql> [--yes] OVERWRITES the site's tables (asks first)
rex db reset <domain> Drop + reinstall WordPress (type the domain)
rex db versions [--set <engine> <version>]
rex db browse Open Adminer in the browser
rex wp <domain> plugin list|install|activate|deactivate|update|delete [slug…] [--activate]
rex wp <domain> theme list|install|activate|update|delete [slug…] [--activate]
rex wp <domain> user list|create|set-password|set-role|delete …
rex wp <domain> user delete <login|id> --reassign <login|id> | --delete-posts
rex wp <domain> search-replace <from> <to> [--dry-run] [--yes]
rex wp <domain> cache-flush | cron run | maintenance [on|off] | core update
rex wp <domain> core versions | core switch <version>

search-replace --dry-run is honoured as typed. 0.4.0’s rex ran the replacement for real on a typo’d --dry-run, and would have written the flag into the database as a value.

These are vetted WP-CLI operations rather than an arbitrary passthrough. For anything not covered, the site’s Terminal tab has a full shell with the right PHP and wp on PATH.

rex repo <domain> list [--status] --status adds live state
rex repo <domain> status <dir> [--theme] Branch, changes, ahead/behind, remote, link target
rex repo <domain> branches <dir> [--theme] Local + remote branches + tags
rex repo <domain> prs <dir> [--theme] PR/MR head refs from the remote
rex repo <domain> add <url> [--branch B] [--name N] [--theme] [--install]
rex repo <domain> adopt <dir> [--theme] Manage an existing checkout (metadata only)
rex repo <domain> link <path> [--name N] [--theme]
rex repo <domain> pull|fetch|push <dir> [--install]
rex repo <domain> checkout <dir> <ref> [--install]
rex repo <domain> run <dir> <script> Run one package.json script to completion
rex repo <domain> check <dir> [--theme] [--install]
rex repo <domain> watch list | watch start <dir> <script> [--tail] | watch tail <dir> | watch stop <dir>
rex repo <domain> delete <dir> [--theme] [--yes]
rex repo tools [--refresh] Detected git/node (login-shell resolution)

Behaviours worth knowing:

  • check runs nothing. It reports whether composer/npm dependencies are missing or stale and offers the steps; --install is what actually runs them, stopping at the first failure.
  • pull is --ff-only. push never forces.
  • --install after a git op re-installs only when the operation changed lockfiles.
  • link symlinks an external folder in. Deleting it later only unlinks — your real folder is never touched.
  • add takes public https/owner/repo URLs, and private repos through your own ssh keys. Output appears at completion; the live view is in the app’s panel.
  • Watchers run inside the app and stop when it quits. --tail on watch start, or watch tail, follows the output in your terminal; Ctrl-C stops following, not the watcher.
  • Checking out a listed PR ref lands you detached — those refs carry a number and a sha only.
rex service start|stop <mysql|mariadb|postgres|redis|mailpit>
rex service restart <nginx|edge|php-8.3> Bounce one web-tier service on a fresh config
rex logs [key] [--lines N] [--follow] No key lists all log files
rex mail List caught messages (Mailpit)
rex mail list --unread [query] Filter to unread, optionally by query
rex mail open Open the Mailpit web UI
rex mail mark-read Mark every message read
rex mail clear [--yes] Deletes ALL messages
rex tunnel list
rex tunnel start|stop <domain> start prints the public URL

rex start/stop cover the web tier; service start|stop is for the optional engines individually. service restart is restart-only and web-tier-only — nginx, the edge, or one PHP pool — on a freshly generated config, because “restart the shared nginx” is the thing you want after a config edit and “stop the shared nginx” is Stop all under another name.

A tunnel start is now written to rexenv.log, and so is its end; rex tunnel start on a stopped site warns that the link will show the stopped page.

rex config get|set <key> [value] Settings the CLI may touch (refusals say why)
rex tld [--set <tld>] [--repair <tld>] [--remove <tld>]
Default TLD for new sites; --repair puts back the OS resolver
file for a TLD your sites answer on (what doctor names);
--remove deletes one that is rexenv's own and no site uses
rex completions zsh|bash Print a shell completion script
rex mcp MCP stdio bridge for an AI agent's client
rex help