@aiaiai-pt/manda-cli 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,17 @@ npm i -g @aiaiai-pt/manda-cli
10
10
 
11
11
  Requires Node.js 22 or later.
12
12
 
13
+ ## Give it to an agent
14
+
15
+ The CLI ships its own skill, the operational reference a coding agent reads instead of `--help`:
16
+
17
+ ```sh
18
+ mkdir -p ~/.claude/skills/manda-cli && npx -y @aiaiai-pt/manda-cli@latest skill > ~/.claude/skills/manda-cli/SKILL.md # Claude Code
19
+ mkdir -p ~/.agents/skills/manda-cli && npx -y @aiaiai-pt/manda-cli@latest skill > ~/.agents/skills/manda-cli/SKILL.md # Codex
20
+ ```
21
+
22
+ `manda instructions` prints a shorter block for a CLAUDE.md or AGENTS.md. Neither needs credentials.
23
+
13
24
  ## Auth
14
25
 
15
26
  Set environment variables (preferred for agents):
@@ -36,7 +47,9 @@ Credentials are saved to `${XDG_CONFIG_HOME:-~/.config}/manda/config.json` with
36
47
  | `manda projects` | List projects |
37
48
  | `manda inbox [filters]` | List submissions |
38
49
  | `manda next [--project <id>]` | What to work on next: open reports you own or nobody owns, owned first, new first, then linked-topic roadmap order |
50
+ | `manda skill` | Print the agent skill (`skills/manda-cli/SKILL.md`), the full reference for a harness |
39
51
  | `manda instructions` | Print a block for CLAUDE.md or AGENTS.md describing the loop and commit trailers |
52
+ | `manda watch [--since <cursor>] [--project <id>] [--interval <s>] [--once]` | Print changes as they land, one JSON object per line when piped: reports, report updates (customer replies carry `author_kind: customer`), topic updates, announcements. Starts from now without `--since`; `--once` polls one time and ends with a `cursor` line to resume from. The cursor is opaque; a bare `YYYY-MM-DD HH:MM:SS` (UTC) is also accepted as a starting point |
40
53
  | `manda report show <id>` | Show a report |
41
54
  | `manda report capture --project <id> --type bug\|feature\|other --message <text> [--name] [--email] [--source-label]` | Create a report |
42
55
  | `manda report assign <id> --to <email\|me\|none>` | Assign a report |
@@ -108,3 +121,11 @@ topic=$(manda topic create --project proj-1 --title "Widget focus trap" --json |
108
121
  manda topic link-report "$topic" --report "$id"
109
122
  manda topic transition "$topic" plan
110
123
  ```
124
+
125
+ # Wake on changes (one JSON line per event; a customer reply on a resolved report reopens it)
126
+ manda watch --since "$cursor" | while read -r event; do
127
+ if [ "$(jq -r .author_kind <<<"$event")" = customer ]; then
128
+ manda report reopen "$(jq -r .report_id <<<"$event")"
129
+ fi
130
+ done
131
+ ```
package/dist/bin.js CHANGED
@@ -3,48 +3,9 @@ import { parseArgs } from "node:util";
3
3
  import { resolveConfig, saveConfigFile } from "./config.js";
4
4
  import { api, exitCodeForStatus } from "./client.js";
5
5
  import { printError, printJson, printKeyValue, isJsonMode } from "./output.js";
6
- import { whoami, projects, inbox, next, instructions, reportShow, reportCapture, reportAssign, reportAck, reportResolve, reportReopen, reportLink, reportComment, topicList, topicShow, topicCreate, topicTransition, topicNote, topicEdit, topicLinkReport, topicUnlinkReport, topicLinkWork, topicUnlinkWork, topicPublish, topicUnpublish, topicEditPublic, topicReorder, topicAnnounce, topicUnannounce, } from "./commands.js";
7
- const USAGE = `Usage: manda <command> [options]
8
-
9
- Commands:
10
- whoami Show authenticated identity
11
- projects List projects
12
- inbox List submissions (inbox)
13
- next What to work on next, in priority order (--project)
14
- instructions Print a CLAUDE.md / AGENTS.md block for this loop
15
- report show <id> Show a report
16
- report capture Create a new report
17
- report assign <id> Assign a report
18
- report ack <id> Acknowledge a report
19
- report resolve <id> Resolve a report
20
- report reopen <id> Reopen a report
21
- report link <id> Link external work to a report (--work-url)
22
- report comment <id> Add a comment to a report
23
- topic list List topics
24
- topic show <id> Show a topic
25
- topic create Create a topic
26
- topic transition <id> <event> Transition a topic
27
- topic note <id> Add a note to a topic
28
- topic edit <id> Edit a topic
29
- topic link-report <id> Link a report to a topic
30
- topic unlink-report <id> Unlink a report from a topic
31
- topic link-work <id> Link external work to a topic (--issue-url)
32
- topic unlink-work <id> Unlink external work from a topic
33
- topic publish <id> Publish a topic
34
- topic unpublish <id> Unpublish a topic
35
- topic edit-public <id> Edit a topic's public view
36
- topic reorder Reorder topics
37
- topic announce <id> Add an announcement to a topic
38
- topic unannounce <id> Remove an announcement (--announcement <id>)
39
- auth login Save credentials
40
- auth status Show credential source and verify
41
-
42
- Global options:
43
- --url <url> Server URL (overrides env/config)
44
- --token <token> Agent token (overrides env/config)
45
- --json Force JSON output
46
- --help Show help
47
- `;
6
+ import { USAGE } from "./usage.js";
7
+ import { skill } from "./skill.js";
8
+ import { whoami, projects, inbox, next, watch, instructions, reportShow, reportCapture, reportAssign, reportAck, reportResolve, reportReopen, reportLink, reportComment, topicList, topicShow, topicCreate, topicTransition, topicNote, topicEdit, topicLinkReport, topicUnlinkReport, topicLinkWork, topicUnlinkWork, topicPublish, topicUnpublish, topicEditPublic, topicReorder, topicAnnounce, topicUnannounce, } from "./commands.js";
48
9
  function usageError(message) {
49
10
  printError(0, message, 1);
50
11
  process.stderr.write(USAGE);
@@ -92,6 +53,10 @@ const parseOptions = {
92
53
  "public-title": { type: "string" },
93
54
  "public-summary": { type: "string" },
94
55
  announcement: { type: "string" },
56
+ // watch flags
57
+ since: { type: "string" },
58
+ interval: { type: "string" },
59
+ once: { type: "boolean", default: false },
95
60
  },
96
61
  // Strict: an unknown or misspelled flag is a usage error, never silently ignored.
97
62
  strict: true,
@@ -114,7 +79,11 @@ const subcommand = positionals[1];
114
79
  if (!command) {
115
80
  usageError("No command provided");
116
81
  }
117
- // instructions needs neither a server nor credentials
82
+ // skill and instructions need neither a server nor credentials
83
+ if (command === "skill") {
84
+ skill();
85
+ process.exit(0);
86
+ }
118
87
  if (command === "instructions") {
119
88
  instructions();
120
89
  process.exit(0);
@@ -199,6 +168,9 @@ try {
199
168
  case "next":
200
169
  await next(config, flags);
201
170
  break;
171
+ case "watch":
172
+ await watch(config, flags);
173
+ break;
202
174
  case "report": {
203
175
  const reportId = positionals[2];
204
176
  switch (subcommand) {
@@ -4,8 +4,31 @@ export declare function whoami(config: Config, flags: Flags): Promise<void>;
4
4
  export declare function projects(config: Config, flags: Flags): Promise<void>;
5
5
  export declare function inbox(config: Config, flags: Flags): Promise<void>;
6
6
  export declare function next(config: Config, flags: Flags): Promise<void>;
7
+ export interface WatchEvent {
8
+ id: string;
9
+ at: string;
10
+ type: string;
11
+ kind: string;
12
+ project_id: string;
13
+ report_id: string | null;
14
+ topic_id: string | null;
15
+ author_email: string | null;
16
+ author_kind: string | null;
17
+ author_agent: string | null;
18
+ body: string;
19
+ }
20
+ /** Keeps the events not yet seen, in order, and remembers them. The feed is at-least-once
21
+ * at the cursor boundary, so the same id can arrive on consecutive polls. */
22
+ export declare function unseenEvents(seen: Set<string>, events: WatchEvent[]): WatchEvent[];
23
+ export declare function formatEvent(e: WatchEvent): string;
24
+ /** `manda watch`: print each change as it lands, one JSON object per line when piped, so a
25
+ * harness can wake on stdout. Without --since it starts from now. --once polls one time and
26
+ * ends with a `cursor` line to resume from. The cursor is opaque to the client; a bare
27
+ * `YYYY-MM-DD HH:MM:SS` is also accepted as a starting point. Dedupe by id covers the
28
+ * inclusive boundary of that bare form. */
29
+ export declare function watch(config: Config, flags: Flags): Promise<void>;
7
30
  /** A block for CLAUDE.md or AGENTS.md: the loop a harness runs with manda as its coordination layer. */
8
- export declare const INSTRUCTIONS = "## Manda: customer feedback and work coordination\n\nManda is the ledger of what customers reported and what they have been told. Use the `manda`\nCLI (`npm i -g @aiaiai-pt/manda-cli`, `MANDA_TOKEN` set) as the coordination layer while\nworking in this codebase, alone or with a person. Every call prints JSON when piped; exit codes:\n0 ok, 1 usage, 2 auth, 3 conflict (reload and decide again), 4 not found, 5 other.\n\nThe loop:\n\n1. `manda next --json` lists open reports you own or nobody owns, in priority order. Take the first.\n2. `manda report show <id> --json` reads the report and its timeline. `manda report assign <id> --to me` takes it\n (owned reports rank first in `next`; an unowned one you are working on will sink below fresh reports), then\n `manda report ack <id>` starts review.\n3. Group it: `manda topic list --json` to find the topic it belongs to, then\n `manda topic link-report <topic> --report <id>`; or `manda topic create --project <project> --title \"<customer intent>\"`\n and `manda topic transition <topic> plan` then `start` when you begin.\n4. Do the work in the repository. Put `Manda-Report: <id>` and `Manda-Topic: <topic>` as commit trailers\n and `manda topic link-work <topic> --tracker github --issue <pr number> --issue-url <pr url>` once a PR exists.\n5. Tell the customer only when it is true, and \"true\" means you exercised their path, not the artefact:\n load the page or call the entry point they use (the hosted script, the endpoint, the screen) on the\n deployed version and watch it work. A green suite, a merged PR or a file read back are not that.\n Then `manda report comment <id> --audience customer --outcome resolved --body \"<what changed, in their words>\"`.\n Internal notes use `--audience internal`. Never resolve without a customer-facing explanation.\n6. `manda topic transition <topic> complete` when the change is available to customers; `manda topic announce <topic> --body \"<progress>\"` for public progress.\n\nRules: read before you write (every write checks the revision); do not reprioritise by editing reports, reorder topics\n(`manda topic reorder`) with the person who owns the roadmap; a 3 exit means someone else changed it, re-read and decide again.\n";
31
+ export declare const INSTRUCTIONS = "## Manda: customer feedback and work coordination\n\nManda is the ledger of what customers reported and what they have been told. Use the `manda`\nCLI (`npm i -g @aiaiai-pt/manda-cli`, `MANDA_TOKEN` set) as the coordination layer while\nworking in this codebase, alone or with a person. Every call prints JSON when piped; exit codes:\n0 ok, 1 usage, 2 auth, 3 conflict (reload and decide again), 4 not found, 5 other.\n\nThe loop:\n\n1. `manda next --json` lists open reports you own or nobody owns, in priority order. Take the first.\n2. `manda report show <id> --json` reads the report and its timeline. `manda report assign <id> --to me` takes it\n (owned reports rank first in `next`; an unowned one you are working on will sink below fresh reports), then\n `manda report ack <id>` starts review.\n3. Group it: `manda topic list --json` to find the topic it belongs to, then\n `manda topic link-report <topic> --report <id>`; or `manda topic create --project <project> --title \"<customer intent>\"`\n and `manda topic transition <topic> plan` then `start` when you begin.\n4. Do the work in the repository. Put `Manda-Report: <id>` and `Manda-Topic: <topic>` as commit trailers\n and `manda topic link-work <topic> --tracker github --issue <pr number> --issue-url <pr url>` once a PR exists.\n5. Tell the customer only when it is true, and \"true\" means you exercised their path, not the artefact:\n load the page or call the entry point they use (the hosted script, the endpoint, the screen) on the\n deployed version and watch it work. A green suite, a merged PR or a file read back are not that.\n Then `manda report comment <id> --audience customer --outcome resolved --body \"<what changed, in their words>\"`.\n Internal notes use `--audience internal`. Never resolve without a customer-facing explanation.\n6. `manda topic transition <topic> complete` when the change is available to customers; `manda topic announce <topic> --body \"<progress>\"` for public progress.\n7. Between tasks, `manda watch` prints one JSON line per change as it lands: new reports, customer replies\n (`type` report_update with `author_kind` customer), topic changes. `manda watch --once` polls once and ends with\n a `cursor` line; resume later with `manda watch --since <cursor>`. A customer reply on a report you resolved\n means it is not done: `manda report reopen <id>` and go back to step 2.\n\nRules: read before you write (every write checks the revision); do not reprioritise by editing reports, reorder topics\n(`manda topic reorder`) with the person who owns the roadmap; a 3 exit means someone else changed it, re-read and decide again.\n";
9
32
  export declare function instructions(): void;
10
33
  export declare function reportShow(config: Config, id: string, flags: Flags): Promise<void>;
11
34
  export declare function reportCapture(config: Config, flags: Flags): Promise<void>;
package/dist/commands.js CHANGED
@@ -163,6 +163,60 @@ export async function next(config, flags) {
163
163
  ]);
164
164
  }
165
165
  }
166
+ /** Keeps the events not yet seen, in order, and remembers them. The feed is at-least-once
167
+ * at the cursor boundary, so the same id can arrive on consecutive polls. */
168
+ export function unseenEvents(seen, events) {
169
+ const fresh = [];
170
+ for (const e of events) {
171
+ if (seen.has(e.id))
172
+ continue;
173
+ seen.add(e.id);
174
+ fresh.push(e);
175
+ }
176
+ return fresh;
177
+ }
178
+ export function formatEvent(e) {
179
+ const subject = e.report_id ? `report ${e.report_id}` : `topic ${e.topic_id}`;
180
+ const who = e.author_agent ? `${e.author_agent} (agent for ${e.author_email})` : e.author_email ?? "";
181
+ const by = e.author_kind === "customer" ? `customer ${who}` : who;
182
+ const body = e.body.replace(/\s+/g, " ");
183
+ return `${e.at} ${e.type}/${e.kind} ${subject} ${by}: ${body.length > 120 ? body.slice(0, 117) + "..." : body}`;
184
+ }
185
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
186
+ /** `manda watch`: print each change as it lands, one JSON object per line when piped, so a
187
+ * harness can wake on stdout. Without --since it starts from now. --once polls one time and
188
+ * ends with a `cursor` line to resume from. The cursor is opaque to the client; a bare
189
+ * `YYYY-MM-DD HH:MM:SS` is also accepted as a starting point. Dedupe by id covers the
190
+ * inclusive boundary of that bare form. */
191
+ export async function watch(config, flags) {
192
+ const once = flags["once"] === true;
193
+ const intervalRaw = flags["interval"] === undefined ? "15" : String(flags["interval"]);
194
+ const interval = Number(intervalRaw);
195
+ if (!Number.isFinite(interval) || interval < 1)
196
+ die(400, "--interval must be a number of seconds, at least 1");
197
+ let since = flags["since"] === undefined ? undefined : String(flags["since"]);
198
+ const seen = new Set();
199
+ const jsonMode = isJsonMode(flags);
200
+ for (;;) {
201
+ const query = { since, project_id: flags["project"] ? String(flags["project"]) : undefined };
202
+ if (flags["limit"])
203
+ query["limit"] = String(flags["limit"]);
204
+ const res = await api(config, "/api/agent/v1/changes", { query });
205
+ if (!res.ok)
206
+ return handleError(res);
207
+ for (const e of unseenEvents(seen, res.data.events)) {
208
+ process.stdout.write(jsonMode ? JSON.stringify(e) + "\n" : formatEvent(e) + "\n");
209
+ }
210
+ since = res.data.cursor;
211
+ if (seen.size > 10_000)
212
+ seen.clear();
213
+ if (once) {
214
+ process.stdout.write(jsonMode ? JSON.stringify({ type: "cursor", cursor: since }) + "\n" : `cursor ${since}\n`);
215
+ return;
216
+ }
217
+ await sleep(interval * 1000);
218
+ }
219
+ }
166
220
  // --- instructions ---
167
221
  /** A block for CLAUDE.md or AGENTS.md: the loop a harness runs with manda as its coordination layer. */
168
222
  export const INSTRUCTIONS = `## Manda: customer feedback and work coordination
@@ -189,6 +243,10 @@ The loop:
189
243
  Then \`manda report comment <id> --audience customer --outcome resolved --body "<what changed, in their words>"\`.
190
244
  Internal notes use \`--audience internal\`. Never resolve without a customer-facing explanation.
191
245
  6. \`manda topic transition <topic> complete\` when the change is available to customers; \`manda topic announce <topic> --body "<progress>"\` for public progress.
246
+ 7. Between tasks, \`manda watch\` prints one JSON line per change as it lands: new reports, customer replies
247
+ (\`type\` report_update with \`author_kind\` customer), topic changes. \`manda watch --once\` polls once and ends with
248
+ a \`cursor\` line; resume later with \`manda watch --since <cursor>\`. A customer reply on a report you resolved
249
+ means it is not done: \`manda report reopen <id>\` and go back to step 2.
192
250
 
193
251
  Rules: read before you write (every write checks the revision); do not reprioritise by editing reports, reorder topics
194
252
  (\`manda topic reorder\`) with the person who owns the roadmap; a 3 exit means someone else changed it, re-read and decide again.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `manda skill`: print the bundled SKILL.md. The file ships in the npm tarball at
3
+ * `<package>/skills/manda-cli/SKILL.md` (see package.json "files"); dist/skill.js and
4
+ * src/skill.ts both sit one level below the package root, so one relative path serves
5
+ * the installed package and a development checkout.
6
+ */
7
+ export declare function skillPath(): string | null;
8
+ export declare function readSkill(): string;
9
+ export declare function skill(): void;
package/dist/skill.js ADDED
@@ -0,0 +1,24 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /**
5
+ * `manda skill`: print the bundled SKILL.md. The file ships in the npm tarball at
6
+ * `<package>/skills/manda-cli/SKILL.md` (see package.json "files"); dist/skill.js and
7
+ * src/skill.ts both sit one level below the package root, so one relative path serves
8
+ * the installed package and a development checkout.
9
+ */
10
+ export function skillPath() {
11
+ const here = dirname(fileURLToPath(import.meta.url));
12
+ const candidate = join(here, "..", "skills", "manda-cli", "SKILL.md");
13
+ return existsSync(candidate) ? candidate : null;
14
+ }
15
+ export function readSkill() {
16
+ const path = skillPath();
17
+ if (!path) {
18
+ throw new Error("SKILL.md is missing from this install. Reinstall: npx -y @aiaiai-pt/manda-cli@latest skill");
19
+ }
20
+ return readFileSync(path, "utf8");
21
+ }
22
+ export function skill() {
23
+ process.stdout.write(readSkill());
24
+ }
@@ -0,0 +1,3 @@
1
+ export declare const USAGE = "Usage: manda <command> [options]\n\nCommands:\n skill Print the agent skill (SKILL.md) for Claude Code, Codex or any harness\n instructions Print a CLAUDE.md / AGENTS.md block for this loop\n whoami Show authenticated identity\n projects List projects\n inbox List submissions (inbox)\n next What to work on next, in priority order (--project)\n watch Print changes as they land (--since, --project, --interval, --once)\n report show <id> Show a report\n report capture Create a new report\n report assign <id> Assign a report\n report ack <id> Acknowledge a report\n report resolve <id> Resolve a report\n report reopen <id> Reopen a report\n report link <id> Link external work to a report (--work-url)\n report comment <id> Add a comment to a report\n topic list List topics\n topic show <id> Show a topic\n topic create Create a topic\n topic transition <id> <event> Transition a topic\n topic note <id> Add a note to a topic\n topic edit <id> Edit a topic\n topic link-report <id> Link a report to a topic\n topic unlink-report <id> Unlink a report from a topic\n topic link-work <id> Link external work to a topic (--issue-url)\n topic unlink-work <id> Unlink external work from a topic\n topic publish <id> Publish a topic\n topic unpublish <id> Unpublish a topic\n topic edit-public <id> Edit a topic's public view\n topic reorder Reorder topics\n topic announce <id> Add an announcement to a topic\n topic unannounce <id> Remove an announcement (--announcement <id>)\n auth login Save credentials\n auth status Show credential source and verify\n\nGlobal options:\n --url <url> Server URL (overrides env/config)\n --token <token> Agent token (overrides env/config)\n --json Force JSON output\n --help Show help\n";
2
+ /** The command names USAGE lists, e.g. "report show", "auth login". */
3
+ export declare function usageCommands(): string[];
package/dist/usage.js ADDED
@@ -0,0 +1,53 @@
1
+ export const USAGE = `Usage: manda <command> [options]
2
+
3
+ Commands:
4
+ skill Print the agent skill (SKILL.md) for Claude Code, Codex or any harness
5
+ instructions Print a CLAUDE.md / AGENTS.md block for this loop
6
+ whoami Show authenticated identity
7
+ projects List projects
8
+ inbox List submissions (inbox)
9
+ next What to work on next, in priority order (--project)
10
+ watch Print changes as they land (--since, --project, --interval, --once)
11
+ report show <id> Show a report
12
+ report capture Create a new report
13
+ report assign <id> Assign a report
14
+ report ack <id> Acknowledge a report
15
+ report resolve <id> Resolve a report
16
+ report reopen <id> Reopen a report
17
+ report link <id> Link external work to a report (--work-url)
18
+ report comment <id> Add a comment to a report
19
+ topic list List topics
20
+ topic show <id> Show a topic
21
+ topic create Create a topic
22
+ topic transition <id> <event> Transition a topic
23
+ topic note <id> Add a note to a topic
24
+ topic edit <id> Edit a topic
25
+ topic link-report <id> Link a report to a topic
26
+ topic unlink-report <id> Unlink a report from a topic
27
+ topic link-work <id> Link external work to a topic (--issue-url)
28
+ topic unlink-work <id> Unlink external work from a topic
29
+ topic publish <id> Publish a topic
30
+ topic unpublish <id> Unpublish a topic
31
+ topic edit-public <id> Edit a topic's public view
32
+ topic reorder Reorder topics
33
+ topic announce <id> Add an announcement to a topic
34
+ topic unannounce <id> Remove an announcement (--announcement <id>)
35
+ auth login Save credentials
36
+ auth status Show credential source and verify
37
+
38
+ Global options:
39
+ --url <url> Server URL (overrides env/config)
40
+ --token <token> Agent token (overrides env/config)
41
+ --json Force JSON output
42
+ --help Show help
43
+ `;
44
+ /** The command names USAGE lists, e.g. "report show", "auth login". */
45
+ export function usageCommands() {
46
+ const block = USAGE.slice(USAGE.indexOf("Commands:"), USAGE.indexOf("Global options:"));
47
+ return block
48
+ .split("\n")
49
+ .slice(1)
50
+ .map((line) => line.trim())
51
+ .filter(Boolean)
52
+ .map((line) => line.split(/\s{2,}/)[0].replace(/ <[^>]+>/g, ""));
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/manda-cli",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for the Manda feedback service",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,6 +20,7 @@
20
20
  },
21
21
  "files": [
22
22
  "dist",
23
+ "skills/manda-cli/SKILL.md",
23
24
  "README.md"
24
25
  ],
25
26
  "scripts": {
@@ -0,0 +1,284 @@
1
+ ---
2
+ name: manda-cli
3
+ 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.
4
+ ---
5
+
6
+ # Manda CLI Reference
7
+
8
+ Manda is the ledger of what customers reported and what they have been told. Reports arrive
9
+ through the hosted widget on a product's own pages; staff and coding agents work them through
10
+ this CLI. One command per staff action, JSON on stdout when piped, no runtime dependencies.
11
+ This skill is the operational reference: assume you have shell access and never need `--help`.
12
+
13
+ ```sh
14
+ npx -y @aiaiai-pt/manda-cli@latest <command> # no install
15
+ npm i -g @aiaiai-pt/manda-cli # then: manda <command>
16
+ ```
17
+
18
+ Requires Node.js 22 or later.
19
+
20
+ ## Conceptual model
21
+
22
+ | Object | What it is | Lifecycle |
23
+ |---|---|---|
24
+ | Project | one product surface (a site, an app); owns reports and topics | created by an admin in the web UI |
25
+ | 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) |
26
+ | 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 |
27
+ | 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 |
28
+ | Announcement | a public progress note on a topic | append-only |
29
+ | 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 |
30
+
31
+ Priority is the topic order on the roadmap, never a column on the report. `manda next` derives
32
+ it; nothing on a report says "P1".
33
+
34
+ ## Authentication
35
+
36
+ An org admin mints a token at `https://manda.aiaiai.pt/admin/org` (section **Agents**, button
37
+ ADD AGENT). The secret (`mn_agent_...`) is shown once. It is its owner: same organisation,
38
+ same checks, same assignee rules.
39
+
40
+ ```sh
41
+ export MANDA_TOKEN=mn_agent_...
42
+ export MANDA_URL=https://manda.aiaiai.pt # optional, this is the default
43
+ manda whoami # email, domain, agent name
44
+ ```
45
+
46
+ Or save it once:
47
+
48
+ ```sh
49
+ manda auth login --token mn_agent_... # --url to point at another server
50
+ manda auth status # prints the credential source and verifies it
51
+ ```
52
+
53
+ Credentials live in `${XDG_CONFIG_HOME:-~/.config}/manda/config.json`, mode 0600.
54
+ Precedence: `--url`/`--token` flags, then `MANDA_TOKEN`/`MANDA_URL`, then the file.
55
+ `manda skill` and `manda instructions` need no credentials and no server.
56
+
57
+ ## Global options and output
58
+
59
+ | Option | Effect |
60
+ |---|---|
61
+ | `--json` | JSON on stdout even on a TTY |
62
+ | `--url <url>` | server, overrides env and file |
63
+ | `--token <token>` | token, overrides env and file |
64
+ | `--help` | usage, anywhere on the line |
65
+
66
+ - stdout: JSON when piped or with `--json`; a human table on a TTY.
67
+ - stderr: errors as JSON, `{ "error": { "status", "message", "exit" } }`. On a 409 the error
68
+ carries `"current"`, the freshly fetched resource, so you can decide again without a second call.
69
+ - Flags are strict: an unknown or misspelled flag is a usage error (exit 1), never ignored.
70
+ - Text flags (`--message`, `--body`, `--resolution`, `--title`, `--description`, `--public-title`,
71
+ `--public-summary`) accept `-` to read stdin. One `-` per call.
72
+
73
+ ## Exit codes
74
+
75
+ | Code | Meaning |
76
+ |---|---|
77
+ | 0 | Success |
78
+ | 1 | Usage error: missing or unknown flag, unknown command, stdin used for two flags |
79
+ | 2 | Auth: 401, 403, or no credentials configured |
80
+ | 3 | Conflict: 409, the revision you wrote against is stale |
81
+ | 4 | Not found: 404 |
82
+ | 5 | Server or network error: 400, 415, 429, 5xx, no connection |
83
+
84
+ Branch on the code, not on the message.
85
+
86
+ ## The revision rule
87
+
88
+ Every write (`assign`, `ack`, `resolve`, `reopen`, `link`, `comment`, every `topic` mutation)
89
+ reads the resource first to learn its current `revision`, unless you pass `--revision N`.
90
+ On 409 the CLI does not retry: it refetches, prints the current state under `error.current`,
91
+ and exits 3. Re-read, decide again, write again. Someone else changed it, and that is
92
+ information, not noise.
93
+
94
+ `manda report comment` always sends a `request_id` (a random UUID, or `--request-id <uuid>`),
95
+ so a retried comment is idempotent.
96
+
97
+ ## Commands
98
+
99
+ ### Identity and projects
100
+
101
+ | Command | Does |
102
+ |---|---|
103
+ | `manda whoami` | the admin this token acts for: `email`, `domain`, `agent.name` |
104
+ | `manda projects` | projects in the organisation: `id`, `name` |
105
+ | `manda skill` | prints this file |
106
+ | `manda watch [--since <cursor>] [--project <id>] [--interval <s>] [--once]` | change feed, see below |
107
+ | `manda instructions` | prints a short CLAUDE.md / AGENTS.md block describing the loop below |
108
+ | `manda auth login --token <t> [--url <u>]` | saves credentials |
109
+ | `manda auth status` | credential source, server, and a live identity check |
110
+
111
+ ### Reading the inbox
112
+
113
+ ```sh
114
+ manda inbox # every report, newest first
115
+ manda inbox --project <id> --state new # filters: --state new|acknowledged|resolved
116
+ manda inbox --assignee me # --assignee me|unassigned|<email>
117
+ manda inbox --q "login" # text search; --page N --limit N
118
+ manda next # what to work on, in priority order
119
+ manda next --project <id>
120
+ manda report show <id> # the report and its timeline (updates)
121
+ ```
122
+
123
+ `manda next` lists open reports you own or nobody owns: owned first, then `new` before
124
+ `acknowledged`, then by the roadmap order of the linked topic (unlinked last), then oldest
125
+ first. Take the first one. An unowned report you are working on sinks below fresh reports,
126
+ so assign it to yourself.
127
+
128
+ JSON shapes: `inbox` returns `{ submissions: [...], total }`; `next` returns
129
+ `{ items: [...], email }` where each item carries `assignee_email` and `topics: [{ title }]`;
130
+ `report show` returns `{ submission: { id, type, status, message, revision, ... }, updates: [...] }`.
131
+
132
+ ### Waking on changes
133
+
134
+ ```sh
135
+ manda watch # from now on, one event per line, polls every 15 s
136
+ manda watch --since "$cursor" --project <id> # resume from a cursor; --interval <seconds>
137
+ manda watch --once --json # one poll, then a final {"type":"cursor","cursor":...} line
138
+ ```
139
+
140
+ Events: new reports, report updates (a customer reply carries `author_kind: customer`), topic
141
+ updates and announcements, as JSON objects when piped. Without `--since` it starts from now.
142
+ The cursor is opaque; keep the last one and pass it back to resume without gaps. A bare
143
+ `YYYY-MM-DD HH:MM:SS` (UTC) is also accepted as a starting point.
144
+
145
+ ### Working a report
146
+
147
+ ```sh
148
+ manda report capture --project <id> --type bug|feature|other --message "<text>" \
149
+ [--name "<reporter>"] [--email <reporter email>] [--source-label "<where it came from>"]
150
+ manda report assign <id> --to me # --to <email> | me | none (unassign)
151
+ manda report ack <id> # new -> acknowledged: review has started
152
+ manda report comment <id> --body "<text>" # internal note (default --audience internal)
153
+ manda report comment <id> --audience customer --outcome resolved --body "<what changed, in their words>"
154
+ manda report comment <id> --audience customer --outcome open --body "<why it is back open>"
155
+ manda report resolve <id> --resolution "<text>"
156
+ manda report reopen <id>
157
+ manda report link <id> --work-url <url> # the PR, issue or commit that carries the fix
158
+ manda report link <id> --none # clear it
159
+ ```
160
+
161
+ `--audience customer` is the only thing the reporter sees. A report that asked for
162
+ follow-through gets that comment by mail. Never resolve a report without a customer-facing
163
+ explanation; `resolve` alone closes it silently.
164
+
165
+ ### Topics: the roadmap
166
+
167
+ ```sh
168
+ manda topic list [--project <id>]
169
+ manda topic show <id>
170
+ manda topic create --project <id> --title "<customer intent>" [--description "<text>"]
171
+ manda topic transition <id> plan|start|complete|decline|reconsider|reopen [--body "<text>"]
172
+ manda topic note <id> --body "<internal note>"
173
+ manda topic edit <id> --title "<text>" --description "<text>"
174
+ manda topic link-report <id> --report <report id>
175
+ manda topic unlink-report <id> --report <report id>
176
+ manda topic link-work <id> --tracker github --issue <number> --issue-url <url>
177
+ manda topic unlink-work <id> --tracker github --issue <number>
178
+ manda topic reorder --project <id> <topic id> <topic id> ... # the full order, first is top
179
+ ```
180
+
181
+ The topic state machine, the only place a topic's state changes:
182
+
183
+ | Event | From | To |
184
+ |---|---|---|
185
+ | `plan` | considering | planned |
186
+ | `start` | planned | in_progress |
187
+ | `complete` | in_progress | available |
188
+ | `decline` | considering, planned, in_progress | not_planned (`--body` with the reason is required) |
189
+ | `reconsider` | not_planned | considering |
190
+ | `reopen` | available | in_progress |
191
+
192
+ An event from a state not in its `from` column is refused by the server (exit 5 with the message);
193
+ read `manda topic show <id>` first when in doubt.
194
+
195
+ ### The public portal
196
+
197
+ ```sh
198
+ manda topic publish <id> [--public-title "<text>"] [--public-summary "<text>"]
199
+ manda topic edit-public <id> --public-title "<text>" --public-summary "<text>"
200
+ manda topic unpublish <id>
201
+ manda topic announce <id> --body "<progress, for customers>"
202
+ manda topic unannounce <id> --announcement <announcement id>
203
+ ```
204
+
205
+ Public wording is separate from the internal title and description: write it for the customer,
206
+ not for the team.
207
+
208
+ ## The loop
209
+
210
+ Use Manda as the coordination layer while working in a codebase, alone or with a person.
211
+
212
+ 1. `manda next --json` and take the first item.
213
+ 2. `manda report show <id> --json`, then `manda report assign <id> --to me` and
214
+ `manda report ack <id>`.
215
+ 3. Group it: `manda topic list --json` to find the topic it belongs to, then
216
+ `manda topic link-report <topic> --report <id>`; or create one
217
+ (`manda topic create --project <project> --title "<customer intent>"`), `transition <topic> plan`,
218
+ then `start` when you begin.
219
+ 4. Do the work in the repository. Put `Manda-Report: <id>` and `Manda-Topic: <topic>` as commit
220
+ trailers, and `manda topic link-work <topic> --tracker github --issue <pr number> --issue-url <pr url>`
221
+ once a PR exists. `manda report link <id> --work-url <pr url>` ties the report to it too.
222
+ 5. Tell the customer only when it is true, and "true" means you exercised their path on the
223
+ deployed version, not the artefact: load the page, call the endpoint, watch it work. A green
224
+ suite, a merged PR or a file read back are not that. Then
225
+ `manda report comment <id> --audience customer --outcome resolved --body "<what changed, in their words>"`.
226
+ 6. `manda topic transition <topic> complete` when the change is available to customers;
227
+ `manda topic announce <topic> --body "<progress>"` for public progress along the way.
228
+
229
+ Rules: read before you write (every write checks the revision). Do not reprioritise by editing
230
+ reports; reorder topics (`manda topic reorder`) with the person who owns the roadmap. Exit 3
231
+ means someone else changed it: re-read and decide again.
232
+
233
+ ## Patterns
234
+
235
+ ```sh
236
+ # Triage everything new in one project
237
+ for id in $(manda inbox --project "$P" --state new --json | jq -r '.submissions[].id'); do
238
+ manda report ack "$id"
239
+ manda report comment "$id" --body "Triaged by automation." --audience internal
240
+ done
241
+
242
+ # Group a report under a new topic and plan it
243
+ topic=$(manda topic create --project "$P" --title "Widget loses the draft on a network error" --json | jq -r .id)
244
+ manda topic link-report "$topic" --report "$id"
245
+ manda topic transition "$topic" plan
246
+
247
+ # Wake on changes: a customer reply on a resolved report reopens it
248
+ manda watch --since "$cursor" | while read -r event; do
249
+ if [ "$(jq -r .author_kind <<<"$event")" = customer ]; then
250
+ manda report reopen "$(jq -r .report_id <<<"$event")"
251
+ fi
252
+ done
253
+
254
+ # A long customer-facing comment from a file
255
+ manda report comment "$id" --audience customer --outcome resolved --body - < reply.md
256
+
257
+ # Idempotent retry of a comment after a flaky network
258
+ manda report comment "$id" --body "Shipped in 0.4.1." --request-id 2f1e5c3a-0b9d-4b7a-9e2c-1d8f6a4b3c21
259
+
260
+ # Decide again after a conflict
261
+ manda report resolve "$id" --resolution "Fixed" || {
262
+ [ $? -eq 3 ] && manda report show "$id" --json
263
+ }
264
+ ```
265
+
266
+ ## Troubleshooting
267
+
268
+ | Symptom | Cause | Fix |
269
+ |---|---|---|
270
+ | `No credentials configured` (exit 2) | no `MANDA_TOKEN`, no config file, no `--token` | set `MANDA_TOKEN` or `manda auth login --token ...` |
271
+ | 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` |
272
+ | exit 3 | stale revision | the error's `current` field is the fresh state; decide again |
273
+ | exit 4 | wrong id, or an object in another organisation | `manda inbox` / `manda topic list` to find it |
274
+ | `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 |
275
+ | transition refused (exit 5) | event not allowed from the current state | `manda topic show <id>`, then the table above |
276
+ | no table, only JSON | stdout is a pipe | that is the contract; `--json` makes it explicit |
277
+
278
+ ## Conventions
279
+
280
+ - Ids are UUIDs. Keep them in variables; never retype them.
281
+ - Internal notes stay `--audience internal`; the customer reads only `--audience customer`.
282
+ - Write the customer-facing comment in the customer's words: what they can now do, not what you changed in the code.
283
+ - One source of priority: the topic order. Reorder with the roadmap owner, not alone.
284
+ - Commit trailers `Manda-Report:` and `Manda-Topic:` on every commit that serves a report.