Skip to content

CLI reference

envctl runs against the Docker host in front of you. It derives the environment from the git worktree of the current directory and prints human output by default; add --json for a single JSON document on stdout. Compose progress always goes to stderr.

Terminal window
envctl [-C PATH] [--env NAME] [--backend local] [--port-mode domains|registry] [--json] <command>

-C PATH operates on the worktree containing PATH instead of the current directory. --env NAME names the environment instead of deriving it from the branch; the value is slugified. --feature is a deprecated alias. --backend accepts local today. --port-mode overrides ports.mode from the manifest for this invocation. --json switches output to JSON on every command that reports something.

Terminal window
envctl run workflows
envctl run create --task "Add a --json flag to envctl version" --workflow small
envctl run validate --workflow small

run workflows lists the default workflow and each named workflow in envctl.yaml with its stages; --json returns the same list. run create --workflow and run validate --workflow select one by name, and an unknown name lists the choices. In the dashboard’s new-run input (n), tab and shift+tab cycle the workflow. run show and the dashboard name each run’s workflow. See Workflows.

Version 2 workflow state belongs to its persistent coordinator. These commands inspect retained evidence without starting a worker or requiring the run’s VM:

Terminal window
envctl run diff <run-id> --node code
envctl run diff <run-id> --node qa --revision <revision-id> --from <checkpoint-id> --json
envctl run artifact <digest> --output <new-file>

diff defaults to comparing the selected checkpoint against its revision’s immutable source pins. --from selects any recorded checkpoint in the same run, including historical revisions. The response includes source patches, checkpoint summaries, checks, approvals, and artifact/dataset identities. Repository patches are bounded to 1 MiB each and explicitly report truncation or unavailable retained source. Binary files and LFS objects are represented by Git’s binary/pointer differences; database rows are not diffed. artifact verifies the transferred checksum and refuses to overwrite existing output.

In Bubble Tea, d opens the comparison. b selects the current checkpoint as the comparison base; navigate to the target and press d. B resets the base, and Escape closes the loaded review. Selection and review never mutate the workflow.

Terminal window
envctl run show <run-id>
envctl run message <run-id> --node code --to worker --text "keep the public API unchanged"

run show prints each running attempt’s phase (worker, stack, checks, supervisor), its current check, and its latest agent activity: messages, tool and command invocations, and check output. --json includes the same progress object on each attempt, with at most 20 lines of 300 bytes each. Activity comes from the harness’s own event stream, redacted by the guest journal and again for configured credentials. It is display state only and never checkpoint evidence. Activity-only updates are written at most every few seconds; phase changes and deliveries are written at once.

run message (and i in Bubble Tea) records a message against the current revision. --node limits it to one stage; without it, the message applies to every stage. run show and the Bubble Tea Conversation panel show where each message went:

  • included when … started: the message existed when the agent’s prompt was frozen.
  • delivered live … (resume N): the message reached a running agent.
  • pending live delivery: a matching attempt is running, and the message goes out on the coordinator’s next poll.
  • queued for the next … attempt: nothing matching is running.

Neither harness accepts input mid-run, so live delivery interrupts and resumes. The coordinator durably records a new generation of the agent’s invocation, stops the running guest job, and resumes the same explicit harness session in the same worktree. The resumed prompt contains the new messages. Edits already made stay in place. A worker that finished before the message arrived is resumed too: its proposal is superseded and never accepted without the message. Delivery waits until the harness reports its session ID. A coordinator restart at any step reconciles from the receipt without a duplicate job.

Worker-directed messages reach the running worker. The supervisor’s prompt includes every message for its stage, with an instruction to reject work that ignores steering addressed to the worker. Messages that arrive while the supervisor is reviewing interrupt and resume the supervisor the same way. Each role allows 8 live resumes per attempt. After that, progress reports the limit, and further messages reach the supervisor review or the next attempt. Messages that arrive after an attempt’s review has finished apply to that stage’s next attempt, or to a rewind.

Terminal window
envctl run pr <run> # print the pull requests the run published
envctl run pr <run> --open # open them in the browser

In the dashboard, g opens the selected run’s pull requests from any stage. Changes that also published submodule pull requests list each one. --json prints [{"key", "url"}]; before publication the command exits 1 with “no pull request yet”.

Terminal window
envctl web # serve on 127.0.0.1:4777 and open the browser
envctl web --addr 127.0.0.1:0 --no-open # any free port; print the link only

envctl web serves the workflow dashboard to a browser on this machine. Runs are listed by what they need from you. Each run opens with one sentence saying what it needs next and the action for it, then its stages. For the selected stage:

  • Chat: the worker’s and supervisor’s results, live activity, and your messages. Write to the supervisor or worker from the box below.
  • Result: the summary, supervisor review, checks, documents (opened in a reading pane) and commits. A result waiting for you is approved here.
  • Changes: the source diff against where the revision started or any earlier checkpoint.
  • Log: the run’s decisions across every revision: rewinds, rejections, failures, approvals, messages and pull requests. Pick a revision to browse it read-only.

The side panel shows token use, the VM and services, readiness checks, the stage’s models and limits, and plugins. You can start runs with a workflow, approve, rewind, cancel, set priority and open pull requests. Keyboard shortcuts follow envctl ui (1 to 4 switch views); press ? for the list. Like the terminal dashboard it is only a client, so stopping it never stops a run.

The dashboard can approve and publish work, so it is locked to this machine:

  • It listens only on a loopback address. --addr refuses anything else.
  • The printed link carries a token from web-token in the state directory (created once, mode 0600). Opening it sets a same-site cookie; other visits need the link.
  • Requests with another Host header are refused, which blocks DNS rebinding. Changes also require the page’s token header, JSON, and a same-origin request.
  • The page loads nothing from the network, and its content security policy allows only its own scripts. Artifacts render as Markdown without raw HTML.
  • Every change carries the run version and revision the page showed, so it never applies to state you did not see.

Themes: the page follows the system light or dark setting, and the theme menu also offers the terminal dashboard’s color themes. The choice is saved in the browser.

Terminal window
envctl ui --theme nord # this session only
envctl theme list # built-in themes with color swatches; ▸ marks the current one
envctl theme set gruvbox # save to ~/.config/envctl/config.yaml
envctl theme show # explain the resolved theme, config path, and each color's source
envctl theme show nord --dark # preview a theme as if the terminal were dark

In the dashboard, press T to open the theme picker. Moving the selection previews each theme immediately; enter keeps and saves it, esc restores the previous theme. envctl theme list --json returns every palette’s colors, and envctl theme show [name] [--json] reports the resolved theme, the configuration file path, and each palette role’s value and source (a built-in theme, theme.custom.<role>, disabled by theme.background: false, or the terminal default). See Dashboard themes for the configuration file.

Terminal window
envctl up [--build] [--no-wait]
envctl down [-v|--volumes]
envctl stop
envctl start

up renders the isolated compose file, then runs docker compose up --detach --remove-orphans, and prints status. It converges an existing environment rather than failing. --build rebuilds images first. By default up waits for health checks; --no-wait returns as soon as containers are started, which the worktree hook uses so agents are not blocked by slow services.

down removes containers and the network and keeps volumes, so up restores the data. down -v also deletes volumes, releases this project’s port allocations, and removes .envctl/<feature>/. down renders on demand if nothing has been rendered yet, so it works on a fresh clone.

stop and start map to compose stop and start: containers stay, data stays, nothing is listening in between.

Start a rebuilt environment and print JSON for a script:

Terminal window
envctl up --build --json | jq '.endpoints'
Terminal window
envctl env create <name> [--branch BRANCH] [--no-up]
envctl env link <name> <branch>
envctl env unlink <name>
envctl env list
envctl env rm <name>

An environment named after the branch is created implicitly by up and linked to that branch. env create makes a named environment that is kept: CI never destroys it on merge, only env rm does. --branch links it so pushes to that branch update it; without --branch it is unlinked. --no-up records and renders without starting containers.

env link re-points an environment at another branch. env unlink keeps the environment but stops it following any branch. env list is the same as list. env rm is down --volumes plus removal of the record.

Locally the code always comes from the worktree you run in; the linked branch decides which pushes update the environment in CI. See the environment identity design note.

Terminal window
envctl status
envctl list
envctl render

status reads the rendered files and asks compose which containers exist. It prints the feature, project, backend, each service with its state and health, endpoints, and the env file path. In JSON the env array holds the env file lines. status requires a prior up or render.

list merges the environments recorded in this worktree (.envctl/*/env.json) with the compose projects on the host whose name starts with this manifest’s prefix. Each row has the name, backend, linked branch, whether it is kept, and compose’s status summary or - when it is not running here.

render writes .envctl/<feature>/compose.yaml and env without starting anything, and prints the same status shape without container state. Use it to inspect what a port mode did:

Terminal window
envctl --port-mode registry render
docker compose -p mg-feat-thing -f .envctl/feat-thing/compose.yaml config
Terminal window
envctl logs [-f|--follow] [service...]
envctl exec <service> -- <command...>

logs streams compose logs for the whole environment or the named services. exec runs a command in a running service container with a TTY when stdin is a terminal:

Terminal window
envctl exec postgres -- psql -U metergraph -c 'select now()'
Terminal window
envctl init --project PREFIX [--file PATH ...]
envctl hook claude

init writes a starter envctl.yaml in the target directory and refuses to overwrite one. --project is required; --file may repeat and defaults to docker-compose.yml.

hook claude prints the hooks block to merge into .claude/settings.json. See Agents.

Terminal window
envctl doctor [--json]

Checks whether this machine is ready to use envctl and prints an exact one-line fix for every problem. Each check reports ok, warning, or failed:

  • docker / docker-compose — Docker must be reachable and docker compose version --short must report major version 2 or newer; needed for every command. failed if either is missing.
  • git — must be on PATH; failed if missing.
  • lima — Lima 2.x, needed only to run workflow revisions; warning (not failed) if absent or on an older major version.
  • gh — the GitHub CLI, authenticated (gh auth status), needed only for pull-request/publication output; warning if absent or unauthenticated.
  • envctl.yaml — whether the current worktree has a manifest at its root and whether it parses, reporting the true on-disk version (1 or 2). Not being in a git repository, or the manifest being absent, is a warning; a manifest that fails to parse is failed.
  • agents.worker / agents.supervisor — for a version 2 manifest only, whether each configured agent credential resolves (Claude or Codex). Omitted entirely for a version 1 manifest. Credential values are never printed, only whether resolution succeeded and, on failure, a fix.
  • xcode-clt — on macOS only (omitted elsewhere), whether the Xcode Command Line Tools are installed and current enough for Homebrew to accept them: a Command Line Tools major version older than the running macOS major version is failed, since Homebrew refuses to install anything in that state, even though xcode-select reports the tools present.

Every check runs with a short timeout, so a hung external tool cannot hang doctor itself; a timeout is scored at that check’s own severity. --json prints the same checks as an array of {name, status, detail, fix} objects, omitting fix when the status is ok. Exit code follows the Exit codes convention: 0 unless at least one check failed; warnings alone keep exit code 0.

Terminal window
envctl agent install [--global]
envctl agent snippet

agent install copies the embedded skill (SKILL.md and REFERENCE.md) into .claude/skills/envctl/ and .agents/skills/envctl/ at the worktree root, then prints the CLAUDE.md paragraph. --global writes to ~/.claude/skills, ~/.agents/skills, and ~/.codex/skills instead. agent snippet prints only the paragraph. See Agents.

0 on success. 1 on any failure, with a one-line reason on stderr prefixed envctl:. Compose’s own exit code is passed through when compose fails.

Branch names are lowercased; runs of characters outside a-z0-9 become one -; leading and trailing - are trimmed; the result is cut to 40 characters. feat/Imported Pricing becomes feat-imported-pricing. A detached HEAD uses the worktree directory name. An empty result becomes default.

envctl run readiness <run> includes required capability names, structured discovered Plan requirements (capability, affected nodes, reason), current probes, and unresolved admission items. A reviewed Plan can remain pending while one of its discovered connections is prepared; its worker result remains retained.

envctl mcp serve serves newline-delimited MCP over stdin/stdout and connects to the same persistent coordinator as CLI/TUI. Use --run <id> to restrict the server to one run and disable creation, or --read-only to expose only inspection tools. Closing the MCP client leaves the coordinator running.

Available tools are envctl_runs, envctl_run, envctl_events, envctl_diff, envctl_artifact, envctl_create, and envctl_action. Mutations require a caller-generated operation_id; actions also require the exact observed run version and revision. Reuse the identical request after a lost response. Stale mutations fail instead of silently rebasing onto current state. Agent actions cover steering, rewind, cancel, priority, and invocation plugin attachment/removal. Approval and publication remain outside this tool surface.

Artifact previews require a reference from the selected run, return base64, and default to 64 KiB (maximum 1 MiB), with explicit size/truncation metadata. MCP is a local stdio interface for trusted host clients; run scoping does not create a network authentication service or expose the host coordinator inside a worker VM.

Example MCP client configuration (replace the binary path and run ID):

{
"mcpServers": {
"envctl": {
"command": "/absolute/path/to/envctl",
"args": ["mcp", "serve", "--run", "run_example", "--read-only"]
}
}
}

The implementation pins the official MCP Go SDK at v1.7.0 and uses its stdio server/client transport.