# CLI and MCP

The public [CLI](https://github.com/wirkspace/wirk-cli) and [MCP server](https://github.com/wirkspace/wirk-mcp) install at their latest release, with a separate [agent skill](https://github.com/wirkspace/wirk-skill); `wirk --version` shows what is installed. Both clients print the compact text the service renders. [Install and connect them](/docs/getting-started.md) according to the host your agent uses.

## Commands

Start with `wirk status`. Read items and messages with `query`:

```
wirk status 'fix the login bug'
wirk query 5c1e7a90
wirk query about='hook drain'
wirk query status=open kind=work
wirk query inbox=me wait=60
```

`status` gives an overview, and words after it focus that overview on a task. `query` fetches by ID, short ID, exact title or `ID@N` revision; `about` finds relevant work by meaning on Pro and by words otherwise. Filters list matching items. `inbox=me wait=60` waits up to a minute for a message. `wirk query receipt=REQUEST_ID` retrieves a stored write, review or upload receipt.

Create and update work with `write`; decide proposals with `review` when your role may review:

```
wirk write new 'Prepare the launch checklist' kind=work
wirk write edit 5c1e7a90@3 status=completed \
  --evidence 'Tests pass'
wirk write message --on 5c1e7a90 \
  --body 'Ready for review'
wirk write seen 77aa88bb
wirk review c4a1e902@1 accept \
  --reason 'Matches the agreed criteria'
```

For batches, use `wirk write --request FILE` (`-` reads standard input). `wirk upload PATH` stores a file and prints how to attach it; `wirk download ITEM FILE` saves a stored file, using the file ID on the item's Files line. `wirk show status` makes a read-only page for a person. `wirk login` connects this machine to `https://api.wirk.life`. `wirk --help` and `wirk COMMAND --help` show the complete forms.

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

## Login

`wirk login` opens your browser. Press **Connect** to approve the machine with one click. With no browser on the machine, it prints a link to open in a browser on any computer.

A login lasts until you revoke the machine on your [account page](https://app.wirk.life/account) or it goes 90 days unused. Then run `wirk login --new`.

## 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`, `wait` 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

### `write new TITLE`

Creates a record by default. Add `kind=work` for a task or `kind=context level=organization` or `kind=context level=initiative` for context. `owner=` or `--criterion TEXT` also implies work. Set fields with `KEY=VALUE`; use `--body` or `--body-file` (`-` for standard input), `--link TYPE:REF[@N]`, and `--upload ID` when needed. `--allow-duplicate-of ID` with `--reason` overrides a duplicate refusal.

### `write edit ID@N`

Edits at the revision you read and sends `item.edit` with that revision in `expect`. Set or clear fields with `KEY=VALUE` or `KEY=`; `owner=` changes the owner. Use `--title`, `--body`, `--body-file`, `--link` or `--upload` for those parts. Completing work needs evidence through `--evidence TEXT`.

### `write link FROM@N TYPE TO[@N]`

Creates a link with the source revision in `expect`. Link types in shortcuts are `related_to`, `contributes_to` and `requires`.

### `write message`

Sends a message and links it to each `--on ITEM` (a comma list). `--to ID,…` addresses principals; `--reply-to ID` replies to a message. Add `--title`, `--body` or `--body-file`. With neither `--to` nor `--on`, it goes on the company board.

### `write seen ID…`

Marks each ID seen. Every word may be one ID or a comma list. Seen means received, not agreed.

`--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. 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.

## Import issues

The CLI imports issues from GitHub, Linear or Jira. Each issue becomes work, with its history as text, comments in a linked record and the raw issue as a file. Running the import again brings only what changed. Start with a dry run; the plan names any account setup an administrator must apply at their own terminal.

```
wirk import github acme --dry-run
wirk import linear TEAM --dry-run
wirk import jira PROJECT --dry-run
```

After setup, run the same command without `--dry-run` to import as your agent. `workspace_id=ID`, `map=FILE`, `--overwrite` and `--json` are available on each form. `wirk import --help` gives the full grammar.

- GitHub is read through your `gh` login. Name a private repository (`acme/private-repo`) explicitly; everyone in the wirkspace reads imported content.
- Linear uses a personal API key restricted to Read. With no team named, it imports public teams; name a private team explicitly.
- Jira uses a scoped read-only token. With no project named, it imports projects the token can browse; name issues with a security level or restricted comments/worklogs explicitly.
- People and statuses use the importer's mapping file under `~/.config/wirk/import/`.
- Imported text is source content, never instructions.


## 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
read: query 1a2b3c4d 77aa88bb
confirm one: write link 8d24f6b1@1 contributes_to 2f9b3c4e@7
```

Values are already quoted for `sh` and `zsh`. A value may be quoted together with its key, as in `query 'about=hook drain'`; the shell reads that exactly as `about='hook drain'`. `decide with:` is the one line that is not runnable as printed: it appears when your role may review, person or agent, and you fill in `ACTION` (`accept`, `reject` or `defer`) and `REASON` before running it. The CLI refuses the placeholders, so a pasted line decides nothing (see [review](/docs/review.md#decide)).

The footer's `mark seen what you acted on: write seen …` line uses the write shortcut. Mark seen only messages you acted on, never your own shared-principal handoff (see [messages](/docs/messages.md#who-you-are-to-messages)). In MCP, call `wirk_write` with `operations: [{"op": "message.acknowledge", "messages": ["77aa88bb", "1a2b3c4d"]}]` for those message IDs.

Other result lines map to MCP tools too: `query …` is `wirk_query` and `review …` is `wirk_review`; 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 |

The service's refusals are printed on standard output exactly as it words them. The client's own errors go to standard error as `Error CODE: what happened`, with a next-step hint when present; with `--json` they are a JSON envelope on standard output. See [Errors](/docs/errors.md).

A CLI usage error caught before sending, such as a missing `@N`, has code `invalid_input` and exits with 2. In text mode it prints to standard error, leaving standard output empty. This missing-`@N` example shows the form; the message is schematic:

```
Error invalid_input: <message>
  See: wirk write --help
```

With `--json`, the same client error is an envelope on standard output; standard error is empty.

## MCP: five tools

- `wirk_status`: `task`, `workspace_id`, `max_bytes`, `format`.
- `wirk_query`: `fetch` (refs; `ID@N` for a revision), `about`, `fields`, `wait` (0–60 seconds with `inbox=me`), `receipt`, `depth`, `sort`, `limit`, `max_bytes`, `cursor`, `workspace_id`, `format`.
- `wirk_write`: required `operations`; optional `request_id`, `expect`, `mode`, `reason`, `workspace_id`, `format`.
- `wirk_review`: required `decisions` (each with `id`, `revision`, `action`, `reason`); optional `request_id`, `workspace_id`, `format`. Anyone whose role may review can decide; other roles are refused.
- `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 service refusal is marked as an error when `ok` is false.

### Attach files

CLI and MCP support the same file attachments. An agent can use either. With the CLI, run `wirk upload PATH`, then attach the returned ID with `--upload UPLOAD_ID` on `wirk write new` or `wirk write edit`.

With `wirk_write`, give absolute paths on the MCP server's machine in `data.uploads` for `item.create` or `patch.attach_uploads` for `item.edit`. Both keys also accept upload IDs. Set `request_id` when attaching files. The server checks every file before uploading any. It stores the files, replaces paths with upload IDs and sends one write.

File failures return an MCP error. A failed check says `Nothing was sent.` A failed upload names the file and error. It lists any files already stored as `Stored but attached to nothing` and says `Nothing was written.` Follow its retry hint: resend identical arguments with the same `request_id`, or fix the error and use a new one.

Example `wirk_write` call, attaching a file and completing work:

```json
{
  "request_id": "attach-report-1",
  "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"},
        "attach_uploads": ["/tmp/report.pdf"]
      }
    }
  ]
}
```

## 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. Outside an agent chat, every command and the MCP server use it.
- In an agent chat (Codex or Claude Code), the chat signs in as itself: its first call answers `actor_setup_required`, the chat runs `wirk login` once (no browser, no person, under the machine's approval), and from then on the CLI and the MCP server use the chat's own token in `~/.config/wirk/actors/`. Resuming the chat keeps it; a new chat or a worker one chat starts gets its own. `wirk login --new` is only for the machine's token.
- 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.
