# CLI and MCP

> **Preview.** This page describes the interface WIRK launches with. The hosted service is in private staging and access is by invitation. What is live today: /docs/index.md#what-is-live-today

> **Not published yet.** This is the 0.3 CLI and MCP server. Both print the compact text the service renders; neither keeps state beyond your service address and tokens.

## Commands

```
wirk status                               who you are, your wirk, what is in progress or needs review
wirk status 'fix the login bug'           the same, with what matters for your task
wirk query ID                             fetch an item; short IDs and exact titles work; ID@N is revision N
wirk query about='hook drain'             what matters for these words, ranked by meaning
wirk query status=open kind=work          list with filters; status shows the keys and values
wirk query kind=context                   the organization's context and its initiatives
wirk query proposal=proposed,deferred     proposals waiting for a decision
wirk query receipt=REQUEST_ID             the stored receipt of a write or review
wirk write new 'Title' --link related_to:ID          a doc; add kind=work for a task
wirk write edit ID@N status=completed --evidence 'tests pass'  complete it; N is the rN you read
wirk write link ID@N contributes_to PARENT@N          link two items
wirk write --request FILE                 any write as JSON; - reads standard input
wirk review ID@N ACTION --reason 'Why'    ACTION is accept, reject or defer
wirk show status                          a live page a person can open
wirk upload PATH                          store a file and print how to attach it
wirk download ITEM FILE                   save a stored file
wirk login                                connect this machine to https://api.wirk.life
```

Add `--json` to any command for the full response envelope as JSON. `wirk COMMAND --help` shows each command's keys and examples.

## Arguments

- `KEY=VALUE` is a key when the part before `=` is lowercase letters, digits and underscores. Anything else is a positional word.
- In `query`, request keys are `about`, `receipt`, `depth`, `sort`, `limit`, `max_bytes`, `cursor` and `workspace_id`. Every other key is a filter. A comma makes a list: `status=open,in_progress`. `text`, `linked` and `folder` always take one value.
- Positional words in `query` are fetch refs: an ID, a short ID or an exact title. Quote titles with spaces.
- `ID@N` names an item at revision N, but only when ID looks like an ID (6–32 hex, optionally with a prefix such as `item_`). `Meet @3pm` stays a title.
- In `status`, positional words become the task: `wirk status fix the hook drain` sends `{"task": "fix the hook drain"}`.
- Giving a key twice is an error; use a list.

## Write shortcuts

| Shortcut | What it sends |
|---|---|
| `write new TITLE` | `item.create`. The first argument is always the title. `kind=work`, `kind=doc` (the default) or `kind=context level=organization\|initiative`; `owner=` or `--criterion TEXT` imply `kind=work`; other `KEY=VALUE` set fields; `--body TEXT` or `--body-file PATH` (`-` for standard input); `--link TYPE:REF[@N]` adds a link from the new item; `--upload ID` attaches an upload; `--allow-duplicate-of ID` with `--reason` overrides a duplicate refusal |
| `write edit ID@N` | `item.edit` with `expect: {ID: N}`. `KEY=VALUE` sets fields (`KEY=` clears one); `owner=` sets or clears the owner; `--title`, `--body`, `--body-file`; `--link` adds links from the item; `--upload ID` attaches an upload; `--evidence TEXT` is the evidence when the edit completes work |
| `write link FROM@N TYPE TO[@N]` | `link.create` with `expect` |

`--evidence` is sent as the write's `reason`, so it cannot be combined with `--reason`. `--reason` is for proposing (`--propose --reason`), archiving and overriding a duplicate. `--link` types in shortcuts are `related_to`, `contributes_to` and `requires`. Links that carry more data (`cites`, `relies_on`), link removal, archive, restore, replacing criteria, changing context and folders use `write --request` with the JSON shapes in [write](/docs/write.md).

Common options: `--propose` (needs `--reason`), `--reason TEXT`, `--request-id ID`, `--json`. The CLI makes a request ID (`w-`, `r-` or `u-` and 10 hex characters) when you give none, and prints it.

The CLI stops before calling, with exit code 2, when it can tell a command is wrong: an edit without `@N`, `--propose` without `--reason`, `--evidence` with `--reason`, a placeholder reason such as `REASON`, an unknown link type, two body options.

## Reading result lines

Lines the service prints in the form `label: command` are runnable. Type `wirk`, then everything after the first `: `:

```
23 more: query delivery_phase=current kind=work limit=3 cursor=AQ…k
next: query 9e4c21f7
decide with: review c4a1e902@1 ACTION --reason REASON
list them: query proposal=proposed,deferred
confirm one: write link 8d24f6b1@1 contributes_to 2f9b3c4e@7
```

Values are already quoted for `sh` and `zsh`. Replace `ACTION` and `REASON` yourself. In MCP, the same line maps to a tool call: `query …` is `wirk_query`, `review …` is `wirk_review`, and so on; bare words are `fetch` refs, `ID@N` is a revision, the request keys are arguments, every other `KEY=VALUE` goes in `fields`, and a comma list becomes a JSON list.

## Exit codes and errors

| Exit | Meaning |
|---|---|
| 0 | `ok` |
| 1 | The service refused, or the client failed |
| 2 | Usage error; nothing was sent |

The service's refusals are printed exactly as it words them. In text mode, errors of the client's own go to standard error as `Error CODE: what happened`, followed by the next step. With `--json` they are a JSON envelope on standard output. See [Errors](/docs/errors.md).

## MCP: five tools

| Tool | Arguments |
|---|---|
| `wirk_status` | `task`, `workspace_id`, `max_bytes`, `format` |
| `wirk_query` | `fetch` (list of refs; `ID@N` for a revision), `about`, `fields`, `receipt`, `depth`, `sort`, `limit`, `max_bytes`, `cursor`, `workspace_id`, `format` |
| `wirk_write` | `operations` (required), `request_id` (optional), `expect`, `mode`, `reason`, `workspace_id`, `format` |
| `wirk_review` | `decisions` (required; each with `id`, `revision`, `action`, `reason`), `request_id` (optional), `workspace_id`, `format` |
| `wirk_show` | `preset`, or `title` and `blocks`, or `revoke`; `workspace_id`, `format` |

Arguments are the [HTTP](/docs/http-api.md) request bodies. When you leave out `request_id`, the server makes one and the answer names it; after an uncertain result, resend with that ID. To complete work, put the evidence in `reason`. Each call returns one text result: the service's compact text, or with `"format": "json"` the envelope as JSON text. A result is marked as an error exactly when `ok` is false. There is no MCP tool for files; use the CLI.

Example `wirk_write` call, completing work:

```json
{"expect": {"5c1e7a90": 3},
 "reason": "Fixed in 4f2a9c1; tests/test_webhooks.py::test_retry_backoff passes",
 "operations": [{"op": "item.edit", "id": "5c1e7a90", "patch": {"fields": {"status": "completed"}}}]}
```

## Configuration and the token

- `~/.config/wirk/` (or `$WIRK_CONFIG_DIR`) holds `config.json` with the service address and `agent-token`, the token `wirk login` makes for your agents, readable only by you. Every command and the MCP server use it.
- The MCP server reads the files on every call, so a new login needs no restart.
- `WIRK_CONFIG_DIR` gives a separate identity on one machine, for example a scratch wirkspace in a trial.
- The clients never follow redirects and never send a token anywhere but the address it was made for. Uploads and downloads go to storage through signed links that carry no token.
