Commands
Workspaces of git worktrees for coding agents: dev servers on stable ports, env bindings between projects, Claude Code and editors launched in place. Every command prints rows or --json. Bare crew starts crew's server when needed and opens its page — Set up (projects, workspaces, worktrees, machines, settings) and Voice OS — in the browser; with no terminal, over SSH or with --no-open it prints the link.
Lists print tab-separated rows; --json gives the same data as JSON. crew help <command> prints the same page in the terminal.
crew workspace
The workspaces table (what crew ls workspaces prints) and a pointer to where they are configured now: crew's page in the browser (run crew). The terminal UI that lived here is gone.
crew workspace
Output: <name>\t<n> projects\t<worktree>,<worktree>
Everything the page does is a command: crew add workspace …, crew rm workspace , crew add worktree, crew rename worktree, crew duplicate, crew rm worktree, crew rm The projects table (what crew ls projects prints) and a pointer to where they are configured now: crew's page in the browser (run crew). The terminal UI that lived here is gone. Output: Everything the page does is a command: crew add project Add a project, workspace, worktree, or binding (CLI) Register a project in the global pool by its git URL (git@…, https://…, ssh://…, file://… — a full URL, not owner/repo): the repo is cloned into ~/.crew/projects/ Output: Create a workspace, or add projects to one — any number in one call, the workspace created if it does not exist (and taken back if a name fails the pre-flight). Every name is checked before anything happens; then the members are recorded and, in every worktree of the workspace, one runner per new project starts in the background (checkout, install, smoke of its own servers) — Output: Make a new working copy of every project, in the background: the worktree is recorded, its ports reserved, and one runner per project starts (a window of tmux session crew-setup- Declare an env variable a project needs, and how crew computes it at dev-server start. Value is a template: {{proj}} is http://localhost: Pin a variable for one worktree. Beats whatever the binding would resolve, and is the acknowledgement for a binding that legitimately never resolves here — it stops printing as an anomaly on every start. Key is VAR, or project.VAR to pin one project when two share a name. View and edit crew settings (server IP, SSH host, proxy port, domain). Bare, it is config show; the page's Settings edits the same keys. Show all settings as tab-separated key/value pairs Output: Set a config value. Valid keys: server_ip (LAN IP for dev proxy), ssh_host (for remote editor), proxy_port (reverse proxy port, default 80), proxy_https_port (the proxy's HTTPS port, default 443, -1 turns HTTPS off), domain (custom domain, default server_ip: ipconfig getifaddr en0 (Wi-Fi) or tailscale ip -4. Detected from the first non-loopback interface when unset. domain: needs wildcard DNS (*.dev.example.com) resolving to server_ip; the default <server_ip>.nip.io needs nothing. A changed server_ip, domain or proxy_port takes effect on the next crew dev start|restart --proxy — the proxy is relaunched when its settings differ. Rewrite the tmux config crew manages (~/.crew/tmux.conf) to the current default. Only a file crew wrote is touched. List what crew is running: tmux sessions, and processes that leaked out of them. Loose processes are only those whose parent has exited while still working inside the workspace tree — anything still attached to a live process (a running Claude session, an editor terminal) is reported as left alone. Output: Stop every crew session and reclaim the processes that leaked out of them, without rebooting. Prints the commands to restore what it stopped. Processes with a live parent are never killed, and crew's server (tmux crew-server) is listed as kept, never stopped — crew server stop is its stop. List workspaces, worktrees, projects, bindings, overrides, a workspace's base branches or the plain Voice OS sessions (tab-separated output for scripting; --json for data) List all workspaces with project counts and worktree names. --json adds each member with its mode and the binding wires between the members (ok: the target is in the workspace too). Output: List every working copy — one row per worktree, across all workspaces or one, and a kept check (crew check project) as check/ Output: List all registered projects: the path crew keeps the checkout at, and the git remote that names it (- when the checkout has none — such a project exports as config only). Output: List a project's bindings as declared: the var, the server it is scoped to (- when it applies to every server), the template. With --check, resolve each against a real worktree and show the value it would get there, or why it would be left alone. Output: List a worktree's overrides Output: The base branches a new worktree of the workspace branches from — the table crew add worktree opens with: each project's base, how many commits it is behind origin (fetched now; -1 when unknown), and the branch the canonical checkout is on. crew add worktree Output: --json: [{project, base, current, behind, ahead, error?}] The plain Claude sessions Voice OS runs on this machine beside the worktrees (crew chat add): each one's ref, name and folder. Output: --json: [{"id","dir","name","created"}] Plain Claude sessions for Voice OS: a conversation in a folder of your choosing, with no workspace, worktree or crew orientation. Voice OS lists each as chat/ Records a plain session in a folder that already exists (home when none is given; ~ is home) and prints its ref. Voice OS lists it within seconds; activate it there to start its Claude. Output: --json: {"id","dir","name","created"}; the line goes to stderr. A folder is never created. Drops a plain session's record (chat/ Output: --json: the removed {"id","dir","name","created"}. Show all projects in a workspace with their paths in that worktree and their mode Output: Check a worktree the way creating it does, one runner per project in the background: a project with no checkout is checked out and installed, one whose last install failed is installed again, and every project's servers are smoked — started, watched until each listens, dies, or a minute passes, stopped. Each runner writes or clears its own project's verdict. Name projects to verify only those — the one you just fixed — while the rest keep their record. Returns at once with crew setup status as the way to watch; --wait stays to the end and exits 1 if anything is recorded. Refuses while the worktree's servers are running (it would restart them) or while a setup is already running on it. Open Claude Code in the worktree with what failed in front of it: each issue with its stage (checkout, install, smoke), the error or log tail, the env anomalies, and the instruction to fix the cause and run crew verify. With nothing recorded it checks the running servers (crew dev check) or, with none running, runs verify first, so it is one command either way. Replaces the crew process. --print writes that same prompt to stdout instead — for an agent that is already here; without a terminal that is what happens anyway. --json is the recorded health as data. Run Claude Code in the worktree, in this terminal — the worktree page's 'Claude in terminal'. Permissions skipped, every project passed with --add-dir, the orientation prompt injected (the projects, their paths, and a crew section on driving the servers). Replaces the crew process. Open the worktree in the local editor (Cursor, else VS Code) with the orientation prompt written and Claude wired up — the worktree page's 'Editor + Claude'. For a remote-SSH URL instead, see crew code. Start a shell in the worktree directory — the worktree page's 'Shell here'. Replaces the crew process; exit returns to where you were. Print the remote-SSH URL that opens the worktree in Cursor/VS Code on another machine. Requires ssh_host (crew config set ssh_host Generate and print the orientation prompt for a workspace — the project list, working directories, and worktree/direct framing. Ends with a crew section: the ref, and the commands that session should drive the servers with. Every launch (crew claude, crew edit, the page) injects it; paste it into a Claude opened some other way. Interactive (TUI). The terminal's one interactive view: pick a worktree (bare), then launch on it — Editor + Claude or Claude in this terminal (both skip permissions), remote editor, shell. Its servers show read-only, with l for their logs; starting, stopping, verifying and fixing are crew's page (run crew) or the commands. crew opens the same page. Without a terminal: crew claude and crew edit are the launch rows as commands; crew ls worktrees lists what there is to pick. The page's last line names where to manage the worktree: crew's page (its link without the sign-in token) and crew dev start . Manage dev servers and reverse proxy. Each project can have named dev servers that run in tmux windows behind a shared reverse proxy. Detect a dev server for a project from package.json (a dev script, else start) and print it; --apply records it as a server named after the project. Detection cannot know the port, so --apply needs --port. crew dev add is the full form. Output: Add a dev server to a project. The --port is for reference only — at runtime, crew assigns a random free port via the PORT env var. Without --port the process does not listen (a worker, a queue consumer): crew runs it with no PORT, hands out no URL, and a smoke only checks it stays alive. The command runs with PORT= Sibling URLs come from env vars the project reads at start — the ones crew add binding fills. Remove a dev server configuration from a project. Bindings scoped to that server go with it, and are named. Show configured dev servers for a project (not necessarily running) Output: Start all dev servers for a worktree in tmux windows. Ports are always allocated fresh — the configured --port is reference only — and a worktree keeps its ports across restarts, so any number of worktrees can run at once. URLs are http://localhost: --proxy: one reverse proxy on <server_ip>:<proxy_port> (crew config show) serves every running server at http:// A URL works here but not on another device: open http://<server_ip>:<proxy_port>/ there first. Loads: the hostname is the problem — Does not load: the device cannot reach this machine — different network, guest/AP isolation, VPN, cellular. Tailscale sidesteps both: crew config set server_ip Stop dev servers. Without an argument, stops every running dev server (crew's server is not one — crew server stop). A bare workspace name stops all of its worktrees. Stop and restart dev servers for a worktree Show running dev servers and their URLs. Without an argument, shows all. A bare workspace name shows all of its worktrees. A proxied worktree whose proxy is down gets a ! line on stderr. Output: Look at a worktree's running servers the way the smoke does: a pane that exited is died; one that runs without anything accepting on its port is not listening — a failure when some binding points at it, a note when nothing does. Bare, it is one look; --wait watches each server until it listens, dies, or a minute passes — the thing to run right after a start. Exit 1 on any failure; crew fix --print then carries the evidence. To prove a project's config from a fresh checkout instead, crew check project. Output: The shared reverse proxy: whether its session is up and answering, what domain and port it was launched with, its status page URL, and whether HTTPS answers. The proxy also serves every hostname over HTTPS (default port 443) with a certificate from crew's own CA, one CA per domain, which can only vouch for that domain. trust prints the CA, its SHA-256 and the steps to trust it on a Mac, iPhone or Android — needed once per device, for example for the microphone in Voice OS; --install trusts it on this Mac. stop kills the proxy alone — worktrees keep running on their ports; crew dev restart --proxy brings the hostnames back. Output: Print the log for a dev server. Logs are truncated each time the server starts, so they only cover the current run. Use -f to follow live output, --lines for just the end. --json: {ref, server, lines: []} — the lines as clean text (terminal escape sequences, carriage-return redraws and pure-control lines removed); not with -f. Interactive (TUI). The launch page for a worktree — the same as crew launch : launch rows, its servers read-only, l for their logs Starting and stopping are crew dev start / stop / restart, or crew's page (run crew). Remove workspaces, projects, or workspace projects. Without subcommand, removes an entire workspace (stops dev servers, removes worktrees, directory, and JSON). Remove a project from the global pool, and with it the clone crew made under ~/.crew/projects (to the trash — never a path of yours, which is left alone and said so). Refused while any workspace lists the project or a check of it is kept — the workspace names it, and every worktree is a git worktree off that clone: crew rm workspace Remove a project from a workspace (removes its checkout from every worktree). --dry-run removes nothing and lists each checkout it would take with what is in it: uncommitted files, commits not on the base, size. Output: Remove one worktree — its checkouts (to the trash), their crew/ Remove a binding from a project — the project-wide one, or with Remove a worktree override; the binding applies again Rename things. One noun so far: a worktree. Rename a worktree: its directory, every checkout's crew/ Output: --json: {from, to, warnings: []} Duplicate a worktree within its workspace — fresh checkouts of the same projects, with the source worktree's overrides copied across before its runners start. Made the way crew add worktree makes one: in the background, one runner per project; --wait stays to the end. Print a project's resolved env for a worktree, against the dev servers currently running there. stdout is pure KEY=VALUE so it can be eval'd; the full table and any variables left alone go to stderr. Output: Run a command inside a project's checkout with its bindings resolved into the environment — the project-wide set, or one dev server's with Move pre-worktree workspaces to the nested layout. Write projects and workspace membership to a file for another machine — everything unless --projects narrows it; - as the file writes the bundle to stdout (narration to stderr), which is how crew's page downloads it. Projects carry their dev servers, bindings, setup and env commands and origin remote; workspaces carry which projects, in which mode. Worktrees, ports and overrides stay local. A project is written by its git remote — no path — so the other machine clones it; one whose checkout has no remote still exports (config only) and is named as such. Bring a crew export into this machine. A project is its git remote: one already here under the same remote is left alone (r replaces its config), one not here is cloned into ~/.crew/projects/ Output: Prove a project reproduces from nothing, before it joins a workspace. A fresh checkout of the project's canonical repo run through the setup runner — mise, install, env command, a smoke of its own servers — as the target check/ Output: Clear what crew leaves behind and nobody comes back for — the sweep every crew command runs at most once an hour, now, plus a git worktree prune on every pool repo: failed checks older than seven days; runner files, dev logs and route files of worktrees and checks that no longer exist (a slug whose dev or setup session is still alive is left alone, and so is the route of crew's running server); lock files with no record behind them, older than an hour; the trash. Every path is under ~/.crew or it is refused. --dry-run lists without removing. Output: What crew needs on this machine and whether it is there: tmux (dev servers, setup runners and checks run in it) and git are required, Claude Code is optional (Voice OS and crew claude run it). Exits 1 when a required tool is missing. --install shows the install commands (Homebrew or xcode-select on macOS, apt-get, dnf or pacman on Linux) and runs them after asking; Claude Code is asked for separately. Without a terminal, --install needs --yes and installs Claude Code only with --with-claude. The first crew command that needs tmux or git offers this once. Output: Removed checkouts are moved to ~/.crew/trash and deleted in the background, so removal returns at once. This shows what is still clearing; 'crew trash empty' deletes it now, for when the background delete never finished. Output: Follow the debug log (~/.crew/debug.log): every tmux, git, editor, package-manager, mise and trash command crew ran, with errors. Binding values are never logged. --tail prints the last lines and returns; --json parses them. Output: Re-run every project's install steps in a worktree (or the named projects'), one runner per project in the background: mise install, then the lockfile's package manager (uv sync, pnpm install, yarn, bun install, npm ci) or the project's explicit setup command, then the project's env command when it has one, then a smoke of that project's servers. Idempotent — the fix for an install that failed when the worktree was created. Returns at once; crew setup status is how to watch, --wait stays to the end. Refuses while the worktree's servers are running (the smoke would restart them; --no-smoke) or while a setup is already running on it. What each project's runner has done on a worktree — creation, a verify or a setup, still going or finished: one row per project with its state (starting, running, ok, failed, interrupted), its steps in order with how long each took, the running one marked, a failed one with its reason. A runner that vanished without a verdict (a killed window, a reboot) reads as interrupted and is recorded on the worktree as a failure. Exit 2 while any runner is alive, 1 once all stopped with anything recorded, 0 otherwise — an agent polls this and acts on the first failure while the rest still install. --wait stays until no runner is left (then 1 or 0). Output: The last lines of one project's runner log: its steps as they finished and everything its install printed — live while it runs, kept afterwards. What to read when a step is taking long. --json: {ref, project, lines: []}, cleaned as dev logs --json cleans them. Stop every dev server and crew's server, and remove the crew binary. ~/.crew — workspace config and every worktree checkout — is kept unless --purge is given, which removes the checkouts through git and deletes the directory. crew's server: one process serving crew's page — Home, Set up (every project, workspace, worktree, machine and setting, with a Setup with Claude chat per machine) and Voice OS, the voice and web cockpit for the Claude Code sessions of every worktree. Bare crew starts it and opens the page; bare crew server is its status. It runs in tmux session crew-server on a remembered port with a route on the dev proxy; crew dev stop and crew kill leave it alone, and a server started before the rename (crew-dev-os) is still recognised and stopped. The first start downloads Voice OS from the release matching this crew (crew update refreshes it once installed, never restarting a running one). Voice needs an Anthropic key (kernel and narrator) and a Soniox key (speech): the page asks for them, crew server keys set takes one on stdin, and crew server start at a terminal asks for any that is missing; a running server picks a new key up from your next words. Keys live in ~/.config/crew-voiceos, readable by you alone, never in the shell environment. Output: crew voice … is the same command under its old name, kept for good (Voice OS and older machines call it); bare crew voice still starts the server. At a terminal it notes the new name on stderr. Every start first checks what it needs — tmux, and for crew server start Claude Code on PATH too — and names what is missing with its fix. Bare crew needs tmux only and never asks for keys. Start the server when it is not answering — checking tmux and Claude Code first, downloading Voice OS on the first run, asking at a terminal for a missing key — and print its sign-in links: the localhost one has microphone access, and so does the HTTPS proxy one on any device that trusts crew's CA (crew dev proxy trust). Opens the localhost link in the browser at a terminal on this machine's screen. Output: Stop the server and every Claude session it runs; their conversations resume on the next start. Stop and start the server in a helper of its own (detached), so a restart asked from a Claude session the server runs still finishes; then the links, as start prints them. Whether the server runs and answers, its port and its sign-in links. What bare crew server prints. Output: --json: {running, healthy, port, pid, localhost_url, url, secure, binary, warning?} Voice OS's API keys: bare (or status) lists each with whether it is set and where — never its value; set reads one from stdin (hidden at a terminal), checks it with the service, and saves it owner-only — a rejected key is not saved. A running server picks it up from your next words. Output: Each key: set or missing, and its file. Read a key from stdin, check it, save it. --json: {saved, path}. Voice OS's log, filtered, from every machine at once. On the main (Voice OS runs here, or machines.json lists machines) it reads the main's own log and asks every remote over SSH in parallel (BatchMode, 20 s each), merging the lines by time; a machine that does not answer is named on stderr and in unreachable, and the rest still print (exit 1 only when no machine answered). On a remote it asks the main through the remote daemon's link and prints what the main would; when the main is not connected it shows this machine's own log with a warning. Reads the rotated files too (voiceos.log, .1 … .5) and never the debug notes beside them. Unknown flags are an error. Output: --json: {"lines":[{ts,machine,level,cat,msg,fields}],"unreachable":[{machine,name,reason}]}. Warnings go to stderr ("! asking 2 machines…", "! vm2 (build box) unreachable: …", "! vm1 runs an older crew; run crew update there"). The debug notes said to Voice OS ("debug note: …"), newest last. n is the note's position in debug-notes.jsonl, so a filtered list keeps the numbers show takes. They live on the main; a remote asks the main through its link and fails with the reason when the main is not connected. Output: --json: {"notes":[{n,at,view,text}]}. One debug note whole — what was said, the kernel's words, what was heard on that screen, the sessions, what was waiting, what was said last — then the main's log lines within its time ± --around. Says so when that stretch of the log has rotated out. Output: --json: {"note":{n,at,text,said,view,heardHere,sessions,asks,spoken,devOffer},"lines":[…as logs]}. Your own notes said to Voice OS ("note for store front: …"), one list per workspace. Bare, the general notes; a workspace is named as Voice OS names it (any case, spaces become dashes); --all lists every workspace's. They live on the main; a remote asks the main through its link and fails with the reason when the main is not connected. Output: --json: {"notes":[{workspace,at,text}]}. A note's time is the main's local clock. Make this machine a remote: another machine's Voice OS (the main) drives the Claude sessions of its worktrees over SSH, and it runs no voice or kernel of its own. Bare, it checks tmux and Claude Code, installs Voice OS if needed, and starts the daemon (a tmux session that outlives any SSH link, listening on a socket only you can open); status reports it, stop ends it and every session it runs. A machine is a main or a remote, never both: each refuses while the other runs. After crew update the daemon moves to the new release on the next connect, at once (sessions at work are cut off and resume on the new release); a main on a newer release runs crew update here itself when this machine is behind. The main reaches it with ssh Output: The other machines this Voice OS drives (~/.crew/voiceos/machines.json; Set up's Add machine and "rename vm1 to build box" write the same list). add takes an SSH host — an alias from ~/.ssh/config or user@host, reached with your keys and never a password prompt — and prints the machine's id (from the host); --name is what you call it aloud. A running Voice OS picks a change up within a second. ls shows each machine's status as the running Voice OS last saw it (connecting, syncing, connected, unreachable, error), or stopped when Voice OS is not running. rm stops driving that machine; its sessions keep running there. Output: Each machine with what the running server last saw of it. --json: [{id, host, name, status, detail?}]. Add a machine by SSH host; prints its id. Stop driving a machine; its sessions keep running there. Call a machine something else. Voice OS in a Discord voice channel: while Voice OS runs, a bot of your own joins one channel of one server and takes only your voice there (the server owner's, or --user's). Sessions on any machine can post there with send. Optional — crew server start never asks for it. Checks the bot token with Discord (rejected → nothing saved), saves it to ~/.config/crew-voiceos/discord.key (owner-only), then decides the server (the only one the bot is in, else --guild), whose voice it takes (the server owner, else --user), and the channel (one named Voice OS, else the only voice channel, else --channel by name or id), checks the bot may View Channel, Connect and Speak there, and writes ~/.crew/voiceos/discord.json, which a running Voice OS watches. Each decision prints as one line; a choice it cannot make lists the options ( Output: The token is read from stdin (hidden at a terminal), never from an argument; it is never printed or logged. A rerun with nothing pasted reuses the saved token, so --guild or --channel after an error needs no paste. Only the one user's voice is heard: Voice OS ignores everyone else in the channel. --json: the discord.json object {guild,channel,channel_name,guild_name,owner,text_channel?,text_channel_name?}; the lines go to stderr. The setup (server, channel, owner, whether the token is saved) and what Voice OS last reported from ~/.crew/voiceos/discord-status.json: connected, whether the owner is in the channel, its error. Output: --json: {"set_up":bool,"token":bool,"config":{guild,channel,channel_name,guild_name,owner}|null,"live":{connected,owner_in_channel,error,at}|null}. live null: Voice OS has not reported. The channels of the server a message can go to — text and announcement channels, and the voice channels' own chats — for picking --text-channel. Output: --json: [{"id","name","kind":"text"|"voice","is_voice":bool,"is_current":bool}]. Posts a message to the developer's Discord: where messages go (the voice channel's chat, or the text channel picked with --text-channel), with the bot token. Works from the main and from any remote: a remote stages it and the main fetches and posts it. For a session to use when the developer asks it to send something there. Output: Text over 2000 characters goes as message.md. Every limit is checked before anything is sent: a message posts whole or not at all, and mentions in it never ping anyone. --json: {"channel","channel_name","message","link","files":n,"text_attached":bool,"is_voice_chat":bool}; the line goes to stderr. Voice OS leaves Discord: removes discord.json and the saved token. Output: One dev build of crew and Voice OS on every machine, to try a branch with your remotes before it is released. Any machine — the main or a remote — pushes its own checkout. Builds crew and Voice OS from the crew checkout you are in, once per OS and CPU your machines run, stamped dev- Output: Needs go and bun on the machine you push from. Returns once the push starts; crew server dev status follows it. crew update on a machine goes back to the latest release. The last push, read on the main (a remote asks it): its version, source, phase, and each machine — copied, restarted, skipped or failed with why. Output: --json: {version,source,started_at,phase,running,error,machines:[{id,name,host,target,skipped,staged,installed,error}]}, or null before any push. Update crew to the latest release (and an installed Voice OS with it, never restarting a running server — crew server restart does that). --check only asks whether there is one. Output: Show help for any command. Use --json for machine-readable output of the full command tree.crew projectcrew project
<name>\t<path>\t<remote|->crew addcrew add projectcrew add project <name> <url> [--setup=<cmd>] [--env-cmd=<cmd>] | crew add project <url> [--setup=<cmd>] [--env-cmd=<cmd>] | crew add project <name> --path=<dir> [--setup=<cmd>] [--env-cmd=<cmd>] | crew add project <name> [--setup=<cmd>] [--env-cmd=<cmd>] [--path=<dir>] | crew add project --scan
--scan: <name>\t<path>\t<remote|->\t<known|new>
<url> — Alone, the URL names the project: the last segment of the repo (git@github.com:example/store-api.git → store-api)--scan — List the checkouts this machine already has — ~/code, ~/projects, ~/dev, ~/src, ~/Developer, ~/work, ~/repos, three levels deep, never Documents, Desktop or Downloads; hidden folders, node_modules and git worktrees skipped — each with its remote and whether the pool knows it (by path or by the same repo). Lists only; adding is crew add project --setup=<cmd> — Command that installs a fresh checkout, replacing lockfile detection (mise still runs first). On an existing project, updates it; empty clears it.--env-cmd=<cmd> — Command that writes a fresh checkout's env files (make get-env — sops, a vault); runs after the install, over the .env crew copied in. Must write files, not print values — its output is logged. On an existing project, updates it; empty clears it.--path=<dir> — A checkout you already have, adopted as the canonical instead of a clone; on an existing project, where its canonical checkout now lives (the repo moved)crew add project my-api git@github.com:example/my-api.git
crew add project git@github.com:example/store-api.git
crew add project --scan --json
crew add project signals git@github.com:example/signals.git --env-cmd="make get-env"
crew add project checkout-api --path=~/repos/checkout-api --setup="make sync"
crew add project checkout-api --path=~/code/checkout-api
crew add workspaceadded means recorded and installing. A checkout or install that fails keeps the member, recorded on the worktree for crew fix / verify. --wait stays until every runner is done and reports failed rows.crew add workspace <name> [<project> ...] [--direct] [--wait]
<project>\t<added|failed>\t<worktree|direct>\t<detail>
<project> — A pool project (crew ls projects); the pre-4.0 colon form with a role after the name is refused — roles are gone--direct — Attach the canonical checkouts instead of creating worktrees. Changes are NOT isolated. Only one workspace at a time may direct-mount a given project.--wait — Stay until every runner is done; rows then say added or failed, exit 1 on any failurecrew add workspace feature-auth
crew add workspace feature-auth my-api
crew add workspace store-front store-api store-app checkout-api
crew add workspace quickfix my-api --direct
crew add worktreecrew add worktree <workspace>/<name> [--pull] [--no-install] [--no-smoke] [--wait]
--pull — Fast-forward each project's local base branch to origin first. Never touches a checked-out feature branch; refuses when the base has diverged or is checked out with uncommitted changes.--no-install — Check out only; skip mise and package installs (and so the smoke)--no-smoke — Skip the smoke start--wait — Stay until every runner is done: the table live in a terminal, then the summary; exit 1 if anything is recorded. --json then carries the healthcrew add worktree store-front/wrk3
crew add worktree store-front/wrk3 --wait
crew add worktree store-front/wrk3 --no-install
crew add bindingcrew add binding <project>[/<server>] --var=<VAR> (--url=<proj[/server]> | --host=<proj[/server]> | --port=<proj[/server]> | --value=<template>) [--dry-run] | --scan [--apply]
--var=<VAR> — Environment variable to set--url=<p[/s]> — Shorthand for --value='{{p/s}}' — http://localhost:--host=<p[/s]> — Shorthand for --value='{{p/s.host}}' — localhost:--port=<p[/s]> — Shorthand for --value='{{p/s.port}}' — just the port number--value=<t> — Full template, for composition (e.g. ws://{{signals.host}}/rtc)--scan — Read the project's .env (the server's dir, for --apply — With --scan, add every unambiguous proposal--dry-run — Save nothing: check the binding the way the add would and print its value in every worktree of the project (ref, value or why it is left alone, running or stopped); exit 1 with the reason when it would be refused. --json: {var, server, value, error?, previews: [{ref, value, resolved, running, detail}]}crew add binding checkout-api --var=STORE_API_URL --url=store-api
crew add binding admin/homepage --var=STORE_API_URL --url=store-api
crew add binding checkout-api --var=SIGNALS_URL --value='ws://{{signals.host}}/rtc'
crew add binding checkout-api --var=SIGNALS_AGENT_NAME --value='{{worktree}}'
crew add binding checkout-api --scan
crew add binding checkout-api --scan --apply
crew add binding checkout-api --var=STORE_API_URL --url=store-api --dry-run --json
crew add overridecrew add override <workspace>/<worktree> <VAR>=<value>
crew add override store-front/wrk2 STORE_API_URL=https://dev-api.store.com
crew add override store-front/wrk2 checkout-api.API_URL=https://tutor.dev
crew configcrew config [show|set|refresh]
crew config showcrew config show
<key>\t<value>crew config setcrew config set <key> <value>
crew config set server_ip 192.168.1.50
crew config set ssh_host my-dev-vm
crew config set proxy_port 8080
crew config set domain dev.example.com
crew config refreshcrew config refresh
crew config refresh
crew pscrew ps [--json]
<kind>\t<pid>\t<session|cwd>\t<command>crew ps
crew ps --json
crew killcrew kill [--dry-run]
crew kill
crew kill --dry-run
crew lscrew ls workspacescrew ls workspaces
<name>\t<n> projects\t<worktree>,<worktree>crew ls worktreescrew ls worktrees [<workspace>] [--size]
<workspace>/<worktree>\t<path>\t[<size>\t][dev|installing][\t<recorded failure>]
--size — Add bytes on disk per worktree. Walks every file — slow on one with a full build insidecrew ls worktrees
crew ls worktrees store-front
crew ls worktrees check
crew ls worktrees --size
crew ls projectscrew ls projects
<name>\t<path>\t<remote|->crew ls bindingscrew ls bindings <project> [--check=<workspace>[/<worktree>]] [--preview]
<var>\t<server|->\t<template>[\t<resolved value>][\t→ <preview>]
--check=<ref> — Resolve against this worktree--preview — Every binding's value in every worktree of the project — running, or what the stopped ones get back on their next start; the cell is the first resolved value, else why it is left alone. --json: rows with previews: [{ref, value, resolved, running, detail}]crew ls bindings checkout-api
crew ls bindings checkout-api --check=store-front/wrk1
crew ls bindings checkout-api --preview --json
crew ls overridescrew ls overrides <workspace>/<worktree>
<key>\t<value>crew ls overrides store-front/wrk2
crew ls basescrew ls bases <workspace>
<project> <base> <n behind origin/<base>|up to date>[ (checkout is on <branch>)]crew ls bases store-front
crew ls bases store-front --json
crew ls chatscrew ls chats [--json]
chat/<id>\t<name>\t<folder>crew ls chats
crew chatcrew chat add [--dir=<path>] [--name=<name>] | rm <id>
crew chat addcrew chat add [--dir=<path>] [--name=<name>]
added\tchat/<id>\t<folder>
--dir=<path> — The folder it runs in; must exist (default home)--name=<name> — What it is called aloud and on the page (at most 60 characters)crew chat add --name=research
crew chat add --dir=~/notes --name="Weekly notes"
crew chat rmcrew chat rm <id>
removed\tchat/<id>\t(the folder <folder> is untouched)crew chat rm chat/3fa9c1
crew showcrew show <workspace>[/<worktree>]
<name>\t<path>\t<worktree|direct>crew show feature-auth
crew verifycrew verify <workspace>[/<worktree>] [<project>...] [--wait]
<project>... — Only these projects; the others keep whatever is recorded--wait — Stay until every runner is done; then the issues, exit 1 on anycrew verify store-front/wrk2
crew verify store-front/wrk2 --wait
crew verify store-front/wrk2 store-api
crew fixcrew fix <workspace>[/<worktree>] [--print]
--print — Print the fix prompt (issues, evidence, anomalies) instead of opening Claude; no terminal neededcrew fix store-front/wrk2
crew fix store-front/wrk2 --print
crew fix store-front/wrk2 --json
crew claudecrew claude <workspace>[/<worktree>]
crew claude store-front/wrk1
crew editcrew edit <workspace>[/<worktree>] [--editor=cursor|code]
--editor=<cursor|code> — Which editor; detected when omittedcrew edit store-front/wrk1
crew edit store-front/wrk1 --editor=code
crew opencrew open <workspace>[/<worktree>]
crew open store-front/wrk1
crew codecrew code <workspace>[/<worktree>]
crew code feature-auth
crew startcrew start <workspace>[/<worktree>]
crew start feature-auth
crew launchcrew launch [<workspace>[/<worktree>]]
crew launch
crew launch feature-auth
crew launch store-front/wrk2
crew devcrew dev setupcrew dev setup <project> [--apply --port=<port>]
<detected|added>\t<name>\t<command>
--apply — Record the detected server; needs --port--port=<port> — The reference port for the detected servercrew dev setup web
crew dev setup web --apply --port=3000
crew dev addcrew dev add <project> --name=<name> [--port=<port>] --cmd=<command> [--dir=<subdir>] | crew dev add <project> --name=<new> --rename=<old> [--port=<port>] [--cmd=<command>] [--dir=<subdir>]
--name=<n> — Server name (used as subdomain) (required)--port=<p> — The port the server conventionally uses — reference only. Crew always allocates a free port and passes it as $PORT. Leave it out (or --port=0, which with --rename clears the old port) for a process that does not listen--cmd=<c> — Start command (use $PORT for the dynamic port) (required)--dir=<d> — Subdirectory relative to project root (for monorepos)--rename=<old> — Rename that server to --name in place: the bindings scoped to it follow it (an rm and an add would drop them), and every binding in the pool whose value names it ({{crew dev add my-api --name=api --port=3000 --cmd="npm run dev"
crew dev add my-app --name=web --port=5173 --cmd="npm run dev" --dir=packages/web
crew dev add my-api --name=worker --cmd="npm run worker"
crew dev add my-api --name=api --rename=server
crew dev rmcrew dev rm <project> <server-name>
crew dev rm my-api api
crew dev showcrew dev show <project>
<server-name>\t<port>\t<command>[\t<dir>]crew dev show my-api
crew dev startcrew dev start <workspace>[/<worktree>] [--proxy]
--proxy — Also run the shared reverse proxy and address servers by hostnamecrew dev start feature-auth
crew dev start store-front/wrk2
crew dev start store-front/wrk2 --proxy
crew dev start store-front/wrk2 --json
crew dev stopcrew dev stop [<workspace>[/<worktree>]]
crew dev stop
crew dev stop feature-auth
crew dev restartcrew dev restart <workspace>[/<worktree>] [--proxy]
--proxy — Also run the shared reverse proxy and address servers by hostnamecrew dev restart feature-auth
crew dev restart store-front/wrk2 --proxy
crew dev statuscrew dev status [<workspace>[/<worktree>]]
<workspace>/<worktree>\t<server>\t<port>\t<url>crew dev status
crew dev status feature-auth
crew dev checkcrew dev check <workspace>[/<worktree>] [--wait]
<project>/<server>\t<running|died|not listening>\t<port>\t<took>\t<detail>
--wait — Keep looking until every server has a verdict (up to a minute) instead of one look nowcrew dev check store-front/wrk2 --wait
crew dev check store-front/wrk2 --json
crew dev proxycrew dev proxy [status|trust [--install]|stop]
<up|up (not listening)|down>\t<domain>\t<port>\t<status url>\thttps <up|not listening|off>\t<https port>
--install — trust: add the CA to this Mac's login keychain (asks for the password)crew dev proxy status
crew dev proxy trust
crew dev proxy trust --install
crew dev proxy stop
crew dev logscrew dev logs <workspace>[/<worktree>] <server> [-f|--follow] [--lines=<n>]
-f, --follow — Stream new output as it arrives (tail -f)--lines=<n> — Only the last n linescrew dev logs feature-auth api
crew dev logs feature-auth web -f
crew dev logs feature-auth api --lines=50
crew dev tuicrew dev tui <workspace>[/<worktree>]
crew dev tui store-front/wrk1
crew rmcrew rm <workspace>
crew rm feature-auth
crew rm projectcrew rm project <name> [--keep-clone]
--keep-clone — Remove the pool entry but leave crew's clone where it iscrew rm project my-api
crew rm project signals --keep-clone
crew rm workspacecrew rm workspace <workspace> <project> [--dry-run]
--dry-run: <ref>\t<project>\t<path>\t<n> uncommitted\t<n> commits not on the base\t<size>
--dry-run — Remove nothing; print the cost. --json: {checkouts: [{ref, project, path, direct, missing, uncommitted, commits, size_bytes}], last}crew rm workspace feature-auth my-api
crew rm workspace feature-auth my-api --dry-run --json
crew rm worktreecrew rm worktree <workspace>/<name> [--dry-run]
--dry-run — Remove nothing; list every checkout it would take with its uncommitted files, commits not on the base and size; last says it is the workspace's last worktree (which goes only with crew rm crew rm worktree store-front/wrk3
crew rm worktree check/signals
crew rm worktree store-front/wrk3 --dry-run
crew rm bindingcrew rm binding <project>[/<server>] <var>
crew rm binding checkout-api STORE_API_URL
crew rm binding admin/homepage STORE_API_URL
crew rm overridecrew rm override <workspace>/<worktree> <VAR>
crew rm override store-front/wrk2 STORE_API_URL
crew renamecrew rename worktree <workspace>/<worktree> <new-name>
crew rename worktreecrew rename worktree <workspace>/<worktree> <new-name>
Renamed <workspace>/<worktree> → <workspace>/<new-name>crew rename worktree store-front/wrk2 payments
crew rename worktree store-front/wrk2 payments --json
crew duplicatecrew duplicate <workspace>[/<worktree>] <new-worktree> [--no-install] [--no-smoke] [--wait]
crew duplicate store-front/wrk1 wrk3
crew envcrew run over pasting them anywhere.crew env <workspace>[/<worktree>] <project>[/<server>]
<VAR>=<value>crew env store-front/wrk1 checkout-api
crew env store-front/wrk1 admin/homepage
eval "$(crew env store-front/wrk1 checkout-api)"
crew runcrew run <workspace>[/<worktree>] <project>[/<server>] -- <command...>
crew run store-front/wrk1 checkout-api -- make eval
crew run store-front/wrk2 checkout-api -- uv run python -m tests.smoke
crew migratecrew migrate [--dry-run] [--yes]
--dry-run — Print the plan and stop; with --json the moves as [{workspace, ref}] on stdout and the plan on stderr--yes — Apply without the confirmation prompt; with --json {migrated: crew migrate --dry-run
crew migrate
crew exportcrew export [<file>|-] [--all | --projects=<a,b> [--workspaces=<x,y>]]
<file>|- — Where the bundle goes (default crew-export.json); - is stdout--all — Every project and workspace (the default when nothing is named)--projects=<a,b> — Only these projects--workspaces=<x,y> — Only these workspaces; every project they use must be in --projectscrew export
crew export ~/Desktop/crew.json --all
crew export --projects=store-api,checkout-api --workspaces=store-front
crew export - > crew.json
crew importcrew import <file>|- [--plan | --all [--replace] [--pull] [--no-install] [--no-smoke] [--wait] | project <name> [--path=<dir>] [--replace] [--name=<new>] [--setup=<cmd>] [--env-cmd=<cmd>] | workspace <name> [--pull] [--no-install] [--no-smoke] [--wait]]
<project|workspace>\t<name>\t<status|outcome>\t<detail>
<file>|- — The bundle; - reads it from stdin--plan — The default — inspect only: one row per item with what would happen here — exists, other remote, clone (and where), found (a checkout of that remote already on this machine — --path=--all — Clone every project not here, keep the ones that are (--replace swaps them); refuses up front — before a single clone — on any blocked or missing row, and under --replace on another remote for a project whose worktrees hang off the local checkout. A project that fails on the way is its row and exit 1. Workspaces are made the way crew add worktree makes one--pull — workspace: fast-forward the local base branches from origin before checking out (the base table is printed either way)--no-install — workspace: skip the installs--no-smoke — workspace: skip the smoke start--wait — workspace: stay until its runners are done; the row then carries what was recorded--path=<dir> — project: adopt this checkout as the canonical instead of cloning — the only way for a project with no remote--replace — project: swap out the local record of the same name — same remote, or no remote in the bundle (a config-only export): its config, checkout kept; another remote: a fresh clone (refused while a workspace still has the project)--name=<new> — project: import under another name (bindings pointing at the old name are left alone)--setup=<cmd> — project: override the setup command--env-cmd=<cmd> — project: override the env commandcrew import ~/Desktop/crew.json
crew import crew.json --plan
crew import crew.json project checkout-api
crew import crew.json project store-api --path=~/code/store-api --replace
crew import crew.json workspace store-front
crew import crew.json --all --pull
crew import - --plan --json < crew.json
crew checkcrew check project <name> [--status]
crew check projectcrew check project <name> [--pull] [--no-smoke] [--wait] | crew check project <name> --status
--status: <project>\t<none|running|passed|failed>[\t<verdict>][\t<when>][\t<recorded failure>]
--status — Start nothing; where the project's check stands. --json: {project, state, verdict, at, smoked, health, projects: [runner rows]}--pull — Fast-forward the canonical repo's base branch first, as crew add worktree --pull does--no-smoke — Skip the smoke start--wait — Stay until the runner is done; then the issues, exit 1 on anycrew check project signals --wait
crew check project signals --no-smoke
crew check project signals --status --json
crew setup status check/signals
crew cleancrew clean [--dry-run]
<kind>\t<path>\t<removed|would remove|pruned|would prune|failed: <reason>> | nothing to clean
--dry-run — List what the sweep would remove and remove nothingcrew clean --dry-run
crew clean
crew clean --json
crew doctorcrew doctor [--install [--yes] [--with-claude]]
<name>\t<ok|missing>\t<required|optional>\t<why>\t<install>
--install — Install what is missing, asking first at a terminal--yes — With --install: no questions (the required tools only)--with-claude — With --install --yes: install Claude Code toocrew doctor
crew doctor --json
crew doctor --install
crew doctor --install --yes
crew trashcrew trash [empty]
<path>\t<size>\t<n> entries\t<note> | <path>\temptycrew trash
crew trash empty
crew debugcrew debug [--tail=<n>]
<date> <time> [<category>] <message>
--tail=<n> — Print the last n lines instead of following (--json alone implies 200)crew debug
crew debug --tail=50
crew debug --tail=200 --json
crew setupcrew setup <workspace>[/<worktree>] [<project>...] [--no-smoke] [--wait]
<project>... — Only these projects--no-smoke — Skip the smoke start--wait — Stay until every runner is done; then the issues, exit 1 on anycrew setup store-front/wrk3
crew setup store-front/wrk3 store-api --wait
crew setup statuscrew setup status <workspace>[/<worktree>] [--wait]
✓|✗|▸ <project> <step> <took> · <step> <took> · ▸ <running step> | <step> — <reason>
--wait — Stay until every runner is done, the table live in a terminalcrew setup status store-front/wrk3
crew setup status store-front/wrk3 --wait
crew setup status store-front/wrk3 --json
crew setup logscrew setup logs <workspace>[/<worktree>] <project> [--lines=<n>]
--lines=<n> — How many lines from the end (default 50)crew setup logs store-front/wrk3 store-api
crew setup logs store-front/wrk3 store-api --lines=200
crew uninstallcrew uninstall [--purge] [--yes]
--purge — Also remove every workspace's checkouts and ~/.crew. Uncommitted work in checkouts is lost.--yes — Skip the confirmation promptcrew uninstall
crew uninstall --purge
crew servercrew server [start|stop|restart|status|logs|debug-notes|notes|keys|remote|machines|discord|dev] [--no-open]
<up|up (not answering)|down>\t<port>\t<localhost url>\t<proxy url>crew
crew server
crew server status --json
crew server keys
pbpaste | crew server keys set anthropic
crew server stop
crew server startcrew server start [--no-open]
<up|up (not answering)|down>\t<port>\t<localhost url>\t<proxy url>
--no-open — Print the links only (never opened under --json, without a terminal, over SSH, or on Linux with no display)crew server start
crew server start --no-open --json
crew server stopcrew server stop
crew server stop
crew server restartcrew server restart [--no-open]
--no-open — Print the links onlycrew server restart
crew server statuscrew server status
<up|up (not answering)|down>\t<port>\t<localhost url>\t<proxy url>crew server status
crew server status --json
crew server keyscrew server keys [status] | crew server keys set <anthropic|soniox>
<name>\t<missing|set (<file|env>)>\t<path>crew server keys
crew server keys --json
pbpaste | crew server keys set soniox
crew server keys statuscrew server keys status
crew server keys setcrew server keys set <anthropic|soniox>
pbpaste | crew server keys set anthropic
crew server logscrew server logs [--since=] [--until=] [--cat=<c,…>] [--level=<debug|info|warn|error>] [--grep=] [--lines=<n>] [--machine=<id|name|main,…>] [--exclude=<…>] [--json]
<ts>\t<machine>\t<level>\t<cat>\t<msg>\t<other fields as JSON>
--since=<when> — From this time: a span back (10m, 2h, 3d), a clock time today (10:02; one still ahead is yesterday's) or an ISO time (local without a zone). Converted to UTC where you typed it, so every machine reads the same moment--until=<when> — Up to this time, same forms; before --since is an error--cat=<c,…> — Only these categories, e.g. gateway, kernel, router, worker, speech, remote; an unknown one matches nothing--level=<level> — This level and above: warn is warn and error--grep=<text> — Lines whose message or a field's value holds this text, any case (never the keys)--lines=<n> — The newest n across every machine, printed oldest first (at most 1000) (default 80)--machine=<id|name|main,…> — Only these machines; main is the main's own log — --machine=main is the fast look, no SSH--exclude=<id|name|main,…> — Every machine but thesecrew server logs --since=10m --level=warn
crew server logs --since=10:02 --until=10:05 --cat=router,kernel
crew server logs --machine=main --grep=signals --lines=200
crew server logs --machine=vm1 --json
crew server debug-notescrew server debug-notes [--since=] [--until=] [--grep=] [--lines=<n>] [--json]
<n>\t<at>\t<view>\t<text>
--since=<when> — As on logs--until=<when> — As on logs--grep=<text> — Notes whose text, what was said or view holds this, any case--lines=<n> — The newest n (at most 1000) (default 20)crew server debug-notes
crew server debug-notes --since=2h --grep=speech
crew server debug-notes showcrew server debug-notes show <n> [--around=30s] [--json]
debug note <n>\t<at>\t<view>, the note's parts, then log <from> … <to>: and the log rows
--around=<span> — How much log on each side of the note (default 30s)crew server debug-notes show 3
crew server debug-notes show 3 --around=2m --json
crew server notescrew server notes [<workspace>|--all] [--since=] [--grep=] [--lines=<n>] [--json]
<workspace>\t<date time>\t<text>
--all — Every workspace with notes, one after another--since=<when> — As on logs--grep=<text> — Notes holding this text, any case--lines=<n> — The newest n, per workspace with --all (at most 1000) (default 20)crew server notes store-front
crew server notes
crew server notes --all --since=3d
crew server remotecrew server remote [status|stop]
<up|down>\t<version>\t<busy|idle>\t<socket>crew server remote
crew server remote status --json
crew server remote stop
crew server machinescrew server machines [ls] | add <ssh host> [--name=<name>] | rm <id> | rename <id> <name>
<id>\t<name>\t<host>\t<status>
--name=<name> — add only: what the machine is called (default: its id)crew server machines add dev@vm1.example.com --name="Build box"
crew server machines
crew server machines rename vm1 GPU box
crew server machines rm vm1
crew server machines lscrew server machines ls
crew server machines addcrew server machines add <ssh host> [--name=<name>]
--name=<name> — What the machine is called (default: its id)crew server machines rmcrew server machines rm <id>
crew server machines renamecrew server machines rename <id> <name>
crew server discordcrew server discord setup [--guild=<id>] [--channel=<name|id>] [--text-channel=<name|id|voice>] [--user=<id>] | status | channels | send [--text=<message>] [--file=<path>]… | off
crew server discord setupcrew server discord setup [--guild=<id>] [--channel=<name|id>] [--text-channel=<name|id|voice>] [--user=<id>]
server: <name> (<id>) / you: <who> (<id>) / channel: <name> (<id>) / messages: the voice channel's chat | #<name> (<id>) / ready: Voice OS joins it while it runs
--guild=<id> — The server, when the bot is in several--channel=<name|id> — The voice channel, when there are several and none is named Voice OS--user=<id> — Whose voice Voice OS takes (default the server owner)--text-channel=<name|id|voice> — Where crew server discord send posts: a text channel (checks the bot may View Channel, Send Messages and Attach Files); voice goes back to the voice channel's own chat (default kept from the last setup; at first the voice channel's chat)pbpaste | crew server discord setup
crew server discord setup --guild=155 --channel="Voice OS"
crew server discord statuscrew server discord status [--json]
<field>\t<value>crew server discord status
crew server discord status --json
crew server discord channelscrew server discord channels [--json]
<id>\t<name>\t<text|voice>[, voice channel][, messages go here]crew server discord channels
crew server discord sendcrew server discord send [--text=<message>] [--file=<path>]…
sent to <#channel | the voice channel's chat>: <link>
--text=<message> — The message; without it, text piped on stdin--file=<path> — A file to attach (repeat for more): at most 10, each at most 10 MBcrew server discord send --text="The retry fix is in" --file=screenshot.png
git log -5 --oneline | crew server discord send
crew server discord offcrew server discord off
removed\t<path>crew server discord off
crew server devcrew server dev push [--dry-run] | status
crew server dev pushcrew server dev push [--dry-run]
<machine>\t<goos>_<goarch>|skipped: <why> / Pushing <version> to every machine; …
--dry-run — List the machines and their targets; build and push nothingcrew server dev push --dry-run
crew server dev push
crew server dev statuscrew server dev status [--json]
<version> from <source>: <phase> / <machine>\t<goos>_<goarch>\t<state>crew server dev status
crew server dev status --json
crew updatecrew update [--check]
--check: crew v<current> — v<latest> is available (crew update) | up to date (latest v<latest>) | crew (dev build[ <sha>]) — crew update installs the latest release (v<latest>)
--check — Install nothing; --json: {current, latest, available, dev, error?, line} (line: the text form) — offline is available false with the error, not a failure; a dev build is never available and says dev true (crew update replaces it with the latest release), one ahead of the latest is up to date (crew update never downgrades). Bare crew update --json: {from, to, updated}, narration on stderrcrew update
crew update --check --json
crew helpcrew help [<command>] [<subcommand>] [--json]
crew help
crew help dev add
crew help --json