# CLI reference One binary serves every role. `vincent` with no arguments opens the [TUI](../guides/tui.md); the subcommands are thin clients over the same localhost API. - [Exit codes](#exit-codes) - [Global behavior](#global-behavior) - [`vincent`](#vincent) - [`vincent version`](#vincent-version) - [`vincent doctor`](#vincent-doctor) - [`vincent agents`](#vincent-agents) - [`vincent daemon`](#vincent-daemon) - [`vincent service`](#vincent-service) - [`vincent project`](#vincent-project) - [`vincent task`](#vincent-task) - [`vincent config`](#vincent-config) - [`vincent status`](#vincent-status) - [`vincent statusline`](#vincent-statusline) - [`vincent update`](#vincent-update) - [`vincent skills`](#vincent-skills) - [`vincent chat`](#vincent-chat) - [`vincent workflow`](#vincent-workflow) - [`vincent trigger`](#vincent-trigger) - [`vincent github`](#vincent-github) - [`vincent issue`](#vincent-issue) - [`vincent gc`](#vincent-gc) --- ## Exit codes | Code | Meaning | |---|---| | `0` | Success | | `1` | The request was rejected — the daemon answered no (bad id, invalid state transition), a daemon-free command refused it (`workflow validate` on an invalid file, `workflow render` on a template that does not execute, `workflow init` on a name already taken, `trigger validate` on an invalid file, `trigger ls` that matched no file, `trigger apply` on a proposal it refuses, `workflow ls --global` that found no file, `workflow apply` on a proposal it refuses), or the client refused the input before sending anything (a `--fields-file` that is not one JSON object of strings, a `trigger test --event` file that is not one JSON object, a `github pr link` number that is not a positive integer, a `github pr unlink` on a task with no live link, a `project edit` that names no field or whose `--max-parallel` is not a whole number) | | `2` | No daemon answered | `vincent daemon status` overloads them usefully: `0` healthy, `1` not running, `2` running but unresponsive. `vincent doctor` follows the same shape: `0` healthy, `1` problems found, `2` no daemon answered. So does [`vincent update`](#vincent-update) — see its own table. [`vincent github pr show`](#vincent-github-pr-show) and [`vincent github pr checks`](#vincent-github-pr-checks) set their own for a different reason: the routes behind them answer `200` whatever they found, so `1` there means the task has no linked pull request or GitHub could not be read, not that the request was malformed. ## Global behavior - **`--json`** is available on every subcommand that prints anything. Empty results render as `[]`, never `null`. Advisory warnings go to stderr so stdout stays pipeable. - **No subcommand auto-starts a daemon.** Only the TUI does. A subcommand that cannot reach one exits `2` with a pointer to `vincent daemon start` — except `vincent doctor`, which prints its whole report first, because the daemon being down is one of the things it is there to tell you. - **Colour only on a terminal.** [`vincent task diff`](#vincent-task-diff) is the one subcommand that colours its output, and only when stdout is a terminal and `NO_COLOR` is unset (`TERM=dumb` turns it off too). Piped or redirected, output is exactly the bytes the daemon served. There is no `--color` flag. - Clients discover the daemon by reading `{data_dir}/daemon.json` and then health-probing it, so a stale file from an unclean shutdown produces the same "no daemon" answer rather than a transport error later. - `--help` works at every level. --- ## `vincent` ```sh vincent [--project ] ``` Opens the TUI, starting a daemon in the background if none is reachable. `--project` opens it on that project. An exact project name is matched first, and only then an all-digit value as a project id, so a project named `3` is reached by `--project 3`. A value that names no registered project is not an error: the TUI falls through to the [next rule](../guides/tui.md#the-startup-project) and says so on the line under the header. ### What only the TUI does Everything else the TUI does has a subcommand. These are the exceptions, and a test fails when the TUI starts calling the API for something that is neither a subcommand nor on this list: | In the TUI | From a shell | |---|---| | Creating and editing a workflow in the workflows view | `vincent workflow init`, your `$EDITOR`, then `vincent workflow validate` | | A task's **Workflow** tab | No subcommand; `vincent workflow render` previews a workflow file | | The triggers view: its list, a trigger's delivery ledger, a dry poll, and creating, editing, arming or deleting a trigger | `vincent trigger ls`, `validate`, `test` and `apply`. Arming is done in the TUI or in `$EDITOR`, never by `apply` | | The agent / model / effort preview in the new-task form and the workflows view, and the new-task form's branch-name preview | `vincent workflow render`, which resolves the same triple the same way. No subcommand previews the branch name a task would get; `vincent task show` prints it once the task exists | | The daemon summary on the board header and daemon view | `vincent daemon status`, `vincent agents`, `vincent doctor` and `vincent config get` between them. The daemon-wide count of slots in use right now, with its lanes and on-input breakdown, is shown only in the TUI; `vincent project ls --json` carries each project's `slots_used` | | Listing a project's local branches in the new-task and new-chat branch pickers | No subcommand: `vincent task add --branch --existing-branch` and `vincent chat start --branch --existing-branch` take the name, and a shell standing in the repository already has `git branch` | | The `@` file picker in the chat composer, which completes a file of the directory the chat's next turn would start in as you type | `vincent chat files` lists the same files and `--mention` prints the same text a picked row inserts. Nothing completes a draft for you, and the message is sent as typed by `vincent chat send` either way | | Live updates as they happen | Subcommands poll: `vincent task transcript -f`, `vincent chat transcript -f`, and `vincent chat send` waits for the answer | ## `vincent version` ```sh vincent version ``` Prints one line: version, commit and build date. A binary built with the plain `go build` toolchain falls back to `debug.ReadBuildInfo`, so the fields are never empty. ## `vincent doctor` ```sh vincent doctor [--json] [--fix [--force]] ``` One report answering "why is nothing running?". Twelve groups: | Group | Rows | |---|---| | Paths | config dir, data dir, config file, whether it parses, and any config path readable beyond its owner | | Daemon | running / not running / unresponsive, pid, port, version, uptime | | Log | daemon log path, size, mtime, and the last 20 lines | | Database | path, size, total on disk including WAL/SHM, applied schema version, `PRAGMA integrity_check`, per-table row counts, workflow-snapshot bytes, and how far back the events table reaches | | Agents | per adapter: found, path, version, `logged_in`, whether the build is one vincent has been tested against, and whether the adapter can restrict on this OS. Quota is not here — it needs a daemon this report does not; see [`vincent agents`](#vincent-agents) | | GitHub | whether [`github.enabled`](configuration.md#github) is on, whether `gh` is installed and logged in, whether a token variable is set, whether issue import/sync is available, and one `sync ` row per project the importer has polled or been asked to: `ok` or `not ok ()`, when it last synced, `import incomplete` until the first import finishes, and `writes N pending, N failed, N conflict` while any [state write-back](api.md#state-write-back) is not synced | | Container | whether [`container.image`](configuration.md#container) names an image, which image, whether the configured runtime answered, and whether steps run in it or on this host | | Skills | per [published skill](#vincent-skills): the version this binary ships, the state of the copy in the global skills store, and the agents it is linked into | | Update | whether [`update.check`](configuration.md#update) is on, the latest stable release and when it was last seen, this binary's version, and whether the running daemon is older than it | | Backup | whether [scheduled backups](configuration.md#backup) are on, the directory, interval and keep, the last success and last attempt, when the next run is due, the last archive's size, how many scheduled archives are kept, and the last error | | Storage | disk free under the data dir, worktree count and bytes, orphans | | Tasks | counts by state, so "12 blocked" is visible without opening the board, plus any task whose state and step runs contradict each other | Exit `0` when everything checked is healthy, `1` when problems were found (they are listed under `PROBLEMS`), `2` when no daemon answered. **Unhealthy is a closed set**: `config.yaml` exists and does not parse, the daemon is alive but not answering, `integrity_check` is not `ok`, the database is at a schema version newer than this binary understands, orphaned worktrees are present, scheduled backups are on and the last attempt failed, or a task is **unreconciled** — `queued` (or finished) while one of its step runs is still marked `running`, which means crash recovery could not close the previous attempt and admission will not run that task until it does (a `fan_out` step's own row is excluded: a parent waiting on its lanes holds one by design) ([Troubleshooting](../guides/troubleshooting.md#start-here-vincent-doctor)). The **Update** rows never set it either: a newer release and a daemon still running the previous build both leave everything working, so both are stated as facts with the command that acts on them — never as problems. The **GitHub** rows never set the exit code either, and they say why: every "no" they can report — the toggle off, `gh` missing, `gh` logged out, no token, a failing sync — leaves tasks and local issues working exactly as before, so the row ends with *tasks and local issues are unaffected*. The token row names the **variable** (`GITHUB_TOKEN` or `GH_TOKEN`), never its value: a diagnostic is something people paste into issues. The **Container** rows follow the same rule and for the same reason: containerization is off by default, so a machine with no runtime — or a Windows daemon, which cannot host one — runs every step on the host exactly as it always has. The runtime is probed **even when `container.image` is unset**, because "would this work if I turned it on" is the question the group exists to answer. So do the **Skills** rows: a missing, stale or unreadable skill costs you help in your own agent session and stops no vincent run, because the built-in workflows that use a skill (`create-workflow`, `update-workflows`, `create-trigger` and `update-triggers`) carry its text in their own prompts. The group ends with `run: vincent skills install` when anything is not current. The **Backup** group is the exception to that rule. When [`backup.interval`](configuration.md#backup) is set and the last scheduled backup failed, `PROBLEMS` lists `the last scheduled backup failed: ` and the command exits `1` until a later attempt succeeds. The other rows report defaults nobody chose. This one reports a feature you turned on, and a backup that fails without anyone noticing is found out on the day it is needed. With backups off the group only shows the settings and never sets the exit code. A backup that is merely overdue, for example because the daemon was stopped, is not a problem, and neither is a failure to delete old archives after a successful run, which the group shows as a prune error. An adapter row also ends with what vincent knows about the build itself: `untested version` and the builds it was judged against, `incompatible version` for a build known to break, and `no restricted mode on ` where the adapter cannot honour `permission_mode: restricted` here (cursor on Windows). `untested` is the normal answer for anyone on a current CLI and is not a defect — agent CLIs ship far more often than vincent does. A `tested` build prints nothing: one line of good news per adapter would bury the one warning. A missing or logged-out agent CLI — or an untested, incompatible or cannot-restrict one — is reported and deliberately does *not* set the exit code — most machines have one of three adapters installed, and a doctor that exits `1` almost everywhere is no use in a script. Neither do task *counts*: twelve blocked tasks is information, not a defect. A `permissions` row names a config path whose mode grants group or other access, the mode it should have, and the exact `chmod`. It is a warning: the daemon tightens both paths on every start, so a row means no daemon has started on this config or something widened it since — not a reason to exit `1`. There are no such rows on Windows, where modes carry no access control. A `keymap` row is one thing the lenient load of `tui.keys` let through: an operation id a release retired, ignored; an operation left unbound because your binding took its default; or a fixed key shadowed by your binding. It is a warning too — the daemon runs and the TUI works — so it never sets the exit code. A `warning` row is a setting that loads and takes effect but does not do what it looks like it asks for: a deprecated [`tui.board.group_by`](configuration.md#tuiboardgroup_by) level stripped on load, a half-written `notify` block, or `delete_remote_branch_on_archive` on while `delete_empty_branch_on_archive` is off. These are the same lines the daemon logs as `config warning`. Like `keymap`, it never sets the exit code. The database rows **measure and change nothing**. `total on disk` is the file plus its WAL and SHM sidecars, which is the honest figure — the store runs in WAL mode, so the file alone understates the footprint between checkpoints. `rows` lists every table in the schema, biggest first, so whichever one is growing is the first thing you read; the set comes from the database itself, so a table a later version adds appears without this command being taught about it. `workflow snapshots` totals the per-task workflow YAML, the second growth driver beside `events`. `oldest event` is how far back history reaches, which is what makes a row count extrapolable. There is no threshold, no warning and no retention window: rows are kept indefinitely, and `--fix` is the only thing that touches the file at all. **Without a daemon** the report is still printed in full — paths, whether the config parses, adapter detection, published-skill state, the log tail, disk free and the worktree count — and the database and task rows read `unknown — daemon not running`. The Backup group shows its settings from `config.yaml`, but its last attempt is unknown and it raises no problem: that state lives in the daemon. They are not read from a second process: only the daemon opens the database. The byte figures, the row counts and the span are unknown together, for that reason and no other. `--json` emits the whole report for scripting and for pasting into a bug report. ### `vincent doctor --fix` Reclaims orphaned directories and compacts the database. Both are writes, so the daemon performs them: `--fix` without a running daemon is refused, and the report is printed anyway. - **An orphan** is an entry under a data root that no task row claims — the residue of a forced project delete or of a removal that failed partway. This is the same scan and the same removal [`vincent gc`](#vincent-gc) runs, so the two commands cannot disagree; `gc` is the one to reach for when reclaiming is the whole point. A non-directory there is reported and never removed. - A worktree with **local changes** (untracked files included) is skipped unless you add `--force`. An orphan whose dirty check cannot run at all — the project repo is gone, so the directory is just files — counts as dirty for this purpose: nothing is deleted on the strength of a check that did not happen. - Vincent does **not** run `git worktree prune` in your repositories. A stale registration can therefore survive there; the report says so and names the command. - Compaction is a real `VACUUM`, and it is **skipped while any task is running or awaiting input** — the rewrite takes an exclusive lock, and stalling a step mid-write is worse than declining. The skip is reported with its reason. ```sh vincent doctor --json | jq '.problems[]' vincent doctor --fix --force ``` ## `vincent agents` ```sh vincent agents [--json] [--refresh] ``` One row per agent adapter, in the daemon's registration order: what the TUI's daemon view shows under *adapters*, for a script or a shell. It is a thin client of [`GET /v1/agents`](api.md#daemon) and needs a running daemon — with none it exits `2`. ``` AGENT VERSION BUILD LOGIN QUOTA NOTES claude 2.1.226 tested ok claude status line · 5h 28.5% → 2026-09-16T14:40:00+02:00 · 7d 62% · read 2026-09-16T11:40:02+02:00 codex - - - spent → 2026-09-16T13:40:00+02:00 not found: …; no mid-run input; no @ file expansion cursor 2026.09.02-1c4f7a0 untested NOT LOGGED IN unknown no mid-run input; no skill listing; no @ file expansion ``` | Column | Shows | |---|---| | `VERSION` | The installed CLI's version, `-` when none was found | | `BUILD` | `tested`, `untested` or `incompatible` — whether vincent has been tested against this build. `-` when there is nothing installed to judge | | `LOGIN` | `ok`, `NOT LOGGED IN` or `unknown`, the words [`vincent doctor`](#vincent-doctor) uses. `unknown` is a probe that could not tell — it timed out, or the CLI predates the command — never a no. `-` for an adapter that is not installed | | `QUOTA` | The adapter's usage window, below | | `NOTES` | Bad news only: `not found: `, `no mid-run input` (an `on_input: require` step cannot use it), `no restricted mode on `, `no skill listing` (the adapter cannot list the skills its CLI would load; a daemon that predates the field adds nothing), `no @ file expansion` (the CLI recognizes an `@path` but will not put the file in front of the model itself — the model has to read the path with a tool), `option probe failed (curated catalog)`. A healthy adapter's cell is empty | `QUOTA` is the one block the daemon serves, labelled by where it came from. Nothing is merged on the client: when a source has reported a reading, that is what the daemon serves, and the last usage-limit stop it watched is the fallback ([Agent CLIs](../guides/agents.md#how-much-quota-is-left-and-who-will-say)). | Cell | Meaning | |---|---| | `unknown` | No reading and no recorded stop. Not the same as fine | | ` · → · … · read