coop creates isolated VM environments for running Claude Code and Codex. It runs Firecracker microVMs on Linux and Lima VMs on macOS, selecting the backend automatically based on platform.
| Flag | Description |
|---|---|
--config <path> |
Path to config file (default: ~/.coop/config.toml) |
-v, --verbose |
Increase log verbosity. Once for debug, twice for trace. |
--version |
Print version and exit. |
Most commands accept an optional instance name. coop resolves the target instance with three rules:
- Zero instances exist. The command fails and tells you to run
coop up. - One instance exists. The name is optional. coop selects it automatically.
- Multiple instances exist. The name is required. coop lists available instances on error.
Ensure an environment exists and is running for a project directory.
coop up [DIR] [FLAGS]
DIR defaults to the current directory. coop canonicalizes it and uses it as
the project identity for instance naming, devcontainer discovery, GitHub PAT
lookup, and future coop up DIR affinity. If a matching instance is already
running, up reports success without creating another VM. If a matching
instance is stopped, up restarts it. If no matching instance exists, up
creates one.
By default, up copies/syncs the project into /workspace. Pass --mount
to use the mount transport for the project at /workspace instead. On
macOS/Lima this is a live virtiofs mount; on Linux/Firecracker it is a
one-time sync. --copy is accepted as an explicit spelling of the default.
Use --git-repo <url> instead of DIR to clone a remote repository into
/workspace inside the guest.
| Flag | Description |
|---|---|
DIR |
Project directory (default: current directory) |
--name <name> |
Instance name to use when creating the project environment |
--copy |
Copy/sync DIR into /workspace (default) |
--mount |
Mount DIR at /workspace instead of using --copy |
--extra-mount <spec> |
Additional host directory to mount into the guest (HOST_PATH[:GUEST_PATH], repeatable; specify a guest path other than /workspace when using --copy) |
--git-repo <url> |
Clone a git repository into /workspace instead of copying a local project directory |
--vcpus <N> |
Number of vCPUs when creating a new instance |
--mem <MiB> |
Memory in MiB when creating a new instance |
--disk <GiB> |
Instance disk size when creating a new instance |
--no-agents |
Skip injecting Claude Code and Codex credentials/config into the VM |
--image <name> |
Named image to use when creating a new instance (default: default) |
--profile <list> |
Build or reuse a profile-derived image when creating a new instance, named from the sorted profiles (for example node-python) |
--exclude-git |
Skip .git/ when copying/syncing local directories; does not strip .git from a --git-repo clone |
--no-prompt |
Suppress the interactive prompt to set up a scoped GitHub PAT when one is missing for the resolved repo |
--forward-port <spec> |
Forward a guest port to the host (GUEST[:HOST], repeatable) |
--post-start <cmd> |
Shell command to run inside the guest after boot |
--env KEY=VALUE |
Literal env var to set in the guest (repeatable) |
--devcontainer <path> |
Explicit path to a devcontainer.json to use (skips discovery and prompt) |
--no-devcontainer |
Ignore any discovered devcontainer.json for this invocation |
--dry-run |
Translate devcontainer.json and print the report, then exit before any VM work |
--json |
With --dry-run, emit the resolved plan as JSON on stdout ({ report, profiles, guest_user, vm }) instead of the text report on stderr |
coop up .
coop up ~/code/my-project --mount
coop up . --profile python,node
coop up . --copy --forward-port 3000
coop up . --extra-mount ~/data:/data
coop up --git-repo https://github.com/trailofbits/coop.git
Creation options such as --vcpus, --mem, --disk, --image,
--profile, --extra-mount, --git-repo, --exclude-git, and
--devcontainer are applied only when up creates a new instance. coop up --profile <list>
derives an image name from the sorted profile list, runs the same stale-image
check as coop setup, and builds or rebuilds that image if needed. Explicit
named images are unchanged: use coop setup --image <name> --profile ...
followed by coop up --image <name> when you want to choose the image name
yourself. If a matching project instance already exists, destroy it first to
recreate it with different creation options. Runtime startup options such as
--forward-port, --post-start, and --env can be used when up creates or
restarts an instance; if the matching instance is already running, stop it
first so those options can take effect.
When a local devcontainer.json was applied while creating the instance, coop stores
its path and content hash. Later coop up reconnects or restarts warn if that
file changed, but the existing VM is not mutated automatically. Destroy and
recreate the instance to apply creation-time devcontainer changes such as
features, hostRequirements, mounts, image/build, or remoteUser.
One-shot entry point: ensure the default image exists, bring up an instance for
the current directory, and launch Claude Code inside it. Runs setup, up, and
claude in sequence, short-circuiting any step that is already done.
coop quickstart [FLAGS]
quickstart resolves the workspace to the current directory (unless
--no-workspace) and uses it as the project identity, the same way coop up
does. It then takes one of three branches based on the instance for that
workspace:
- A running instance exists. coop reconnects to it and launches Claude Code — no setup, restart, or recreation.
- A stopped instance exists. coop restarts it (reusing the instance's own
image, not necessarily
default) and launches Claude Code. - No instance exists. coop creates one for the workspace, folding in any
discovered
devcontainer.json, then launches Claude Code.
Image setup runs only when the default template image is missing; otherwise it
is skipped. Setup runs non-interactively (no confirmation prompts).
| Flag | Description |
|---|---|
--no-workspace |
Skip mounting the current directory as the workspace. Without a workspace there is no instance to match, so quickstart always creates a fresh instance rather than reusing an existing one. |
--no-devcontainer |
Ignore any discovered devcontainer.json (escape hatch for CI). |
coop quickstart
coop quickstart --no-devcontainer
coop quickstart --no-workspace
quickstart takes no instance name or per-instance tuning flags. For control
over profiles, mounts, image, resources, or named instances, use coop setup
and coop up directly. When run from your $HOME or /, coop prompts before
mounting (or bails in a non-TTY); pass --no-workspace to skip the mount.
Generate a starter config file at ~/.coop/config.toml.
coop init
No additional flags.
Run this once after installing coop. It checks prerequisites, installs the backend runtime, fetches a kernel, and builds a template root filesystem.
coop setup [FLAGS]
| Flag | Description |
|---|---|
-y, --yes |
Skip confirmation prompts (accept all) |
--vcpus <N> |
Number of vCPUs (overrides config) |
--mem <MiB> |
Memory in MiB (overrides config) |
--rebuild |
Force rebuild of template rootfs |
--profile <list> |
Comma-separated install profiles: python, node, c, fuzz, rust, go |
--extra-packages <list> |
Comma-separated extra apt packages to install |
--post-install <path> |
Path to a post-install script to run in the chroot |
--template-size <GiB> |
Template rootfs size in GiB (default: 8) |
--image <name> |
Named image to build (default: default) |
--guest-user <name> |
Guest username to bake into the image (default: ubuntu). Use this for devcontainers that declare another remoteUser, such as vscode. |
--builder-timeout <duration> |
Duration to wait for setup image build commands before timing out. Accepts seconds by default, or s, m, and h suffixes. |
--workspace <dir> |
Scan for .devcontainer/devcontainer.json and offer to apply its features / hostRequirements to this setup. Supported public ghcr.io/devcontainers/features/* entries are resolved and baked into the image. |
--devcontainer <path> |
Explicit path to a devcontainer.json to use (skips discovery and prompt). |
--no-devcontainer |
Ignore any discovered devcontainer.json for this invocation. |
--dry-run |
Translate devcontainer.json and print the report, then exit before any setup work. |
coop setup -y --profile python,node --template-size 12
coop setup --image ml-dev --profile python --extra-packages libopenblas-dev
coop setup -y --workspace . --devcontainer .devcontainer/devcontainer.json
See docs/devcontainer.md for the subset of devcontainer.json coop reads.
Parse a devcontainer.json file and print the same translation report that setup --dry-run and start --dry-run use, without loading coop config, checking for updates, setting up an image, or starting a VM. Setup-stage checks resolve supported public GHCR OCI Features so the report can show the digest and install.sh hash that would run.
coop devcontainer check <path> [--stage setup|start|both]
| Flag | Description |
|---|---|
<path> |
Path to the devcontainer.json file to inspect |
--stage <stage> |
Which lifecycle translation to report: setup, start, or both (default: both) |
--json |
Emit the report as JSON on stdout instead of the text table on stderr |
Use --stage setup to inspect setup-time keys such as features, hostRequirements.cpus, hostRequirements.memory, and remoteUser. Use --stage start to inspect start-time keys such as postStartCommand, containerEnv, forwardPorts, mounts, and hostRequirements.storage.
With --json, a single stage emits one report object ({ entries, source_path, ignored_paths }, or null when no file applied); --stage both emits { "setup": <report>, "start": <report> }. Each entry is { key, status, source, value, note }, with status one of applied/overridden/unsupported/invalid and source one of cli/devcontainer. CI can branch on the translation without scraping the table.
Record a persistent opt-out for a project directory. Future automatic discovery for that project skips .devcontainer/devcontainer.json and reports that the stored preference was used. Explicit --devcontainer <path> still applies a file for that run.
coop devcontainer ignore <project-dir>
Inspect persistent devcontainer opt-outs. With no project argument, this lists all stored opt-outs.
coop devcontainer status [project-dir]
Remove a persistent devcontainer opt-out for a project. If the project directory was moved or deleted, use the absolute path shown by coop devcontainer status.
coop devcontainer clear <project-dir>
Restart a stopped VM.
coop start [NAME] [FLAGS]
start normally restarts existing stopped instances. Use coop up [DIR] to
create or reconnect to a project environment. Without NAME, start restarts
the only stopped instance if exactly one exists; with multiple stopped
instances, pass the instance name.
| Flag | Description |
|---|---|
NAME |
Stopped instance name (optional only when exactly one stopped instance exists) |
--workspace <dir> |
Restart the stopped instance associated with this project path |
--no-agents |
Skip injecting Claude Code and Codex credentials/config into the VM |
--forward-port <spec> |
Forward a guest port to the host (GUEST[:HOST], repeatable). Lives for the lifetime of the VM; torn down on coop stop. |
--no-prompt |
Suppress the interactive prompt to set up a scoped GitHub PAT when one is missing for the resolved repo (see coop github setup-pat). |
--post-start <cmd> |
Shell command to run inside the guest after boot. Overrides the post_start field in config.toml. Failure is logged but does not fail the start. |
--env KEY=VALUE |
Literal env var to set in the guest (repeatable). Overrides guest_env config entries and any forwarded values with the same name. |
--devcontainer <path> |
Dry-run translation aid; normal restarts reject devcontainer creation options. |
--no-devcontainer |
Ignore any discovered devcontainer.json for this invocation (escape hatch for CI). |
--dry-run |
Translate devcontainer.json and print the report, then exit before any VM work. |
--json |
With --dry-run, emit the resolved plan as JSON on stdout instead of the text report on stderr. |
Normal start restarts an existing VM without re-reading or re-applying
devcontainer.json. If the instance was created with a devcontainer file, coop
warns when the recorded file path now has different contents. See
docs/devcontainer.md for the supported keys, discovery
rules, and recreate guidance.
coop start
coop start my-project
coop start my-project --no-agents
coop start --env RUST_LOG=info --env MY_FLAG=1
coop start --forward-port 3000 --forward-port 8080:18080
--no-claude is accepted as a deprecated alias for --no-agents and will be removed in a future release. Using it prints a deprecation warning.
Open an interactive shell in the VM, or run a single command non-interactively.
coop shell [NAME] [FLAGS] [-- COMMAND...]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
-- COMMAND... |
Command to run non-interactively (no PTY allocated) |
Without a trailing command, shell drops you into an interactive shell at /workspace. With a trailing command, it executes the command and returns its exit code.
coop shell
coop shell my-project
coop shell my-project -- cat /etc/os-release
Launch Claude Code inside the VM. The guest's ~/.claude/settings.json (written during VM startup) sets defaultMode: bypassPermissions and skipDangerousModePermissionPrompt: true, so Claude Code runs without permission prompts — the VM itself is the isolation boundary. Use --ask to override the guest default for that session (coop passes --permission-mode default).
coop claude [NAME] [FLAGS] [ARGS...]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
--ask |
Prompt for permissions instead of skipping them |
ARGS... |
Extra arguments passed through to claude |
coop claude
coop claude my-project --ask
coop claude my-project -- --model sonnet
Open the Claude Code agent view (claude agents) inside the VM. Claude Code's background agents are managed by its own daemon, so closing the terminal does not stop in-flight sessions; reconnect with coop claude-agents to see them again.
If the remote TUI stops responding, type Enter, then ~. to disconnect the SSH session. coop forces OpenSSH's interactive escape character to ~, so this works even if your user SSH config disables or changes EscapeChar. If the terminal remains in a broken raw/no-echo state afterward, run stty sane.
coop claude-agents [NAME] [FLAGS] [ARGS...]
coop ca [NAME] [FLAGS] [ARGS...]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
ARGS... |
Extra arguments passed through to claude agents |
Alias: ca.
coop claude-agents
coop ca my-project
coop ca my-project -- --cwd /workspace
Launch Codex inside the VM. By default coop passes --dangerously-bypass-approvals-and-sandbox, so Codex runs without its sandbox or approval prompts — parity with coop claude. The VM is the isolation boundary, and Codex's own Linux sandbox does not work in the guest (no functioning bubblewrap), so leaving it enabled makes every shell command Codex runs fail. Use --ask to keep Codex's sandbox and approval prompts for that session.
coop codex [NAME] [FLAGS] [ARGS...]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
--ask |
Keep Codex's sandbox and approval prompts instead of bypassing them |
ARGS... |
Extra arguments passed through to codex |
coop codex
coop codex my-project --ask
coop codex my-project -- --model gpt-5
Run a command in the VM and print its output. No PTY is allocated and stdin is not forwarded; use shell for interactive work.
The command and its arguments must follow -- so they are not mistaken for the instance name.
coop exec [NAME] -- COMMAND...
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
COMMAND... |
Command and arguments to run after -- (required) |
coop exec -- uname -a
coop exec my-project -- docker ps
Gracefully stop a running VM. The instance disk is preserved. Use start to relaunch or destroy to remove it.
coop stop [NAME]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
coop stop
coop stop my-project
Stop the VM and remove its resources: disk, config, and SSH entries. Templates and the kernel are preserved unless you pass --all.
coop destroy [NAME] [FLAGS]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
--all |
Remove all instances, templates, kernel, Firecracker binary, and SSH keys |
coop destroy my-project
coop destroy --all
Print every instance with its state (running or stopped). Reads from local on-disk state only — no SSH probing, so it returns instantly even when VMs are unreachable. Use status instead when you need resource usage or per-instance detail.
coop list
coop ls
Alias: ls.
| Flag | Description |
|---|---|
--json |
Emit a JSON array ([{ "name", "state" }, …]) instead of the text table |
Print instance status. Without a name, lists every instance with its state, image, backend, and resource usage (for running instances). With a name, prints detailed status for that instance.
coop status [NAME]
| Flag | Description |
|---|---|
NAME |
Instance name (shows all if omitted) |
--json |
Emit machine-readable JSON instead of the text output |
coop status
coop status my-project
With --json, a bare coop status emits a JSON array and coop status NAME
emits a single object. Each carries the common fields — name, state
(running/stopped), image, backend (firecracker/lima), and usage
(raw MiB / load, or null when stopped or the query fails). The rich
single-instance text report (guest IP, PID, SSH port, …) is text-only. JSON goes
to stdout; tracing stays on stderr, so coop status --json | jq stays clean.
$ coop status my-project --json
{
"name": "my-project",
"state": "running",
"image": "default",
"backend": "firecracker",
"usage": { "load_1m": 0.12, "mem_used_mib": 512, "mem_total_mib": 2048,
"disk_used_mib": 8192, "disk_total_mib": 20480 }
}
Update the coding agents (Claude Code and Codex) installed inside a running VM
to their latest versions, without rebuilding the golden image. Both agents are
installed "latest at build time" during coop setup, so they can go stale in
long-running VMs and in new VMs created from an old image. To refresh the image
itself instead, rebuild it with coop setup --rebuild.
coop agent update [NAME] [--claude] [--codex] [--check] [-y]
| Argument / Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
--claude |
Update Claude Code |
--codex |
Update Codex |
--check |
Only report installed vs. latest versions — change nothing |
-y, --yes |
Skip the confirmation prompt |
With no agent flag, both agents are updated; passing both --claude and
--codex is the same as passing neither. The VM must be running.
Codex has no background updater, so coop agent update --codex re-runs coop's
own installer inside the guest as root, overwriting /usr/local/bin/codex with
the current release. Claude Code already auto-updates in the background;
coop agent update --claude runs claude update now, synchronously — a
convenience rather than a fix.
--check reports each agent's installed version and, for Codex, the latest
release on GitHub, changing nothing:
$ coop agent update my-project --check
Claude Code 1.2.3 up to date (auto-updates in background)
Codex 0.4.1 → 0.5.0 update available — run: coop agent update --codex
coop agent update # both agents, resolved instance
coop agent update my-project # both agents, instance "my-project"
coop agent update --codex # Codex only
coop agent update --check # report versions, change nothing
Show or switch a VM's model backend between cloud (Anthropic / OpenAI) and a host-side local model server. The selection is per-instance and persists across restarts. Switching rewrites the guest agent config; it never rebuilds or restarts the VM.
coop model [NAME] [local|remote]
| Argument | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
local |
Route this VM's agents at a host-side local model server |
remote |
Restore cloud defaults (Anthropic / OpenAI) |
With no subcommand, model prints the current mode and the endpoint each tool
(Claude, Codex) resolves to:
$ coop model my-project
Instance: my-project
Mode: local
Claude local — qwen2.5-coder:32b @ http://172.16.0.1:11434
Codex cloud (no local endpoint configured)
coop model NAME local switches the VM to local mode. Each tool routes locally
only if it resolves an endpoint — from [claude.local_model] /
[codex.local_model] in config.toml, or from one saved earlier. For any tool
that has neither, and only in an interactive terminal, coop prompts for a host
URL, model name, and optional auth token, then saves that endpoint for the
instance. (A non-interactive run declines the prompt.) If no tool ends up with
an endpoint, the command fails. Claude and Codex are independent: you can put
one on a local model and leave the other on cloud.
coop model NAME remote switches back to cloud defaults for both tools. Saved
endpoints are kept, so a later local does not re-prompt.
Switching never requires a VM restart — coop rewrites the guest config live over
SSH when the VM is running, or saves it to apply on the next start. An
already-running claude/codex reads its config at launch, so relaunch the
agent (for example coop claude NAME) to pick up the change.
coop model
coop model my-project
coop model my-project local
coop model my-project remote
See the local_model configuration
reference and the local-model sections of
docs/claude-integration.md and
docs/codex-integration.md for the resolution and
materialization details.
Stream the VM serial console output.
coop logs [NAME] [FLAGS]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
-f, --follow |
Follow log output (like tail -f) |
coop logs
coop logs my-project -f
Copy a local directory into the running VM at /workspace. Defaults to the
host path recorded when the instance was created with coop up.
coop push [NAME] [FLAGS]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
--dir <dir> |
Local directory to push (defaults to the workspace host path) |
--force |
Overwrite guest changes without confirmation |
--exclude-git |
Skip the .git/ directory in this transfer |
coop push
coop push my-project --dir ./src --force
Copy the VM's /workspace to a local directory. Defaults to the host path
recorded when the instance was created with coop up.
coop pull [NAME] [FLAGS]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
--dir <dir> |
Local directory to pull into (defaults to the workspace host path) |
--force |
Overwrite local changes without confirmation |
--exclude-git |
Skip the .git/ directory in this transfer |
coop pull
coop pull my-project --dir ./local-copy --force
Open VS Code connected to the guest VM over SSH remote.
coop vscode [NAME] [--project PATH] [--editor EDITOR] [--clean]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
--project <path> |
Remote path to open in VS Code (default: /workspace) |
--editor <name> |
Editor to use (e.g. code). Overrides auto-detection. |
--clean |
Remove the SSH config entry for this instance and exit |
coop vscode
coop vscode my-project --project /workspace/subdir
coop vscode my-project --editor code
coop vscode my-project --clean
Install a coop-<name> alias into ~/.ssh/config so plain ssh, scp, and
rsync reach the guest without remembering its host, port, user, or key. This
is the same SSH config block coop vscode writes, but without launching an
editor.
coop ssh-config [NAME] [--clean]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
--clean |
Remove the SSH config entry for this instance and exit |
coop ssh-config
coop ssh-config my-project
ssh coop-my-project
scp ./file coop-my-project:/workspace/
rsync -az ./dir/ coop-my-project:/workspace/dir/
coop ssh-config my-project --clean
The alias is created only when you run coop ssh-config (or coop vscode).
The lifecycle keeps it tidy: coop stop and coop destroy remove the block,
and coop start refreshes an already-installed block so it stays valid across
a restart. On macOS/Lima the forwarded SSH port changes on each start; the
refresh keeps the alias current without you re-running the command. On
Linux/Firecracker the host and port are stable, so the refresh is a no-op.
The block sets StrictHostKeyChecking no and UserKnownHostsFile /dev/null,
so ssh coop-* connections skip host-key verification. This is intentional —
these VMs regenerate their host keys, so pinning them would only produce
spurious mismatch warnings.
Use ssh-config for ad-hoc copies of arbitrary paths. To sync the tracked
workspace directory in bulk, use push / pull instead.
List or delete golden images. Without flags, prints every image with its profiles, creation date, and size.
coop images [FLAGS]
| Flag | Description |
|---|---|
--delete <name> |
Delete a named image |
--json |
Emit a JSON array instead of the text table |
coop images
coop images --delete old-image
With --json, each element is { "name", "profiles", "created", "size_bytes" }.
Absence is modelled honestly: profiles is [] (not "none"), created is
null (not "unknown"), and size_bytes is the raw byte count (the text path's
"8.0 GiB" is presentation only).
Change a stopped instance's disk size, memory, or vCPU count. The VM must
be stopped first. At least one of --size, --mem, or --vcpus is required;
they can be combined in a single command.
coop resize [NAME] [--size <SIZE>] [--mem <MIB>] [--vcpus <N>] [--start]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
--size <size> |
New disk size. Absolute: 150 or 150G. Relative: +20 or +20G. |
--mem <mib> |
New memory in MiB (minimum 128). |
--vcpus <n> |
New vCPU count (> 0). |
--start |
Start the instance after applying the change instead of leaving it stopped. |
Absolute disk values set the disk to that exact size; a + prefix adds to the
current size. Memory and vCPU changes are written to the instance's backend
config (the Firecracker per-instance JSON or the Lima lima.yaml), which is
authoritative — the value survives restarts and is reported by coop status.
The global [vm] settings in config.toml only seed these values for new
instances.
By default the instance is left stopped and the change takes effect on the next
coop start. Pass --start to boot it immediately. On Firecracker, if a
--start boot fails (e.g. more memory than the host has), the previous mem/vcpu
is restored so a plain coop start still works; on Lima the lima.yaml is
likewise restored.
Combining --size with --mem/--vcpus applies the disk change first, then the
machine-resource change; the two are separate artifacts and are not applied
transactionally, so if the second step fails the disk change has already taken
effect.
coop resize my-project --size 150G
coop resize --size +20
coop resize my-project --mem 8192 --vcpus 4
coop resize my-project --mem 4096 --start
Save a stopped instance's filesystem as a reusable image, like docker container commit. The committed image is an ordinary coop image: coop images lists it and coop up --image <name> launches new instances from it. The instance must be stopped first so the filesystem is consistent.
coop commit [NAME] --image <name> [FLAGS]
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
--image <name> |
Name of the image to create (required) |
--force |
Overwrite an existing image with the same name |
coop stop my-project
coop commit my-project --image my-project-baseline
coop up . --image my-project-baseline --name fork
Roll a stopped instance back to an image's filesystem in place. The instance keeps its name, index, IP, and workspace association — only the disk is replaced and its recorded image is updated. Run coop start afterwards to bring it back up.
This pairs with commit for a known-good checkpoint before a risky run:
coop stop my-project
coop commit my-project --image safe-point # checkpoint
coop start my-project
# ... a bypass-permissions agent run trashes the environment ...
coop stop my-project
coop restore my-project --image safe-point # back to the checkpoint, same VM
coop start my-project
coop restore [NAME] --image <name>
| Flag | Description |
|---|---|
NAME |
Instance name (required if multiple instances exist) |
--image <name> |
Name of the image to restore from (required) |
Unlike destroy + up --image, restore keeps the same instance identity (name, index, IP) instead of allocating a new one. The disk is reset to the image's size, so restoring an image built before a coop resize returns the instance to the smaller size.
List or inspect available profiles. With no subcommand, lists every profile (builtin and custom).
coop profiles [SUBCOMMAND]
| Subcommand | Description |
|---|---|
list |
List builtin and custom profiles with a one-line summary each (default) |
show <name> |
Print the full definition of a profile: apt packages, pre/post-install scripts, marketplaces, plugins |
coop profiles
coop profiles list
coop profiles list --json
coop profiles show rust
list groups builtin and custom profiles separately. show resolves the name against custom profiles first, then builtins, and prints (custom) or (builtin) next to the name.
coop profiles list --json emits { "builtin": [...], "custom": [...] }, each entry { "name", "summary" }.
Replace the running coop binary with a release from github.com/trailofbits/coop. Downloads the tarball matching the current host triple, verifies its SHA-256 against the release's SHA256SUMS, and (when gh is installed) verifies the GitHub build-provenance attestation before swapping the binary atomically.
No authentication is required. When gh is authenticated against github.com or GITHUB_TOKEN is set, coop update uses it, which helps avoid GitHub API rate limits.
coop update [FLAGS]
| Flag | Description |
|---|---|
--check |
Report whether a newer release exists. Do not download or install. |
--force |
Reinstall even if the current binary is already at the target version. |
--version <VERSION> |
Install a specific release tag (e.g. v0.3.2 or 0.3.2). |
-y, --yes |
Skip the interactive confirmation prompt. |
If coop is installed in a protected directory (e.g. /usr/local/bin), run with sudo. Dev builds (built from an untagged or dirty tree) refuse to self-update; use install.sh to replace them.
coop update --check
coop update
coop update --yes
coop update --version v0.3.2
coop update --force
See also the updates section of the configuration reference for the background-notification settings.
Remove the coop binary and, optionally, its data directories (~/.coop and the update-check state). Refuses to remove the binary when it lives under target/debug/ or target/release/ so cargo run -- uninstall does not delete your build artifact.
coop uninstall [FLAGS]
| Flag | Description |
|---|---|
-y, --yes |
Skip interactive confirmation prompts. Removes data unless --keep-data is set. |
--keep-data |
Remove only the binary; preserve ~/.coop and the update-check state. Conflicts with --purge. |
--purge |
Also remove ~/.coop and the update-check state without prompting. Conflicts with --keep-data. Pairs with --yes for CI. |
Without --yes, the command prints a summary (binary path, data directory, instance and image counts) and asks for confirmation. A second prompt asks whether to also remove the data directory unless --keep-data or --purge is set. Non-interactive runs require --yes.
If the binary lives in a protected directory (e.g. /usr/local/bin), run with sudo. A config file outside the data directory is left in place and a note is printed.
coop uninstall # interactive: prompts for binary and data
coop uninstall --yes # CI: remove binary and data, no prompts
coop uninstall --yes --keep-data # CI: remove binary only
coop uninstall --yes --purge # CI: remove binary and data, explicit
Print a static shell completion script. Pair with source <(COMPLETE=<shell> coop) in your shell rc for dynamic completion of live instance, image, and profile names. See docs/shell-completion.md for full setup recipes per shell.
coop completions <SHELL>
| Argument | Description |
|---|---|
SHELL |
Target shell: bash, zsh, fish, powershell, or elvish |
coop completions bash | sudo tee /etc/bash_completion.d/coop > /dev/null
coop completions bash > ~/.local/share/bash-completion/completions/coop
coop completions zsh > ~/.zfunc/_coop
coop completions fish > ~/.config/fish/completions/coop.fish
Manage GitHub authentication. Specifically, the scoped fine-grained PAT (FGPAT) workflow that pairs github = "pat" mode with per-repo [github.pat."owner/repo"] entries in config.toml. See the GitHub auth section of the configuration reference for the full data model.
coop github <subcommand>
| Subcommand | Effect |
|---|---|
setup-pat [--repo owner/name] |
Run the wizard end-to-end: open the GitHub PAT-creation form, validate the pasted token against api.github.com, store it in a chosen secret manager (Keychain / Secret Service / 1Password / file), and write a [github.pat."owner/repo"] entry. The repo is auto-detected from git remote get-url origin when --repo is omitted. |
rotate-pat --repo owner/name |
Re-run the wizard for an existing entry (FGPATs expire — max 1 year). |
status [--probe] [--json] |
List configured entries and their storage backend. By default the cmd-invocation is not resolved (so Keychain / 1Password prompts don't fire). Pass --probe to also resolve each entry and report whether the secret store still serves it. Pass --json for machine-readable output. |
forget-pat --repo owner/name |
Delete the stored secret from its backend and drop the [github.pat."owner/repo"] entry. Does not add a skip marker — use the auto-prompt's never answer if you want coop to stop asking about this repo. Does not revoke the PAT on GitHub. |
coop github setup-pat --repo trailofbits/coop
coop github status
coop github status --probe
coop github status --json
coop github rotate-pat --repo trailofbits/coop
coop github forget-pat --repo trailofbits/coop
coop github status --json emits { "mode", "entries", "skip" }. mode is
off/auto/env/pat; each entry is { "repo", "storage", "probe" } with
storage a stable token (macos_keychain/linux_secret_service/one_password/
file, or null when unparseable) and probe (ok/unexpected_format/
resolve_failed, or null unless --probe). The token value is never emitted.
Check the configuration file and prerequisites. Prints warnings and confirms the config loads correctly. With --probe, also exercises each [github.pat] entry against api.github.com to confirm the token is still live.
coop validate
coop validate --probe
| Flag | Description |
|---|---|
--probe |
For each [github.pat] entry, resolve the token and call GET /user on api.github.com to confirm it authenticates. Network-dependent; may trigger Keychain / 1Password prompts on macOS. |