---
name: manda-cli
description: Complete reference for the Manda CLI. Use this whenever you need to work customer feedback from the command line - read the inbox, pick what to do next, acknowledge, assign, comment and resolve reports, group them into roadmap topics, link pull requests, publish progress to the public portal, or run the report-to-release loop as a coding agent.
---

# Manda CLI Reference

Manda is the ledger of what customers reported and what they have been told. Reports arrive
through the hosted widget on a product's own pages; staff and coding agents work them through
this CLI. One command per staff action, JSON on stdout when piped, no runtime dependencies.
This skill is the operational reference: assume you have shell access and never need `--help`.

```sh
npx -y @aiaiai-pt/manda-cli@latest <command>   # no install
npm i -g @aiaiai-pt/manda-cli                   # then: manda <command>
```

Requires Node.js 22 or later.

## Conceptual model

| Object | What it is | Lifecycle |
|---|---|---|
| Project | one product surface (a site, an app); owns reports and topics | created by an admin in the web UI |
| Report (submission) | one thing a customer said: `bug`, `feature` or `other`, with a message and an optional reporter email | `new` -> `acknowledged` -> `resolved` (and back via reopen) |
| Update | an entry on a report's timeline: an internal note or a customer-facing comment, optionally closing (`resolved`) or reopening (`open`) the report | append-only |
| Topic | a roadmap item that groups reports by customer intent; has a public view on the portal when published | `considering` -> `planned` -> `in_progress` -> `available`; `not_planned` from any of the first three |
| Announcement | a public progress note on a topic | append-only |
| Agent token | a bearer token minted by an org admin that acts **as that admin** on the agent lane; every write records the agent name beside the owner's email | revocable; dies with the owner |

Priority is the topic order on the roadmap, never a column on the report. `manda next` derives
it; nothing on a report says "P1".

## Authentication

An org admin mints a token at `https://manda.aiaiai.pt/admin/org` (section **Agents**, button
ADD AGENT). The secret (`mn_agent_...`) is shown once. It is its owner: same organisation,
same checks, same assignee rules.

```sh
export MANDA_TOKEN=mn_agent_...
export MANDA_URL=https://manda.aiaiai.pt   # optional, this is the default
manda whoami                               # email, domain, agent name
```

Or save it once:

```sh
manda auth login --token mn_agent_...      # --url to point at another server
manda auth status                          # prints the credential source and verifies it
```

Credentials live in `${XDG_CONFIG_HOME:-~/.config}/manda/config.json`, mode 0600.
Precedence: `--url`/`--token` flags, then `MANDA_TOKEN`/`MANDA_URL`, then the file.
`manda skill` and `manda instructions` need no credentials and no server.

## Global options and output

| Option | Effect |
|---|---|
| `--json` | JSON on stdout even on a TTY |
| `--url <url>` | server, overrides env and file |
| `--token <token>` | token, overrides env and file |
| `--help` | usage, anywhere on the line |

- stdout: JSON when piped or with `--json`; a human table on a TTY.
- stderr: errors as JSON, `{ "error": { "status", "message", "exit" } }`. On a 409 the error
  carries `"current"`, the freshly fetched resource, so you can decide again without a second call.
- Flags are strict: an unknown or misspelled flag is a usage error (exit 1), never ignored.
- Text flags (`--message`, `--body`, `--resolution`, `--title`, `--description`, `--public-title`,
  `--public-summary`) accept `-` to read stdin. One `-` per call.

## Exit codes

| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Usage error: missing or unknown flag, unknown command, stdin used for two flags |
| 2 | Auth: 401, 403, or no credentials configured |
| 3 | Conflict: 409, the revision you wrote against is stale |
| 4 | Not found: 404 |
| 5 | Server or network error: 400, 415, 429, 5xx, no connection |

Branch on the code, not on the message.

## The revision rule

Every write (`assign`, `ack`, `resolve`, `reopen`, `link`, `comment`, every `topic` mutation)
reads the resource first to learn its current `revision`, unless you pass `--revision N`.
On 409 the CLI does not retry: it refetches, prints the current state under `error.current`,
and exits 3. Re-read, decide again, write again. Someone else changed it, and that is
information, not noise.

`manda report comment` always sends a `request_id` (a random UUID, or `--request-id <uuid>`),
so a retried comment is idempotent.

## Commands

### Identity and projects

| Command | Does |
|---|---|
| `manda whoami` | the admin this token acts for: `email`, `domain`, `agent.name` |
| `manda projects` | projects in the organisation: `id`, `name` |
| `manda skill` | prints this file |
| `manda watch [--since <cursor>] [--project <id>] [--interval <s>] [--once]` | change feed, see below |
| `manda instructions` | prints a short CLAUDE.md / AGENTS.md block describing the loop below |
| `manda auth login --token <t> [--url <u>]` | saves credentials |
| `manda auth status` | credential source, server, and a live identity check |

### Reading the inbox

```sh
manda inbox                                   # every report, newest first
manda inbox --project <id> --state new        # filters: --state new|acknowledged|resolved
manda inbox --assignee me                     # --assignee me|unassigned|<email>
manda inbox --q "login"                       # text search; --page N --limit N
manda next                                    # what to work on, in priority order
manda next --project <id>
manda report show <id>                        # the report and its timeline (updates)
manda report screenshot <id> --out shot.png   # the customer's screenshot, when screenshot_key is set
```

`manda next` lists open reports you own or nobody owns: owned first, then `new` before
`acknowledged`, then by the roadmap order of the linked topic (unlinked last), then oldest
first. Take the first one. An unowned report you are working on sinks below fresh reports,
so assign it to yourself.

JSON shapes: `inbox` returns `{ submissions: [...], total }`; `next` returns
`{ items: [...], email }` where each item carries `assignee_email` and `topics: [{ title }]`;
`report show` returns `{ submission: { id, type, status, message, revision, ... }, updates: [...] }`.

### Waking on changes

```sh
manda watch                                   # from now on, one event per line, polls every 15 s
manda watch --since "$cursor" --project <id>  # resume from a cursor; --interval <seconds>
manda watch --once --json                     # one poll, then a final {"type":"cursor","cursor":...} line
```

Events: new reports, report updates (a customer reply carries `author_kind: customer`), topic
updates and announcements, as JSON objects when piped. Without `--since` it starts from now.
The cursor is opaque; keep the last one and pass it back to resume without gaps. A bare
`YYYY-MM-DD HH:MM:SS` (UTC) is also accepted as a starting point.

### Working a report

```sh
manda report capture --project <id> --type bug|feature|other --message "<text>" \
  [--name "<reporter>"] [--email <reporter email>] [--source-label "<where it came from>"]
manda report assign <id> --to me               # --to <email> | me | none (unassign)
manda report ack <id>                          # new -> acknowledged: review has started
manda report comment <id> --body "<text>"      # internal note (default --audience internal)
manda report comment <id> --audience customer --outcome resolved --body "<what changed, in their words>"
manda report comment <id> --audience customer --outcome open --body "<why it is back open>"
manda report resolve <id> --resolution "<text>"
manda report reopen <id>
manda report link <id> --work-url <url>        # the PR, issue or commit that carries the fix
manda report link <id> --none                  # clear it
```

`--audience customer` is the only thing the reporter sees. A report that asked for
follow-through gets that comment by mail. Never resolve a report without a customer-facing
explanation; `resolve` alone closes it silently.

### Topics: the roadmap

```sh
manda topic list [--project <id>]
manda topic show <id>
manda topic create --project <id> --title "<customer intent>" [--description "<text>"]
manda topic transition <id> plan|start|complete|decline|reconsider|reopen [--body "<text>"]
manda topic note <id> --body "<internal note>"
manda topic edit <id> --title "<text>" --description "<text>"
manda topic link-report <id> --report <report id>
manda topic unlink-report <id> --report <report id>
manda topic link-work <id> --tracker github --issue <number> --issue-url <url>
manda topic unlink-work <id> --tracker github --issue <number>
manda topic reorder --project <id> <topic id> <topic id> ...   # the full order, first is top
```

The topic state machine, the only place a topic's state changes:

| Event | From | To |
|---|---|---|
| `plan` | considering | planned |
| `start` | planned | in_progress |
| `complete` | in_progress | available |
| `decline` | considering, planned, in_progress | not_planned (`--body` with the reason is required) |
| `reconsider` | not_planned | considering |
| `reopen` | available | in_progress |

An event from a state not in its `from` column is refused by the server (exit 5 with the message);
read `manda topic show <id>` first when in doubt.

### The public portal

```sh
manda topic publish <id> [--public-title "<text>"] [--public-summary "<text>"]
manda topic edit-public <id> --public-title "<text>" --public-summary "<text>"
manda topic unpublish <id>
manda topic announce <id> --body "<progress, for customers>"
manda topic unannounce <id> --announcement <announcement id>
```

Public wording is separate from the internal title and description: write it for the customer,
not for the team.

## The loop

Use Manda as the coordination layer while working in a codebase, alone or with a person.

1. `manda next --json` and take the first item.
2. `manda report show <id> --json`; when it has a `screenshot_key`, `manda report screenshot <id> --out shot.png`
   and look at it before reasoning about a layout or widget report. Then `manda report assign <id> --to me` and
   `manda report ack <id>`.
3. Group it: `manda topic list --json` to find the topic it belongs to, then
   `manda topic link-report <topic> --report <id>`; or create one
   (`manda topic create --project <project> --title "<customer intent>"`), `transition <topic> plan`,
   then `start` when you begin.
4. Do the work in the repository. Put `Manda-Report: <id>` and `Manda-Topic: <topic>` as commit
   trailers, and `manda topic link-work <topic> --tracker github --issue <pr number> --issue-url <pr url>`
   once a PR exists. `manda report link <id> --work-url <pr url>` ties the report to it too.
5. Tell the customer only when it is true, and "true" means you exercised their path on the
   deployed version, not the artefact: load the page, call the endpoint, watch it work. A green
   suite, a merged PR or a file read back are not that. Then
   `manda report comment <id> --audience customer --outcome resolved --body "<what changed, in their words>"`.
6. `manda topic transition <topic> complete` when the change is available to customers;
   `manda topic announce <topic> --body "<progress>"` for public progress along the way.

Rules: read before you write (every write checks the revision). Do not reprioritise by editing
reports; reorder topics (`manda topic reorder`) with the person who owns the roadmap. Exit 3
means someone else changed it: re-read and decide again.

## Patterns

```sh
# Triage everything new in one project
for id in $(manda inbox --project "$P" --state new --json | jq -r '.submissions[].id'); do
  manda report ack "$id"
  manda report comment "$id" --body "Triaged by automation." --audience internal
done

# Group a report under a new topic and plan it
topic=$(manda topic create --project "$P" --title "Widget loses the draft on a network error" --json | jq -r .id)
manda topic link-report "$topic" --report "$id"
manda topic transition "$topic" plan

# Wake on changes: a customer reply on a resolved report reopens it
manda watch --since "$cursor" | while read -r event; do
  if [ "$(jq -r .author_kind <<<"$event")" = customer ]; then
    manda report reopen "$(jq -r .report_id <<<"$event")"
  fi
done

# A long customer-facing comment from a file
manda report comment "$id" --audience customer --outcome resolved --body - < reply.md

# Idempotent retry of a comment after a flaky network
manda report comment "$id" --body "Shipped in 0.4.1." --request-id 2f1e5c3a-0b9d-4b7a-9e2c-1d8f6a4b3c21

# Decide again after a conflict
manda report resolve "$id" --resolution "Fixed" || {
  [ $? -eq 3 ] && manda report show "$id" --json
}
```

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| `No credentials configured` (exit 2) | no `MANDA_TOKEN`, no config file, no `--token` | set `MANDA_TOKEN` or `manda auth login --token ...` |
| 401 or 403 (exit 2) | token revoked, owner left the organisation, or the wrong server | `manda auth status`; mint a new token at `/admin/org` |
| exit 3 | stale revision | the error's `current` field is the fresh state; decide again |
| exit 4 | wrong id, or an object in another organisation | `manda inbox` / `manda topic list` to find it |
| `Unknown option` (exit 1) | a flag this command does not take, or a typo | the flags above are the whole set; stdin `-` feeds one flag only |
| transition refused (exit 5) | event not allowed from the current state | `manda topic show <id>`, then the table above |
| no table, only JSON | stdout is a pipe | that is the contract; `--json` makes it explicit |

## Conventions

- Ids are UUIDs. Keep them in variables; never retype them.
- Internal notes stay `--audience internal`; the customer reads only `--audience customer`.
- Write the customer-facing comment in the customer's words: what they can now do, not what you changed in the code.
- One source of priority: the topic order. Reorder with the roadmap owner, not alone.
- Commit trailers `Manda-Report:` and `Manda-Topic:` on every commit that serves a report.
