CLI reference
The mend CLI talks to the Mend server. Run mend help for the help text installed with your
version. One-shot commands require Node 22.13 or newer. The terminal dashboard requires Node 26 and
node:ffi; every other command still works when the dashboard cannot start.
Setup commands
Section titled “Setup commands”| Command | Purpose |
|---|---|
mend login [--url <server>] [--open | --no-open] |
Sign in through the browser. The CLI opens <server>/authorize, waits for you to press Authorize, and saves a revocable device token with mode 0600. Over SSH, or on Linux with no display, it prints the link and code instead of opening a browser; --open and --no-open decide outright. The CLI counts down from the time the server says is left, so a server clock that disagrees with this machine’s does not expire the request |
mend logout |
Revoke this terminal’s device token on the server and remove it from this machine |
mend uninstall [--all | --server | --home] [--yes] |
Remove the local server (live sessions’ workspaces, containers, volumes, networks, image, private configuration), this machine’s Mend files (sign-in, workspace SSH key, and the ~/.ssh/config blocks of the servers it removes or signs out of), or both, with Mend’s images; asks for the scope and prints the plan first, and needs the word delete before the server goes |
mend connect <claude|codex|github> [--use-my-login] [--from-stdin] [--remove] |
Connect or remove a provider for the signed-in user; the credential is stored under your own user and shared with no one |
mend connect pi [--dir <path>] [--dry-run] [--remove] |
Save pi’s agent directory on this machine (~/.pi/agent, or $PI_CODING_AGENT_DIR) as your pi profile, which every pi session you start receives; --dry-run says what would be sent and sends nothing, --remove deletes the profile |
mend accounts |
List the signed-in user’s connected provider accounts |
mend doctor |
Run a read-only setup checklist and print a repair command for unfinished items: the server, the sign-in, the platform connection, the provider accounts (Claude, Codex, GitHub) and Mend’s own Claude login, the adopted projects, the claude, codex and gh CLIs on this machine, the exposure, whether the server’s host allows rootless Docker, a server clock more than two minutes off this machine’s, and, where it applies, a Mac that sleeps on its own or when its lid closes, and what a stop of the Docker daemon waits for against what systemd allows docker.service |
mend doctor --bundle [--out <path>] [--tail <n>] |
Write one redacted diagnostic archive for a bug report; default path ~/.config/mend/bundles/mend-bundle-<time>.tgz, --tail 1 to 2000 lines per log, default 500 |
mend pair [--url <base-url>] |
Create a single-use, ten-minute pairing code for another device; --url selects one of the server’s configured origins |
mend version |
Print this CLI’s version and the server’s, when it answers within two seconds; mend --version and mend -v do the same |
mend connect claude and mend connect codex log in afresh through the provider’s own flow (a
browser login for Claude, a device code for Codex) into a throwaway directory, send that login and
delete it, so your own logins stay as they are and this machine keeps no copy. Both rotate their
refresh token, and two copies of one login sign each other out; --use-my-login sends the login
this machine already uses instead, and both sides then share it. mend connect github asks gh for
its token. Run mend connect claude or mend connect codex again when Mend says a login needs
reconnecting. Read Connect provider accounts for credential sources
and removal.
In a per-person workspace each person’s processes use their own GitHub login, in their own home: git
over HTTPS to GitHub reads it through Mend’s credential helper, and no GITHUB_TOKEN is set in the
environment. A command that needs the token takes it for itself:
GITHUB_TOKEN=$(/run/mend/bin/mend-git-credential token) <command>.
mend connect pi sends extensions, themes, prompt templates, settings, mcp.json and keybindings,
following links, so a setup Home Manager links in is read as the files it points at. Your pi login
stays on this machine: pi runs on the ChatGPT login mend connect codex makes. Run it again after
you change your setup; running sessions keep the profile they started with. Read
Your pi setup.
mend uninstall asks for a scope when none is given, prints exactly what will go, and needs the
word delete before the server is touched (--yes skips the question; with no terminal it is
required); any other answer removes nothing and exits 1. With Docker stopped, --server and --all
refuse and change nothing. The server scope stops and removes its sessions’ workspaces first, live
ones included, then the server’s containers, volumes, networks, image and private configuration; the
mend-store volume and identity.env go last. What Docker would not remove keeps them and is
written to uninstall-left.json, so running mend uninstall again finishes the job and
mend server setup can reinstall over it. Only the ~/.ssh/config blocks of the servers it removes
go; other servers’ blocks stay, and the plan names each. --all also offers the images Mend pulled
and built, with their size, and removes the user-namespace sysctl file setup wrote, putting back the
setting it replaced (a file written by hand stays). Docker’s build cache gets its own question and
stays under --yes. The CLI itself stays: npm uninstall -g @sealant/mend removes it.
mend doctor --bundle collects the CLI and its environment, the doctor lines, the server’s health,
the local server’s compose file and the names of its .env keys (never their values), Docker facts,
container logs, every session with its processes, exit codes, argv and recorded terminal output, the
connected accounts, and the versions of claude, codex, gh, git and docker. A part that
fails leaves a <name>.error.txt in the archive. One redactor runs over every file, and the archive
is written with mode 0600. It still contains logs and configuration; read it before you share it.
See Troubleshooting.
Server commands
Section titled “Server commands”Server commands run on the machine that hosts Mend. They manage the local Docker Compose
installation created by mend server setup; they do not sign this CLI in or change Docker’s global
context.
| Command | Purpose |
|---|---|
mend server setup |
On a terminal: ask how the server is reached, Remote-SSH, the T3 Code gateway, the mirrors and the tenancy, show what is saved on a rerun, then what changes and the same command with flags, and apply on a yes |
mend server setup [options] |
Install or change the local server with flags, for scripts: check the Docker context and Compose plugin, download one release’s assets, claim the persistent volumes, pull and verify the pinned images, start the Mend, Postgres and Garage containers, then verify health |
mend server status |
Show the saved pin, active generation, container state, each mirror’s container, cache and traffic as observed, the edge and the posture declared beside what the server observes, without changing anything; a running Mend must answer health with the exact pinned version |
mend server start [--offline] |
Start the selected generation from preloaded images; never downloads assets or pulls release images |
mend server stop |
Stop Mend, Postgres and Garage without deleting volumes; workspace containers remain |
mend server restart [--offline] |
Restart Mend on the same generation and pin; Postgres and Garage keep running |
mend server logs [--tail <n>] |
Print a bounded tail of each container’s logs, 1 to 1000 lines per service, default 100; no follow mode |
mend server upgrade --version <target|latest> [--assets-dir <dir>] [--offline] [--from-preview] [--keep-backups <n>] |
Upgrade to an explicit version after validating its assets and image label, stopping application writers, and saving a private database backup; the edge and the posture are carried into the new generation, and downgrades are refused. --from-preview moves a X.Y.Z-preview.K server to X.Y.Z-next.N once, after checking the target carries every applied migration. After a healthy upgrade, older completed backups are removed, keeping the newest --keep-backups (default 2; 0 keeps all) |
mend server setup with no flags on a terminal asks its questions in plain words; read
Install Mend for the conversation. The flags below
are the same choices for scripts and CI. On an existing install each changes only what it names, and
the rest is kept from the saved install. With no terminal and no flags, a fresh install is refused
rather than guessed; --yes takes the defaults. On a terminal, a fresh install with more than one
Docker engine answering asks which one Mend runs in, the current context in brackets, and carries
the answer into the command it prints as --context. --context and --docker-socket choose the
Docker engine and answer only that question: on a Mac with Docker Desktop and OrbStack,
mend server setup --context orbstack asks the rest. A fresh install without --context takes
DOCKER_CONTEXT, as docker does, and with --yes or no terminal, the current context, naming the
others. An existing install stays in the engine its data is in; another engine is a new install.
Before anything is pulled, setup checks that Mend’s web and SSH ports are free where they are to be
published, and names what holds one that is not: another Mend with its version, or something else.
On a terminal it offers the next free port; with flags it refuses and names --port or
--ssh-port. After starting, it waits for health from this install (its version and the instance id
the server reports), so another Mend answering on the same port is never taken for it. When this
machine’s CLI points at another server, or at nothing, setup offers to point it at the one it
installed. When setup changes Mend’s URL, a CLI signed in at the old one stays pointed there; setup
prints the command each one runs, mend login --url <new>, which is a new browser authorization.
Two mirrors run beside Mend unless turned off: an npm mirror and a pull-through cache of Docker Hub. Neither publishes a host port. Sessions install npm packages and pull Docker Hub images through them, a project’s own npm settings win, and a mirror that is down sends sessions upstream. An upgrade adds them to an install from before them. Read npm mirror and Docker mirror.
Setup options:
| Option | Meaning |
|---|---|
--yes |
No questions: a fresh install takes the defaults (this machine only, http://localhost:3105), an existing one keeps what is saved |
--context <name> |
Local Unix-socket Docker context to persist; the global context is unchanged. Answers the engine question. Default: DOCKER_CONTEXT on a fresh install, else the saved one |
--version <v> |
Exact Mend server version, or latest; a fresh setup pins the CLI’s own version, and a rerun keeps the existing pin |
--bind <ip> |
Published listen address for web and SSH; default 127.0.0.1 |
--ssh-bind <ip> |
Published address for workspace SSH alone, when not --bind; with --edge, Remote-SSH from another machine needs it. Kept across reruns; the --bind address takes it away |
--url <origin> |
Advertised browser URL; required with a non-loopback bind |
--origin <origin> |
Additional exact browser origin; repeat for more than one. The list given replaces the saved one; none clears it |
--port <n> |
External web port; default 3105; must differ from the SSH port. A saved --url on the old port moves to the new one |
--ssh-port <n> |
External workspace SSH port; default 2222 |
--edge <host> |
Run the Caddy TLS edge for this DNS name on ports 80 and 443; the origin becomes https://<host>, --bind stays on loopback, and the first account must already exist |
--no-edge |
Take a saved edge away; the origin returns to http://localhost |
--exposure <v> |
Declare loopback, private or public; kept across reruns and upgrades. public needs --edge and an existing first account |
--tenancy <v> |
Declare single or multi; kept across reruns and upgrades. multi, like public, also sets the multi mode gate’s variables |
--declare <item> |
State a gate item you verified from outside: core-private, edge-tls, workspace-ssh or t3code-gateway; adds to the saved ones; repeat for more, none clears. public with --ssh-bind beyond loopback needs workspace-ssh |
--undeclare <item> |
Take one statement back; the others stay. Repeat for more |
--t3-gateway |
Turn on the t3code gateway: its own listener, published where Mend’s web port is (loopback, or the tailnet or LAN address); kept across reruns and upgrades. Refused on a Mend image without it, or a port already in use |
--t3-gateway-port <n> |
Its port; default 3120; implies --t3-gateway |
--no-t3-gateway |
Turn it off; its state stays in the mend_mend-t3-gateway volume |
--t3-gateway-origin <host> |
With --edge: the edge serves the gateway over HTTPS on this host of its own, whose DNS points where the edge’s does; implies --t3-gateway |
--no-t3-gateway-origin |
The edge stops serving the gateway; it stays on, on 127.0.0.1 |
--npm-mirror, --no-npm-mirror |
Run or stop the npm mirror; kept across reruns and upgrades. Default: on |
--npm-mirror-max-size <size> |
The npm mirror’s disk cap, such as 20g or 1536m, at least 1g. Default: 10g |
--docker-mirror, --no-docker-mirror |
Run or stop the Docker Hub mirror; kept across reruns and upgrades. Default: on |
--docker-mirror-max-size <size> |
The Docker mirror’s cap, such as 40g; over it the cache is cleared and fills again. Default: 20g |
--docker-hub-username <name> |
The Docker mirror pulls as this Docker Hub account; needs --docker-hub-token-stdin |
--docker-hub-token-stdin |
Read that account’s access token from standard input; kept in server.env, never in argv or a session |
--docker-hub-public-only |
Required with a login: you state the token is scoped Public Repo Read-only, since every session can pull what it can read |
--no-docker-hub-login |
The Docker mirror pulls anonymously again |
--assets-dir <dir> |
Copy compose.v2.yaml and postgres-init.sh from a local release directory; a fresh setup then requires --version |
--offline |
Use retained or supplied assets and preloaded images only; no GitHub requests or release-image pulls |
--docker-socket <path> |
Daemon-side socket mount override for diagnostics; retained on reruns |
--allow-userns |
On a Docker host whose kernel refuses unprivileged user namespaces, allow them without asking: writes /etc/sysctl.d/60-mend-rootless-docker.conf on the host and applies it |
--no-allow-userns |
Leave the host’s kernel as it is without asking; setup prints the command instead |
Setup holds an exclusive lock through startup and health checks, keeps private configuration in
immutable generations, and never deletes Docker volumes. A changed --version on a rerun is
refused; use mend server upgrade. Read Install Mend and the
self-hosting guide for offline
assets, locks, and upgrade recovery, and Exposure and budgets for how the
server is reached. --edge runs the TLS edge and --exposure and --tenancy declare the posture;
all three are written into the generation and kept by every rerun and upgrade. Without them a server
it installs runs as private and single.
Project commands
Section titled “Project commands”| Command | Purpose |
|---|---|
mend adopt [git-url] [--name <name>] [--auth <mode>] [--private|--shared] |
Clone a network Git repository into the store; with no URL, the current checkout’s origin |
mend refresh [project] |
Fetch origin’s branches into the store so new sessions base on current tips |
mend projects [--json] |
List adopted projects and their live sessions; --json for scripts |
mend env load [file] [--secret [A,B]] [--project <name>] |
Load dotenv values into project configuration and secrets |
mend env show [--project <name>] |
List configuration, secret, and cluster-binding names; never secret values |
mend env cluster add secret|configmap <name>, remove <kind>/<name>, sa <name>|--clear |
Bind Kubernetes Secrets, ConfigMaps, and a service account to a project’s workspaces; Mend stores names only |
Git authentication modes for mend adopt are mend-key and bridge. The default is your Git
access mode from mend keys mode, which is mend-key until you change it. Local paths, file://
URLs, option-like sources, and Git remote helpers are rejected.
A project is --private by default: only you see it. --shared makes it visible to everyone in
your organization, who can then start sessions in it. See Organizations.
mend projects --json prints {"version": 1, "projects": [...]}, with an empty list when there are
none. The shape is stable:
| Key | Type | Meaning |
|---|---|---|
id |
string | The project’s id |
name |
string | Its name, as --project takes it |
originUrl |
string or null |
The repository it was adopted from, without credentials |
defaultBranch |
string | The branch new worktrees start from |
storePath |
string | Where the store holds it on the server |
liveSessions |
number | Sessions live in it now |
current |
boolean | The current directory is inside it |
The CLI never prints a URL’s credentials in what it says itself: an origin adopted as
https://oauth2:TOKEN@github.com/acme/repo.git reads as https://github.com/acme/repo.git in JSON,
in tables, in messages and in the mend service init preview. The userinfo is everything before the
last @ of the URL’s authority, whitespace and control characters included; an ssh URL keeps its
user and loses only the password (ssh://git:pw@host/… reads ssh://git@host/…). A command’s
recorded output (mend run, mend logs, mend service logs, mend attach) and a file’s contents
(mend memory show) are printed as they are. A session’s record is not its owner’s alone: anyone
who can read the project can read it, so a password a command printed reaches them too.
Start agents and commands
Section titled “Start agents and commands”mend codex ["prompt"] [options]mend claude ["prompt"] [options]mend opencode ["prompt"] [options]mend pi ["prompt"] [options]mend run [--detach] [--json] [options] -- <command...>Agent options:
| Option | Meaning |
|---|---|
--model <id> |
The harness’s model id, one mend models lists. Default: the server’s; Claude and opencode choose their own |
--effort <level> |
low, medium, high, xhigh, max, or ultra where the model takes it. Default: the harness’s own |
--base <ref> |
Create the worktree from another Git base |
--name <worktree> |
Name the worktree; an existing name joins it as a new session |
--worktree <name> |
Join an existing worktree only; fails naming candidates when absent |
--ask |
Restore the harness’s permission prompts |
--fast |
Request the Codex priority service tier |
--detach, -d |
Launch without attaching; reattach anywhere with mend attach |
--foreground |
Stop the session when this CLI exits (the detach key still detaches) |
--no-tunnel |
Do not tunnel the session’s browser Services to this machine |
--land, --no-land |
Land, or do not land, when a turn completes, for this session only |
--project <name> |
Select an adopted project instead of matching the current directory |
A quoted prompt becomes the first message and supplies the initial session name. Interactive
launches ask for the worktree’s name first (enter accepts an automatic one); --name answers it up
front. The CLI creates or joins the worktree, says so when a name joins an existing one, waits for
the workspace and process, then attaches the current terminal. Requesting a --base that differs
from an existing worktree’s base is refused rather than silently re-basing it.
In a per-person workspace, joining a worktree where another person’s session runs says so before the
session starts:
Anna's session is running in this worktree. You share its workspace: everything you run runs as you, on your own logins, but either of you can read the other's files, logins included.
Background sessions
Section titled “Background sessions”Sessions run in the background: closing the terminal, losing the network, or the CLI dying leaves
the session running, and stops are explicit. The Sessions switch in Settings (overridable per
project, inherit · on · off) turns this off for interactive launches, giving them foreground
semantics: the session stops when the launching mend exits. --detach and --foreground override
both for one launch. Foreground stops are best-effort on signals: a SIGKILL or power loss cannot
stop anything; mend sessions shows what still runs and mend stop ends it. A terminal that closes
(its window, tmux kill-session) stops a foreground session as SIGHUP does. The switch applies to
interactive CLI launches (mend codex|claude|opencode|pi); mend run prints the command’s recorded
output without attaching, and browser or phone clients never stop a session by disconnecting.
Inside a session workspace, the staged helper accepts mend stop too, so a workspace shell (or the
agent itself) can end its own session, and mend land, which lands the session’s change as its
owner (see Ask the agent to land).
--land and --no-land override the project’s “Land when a turn completes” setting for one session
(see Landing commands); a project set to off wins over --land. Mend lands
after turns it runs itself, so an agent attached to a terminal lands on its own only once the
session is picked up on the phone. From the terminal, land it with mend land.
Codex uses model, effort, permission, and speed options. Claude uses model, effort, and permission
options. opencode uses model and permission options; its models are named provider/model, and
without one it opens on openai/gpt-6.1-sol through your ChatGPT login. pi uses model and effort
options (effort up to max, as its --thinking) and runs on the ChatGPT login mend connect codex
makes; mend connect pi sends it your setup. See Your pi setup.
Run a command
Section titled “Run a command”mend run starts a session whose process is your command instead of a harness. It takes
--project, --name, --worktree and --base as the agents do, and prints what the command
writes to its terminal, as the record holds it, then exits with the command’s exit code. The command
runs in a terminal, so its stdout and stderr arrive together, on stdout. What mend run says itself
(the project, the worktree, the session, how the command ended) goes to stderr, so a script reads
the command’s output alone:
out=$(mend run --project api -- git log -1 --format=%H)An attached mend run shows output and sends no keys, so a shell given no script and no -c
(mend run -- bash) would wait for input forever: it is refused before anything is created. For a
shell, use Open a shell on the web, mend shell <session>, or mend run --detach -- bash
followed by mend attach; for commands, mend run -- bash -c '…'.
Ctrl+C stops watching and puts back the terminal modes the command’s output set (alternate screen,
hidden cursor, mouse, keyboard); the command keeps running, and mend stop ends it. Output that
could not be given in full, because the server refused a read or the reader went away, fails
mend run with exit 1 even when the command succeeded, and stderr says the command’s own code.
Stopped by a signal, mend run, mend logs and mend wait exit 128 + its number: 130 for
SIGINT (Ctrl+C), 129 for SIGHUP, 143 for SIGTERM. mend run and mend logs put the
terminal back first. A second signal exits at once, and an exit waits at most 5 seconds for a reader
that takes nothing, then says the output may be incomplete and fails.
A reader that stops reading early (mend service list | grep -q web, | head -1) closes the pipe,
and every mend command then exits 0 at once, quietly, so a pipeline under set -o pipefail
reads as the reader’s own result. mend run and mend logs are the exception above: the command’s
output is what they deliver, and output that could not be delivered fails them.
--detach returns as soon as the command runs. --json prints one JSON object on stdout in place
of the output: sessionId, processId, worktree, branch, url, and the process’s status and
exitCode as last observed (exitCode is null until it ended). With --detach it is printed
once the command runs, and says so when it already ended; without, once it ended. Pick the session
up again with mend logs and mend wait:
id=$(mend run --detach --json -- pnpm test | jq -r .sessionId)mend logs "$id" --followmend wait "$id" --timeout 900 # exits with the test run's exit code, or 124| Command | Purpose |
|---|---|
mend logs [session] [--follow|-f] [--from <seq>] [--process <id>] |
Print a session’s recorded terminal output on stdout; settled sessions count |
mend logs [session] --service <name-or-id> [--follow] [--from <seq>] |
Print a Service’s current attempt’s recorded output |
mend wait [session] [--timeout <duration>] [--process <id>] [--json] |
Return once the session’s command ended, with its exit code; 124 when the timeout passes first |
mend logs reads the session’s command (or agent) by default, and another of its processes, a shell
or a Service attempt, with --process and a prefix of its id; a Service’s id or name there reads
its current attempt. --service takes a Service’s id, its name or a prefix of its id, the ids
mend service list prints, and reads its current attempt, ended or not. A full id names its Service
before any name does; a name two Services carry is refused, with both ids listed. A Service with no
attempt (an adopted port, which Mend runs no process for) has nothing recorded, and mend logs says
so and exits 1. --follow keeps printing until the process ends. A record is printed as it is,
with nothing taken out, and it is not the session owner’s alone: anyone who can read the project can
read it with mend logs or mend service logs, so what a command printed, a password included,
reaches them too. mend wait exits with the code the platform reported, or 1 when it reported
none. A session that is still starting (its workspace building, its image pulling) is waited
through, and while a launch or a resume is starting the previous process’s end does not count;
--process binds the wait to one process, the processId that mend run --json prints.
--timeout takes seconds (90, 90s or .5), minutes (5m) or hours (1h), and covers
everything, finding the session and every read and retry; --json then prints the last state read.
Without it, mend wait waits as long as the command runs, as mend run does. Both take the session
id, a prefix of it, or the worktree’s name.
The platform takes a command of at most 64 words and 1 MiB, at most 131,071 bytes a word, whose
program has no leading or trailing whitespace and with no NUL byte in any word. An argument may be
empty, start with a newline or span lines, so bash -lc "<script>" takes any script. mend run
refuses a command outside that before anything is created and names the word by its position.
Workspaces set PAGER=cat, because the workspace images carry no less and git log in a terminal
would otherwise fail with unable to execute pager 'less'. A project variable named PAGER, a
PAGER your shell profile exports, or core.pager in your git config wins.
Models
Section titled “Models”mend models [--json]The server owns the model list, one per harness. mend models prints each harness’s models with
their ids, the one a launch runs when --model is not given marked default, and the efforts a
model takes where they are fewer than the harness’s. Every client offers the same list and
preselects the same default. A launch that names no model runs that default. Claude and opencode
have none marked: a launch of theirs that names no model is sent none, and Claude Code runs the
model your Claude settings name, else its own default, as a claude you run yourself does. The
session records the model and effort it was started with, which mend sessions shows after the
branch. An id the list does not know is passed through as given; the harness decides whether it
exists.
Session commands
Section titled “Session commands”| Command | Purpose |
|---|---|
mend or mend ui [--no-tunnel] |
Open the terminal dashboard of projects, worktrees and sessions |
mend snake |
Open the dashboard with a game of snake over it; Esc or q closes the game |
mend worktrees [--project <name>] [--json] |
List worktrees and the sessions inside them |
mend worktrees rm <name> [--force] [--project <name>] |
Remove a worktree, its sessions and its change, with --force when its change is not landed |
mend sessions [--all] [--project <name>] [--json|--json=v2] |
List live sessions, or include settled sessions with --all |
mend status |
Alias for mend sessions |
mend attach [session-id-prefix] [--no-tunnel] |
Reattach to a running session; a session picked up on the phone moves to this terminal |
mend stop [session-id-prefix] |
Stop the agent; the worktree, record and review remain |
mend stop --all [--project <name>] |
Stop every live session, or every live session in one project |
mend stop --services [session-id-prefix] |
Stop the session’s Services instead of its agent |
mend shell [session-id-prefix] |
Open a shell in a live session workspace |
mend continue [session-id] |
Resume a session with its pending review follow-up |
mend resume [session-id] [--with <harness>] |
Restore provider state and resume a settled session |
mend rejoin [session-id] [--harness <harness>] [--no-tunnel] |
Attach when live, otherwise resume |
mend workspace replace <session> [--yes] |
The change’s owner replaces a workspace that started before Mend 0.36; prints what would stop first |
With no session ID, attach, stop and shell take the one live session; with several, a picker
opens. A prefix of the ID is enough. Other commands narrow candidates by the current project and
then use a picker when needed. An ID you give is the session acted on, by stop, resume, rejoin
and mend service logs alike.
A mend run session has no agent to resume: mend resume on its own harness is refused with
This run session has no agent to resume. Resume it as a shell, or start another session in its worktree.
In a session someone else owns, mend shell is refused: a shell is the owner’s alone.
Services keep running after mend stop, and a running Service keeps the workspace up. The stop then
says so, for example agent stopped · 3 services keep the workspace up. mend stop --services
stops them; once nothing is live, the workspace closes.
mend sessions --json is the stable automation output ("version": 1, flat sessions) and does not
change shape. mend sessions --json=v2 and mend worktrees --json emit the worktree-grouped
envelope ("version": 2); against an older server every session appears as its own worktree with
"id": null, so the shape is stable either way. Human-readable rows may change as the interface
improves.
In a per-person workspace, mend sessions adds a line under a
session when there is something to say about its workspace: who else is live in it
(Shared workspace with Anna · each of you runs as yourself · either of you can read the other's files.),
the waiting line while a turn waits for another person’s work, and the line of a workspace that
waits to be replaced. mend workspace replace is Replace this workspace now from a terminal: it
says when Mend last checked and lists what would stop (terminal sessions, shells, Services started
by hand, processes Mend did not start, running containers, anything it could not check), asks, and
replaces the workspace once its last save is done. It ends nothing that was not on that list: if
more would stop by then, the server refuses. An agent turn in flight is never stopped.
Deleting a session removes only the conversation record; the worktree, with its change and
checkpoints, remains. Removing a worktree is its own explicit act (mend worktrees rm <name>,
dashboard Shift+D, the web app’s worktree menu, or the API): refused while any session is live,
and refused while the worktree holds work that is not on origin (a change never landed, or one
changed since its last landing) unless forced. The refusal names the files and line counts that are
not on origin, and mend worktrees rm prints it as the server said it, with the --force form that
removes the worktree anyway. A worktree whose workspace is still saving is refused either way.
Landing commands
Section titled “Landing commands”mend land <session> [--branch <name>] [--no-pr] [--title <text>] [--project <name>]mend land <session> --check [--project <name>]mend pull <session> [--branch <name>] [--force] [--project <name>]<session> is a prefix of the session ID or the worktree’s name; settled sessions count.
mend land publishes a session’s change. Only the change’s owner lands it: the owner of the
worktree’s first session. Mend takes a checkpoint, commits what the agent left uncommitted on top of
the agent’s own commits and the last landing (never squashing or rewriting them, and never moving
the session’s branch), and pushes that to origin with the project’s git access, as mend/<name>
unless --branch names another. The project’s default branch and the pull request’s base are
refused, and a landing with nothing new since the last one pushes nothing and says so. The push only
fast-forwards: when origin’s branch has commits Mend has not seen, or origin refuses, nothing is
pushed and the command prints the remote’s own words. When origin is on GitHub, Mend then opens a
pull request into the session’s base branch or updates the one it opened before, through your
connected GitHub account; --no-pr skips it. The command prints the landing and what Mend observed,
and exits 1 when the push was refused or a step failed:
✓ pushed · mend/fix-login · 3f2a1c0 · pull request #412 · opened checkpoint 9e8d7c6 commit 1a2b3c4 · Mend's, for the work left uncommitted pull request https://github.com/acme/api/pull/412 observed pushed · mend/fix-login · 3f2a1c0 · observed pull request #412 · open · observed 0 s agoA pull request someone opened outside Mend, including one the agent opened with gh, is found and
recorded as the change’s own. Mend looks after the agent pushes a branch and when its turn ends, and
mend land --check looks now without pushing anything. It asks GitHub, as you, about the worktree’s
branch, every branch the agent pushed, and the agent’s head commit. A later landing pushes that pull
request’s branch and updates it. A pull request from a fork is shown and never updated: Mend pushes
to origin only, and opens no second pull request beside it. Read
Land a change.
mend pull fetches a change into the local clone you run it in, as a branch of the same name. Mend
commits the latest checkpoint the way mend land does, without pushing, and sends the commits from
the session’s base as a git bundle, so it works before landing and without origin. Only the change’s
owner gets a checkpoint taken first; anyone else gets the latest one, and pulling moves nothing on
the server. One of the clone’s remotes must be the project’s origin (ssh and https spellings match;
--force skips the check), and the clone needs the session’s base commit. The working tree, the
index and the current branch are not touched, and an existing local branch only fast-forwards. The
clone records each pull under refs/mend/pulled/<branch>, and the next pull asks the server to
build on it, so pulling again after more work fast-forwards. With nothing new, the branch stays and
the CLI says unchanged since the last pull · nothing moved. A branch that cannot fast-forward (you
committed on it, or the server no longer holds the last pull) is left as it is, and the CLI says
why; --branch <name> fetches into a new local branch instead. A bundle over the server’s
MEND_BUDGET_BUNDLE_BYTES limit is refused with its size.
Dashboard keys
Section titled “Dashboard keys”The session pane takes three quarters of the screen: a read-only detail of the selected session with
the conversation record it has written so far. The remaining quarter is a sidebar of three stacked
sections, projects, then worktrees, then sessions. The section you are in stands open, and the other
two fold to the line that says what is selected. Every pane carries its number in its title, as
lazygit numbers its panels: [1] projects, [2] worktrees, [3] sessions and [0] for the
session pane. Moving the selection only navigates; nothing takes the terminal until you press a
verb. The footer names the keys that apply where you are and always ends with ? keys.
| Key | Action |
|---|---|
↑ ↓, k j, PgUp PgDn |
Move inside the open section, or scroll the session pane |
1 2 3 0 |
Jump to projects, worktrees, sessions, or the session pane |
Tab, Shift+Tab |
Next or previous pane, coming back round at either end |
Enter, →, l |
Move right: projects, worktrees, sessions, then the session pane |
←, h, -, Backspace |
Move left |
Esc |
Clear this list’s filter, else go back to the pane you came from |
/ |
Filter the focused list; Enter keeps the filter, Esc clears it |
? |
List every key the dashboard answers to |
+ _ |
Cycle the screen mode: normal, half, full |
a |
Attach a live session |
r |
Resume a settled session, on a harness you pick |
n |
Start another session in the selected worktree; on the projects section, start a new worktree |
w |
Start a new worktree |
e |
Rename the session |
v |
Review the change |
o |
Open the session in the browser |
Shift+K or x |
Stop the session (press twice). On the worktrees section, stop every live session in it. On a session whose agent stopped while Services keep the workspace up, stop those Services |
Shift+D |
Remove (press twice). On the worktrees section, remove the worktree, refused while a session is live; elsewhere, remove the session’s record and keep the worktree |
Shift+R |
Refresh |
q |
Quit |
A session you start or resume from the dashboard attaches by itself once it runs, while you are
still waiting on it; moving to another session, column or project, or a dialog open at that moment,
leaves it to a. MEND_AUTO_ATTACH=off leaves every attach to a. A terminal too narrow for both
sides gives the whole width to the side you are on, and one too short for three sections shows the
open section alone. On a server that is not this machine, the selected session’s live Services
declared --http or --https are tunneled to this machine’s loopback while it stays selected,
unless you started the dashboard with --no-tunnel. See
Terminal dashboard and attach.
Terminal attachment
Section titled “Terminal attachment”The CLI sends terminal input and resize events over one WebSocket and replays recorded output before following live frames.
Press Ctrl+] to detach without stopping the process. Set:
export MEND_DETACH_KEY=nonewhen tmux, Zellij, or another outer tool owns detaching.
Ctrl+V with an image on this machine’s clipboard sends the image to the session and pastes its
path; Codex and Claude read it. It needs wl-paste on Wayland or xclip on X11, and nothing extra
on macOS.
Dotfiles commands
Section titled “Dotfiles commands”| Command | Purpose |
|---|---|
mend dotfiles |
Show the configured repository, with its manager, and the synced file snapshot |
mend dotfiles repo <url> [--ref <r>] [--subdirectory <d>] [--manager <m>] [--no-bootstrap] |
Set the repository the server clones at launch; <m> is auto, copy, stow or chezmoi; saving tries the clone once |
mend dotfiles repo --clear |
Remove the repository |
mend dotfiles sync |
Preview known dotfile candidates on this machine |
mend dotfiles sync --all |
Replace the snapshot with all discovered candidates |
mend dotfiles sync <paths...> |
Replace the snapshot with selected home-relative paths |
Read Dotfiles before syncing credentials or machine-specific files.
Secret file commands
Section titled “Secret file commands”| Command | Purpose |
|---|---|
mend secrets |
List your secret files: the path each takes in the workspace, its size, and when it last changed |
mend secrets add <path> [--from <file>] |
Keep a file at <path> under the workspace home, reading it from <file> or stdin; replaces one kept there |
mend secrets rm <path> |
Remove a secret file |
Every session you own receives your secret files before its agent starts. They are never captured. See Secret files.
Skills commands
Section titled “Skills commands”| Command | Purpose |
|---|---|
mend skills [--project [p]] |
List your skill library on the server, or a project’s |
mend skills push [--project [p]] [--prune] [--dir <path>] |
Upload skill bundles from ~/.agents/skills; sessions receive them at launch, and --prune removes server-side skills the directory no longer has |
Your library applies to every session you start; a project’s library applies to sessions in that project. See Skills.
Memory commands
Section titled “Memory commands”| Command | Purpose |
|---|---|
mend memory [list] [--project <p>] |
List your agent memory for the project |
mend memory show <file> [--project <p>] |
Print one file as Mend stores it; a name alone is Claude’s, another harness’s takes its prefix, as in codex:MEMORY.md |
mend memory rm <file> [--project <p>] |
Remove one file; the last version stays on the server, and running sessions keep their copy |
mend memory import [--project <p>] [--dry-run] |
Run inside the repository’s checkout: bring the memory Claude Code keeps on this machine for it, and Codex’s summaries of conversations held in it |
Mend keeps Claude Code’s and Codex’s memory per person per project: every session you start on the
project receives yours, and what the agent learned is saved back when it ends. Other people’s
sessions never see it. An import merges a file both sides have, keeping both sides’ lines, reads no
transcripts, logins or settings, and keeps every version it replaces; --dry-run shows the plan and
writes nothing. See Agent memory.
Git commands
Section titled “Git commands”| Command | Purpose |
|---|---|
mend keys init |
Create your Mend key (Ed25519), held on the server; one key per user |
mend keys show |
Print your Mend public key |
mend keys mode [mend-key|bridge] |
Show or set your Git access mode for new projects; mend-key is the default |
mend keys share |
Relay this machine’s SSH agent to the Mend server until interrupted |
mend git-author [<name> <email>] |
Show or set the name and email your workspaces commit as |
mend git-author --clear |
Go back to your account’s name and email |
Add your Mend public key to your Git account’s SSH keys and every repository you can reach works,
from detached sessions and the phone too. For one repository only, add it as that repository’s
deploy key instead. In bridge mode every attaching mend command and the dashboard relay this
machine’s SSH agent while they run, so mend keys share is for a machine that runs nothing else.
The server takes one signer at a time; a newer share replaces the older one. The bridge requires a
reachable local SSH agent, and signing happens on the machine holding the key. See
Git access.
The Git author is set as system Git config in each workspace before the agent starts (in a
per-person workspace, in your own home’s Git config), so a user section in your dotfiles’
.gitconfig or in a repository’s own config still decides. Until you set one, it is the name and
email you registered with. Sessions launched after a change commit as the new author.
Workspace SSH commands
Section titled “Workspace SSH commands”| Command | Purpose |
|---|---|
mend ssh [status] |
Report the gateway, this client’s registered key, and the managed ~/.ssh/config block; it does not test a connection or verify the host key |
mend ssh <session> [--host <hostname>] |
Print the exact ssh ws-<workspace>@mend-ws-… command for one session’s running workspace; the workspace id is not the container’s name |
mend ssh setup [--key <path>] [--host <hostname>] |
Register this client’s public key and write a server-specific Host block before wildcard defaults; the hostname defaults to the configured Mend URL and the server supplies the port |
mend ssh keys [--json] |
List the keys your account registered from every machine: fingerprint, name, algorithm, the day it was registered, and which one this machine offers |
mend ssh keys remove <fingerprint> |
Remove one of your keys; the gateway refuses it from the next connection, and the removal says whether connections already open with it end. Where the platform cannot end them, it lists each running session with the mend stop that ends it |
You see and remove only your own keys. mend uninstall --home removes the key this machine
registered before it revokes the terminal’s device token. The VS Code extension uses the same
configuration. Read
workspace SSH for identity
selection and host-key verification.
Service commands
Section titled “Service commands”| Command | Purpose |
|---|---|
mend service init [--yes] |
Scaffold mend.toml from package and Compose files |
mend service run [session] --port <port> [options] -- <command...> |
Start and supervise a Service command; tunnels the port here when remote |
mend service run [session] <name> |
Start a recipe from mend.toml |
mend service <name> |
Shorthand for a named recipe |
mend service add [session] <port> [--name <name>] [--udp] [--http|--https] |
Forward an existing workspace listener without supervising it |
mend service connect [name...] [--port <port>] |
Bring live Services to this machine’s loopback over an authenticated tunnel |
mend service list [--json] |
List live Services, their process ids and observed endpoints |
mend service logs <name-or-id> [--from <sequence>] |
Replay and follow recorded Service output |
mend service restart <name-or-id> |
Start another attempt for a supervised Service |
mend service stop <name-or-id> |
Stop the process and close its host port |
mend service run accepts --name, --port, --udp, --http, --https, --wait, --timeout
and --no-connect. Mend holds the start for up to a minute until the port answers. --wait keeps
waiting while the Service is still starting, which means its process runs and its port has not
answered yet (building, installing, booting), up to --timeout (a duration as mend wait takes it;
default 10m). Mend probes a started Service’s port every 20 seconds, so the wait ends at the first
probe that answers. The exit status says how it ended:
| Exit | What was observed |
|---|---|
0 |
The port answered |
1 |
Mend refused the start (no such session or recipe, not yours to run, the session not live) or did not answer |
2 |
The Service’s process ended before its port answered; the line says its status and exit code |
3 |
The server no longer has the session; its workspace went with it |
124 |
The Service was still starting when the timeout passed; it keeps starting |
The wait judges the one process this start began: the CLI sends a fresh id with the start, the
server stamps it on the attempt it begins, keyed by the Service (launchCorrelationId
service-start:<service id>:<id>), and the wait finds that attempt by it once, in the session it
started in, whatever another client starts, restarts or stops meanwhile. An id the same Service took
before is refused; another Service’s ids, another person’s included, are neither refused nor seen.
Its port answering counts only from a probe made after that attempt began. A process its workspace
took ends as exited · no exit code reported, exit 2: Mend records the end, not its cause. A
server older than the CLI stamps no id: a start whose answer an edge cut is then not followed, and a
command that exits inside the server’s minute reads as the refusal the server answers with, both
exit 1.
A waited start returns and opens no tunnel; mend service connect reaches the port. --wait takes
TCP ports only (UDP has no probe), and not a recipe that declares only a port, which Mend adopts
with one probe.
mend service list prints each Service’s id beside its current attempt’s process id, which is what
mend logs --process and mend wait --process take. --json prints one object: version 1 and
services, each with id, name, sessionId, processId (null for an adopted port), status
as last observed, workspacePort, protocol, and hostPort, authority and browserUrl (null
when not bound).
mend service run "$id" --port 3000 --name web --wait --timeout 15m -- pnpm devstatus=$?[ "$status" -eq 124 ] && echo "web is still starting; it keeps starting"[ "$status" -ne 0 ] && { mend logs --service web; exit "$status"; }On a server that is not this machine, mend attach, mend codex|claude|opencode|pi, mend rejoin,
and the dashboard tunnel the session’s live Services declared --http or --https to this
machine’s loopback while attached, on the Service’s own port when it is free. One line each says
where it opens (web → http://localhost:5173); a Service that stops closes its tunnel, and
detaching closes them all. --no-tunnel opts out.
Read Development services for network and authentication boundaries.
mend inside the workspace
Section titled “mend inside the workspace”Every session workspace has a mend command of its own on the PATH. It is not the CLI above and not
part of the workspace image: the server stages a small helper into the session’s run directory,
mounted read-only at /run/mend and linked to /usr/local/bin/mend, so it is always version-locked
to the server. It talks only to its own session, over the session socket (or the authenticated
session endpoint), and it speaks Services, repositories, mend land and mend stop:
| Command | Purpose |
|---|---|
mend service or mend service list |
List this session’s live Services |
mend service run --port <port> [--name <n>] [--udp] [--http|--https] -- <command...> |
Start and supervise a Service |
mend service run <name> or mend service <name> |
Start a recipe from mend.toml |
mend service add <port> [--name <n>] [--udp] [--http|--https] |
Adopt an existing workspace listener |
mend service stop <name-or-id> |
Stop a Service |
mend service restart <name-or-id> |
Start another attempt |
mend repo or mend repo list |
List the repositories added beside /workspace/repo, with their state |
mend repo projects |
List the projects of the store this session may add |
mend repo add <project> [--as <name>] [--worktree <name>] |
Add a project as a repository of this session at /workspace/repos/<name> |
mend land |
Land the session’s change as its owner: the push, the pull request, or why not |
mend stop |
Stop this session; the record and review remain |
The helper has no init, no logs, and no connect: history and reaching the endpoint stay on
your side. Its job is declaration: an agent that starts a dev server can register it as a real
Service instead of leaving an unobserved listener, and --http/--https says it is something to
open in a browser (refused with --udp). mend repo add makes the worktree, answers, and brings
the files in while the session goes on; read
Several repositories in one session for what a repository is
and how it is saved.
Organization commands
Section titled “Organization commands”| Command | Purpose |
|---|---|
mend members |
Print the organization’s name and one row per member: name, email, role and the day they joined |
mend invite [--role member|owner] [--email <address>] [--days <n>] |
Owners only. Print a single-use invitation link; default role member, default 7 days, at most 30 |
mend folder list |
List the organization’s folders |
mend folder create <name> |
Owners only. Create a folder; names use lowercase letters, digits, dots, underscores and dashes |
mend folder push <name> <dir> [--replace] |
Owners only. Upload a local directory into a folder; --replace empties the folder first |
mend folder rm <name> |
Owners only. Remove a folder; refused while a project still selects it |
mend session share <session> on|off [--yes] |
Turn shared control on or off for a session; turning it on asks first, --yes answers for a script |
Mend sends no email: you share the invitation link yourself. With --email, only an account with
that address can accept it. Owners change roles and remove members in Settings on the web.
A project selects which folders its sessions receive, at /workspace/home/<name>, read-only unless
chosen otherwise, from its next session. mend folder push skips .git and node_modules
directories, symlinks, and files over 1 MiB, and counts what it skipped. See
Folders.
Only the session’s owner turns shared control on; the owner or an organization owner turns it off. While it is on, anyone who can see the project sends turns, answers approvals and interrupts. In a workspace that shares one home they do so on the owner’s provider logins and Git access; in a per-person workspace each turn runs on its sender’s login. They can attach to the terminal to read it; only the owner types in it, opens a shell or starts a Service. Every act is recorded with who did it. See Organizations.
Operator commands
Section titled “Operator commands”These commands need the operator role, which the instance’s first account holds. The operator administers the instance and reads no organization content.
| Command | Purpose |
|---|---|
mend operator exposure |
Print MEND_EXPOSURE as declared and each item of the public exposure gate |
mend operator gate |
Print each item of the multi mode gate, what this instance shows, and what would satisfy it |
mend operator org list |
List organizations with their member and owner counts |
mend operator org create <name> |
Create an organization; only with MEND_TENANCY=multi |
mend operator org rename <org> <name> |
Rename an organization |
mend operator org invite-owner <org> [--email <address>] |
Print a single-use link that makes whoever opens it an owner of the organization |
mend operator grant-owner <org> <email> |
Make an existing member an owner, for an organization whose owners are gone or locked out |
mend operator reset-link <email> |
Print a single-use password reset link, valid for a day |
mend operator exposure marks each gate item observed (the server read it), carried (this build
contains it and the server cannot see it in effect), declared (you stated it and the server cannot
check it) or open. MEND_EXPOSURE=public refuses to start while an item the server can observe is
open. The report is what was observed; it does not say an instance is fit to expose. See
Exposure and budgets.
MEND_TENANCY=multi refuses to start while any item of the multi mode gate is open. Organization
changes the operator makes are recorded in that organization’s audit log. Setting a password with a
reset link signs the account out everywhere; owners reset their members’ passwords from Settings.
Shell completion
Section titled “Shell completion”mend completions zshmend completions bashThe generated hook completes command names and live session IDs. Add its output to the matching shell configuration. Fish completion is not currently generated.
CLI configuration
Section titled “CLI configuration”The CLI reads:
| Input | Default |
|---|---|
MEND_URL |
http://localhost:3105 |
MEND_TOKEN |
Saved token from the configuration file |
MEND_DETACH_KEY |
Ctrl+]; none turns the detach key off |
MEND_AUTO_ATTACH |
On; off keeps the dashboard from attaching a session you started there once it runs |
MEND_DEBUG |
Off; 1 prints each refused call, its status and its tag on stderr |
$XDG_CONFIG_HOME/mend/cli.json |
~/.config/mend/cli.json when XDG is unset |
The configuration file contains:
{ "url": "http://localhost:3105", "token": "<redacted>"}A legacy ~/.mend directory remains authoritative when it is the only Mend configuration directory
on the machine.
Exit behavior
Section titled “Exit behavior”One-shot commands print a readable server error and exit nonzero when a request fails. Interactive
commands return terminal control when the process ends or the user detaches. mend doctor does not
change server or machine state.
mend help <command> prints one command’s page and mend man <command> opens the same page in
man; the npm package also installs mend(1) and mend-<command>(1) for a global install.