@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 +21 -0
- package/dist/bin.js +15 -43
- package/dist/commands.d.ts +24 -1
- package/dist/commands.js +58 -0
- package/dist/skill.d.ts +9 -0
- package/dist/skill.js +24 -0
- package/dist/usage.d.ts +3 -0
- package/dist/usage.js +53 -0
- package/package.json +2 -1
- package/skills/manda-cli/SKILL.md +284 -0
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 {
|
|
7
|
-
|
|
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
|
|
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) {
|
package/dist/commands.d.ts
CHANGED
|
@@ -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.
|
package/dist/skill.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/usage.d.ts
ADDED
|
@@ -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.
|
|
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.
|