Commands

Everything is driven through ztd <verb>. ztd is a Go (Cobra) binary with its runtime assets embedded, so an installed copy needs no source checkout — see Install. The tables below spell it ./ztd, which is the shim in a source checkout: it builds the binary and execs it, pointing it at the live tree so script edits take effect without a rebuild. If you installed ztd, drop the ./ — nothing else differs.

The CLI dispatches each verb down one of two lanes: most run Task inside the toolbox container (docker compose run --rm ztd task …), while check, setup, test, mount, unmount, fetch, auth, skills, commands, run, attach, cattle, and audit run on the host (they inspect or mutate host state, shell out to virsh, do a host-side FUSE mount, drive the operator’s own git, or ssh into the guest to sync credentials/skills, launch the agent, or export a run record). Prefix any command with TARGET=proxmox to act on the remote backend instead of local kvm.

The table below is the task-oriented overview; for the exhaustive per-command detail (every flag and usage line, auto-generated from the CLI), see the CLI reference.

CommandDoes
./ztdList the available commands
./ztd versionPrint the CLI version (also decides which toolbox image tag it pulls)
./ztd checkVerify host requirements for your TARGET (host-side)
./ztd setupPrepare the host for the local backend — installs the persistent Docker→libvirt forwarding fix (host-side; --transient, --undo)
./ztd testFull clean-slate acceptance test — routes by TARGET: local (virsh + virtiofs/ownership/confinement), TARGET=proxmox, or TARGET=ec2 (each boots a real VM/instance and checks ephemeral-key SSH, cloud-init, Claude+auth, guest docker, mount/fetch/run/audit, clean teardown). Host-side. Its preflight also runs test:config.
./ztd test:configFast, VM-free unit test of the .ztd/ztd.toml config loader (parsing + precedence). Runs in ~a second
./ztd initScaffold this project’s .ztd/ztd.toml (every key commented out) plus a .ztd/.gitignore that keeps keys, secrets and state out of git. Idempotent — never clobbers an existing config. Terraform providers are downloaded automatically by up/plan; there is nothing to run once.
./ztd validateCheck the Terraform config is valid (no VM)
./ztd planShow what up would change
./ztd upCreate / update the VM, print its IP
./ztd statusShow whether a VM is running, on which target, and its IP. Add --watch for a live readiness dashboard — a red→green checklist (SSH reachable → cloud-init done → repo mounted → Docker → Node → Claude) that updates in place as the VM comes online (--timeout, default 10m). Since up returns on the DHCP lease while cloud-init keeps provisioning, this shows you when the box is actually ready.
./ztd configPrint the resolved orchestration config — effective TARGET, NAME, skills/commands modes, and time/turn caps after env > .ztd/ztd.toml > default resolution
./ztd ipPrint the current VM’s IP
./ztd logsFollow the guest provisioning (cloud-init) log
./ztd authSync your existing Claude Code login (~/.claude/.credentials.json) into the guest over the ephemeral key, so the in-VM agent is authenticated (host-side; both backends). Run after up.
./ztd skills / ./ztd commandsPush Claude Code skills/commands into the guest per ZTD_SKILLS / ZTD_COMMANDS (none|user|project|both; default project). Add pull to bring VM-written ones back to .ztd/claude/<asset>-incoming/ for review (never auto-installed). Host-side; both backends.
./ztd run "<task>"Launch the agent (claude, subscription-billed) in a guest tmux session on a fresh ztd/<run-id> branch, and attach. --detach starts without attaching; task falls back to .ztd/task.md. Caps: ZTD_MAX_TIME (wall-clock, default 3600s) and ZTD_MAX_TURNS (agent iterations, default 0 = off) — whichever trips first ends the run. Never tears the VM down.
./ztd attachReattach to a running ztd run session (Ctrl-b d to detach)
./ztd cattle "<task>"One-shot: up → (mount, remote) → authskills+commandsrun --detach. Boots a VM and launches the agent autonomously in one command. Leaves fetch + down to you. Task falls back to .ztd/task.md.
./ztd audit [run-id]Export a run’s record (manifest, console, agent transcript, derived shell-command log) to .ztd/runs/<run-id>/ — run it before down. Defaults to the most recent run. Host-side; both backends.
./ztd sshOpen a shell in the VM (ephemeral key)
./ztd tunnel -- <port>Forward a VM port to host localhost
TARGET=proxmox ./ztd mountSSHFS-mount a remote guest’s work dir under .ztd/mnt/<name>, and on first mount sync your working tree up into it (host-side; no-op locally — virtiofs already live-shares the tree)
TARGET=proxmox ./ztd unmountUnmount that SSHFS view
TARGET=proxmox ./ztd fetchAdd/refresh a ztd git remote on your local repo pointing at the remote guest’s checkout and git fetch its commits (host-side, over the ephemeral key; removed by down; no-op locally). The host then pushes to the forge.
./ztd downDestroy the VM
./ztd resetdown then up from clean cloud-init
./ztd list:orphansList ZTD libvirt resources present in libvirt but not in Terraform state
./ztd purgeForce-remove those orphans (e.g. a domain left by a failed up)

Argument passing

Pass a port to tunnel positionally — the CLI supplies the Task -- separator for you:

./ztd tunnel 8080        # forward guest:8080 → localhost:8080
./ztd tunnel -- 8080     # also works (explicit separator)

Verbs that wrap a script with flags (e.g. run --detach, skills pull) forward their arguments straight through to the underlying command.

Selecting a backend

./ztd up                 # local kvm (default)
TARGET=proxmox ./ztd up   # proxmox

TARGET defaults to local. See Backends.

The ztd Go CLI

Phase 2 replaced the bash wrapper with a Go (Cobra) binary, rooted at the repo so its runtime assets can be embedded in it. It offers every verb above over the same Terraform/Task engine, with all the host-side orchestration (config resolution, folder→name derivation, ephemeral-key lifecycle, secret sourcing, up/down/reset) native to Go.

It ships as a standalone installable binary — a Homebrew cask on macOS, a tarball on Linux (Install) — and carries the Terraform root, the lima template, cloud-init and the toolbox compose file inside itself, extracting them to a version-scoped cache on first use. That is why a project directory needs nothing but its own .ztd/. The per-command detail in the CLI reference is generated from the same Cobra tree, so it cannot drift from the binary.