CLI reference

Every devhost command, explained.

What each command does, its flags, and when to reach for it. devhost --help and devhost <command> --help print the short version of everything on this page.

Concepts

Three words carry the whole tool.

Account

One claude.ai login. At most one is the default; every other account gets its own config dir (~/.claude-<name>) and a root folder (~/work/<name>).

Environment

One project: a git repo under an account root, with its own Remote Control service, a block of 100 ports, and optional Docker Compose services.

Routing

direnv sets CLAUDE_CONFIG_DIR below each account root and git switches identity per root, so the right login and identity follow the folder.

Global flags & exit codes

These work on every command.

FlagMeaning
--jsonMachine-readable output in the versioned contract envelope. What the desktop app uses.
--verboseShow every command devhost runs.
-y, --yesNever ask questions; take the defaults.
--config <path>Use another config file (default ~/.config/devhost/config.yaml).
-V, --cli-versionPrint the CLI version.
Exit codeMeaning
0Success.
1A check or step failed (for example doctor found a ❌).
2Bad flags or arguments, or the config is missing or invalid.
3A human has to act first; the output says exactly what (for example macOS blocking Remote Login).
130Cancelled with Ctrl-C. devhost finishes the current step and leaves a consistent state.

Quick start

On the Mac mini, from a clone of the repository, ./bootstrap.sh installs the tools, builds devhost, then runs init, apply --dry-run and apply. After that:

# your main login
$ devhost account add --name personal --default --git-email me@example.com
# a second login: prints a URL, open it anywhere, sign in, paste the code
$ devhost account add --name business --git-email me@work.example
# one project, with its own Remote Control service
$ devhost env create --account business --name acme-api --repo git@github.com:acme/api.git
$ devhost doctor   # everything green?
$ devhost top      # live view, works over SSH from a phone

The environment now shows up in the Claude app under the business account and comes back by itself after a reboot.

Set up the machine

init

devhost init [--force] [--hostname <name>] [--docker colima|orbstack|none] [--no-tailscale]

A short wizard that writes ~/.config/devhost/config.yaml. It asks about three things (hostname, Docker runtime, Tailscale) and generates a random ntfy topic for notifications. Without a terminal it uses the flags and defaults.

FlagMeaning
--forceOverwrite an existing config; a timestamped backup is kept.
--hostname <name>The host's name.
--docker <runtime>colima, orbstack or none.
--no-tailscaleDo not set up Tailscale.
Treat the ntfy topic like a password. Anyone who knows it can read your notifications.

apply

devhost apply [--dry-run] [--only <step>…]

Makes this Mac match the config. It is idempotent: run it as often as you like, and only what differs is changed. Any existing file is backed up first, every sudo is announced with its reason, and it is safe to interrupt and re-run.

FlagMeaning
--dry-runPrint the plan, including every sudo command, and change nothing. Run this first.
--only <step…>Run only these steps. A prefix selects a group: system = all system.*, storage = all storage.*, env.acme-api = one environment.

The steps, in order:

StepDoes
system.helperWith machine.rootHelper: true, installs a small root helper plus a sudoers rule so later root operations need no password (the desktop app and ssh then need nobody at the keyboard).
system.hostname / power / sshHostname, pmset power settings (never sleep, restart after power loss, wake on network), Remote Login.
packagesRenders a Brewfile and installs what is missing (direnv, Colima/OrbStack + docker, tailscale, claude-code). Never upgrades; that is update.
runtimesInstalls mise and the language runtimes listed in the config.
shellA managed block in ~/.zshrc: Homebrew, mise, the direnv hook, claude-<account> aliases.
docker.config / runtimeCompose plugin path; Colima as a boot-time service with the configured resources.
tailscaleStarts the system daemon. Logging in is never automated; you get the tailscale up instructions.
claude.binaryRecords the absolute path of claude for the service definitions.
accounts.claude / routing / gitPer account: merged Claude settings (OTel and hooks), a managed CLAUDE.md block, the account root with its .envrc, git identity per root.
collectorThe collector LaunchDaemon (com.devhost.collector).
agentsThe subagent definitions chosen per account and environment.
storage.*With a work volume: identity check, ownership, Spotlight off, automount, boot-time unlock.
services.orphansFinds devhost services nothing owns any more and asks before removing them.
env.<name>Per environment: repo, .envrc, compose, and the Remote Control LaunchDaemon (KeepAlive, waits for the network at boot).
$ devhost apply --dry-run          # read the plan
$ devhost apply                    # do it
$ devhost apply --only agents       # after changing agent choices
$ devhost apply --only env.acme-api

config

devhost config get [pointer] · set <pointer> <value> · validate [--stdin] · edit · raw · put

Read and change the config file. Every write is validated against the schema first, comments are preserved, and a backup is kept. Changing the config does not apply it: run devhost apply afterwards.

SubcommandDoes
get [pointer]Print the effective config, or the value at a JSON pointer such as /machine/hostname.
set <pointer> <value>Set a value (JSON/YAML syntax) and validate the result. Nothing is written if it is invalid.
validate [--stdin]Validate the file, or a candidate document read from stdin.
editOpen the file in $VISUAL/$EDITOR and validate afterwards. Needs a terminal.
rawPrint the file exactly as on disk, comments included (used by the app's editor).
putReplace the file with the YAML on stdin, only if it validates.
$ devhost config get /machine/hostname
$ devhost config set /machine/hostname mini
$ devhost config validate

update

devhost update [--check] [--all] [--force] [--dry-run]

Updates Claude Code and moves everything onto it. It upgrades the claude-code cask, restarts every Remote Control service (they run from a versioned path the upgrade deletes) and lists the sessions that were open; each continues as soon as you write in it on claude.ai. It waits while a session is in the middle of a turn, and refuses to run from inside a session of a service it would restart.

FlagMeaning
--checkOnly report what is outdated.
--allAlso upgrade the other outdated Brewfile packages.
--forceDo not wait for sessions mid-turn; they are interrupted, then continue on claude.ai.
--dry-runOnly print the plan.

The collector can do this by itself inside claude.updateWindow (default 02:00–06:00) when claude.update is auto.

uninstall

devhost uninstall [--keep-packages] [--purge-data] [--purge-storage] [--dry-run]

Removes what devhost installed: launchd jobs (including orphaned com.devhost.* ones), the managed shell and git blocks, and generated files. It never touches your projects, Claude config dirs or credentials.

FlagMeaning
--keep-packagesLeave the Homebrew packages installed.
--purge-dataAlso delete the collector database.
--purge-storageAlso remove the work volume's unlock service and Keychain item (never the volume or its data).
--dry-runPrint what would be removed, including every sudo command.

Accounts & projects

account

devhost account list · add · remove <name>

list shows each account with its login state and environment count.

add creates the account's config, settings, routing and git identity, then starts the login. A second account prints a URL: open it on any device, sign in, paste the code back.

Flag (add)Meaning
--name <name>Required. Account name (a slug).
--git-email <email>Required. Git email for repositories under the account root.
--git-name <name>Git author name.
--defaultUse Claude Code's default config dir. Only one account may.
--daily-cost-alert <usd>Notify when the estimated cost today passes this.
--no-loginSkip the login; doctor will report it as not logged in.
--root <path>Account root (default: on the work volume if configured, else ~/work/<name>).

remove <name> is refused while the account has environments. It never deletes the root, the config dir or credentials; --logout also signs the account out.

login

devhost login <account> [--switch-login]

Signs an account in (again), accepts workspace trust and the one-time Remote Control confirmation for each of its environments, and restarts their services. Use it when doctor reports an expired login.

The first sign-in records which claude.ai login the account belongs to. A sign-in as anyone else is signed out again and refused, because the environments would otherwise register under the wrong claude.ai account. --switch-login accepts a new login on purpose and makes the account's environments register afresh.

env

devhost env list · create · delete · start · stop · restart · sleep · wake · logs

An environment is one project with its own Remote Control service.

SubcommandDoes
listEnvironments and their service state.
createClones the repo (or git init), allocates a port block, writes .envrc and compose, installs the service. Running it again continues a setup that failed part-way.
delete <name>Stops and removes the environment. Files stay unless --delete-files (shows unpushed commits and asks you to type the name).
start / stop / restart <name>Control the Remote Control service.
sleep <name>Stop the compose services that are running now, until a session uses the environment. Only those come back on wake.
wake <name>Start exactly the compose services the environment had running when it fell asleep.
logs <name>The service log. -n, --lines (default 50), -f, --follow.
Flag (create)Meaning
--account <account>Required. Owning account.
--name <name>Required. Environment name (a slug).
--repo <url>Repository to clone. Omit for git init.
--no-rcDo not install a Remote Control service.
--compose auto|offDetect and manage Docker Compose services (default auto).
--spawn worktree|same-dirWhere Remote Control sessions run; passed to claude remote-control.
--permission-mode <mode>Permission mode for its sessions; passed to claude remote-control.
--daily-cost-alert / --hourly-cost-alert <usd>Cost alerts for this environment. The hourly one names the session that spent most, to catch a runaway.
$ devhost env create --account business --name acme-api --repo git@github.com:acme/api.git --spawn worktree
$ devhost env logs acme-api -f
$ devhost env delete acme-api --delete-files

With docker.idleSleepMinutes (default 60) the collector puts an environment to sleep after an hour with no session and wakes it with the next one. A service you stopped by hand stays stopped.

agents

devhost agents list

Shows the subagent catalog (built-in explorer, reviewer, test-runner, docs, plus your agents.custom) and which agents each account and environment has. To change them, set agents: [name, …] on an account or environment with config set, then run devhost apply --only agents. Only files devhost marked are ever changed.

session

devhost session reattach <id> [--dry-run] · handoff <id> [--new]

reattach brings an ended Remote Control session back (for example after a reboot) by running it again in its worktree, once, as a one-shot service. It usually is not needed: writing in the session on claude.ai continues it under the environment's service. It only works for sessions a service started in its own worktree.

handoff prints a summary of a session so you can continue its work in a new one: the note written since the session's last activity, or, with --new, a fresh one. The note is written by a one-off fork of the session, which costs about one cache write of its context. The collector writes one on its own for a session that has been idle for 55 minutes, just before its prompt cache expires.

Watch it run

doctor

devhost doctor

Read-only health checks, each with a one-line fix: ✅ fine, ⚠️ worth a look, ❌ broken. Exits 1 if any check fails. It reads the service logs too, so an expired login shows up as devhost login <account>. Plugins add their own checks as plugin.<name>.<check>.

status

devhost status

A one-screen summary of the host, its accounts and environments.

dash & top

devhost dash [--range 1h|24h|7d|30d] [--account <n>] [--env <n>] [--model <n>] [--follow] · devhost top [--once]

dash prints a snapshot of sessions, tokens, cost and CPU/RAM; with --json it is the data behind the desktop app's dashboard. --follow streams a new snapshot every sample interval (NDJSON with --json). The filters narrow it to one account, environment or model.

top is the same data as a refreshing terminal table. It works over SSH, so you can check the server from a phone. --once prints once and exits.

Limits. Cost is an estimate (on a subscription it is API-equivalent value, not billed money). Plan usage limits are not in the telemetry. Per-session CPU/RAM covers Remote Control sessions only.

notify

devhost notify --test

Sends a test notification through your ntfy topic, so you know alerts reach your phone. Alerts cover memory, disk, cost limits, a restart loop and an expired login. The default relay is the public ntfy.sh; set notify.server to use your own.

collector

devhost collector run · logs [-n <lines>]

The collector is the one long-running process: it receives Claude Code telemetry and hook calls on 127.0.0.1:4318, samples CPU, RAM, Docker, disk and Tailscale, and stores everything in a local SQLite database. launchd runs it for you; run starts it in the foreground for debugging, and logs shows its log. It listens on localhost only and requires a bearer token.

More

storage

devhost storage list · status · wait · adopt · prepare · set-passphrase · migrate

Keep project data on an external work volume instead of the internal disk.

SubcommandDoes
listExternal disks and their APFS volumes.
statusWhether the work volume is mounted, its identity, encryption, ownership, free space, Spotlight.
wait [--timeout s]Exit 0 once the configured volume is mounted (default 300 s). Services start through this at boot.
adopt <volume> [--root p]Make an existing APFS volume the work volume (config plus a marker file on it).
prepare <disk> --label n [--encrypted] --yes-eraseErases the disk as APFS (encrypted with --encrypted, passphrase from stdin), then adopts it. CLI only, never from the app.
set-passphraseStore or rotate the passphrase in the System keychain (from stdin).
migrate --account a [--cleanup] [--dry-run]Move an account's root onto the volume. The old copy stays until a later --cleanup.
prepare is destructive. Everything on the disk is destroyed. It will not run without --yes-erase.

plugin

devhost plugin list · widgets · run · install · upgrade · uninstall · enable · disable

Plugins add actions, checks and dashboard cards. See the plugins page for what exists and how to build one.

SubcommandDoes
listPlugins with their actions and checks.
widgets [--param plugin.filter=value]The dashboard controls and cards of enabled plugins (what the app shows).
run <plugin> <action>Run an action. --env / --session pick the target, --dry-run shows what would change. Anything that removes or stops something asks first (--yes answers).
install <source>From GitHub: URL or owner/repo, with #branch or #tag. Stays off until enabled. --name, --enable, --dry-run.
upgrade <name>Fetch the latest commit; its configuration stays.
uninstall <name>Remove an installed plugin and its config entry. For built-in ones, disable.
enable / disable <name>Turn a plugin on or off.
Only enable code you trust. An enabled plugin runs code from its repository on this Mac, as you.

version

devhost version

Prints the CLI version, the --json contract version, the commit it was built from, and the Node and platform it runs on.