# CLI reference

> Every comity command, flag, exit code and config file.

The `comity` CLI signs you in, writes agent configuration into your repos, checks your setup and runs inside your agents' hooks. It needs Node.js 22 or newer.

```sh
npm i -g https://api.comity.so/cli/comity-cli.tgz
comity --version
```

Running `comity` with no arguments shows a home screen: your account, the server, the current repo, which agents are set up there (`✔` ready, `!` partial, `–` missing) and the next command to run.

## Global options

| Option | Effect |
|---|---|
| `--help`, `-h` | With no command: the command overview. After a command (`comity init --help`): that command's usage, options and examples. |
| `--version`, `-v` | Print the CLI version. `comity version` does the same. |
| `--no-update-check` | Skip the update check for this run. See [Updates](#updates). |

Unknown commands exit with code `2` and suggest the closest match.

## comity login

```text
comity login [--server <url>] [--token <cmt_…>]
```

Signs in and stores the credentials on this machine.

| Option | Description |
|---|---|
| `--server <url>` | Comity server. Default `https://api.comity.so`, or `COMITY_SERVER` when set. |
| `--token <cmt_…>` | Use an existing token instead of the browser. For headless machines and CI. |

Without `--token` (and without `COMITY_TOKEN`), login uses a browser device flow:

1. The CLI prints a short code and a URL, and opens the URL in your browser (unless `COMITY_NO_BROWSER` is set).
2. You approve the code in the browser while signed in to Comity.
3. The CLI polls until the server mints a personal token named `cli · <hostname>` and saves it.

The CLI then verifies the token with `GET /v1/me` and prints the account, org and role:

```text
✔ Logged in as diego@acme.com · org acme · role member
  Server https://api.comity.so · credentials saved to /home/diego/.config/comity/config.json

Next: run comity init in each repo.
```

A token must look like `cmt_` followed by 43 base64url characters. A rejected token (revoked, expired, or issued by a different server) fails with `token rejected by server`.

```sh
comity login
comity login --server https://comity.internal.example.com
comity login --token cmt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

## comity logout

```text
comity logout
```

Deletes the stored credentials file. If `COMITY_TOKEN` is set in the environment, the CLI warns that it keeps authenticating.

## comity init

```text
comity init [--tools <ids>|all] [--yes] [--server <url>]
```

Writes Comity's hook, MCP and agent-instruction configuration into the current repository (the git top level; outside a git repo it writes into the current folder and warns).

| Option | Description |
|---|---|
| `--tools <list>` | Comma-separated agent ids, or `all`. Skips the question. Ids: `claude_code`, `codex`, `cursor`, `copilot`, `gemini`, `windsurf`, `kiro`, `cline`, `roo`, `opencode`, `amp`, `droid`, `zed`, `continue`, `goose`, `junie`, `augment`, `omp`. |
| `--yes`, `-y` | No question: use `--tools`, or the default `claude_code,codex`. |
| `--server <url>` | Server the MCP config points at. Default: the server you logged in to. |

In an interactive terminal without `--tools` or `--yes`, `init` lists every supported agent, grouped by how Comity reaches it (hooks + MCP, or MCP + instructions only). Agents found on this machine (an executable on `PATH` or a config folder in your home directory) are pre-selected; when none is found, Claude Code and Codex are. Space toggles, `a` toggles all, Enter confirms. Cancelling writes nothing (exit `130`).

In a pipe, in CI, or with `--yes`, `init` sets up `claude_code,codex` unless `--tools` says otherwise. Every other agent is opt-in: name it in `--tools`, or use `--tools all`.

Files written per agent:

| Agent | Files |
|---|---|
| `claude_code` | `.claude/settings.json` (hooks), `.mcp.json` (MCP server `comity`) |
| `codex` | `.codex/hooks.json` (hooks), `.codex/config.toml` (`[mcp_servers.comity]`) |
| `cursor` | `.cursor/hooks.json`, `.cursor/mcp.json`, `.cursor/rules/comity.mdc` |
| `copilot` | `.github/hooks/comity.json`, `.github/mcp.json` (Copilot CLI), `.vscode/mcp.json` (VS Code) |
| `gemini` | `.gemini/settings.json` (hooks + MCP), `GEMINI.md` (Comity block) |
| `windsurf` | `.windsurf/hooks.json`, `.windsurf/rules/comity.md` (MCP is user-level: init prints the entry) |
| `kiro` | `.kiro/hooks/comity.json`, `.kiro/settings/mcp.json`, `.kiro/steering/comity.md` |
| `cline` | `.clinerules/hooks/<Event>` scripts (MCP is user-level: init prints the command) |
| `roo` | `.roo/mcp.json`, `.roo/rules/comity.md` |
| `opencode` | `.opencode/plugins/comity.ts` (plugin), `opencode.json` (MCP) |
| `amp` | `.amp/plugins/comity.ts` (plugin), `.amp/settings.json` (MCP) |
| `droid` | `.factory/hooks.json`, `.factory/mcp.json`, `AGENTS.md` (Comity block) |
| `zed` | `AGENTS.md` (or the instructions file Zed already reads; MCP is user-level: init prints the entry) |
| `continue` | `.continue/mcpServers/comity.yaml`, `.continue/rules/comity.md` |
| `goose` | `.agents/plugins/comity/plugin.json` + `hooks/hooks.json`, `AGENTS.md` (MCP is user-level) |
| `junie` | `.junie/comity-hooks.json`, `AGENTS.md` (hooks and MCP are user-level: init prints both) |
| `augment` | `.augment/hooks/comity.sh` + `comity.cmd`, `.augment/rules/comity.md`, personal `.augment/settings.local.json` (not committed) |
| `omp` | `.omp/extensions/comity/index.ts` (bridge extension), `.omp/mcp.json` (MCP server `comity`) |

`init` merges into existing files: it replaces only handlers whose command runs `comity hook`, only the `comity` MCP entry and only the marked Comity block of shared instruction files (`AGENTS.md`, `GEMINI.md`), keeps everything else, and is safe to re-run. No token is ever written to the repo. The exact contents, approvals and limits are on each [agent's page](/docs/agents/claude-code).

After writing, `init` prints next steps per agent (trust prompts, approvals, user-level MCP entries to paste) and, inside a git repo, the `git add` / `git commit` command for the repo files that changed, so teammates get the same setup when they pull.

```sh
comity init
comity init --yes
comity init --tools claude_code,omp
comity init --tools cursor,gemini
comity init --tools all --yes
```

## comity status

```text
comity status [--json]
```

Shows:

- **Account**: user, org, role, server (and whether it came from the environment or the config file).
- **Plan**: plan name, active developers this month and repos against the plan's limits, and who is not coordinated when over a limit.
- **Repo**: path, remote fingerprints, branch, HEAD and merge base with the default branch. A repo without a remote shows `(none — hooks are inactive here)`.
- **Your intents**: your live intents in this repo with status, branch and claim count.
- **Teammates online**: other developers' live agents (`●` active, `○` idle) with tool, branch and intent.

`--json` prints the same data as one JSON object (`server`, `server_source`, `token_source`, `user`, `org`, `usage`, `repo`, `intents`, `teammates`, plus `*_error` fields for sections that failed).

## comity doctor

```text
comity doctor [--json]
```

Checks the whole chain from config to a real hook round trip. Each warning or failure prints a `›` line with the fix.

| Check | What it verifies |
|---|---|
| `config` | Server and token are configured (and where from); the token has the `cmt_` shape. |
| `server` | `GET /healthz` answers. Warns above 400 ms, because pre-edit checks that take longer fail open. |
| `token` | `GET /v1/me` accepts the token and it has the `hooks:evaluate` and `mcp:tools` scopes. |
| `git` | You are in a git repo that has at least one remote. Hooks are inactive in repos without a remote. |
| `<agent> hooks`, `<agent> mcp`, … | One group per agent that has Comity config in this repo or your user config (agents without any are skipped): hooks installed for every event, the `comity` MCP entry present and authenticating, the rules/instructions block current. Hooks from an older `comity init` with a different matcher (Claude Code before 0.4.1: no `ExitPlanMode`) warn "update available"; rerun `comity init`. Agents whose MCP entry lives in user-level config the CLI does not write (Windsurf, Cline, Zed, goose, Junie, Augment) warn until you add it. |
| `agents` | Warns when no agent is set up here at all. |
| `round trip` | Sends a `session_start` and `session_end` for a throwaway session to `/v1/hooks/evaluate`. Warns above 400 ms. |
| `CLI up to date` | Compares your version with the latest release. |

Exit codes: `0` all checks passed, `1` degraded (warnings), `2` broken (failures). `--json` prints `{"result": "ok" | "warn" | "fail", "checks": [{"name", "level", "detail", "fix"?}]}`.

```json
{
  "result": "warn",
  "checks": [
    { "name": "config", "level": "ok", "detail": "server https://api.comity.so [file], token [file], /home/diego/.config/comity/config.json" },
    { "name": "codex hooks", "level": "warn", "detail": "missing SessionEnd", "fix": "run `comity init --tools codex`" }
  ]
}
```

## comity whoami

```text
comity whoami [--json]
```

Prints the signed-in user, org (name and slug), role, server, and token name and kind, including whether the token came from `COMITY_TOKEN` or the config file. `--json` prints the `GET /v1/me` response plus `server`. Exits `1` when not logged in.

## comity handoff

```text
comity handoff <email|@name> [--intent INT-n] [--note "…"] [--yes] [--json]
```

Hands one of your intents off to a teammate: they become its owner and their agents pick it up at their next tool call or prompt. See [Handoffs](/docs/collaboration/handoffs).

| Option | Description |
|---|---|
| `<email>` / `@name` | The teammate. `@name` matches a member by name, first name or email local part, case-insensitive (`@jordan`). |
| `--intent INT-n` | The intent to hand off. Default: your only live intent in the current repo; with several, the command fails and lists them. |
| `--note "…"` | A note for the teammate, up to 2,000 characters. |
| `--yes` | Don't ask for confirmation. In a terminal the CLI asks before handing off. |
| `--json` | Print the result as JSON. |

It uses your `comity login` token, whose default scopes include `intents:write` and `members:read`.

```sh
comity handoff @jordan --note "PDF layout is done; totals still wrong"
comity handoff priya@acme.com --intent INT-12 --yes
```

## comity open

```text
comity open [dashboard|messages|conflicts]
```

Opens the web dashboard (`/dashboard`, `/dashboard/messages` or `/dashboard/conflicts`) in your browser, in the org you are logged in to. The web address is the one you logged in through, or `COMITY_WEB_URL`. With `COMITY_NO_BROWSER` set, the URL is printed instead.

## comity update

```text
comity update [--check]
```

Fetches the release manifest (`<server>/cli/latest.json`) and, if a newer version exists, runs `npm install -g <tarball url>` and confirms that `comity --version` on your `PATH` reports the new version. `--check` only reports whether an update is available. `comity update` always fetches the manifest; it ignores the 12-hour throttle and any earlier "no".

If the install fails, the CLI prints the exact command to run by hand and the likely cause (npm not on `PATH`, files in use on Windows, `EACCES` permissions).

## comity help

```text
comity help [command]
```

Without an argument, lists all commands. With one, shows that command's usage, options and examples (same as `comity <command> --help`).

## Commands run by your agents

These exist for agent configuration files. You don't run them yourself.

| Command | Used by | Output |
|---|---|---|
| `comity hook --tool <tool> --event <Event>` | Every hook entry `comity init` writes (directly, or through a `node -e` launcher for hosts that run hooks through PowerShell on Windows and sh elsewhere), and the omp/OpenCode/Amp plugins | Reads the host's hook JSON on stdin, calls `/v1/hooks/evaluate`, prints the host's native hook output. Exits `0`, except `2` with the reason on stderr where the host blocks by exit code (Kiro, Windsurf). See [Hook events](/docs/reference/hook-events). |
| `comity mcp` | Stdio MCP entries written by `comity init` (`"command": "node"` with a one-line launcher that runs `comity mcp` through the system shell, the same entry on every OS) | A stdio MCP server. It forwards every message to `<server>/mcp` with your login's token and writes the responses (streamed events included) to stdout, one JSON-RPC message per line. See [Any MCP client](/docs/agents/mcp-clients#stdio-bridge-comity-mcp). |
| `comity mcp-headers` | Claude Code `headersHelper`, Codex `http_headers_helper` | `{"Authorization":"Bearer cmt_…"}` |
| `comity auth-header` | omp MCP header `"!comity auth-header"` | `Bearer cmt_…` |

`mcp`, `mcp-headers` and `auth-header` read `COMITY_TOKEN` first, then the stored login. `mcp-headers` and `auth-header` exit `1` when neither exists. `comity mcp` keeps running and answers every request with an error that says to run `comity login`, so your MCP client shows the reason. `comity mcp` writes only protocol messages to stdout. It logs errors to stderr, and with `COMITY_DEBUG=1` it also logs each message it forwards.

## Updates

Interactive commands (`login`, `logout`, `init`, `status`, `doctor`, `whoami`, `open`, and the home screen) check for a newer CLI at most once every 12 hours, with a 1.5-second timeout. The `hook`, `mcp`, `mcp-headers` and `auth-header` commands never check.

- In a terminal, the CLI asks `Update now? (Y/n)`. Answering no silences that version for 24 hours.
- Otherwise it prints one line on stderr.

Turn checks off with `--no-update-check`, `COMITY_NO_UPDATE_CHECK=1`, or by running in CI (`CI` set). The result of the last check is cached in `update-check.json` next to the config file.

## Files

| File | Contents |
|---|---|
| Config: `~/.config/comity/config.json` (`$XDG_CONFIG_HOME/comity/` when set); Windows `%APPDATA%\comity\config.json` | `server`, `token`, `machine_id`, `data_mode`, `web_url`. Written with owner-only permissions. |
| `update-check.json` (same folder) | Last update check and any skipped version. |
| `hook.log` (same folder) | One line per hook that failed open, rotated at 1 MiB. |
| `<os temp>/comity-spool/` | Per-session hook state (deduplication, queued notices, workspace map cache). Pruned after 48 hours. |

`machine_id` is a random UUID. The server only receives a hash of it, and an HMAC of your worktree path keyed by it; raw local paths never leave your machine.

## Exit codes

| Code | Meaning |
|---|---|
| `0` | Success |
| `1` | Error (not logged in, server unreachable, `doctor` degraded) |
| `2` | Usage error (unknown command or flag), or `doctor` found failures |
| `130` | `init` cancelled at the question |

See [Environment variables](/docs/reference/environment-variables) for everything the CLI reads from the environment.
