@aiaiai-pt/manda-cli 0.0.0-stage → 0.1.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 +109 -2
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +348 -0
- package/dist/client.d.ts +23 -0
- package/dist/client.js +62 -0
- package/dist/commands.d.ts +34 -0
- package/dist/commands.js +710 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +64 -0
- package/dist/output.d.ts +12 -0
- package/dist/output.js +48 -0
- package/package.json +32 -4
package/README.md
CHANGED
|
@@ -1,3 +1,110 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @aiaiai-pt/manda-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for the [Manda](https://manda.aiaiai.pt) feedback service. One command per staff action, JSON on stdout, no runtime dependencies.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm i -g @aiaiai-pt/manda-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 22 or later.
|
|
12
|
+
|
|
13
|
+
## Auth
|
|
14
|
+
|
|
15
|
+
Set environment variables (preferred for agents):
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
export MANDA_TOKEN=mn_agent_...
|
|
19
|
+
export MANDA_URL=https://manda.aiaiai.pt # optional, this is the default
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or save to a config file:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
manda auth login --url https://manda.aiaiai.pt --token mn_agent_...
|
|
26
|
+
manda auth status
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Credentials are saved to `${XDG_CONFIG_HOME:-~/.config}/manda/config.json` with mode 0600. Environment variables take precedence over the config file; `--url` and `--token` flags override both.
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
| Command | Description |
|
|
34
|
+
|---------|-------------|
|
|
35
|
+
| `manda whoami` | Show authenticated identity |
|
|
36
|
+
| `manda projects` | List projects |
|
|
37
|
+
| `manda inbox [filters]` | List submissions |
|
|
38
|
+
| `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 |
|
|
39
|
+
| `manda instructions` | Print a block for CLAUDE.md or AGENTS.md describing the loop and commit trailers |
|
|
40
|
+
| `manda report show <id>` | Show a report |
|
|
41
|
+
| `manda report capture --project <id> --type bug\|feature\|other --message <text> [--name] [--email] [--source-label]` | Create a report |
|
|
42
|
+
| `manda report assign <id> --to <email\|me\|none>` | Assign a report |
|
|
43
|
+
| `manda report ack <id>` | Acknowledge a report |
|
|
44
|
+
| `manda report resolve <id> --resolution <text>` | Resolve a report |
|
|
45
|
+
| `manda report reopen <id>` | Reopen a report |
|
|
46
|
+
| `manda report link <id> (--work-url <url> \| --none)` | Link or clear external work |
|
|
47
|
+
| `manda report comment <id> --body <text> [--audience internal\|customer] [--outcome open\|resolved] [--request-id <uuid>]` | Add a comment, optionally closing or reopening |
|
|
48
|
+
| `manda topic list [--project <id>]` | List topics |
|
|
49
|
+
| `manda topic show <id>` | Show a topic |
|
|
50
|
+
| `manda topic create --project <id> --title <text> [--description <text>]` | Create a topic |
|
|
51
|
+
| `manda topic transition <id> plan\|start\|complete\|decline\|reconsider\|reopen [--body <text>]` | Transition a topic (decline needs a reason) |
|
|
52
|
+
| `manda topic note <id> --body <text>` | Add a note |
|
|
53
|
+
| `manda topic edit <id> --title <text> --description <text>` | Edit a topic |
|
|
54
|
+
| `manda topic link-report <id> --report <id>` | Link a report to a topic |
|
|
55
|
+
| `manda topic unlink-report <id> --report <id>` | Unlink a report |
|
|
56
|
+
| `manda topic link-work <id> --tracker <t> --issue <i> --issue-url <url>` | Link external work |
|
|
57
|
+
| `manda topic unlink-work <id> --tracker <t> --issue <i>` | Unlink external work |
|
|
58
|
+
| `manda topic publish <id> [--public-title <text>] [--public-summary <text>]` | Publish a topic to the portal |
|
|
59
|
+
| `manda topic unpublish <id>` | Unpublish a topic |
|
|
60
|
+
| `manda topic edit-public <id> --public-title <text> --public-summary <text>` | Edit the public wording |
|
|
61
|
+
| `manda topic reorder --project <id> <topicId> [...]` | Reorder topics |
|
|
62
|
+
| `manda topic announce <id> --body <text>` | Add a public progress announcement |
|
|
63
|
+
| `manda topic unannounce <id> --announcement <id>` | Remove an announcement |
|
|
64
|
+
|
|
65
|
+
### Inbox filters
|
|
66
|
+
|
|
67
|
+
`--project <id>`, `--state new|acknowledged|resolved`, `--assignee me|unassigned|<email>`, `--q <text>`, `--page N`, `--limit N`
|
|
68
|
+
|
|
69
|
+
## Output
|
|
70
|
+
|
|
71
|
+
- **JSON** on stdout when stdout is not a TTY or when `--json` is given.
|
|
72
|
+
- **Human table** on a TTY without `--json`.
|
|
73
|
+
- **Errors** as JSON on stderr: `{ "error": { "status", "message", "exit" } }`.
|
|
74
|
+
- On a 409 conflict, the error includes a `"current"` field with the freshly fetched resource.
|
|
75
|
+
|
|
76
|
+
## Exit codes
|
|
77
|
+
|
|
78
|
+
| Code | Meaning |
|
|
79
|
+
|------|---------|
|
|
80
|
+
| 0 | Success |
|
|
81
|
+
| 1 | Usage error (missing or unknown flag, unknown command, stdin used for two flags) |
|
|
82
|
+
| 2 | Auth failure (401, 403) or no credentials configured |
|
|
83
|
+
| 3 | Conflict (409, stale revision) |
|
|
84
|
+
| 4 | Not found (404) |
|
|
85
|
+
| 5 | Server or network error (400, 415, 429, 5xx) |
|
|
86
|
+
|
|
87
|
+
## Revision rule
|
|
88
|
+
|
|
89
|
+
Every write reads the resource first to learn the current `revision` unless `--revision N` is given. On 409 the CLI does not retry: it refetches, prints the current state, and exits 3.
|
|
90
|
+
|
|
91
|
+
`report comment` always sends a `request_id` (random UUID, or `--request-id` for idempotent retries).
|
|
92
|
+
|
|
93
|
+
Text flags (`--message`, `--body`, `--resolution`, `--title`, `--description`, `--public-title`, `--public-summary`) accept `-` to read from stdin; one flag per call.
|
|
94
|
+
|
|
95
|
+
## Agent loop example
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
export MANDA_TOKEN=mn_agent_...
|
|
99
|
+
|
|
100
|
+
# Triage new reports
|
|
101
|
+
for id in $(manda inbox --state new --json | jq -r '.submissions[].id'); do
|
|
102
|
+
manda report ack "$id"
|
|
103
|
+
manda report comment "$id" --body "Triaged by automation." --audience internal
|
|
104
|
+
done
|
|
105
|
+
|
|
106
|
+
# Create a topic and link a report
|
|
107
|
+
topic=$(manda topic create --project proj-1 --title "Widget focus trap" --json | jq -r .id)
|
|
108
|
+
manda topic link-report "$topic" --report "$id"
|
|
109
|
+
manda topic transition "$topic" plan
|
|
110
|
+
```
|
package/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { resolveConfig, saveConfigFile } from "./config.js";
|
|
4
|
+
import { api, exitCodeForStatus } from "./client.js";
|
|
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
|
+
`;
|
|
48
|
+
function usageError(message) {
|
|
49
|
+
printError(0, message, 1);
|
|
50
|
+
process.stderr.write(USAGE);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
function showHelp() {
|
|
54
|
+
process.stdout.write(USAGE);
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
const parseOptions = {
|
|
58
|
+
options: {
|
|
59
|
+
url: { type: "string" },
|
|
60
|
+
token: { type: "string" },
|
|
61
|
+
json: { type: "boolean", default: false },
|
|
62
|
+
help: { type: "boolean", default: false },
|
|
63
|
+
// report flags
|
|
64
|
+
project: { type: "string" },
|
|
65
|
+
type: { type: "string" },
|
|
66
|
+
message: { type: "string" },
|
|
67
|
+
name: { type: "string" },
|
|
68
|
+
email: { type: "string" },
|
|
69
|
+
"source-label": { type: "string" },
|
|
70
|
+
to: { type: "string" },
|
|
71
|
+
revision: { type: "string" },
|
|
72
|
+
resolution: { type: "string" },
|
|
73
|
+
none: { type: "boolean", default: false },
|
|
74
|
+
body: { type: "string" },
|
|
75
|
+
audience: { type: "string" },
|
|
76
|
+
outcome: { type: "string" },
|
|
77
|
+
"request-id": { type: "string" },
|
|
78
|
+
// inbox flags
|
|
79
|
+
state: { type: "string" },
|
|
80
|
+
assignee: { type: "string" },
|
|
81
|
+
q: { type: "string" },
|
|
82
|
+
page: { type: "string" },
|
|
83
|
+
limit: { type: "string" },
|
|
84
|
+
// topic flags
|
|
85
|
+
title: { type: "string" },
|
|
86
|
+
description: { type: "string" },
|
|
87
|
+
report: { type: "string" },
|
|
88
|
+
tracker: { type: "string" },
|
|
89
|
+
issue: { type: "string" },
|
|
90
|
+
"issue-url": { type: "string" },
|
|
91
|
+
"work-url": { type: "string" },
|
|
92
|
+
"public-title": { type: "string" },
|
|
93
|
+
"public-summary": { type: "string" },
|
|
94
|
+
announcement: { type: "string" },
|
|
95
|
+
},
|
|
96
|
+
// Strict: an unknown or misspelled flag is a usage error, never silently ignored.
|
|
97
|
+
strict: true,
|
|
98
|
+
allowPositionals: true,
|
|
99
|
+
};
|
|
100
|
+
function parse() {
|
|
101
|
+
try {
|
|
102
|
+
return parseArgs(parseOptions);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
usageError(err instanceof Error ? err.message : String(err));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const { values: globalFlags, positionals } = parse();
|
|
109
|
+
// --help anywhere prints usage, including `manda report --help`.
|
|
110
|
+
if (globalFlags.help)
|
|
111
|
+
showHelp();
|
|
112
|
+
const command = positionals[0];
|
|
113
|
+
const subcommand = positionals[1];
|
|
114
|
+
if (!command) {
|
|
115
|
+
usageError("No command provided");
|
|
116
|
+
}
|
|
117
|
+
// instructions needs neither a server nor credentials
|
|
118
|
+
if (command === "instructions") {
|
|
119
|
+
instructions();
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
// Auth commands don't need a resolved config
|
|
123
|
+
if (command === "auth") {
|
|
124
|
+
if (subcommand === "login") {
|
|
125
|
+
const url = globalFlags.url ? String(globalFlags.url) : undefined;
|
|
126
|
+
const token = globalFlags.token ? String(globalFlags.token) : undefined;
|
|
127
|
+
const envUrl = process.env["MANDA_URL"];
|
|
128
|
+
const envToken = process.env["MANDA_TOKEN"];
|
|
129
|
+
const finalUrl = url || envUrl || "https://manda.aiaiai.pt";
|
|
130
|
+
const finalToken = token || envToken;
|
|
131
|
+
if (!finalToken) {
|
|
132
|
+
usageError("--token is required for auth login (or set MANDA_TOKEN)");
|
|
133
|
+
}
|
|
134
|
+
saveConfigFile(finalUrl, finalToken);
|
|
135
|
+
process.stdout.write(`Credentials saved for ${finalUrl}\n`);
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
if (subcommand === "status") {
|
|
139
|
+
const config = resolveConfig({
|
|
140
|
+
url: globalFlags.url,
|
|
141
|
+
token: globalFlags.token,
|
|
142
|
+
});
|
|
143
|
+
if (!config) {
|
|
144
|
+
printError(0, "No credentials configured. Run: manda auth login --token <token>", 2);
|
|
145
|
+
process.exit(2);
|
|
146
|
+
}
|
|
147
|
+
if (isJsonMode(globalFlags)) {
|
|
148
|
+
const meRes = await api(config, "/api/agent/v1/me");
|
|
149
|
+
if (!meRes.ok) {
|
|
150
|
+
const exit = exitCodeForStatus(meRes.status);
|
|
151
|
+
printError(meRes.status, meRes.message, exit);
|
|
152
|
+
process.exit(exit);
|
|
153
|
+
}
|
|
154
|
+
printJson({ source: config.source, url: config.url, ...meRes.data });
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
printKeyValue([
|
|
158
|
+
["source", config.source],
|
|
159
|
+
["url", config.url],
|
|
160
|
+
]);
|
|
161
|
+
const meRes = await api(config, "/api/agent/v1/me");
|
|
162
|
+
if (!meRes.ok) {
|
|
163
|
+
const exit = exitCodeForStatus(meRes.status);
|
|
164
|
+
printError(meRes.status, meRes.message, exit);
|
|
165
|
+
process.exit(exit);
|
|
166
|
+
}
|
|
167
|
+
const d = meRes.data;
|
|
168
|
+
printKeyValue([
|
|
169
|
+
["email", d.email],
|
|
170
|
+
["domain", d.domain],
|
|
171
|
+
["agent", d.agent.name],
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
174
|
+
process.exit(0);
|
|
175
|
+
}
|
|
176
|
+
usageError(`Unknown auth command: ${subcommand}`);
|
|
177
|
+
}
|
|
178
|
+
// All other commands need auth
|
|
179
|
+
const config = resolveConfig({
|
|
180
|
+
url: globalFlags.url,
|
|
181
|
+
token: globalFlags.token,
|
|
182
|
+
});
|
|
183
|
+
if (!config) {
|
|
184
|
+
printError(0, "No credentials configured. Set MANDA_TOKEN or run: manda auth login --token <token>", 2);
|
|
185
|
+
process.exit(2);
|
|
186
|
+
}
|
|
187
|
+
const flags = globalFlags;
|
|
188
|
+
try {
|
|
189
|
+
switch (command) {
|
|
190
|
+
case "whoami":
|
|
191
|
+
await whoami(config, flags);
|
|
192
|
+
break;
|
|
193
|
+
case "projects":
|
|
194
|
+
await projects(config, flags);
|
|
195
|
+
break;
|
|
196
|
+
case "inbox":
|
|
197
|
+
await inbox(config, flags);
|
|
198
|
+
break;
|
|
199
|
+
case "next":
|
|
200
|
+
await next(config, flags);
|
|
201
|
+
break;
|
|
202
|
+
case "report": {
|
|
203
|
+
const reportId = positionals[2];
|
|
204
|
+
switch (subcommand) {
|
|
205
|
+
case "show":
|
|
206
|
+
if (!reportId)
|
|
207
|
+
usageError("report show requires an ID");
|
|
208
|
+
await reportShow(config, reportId, flags);
|
|
209
|
+
break;
|
|
210
|
+
case "capture":
|
|
211
|
+
await reportCapture(config, flags);
|
|
212
|
+
break;
|
|
213
|
+
case "assign":
|
|
214
|
+
if (!reportId)
|
|
215
|
+
usageError("report assign requires an ID");
|
|
216
|
+
await reportAssign(config, reportId, flags);
|
|
217
|
+
break;
|
|
218
|
+
case "ack":
|
|
219
|
+
if (!reportId)
|
|
220
|
+
usageError("report ack requires an ID");
|
|
221
|
+
await reportAck(config, reportId, flags);
|
|
222
|
+
break;
|
|
223
|
+
case "resolve":
|
|
224
|
+
if (!reportId)
|
|
225
|
+
usageError("report resolve requires an ID");
|
|
226
|
+
await reportResolve(config, reportId, flags);
|
|
227
|
+
break;
|
|
228
|
+
case "reopen":
|
|
229
|
+
if (!reportId)
|
|
230
|
+
usageError("report reopen requires an ID");
|
|
231
|
+
await reportReopen(config, reportId, flags);
|
|
232
|
+
break;
|
|
233
|
+
case "link":
|
|
234
|
+
if (!reportId)
|
|
235
|
+
usageError("report link requires an ID");
|
|
236
|
+
await reportLink(config, reportId, flags);
|
|
237
|
+
break;
|
|
238
|
+
case "comment":
|
|
239
|
+
if (!reportId)
|
|
240
|
+
usageError("report comment requires an ID");
|
|
241
|
+
await reportComment(config, reportId, flags);
|
|
242
|
+
break;
|
|
243
|
+
default:
|
|
244
|
+
usageError(subcommand
|
|
245
|
+
? `Unknown report command: ${subcommand}`
|
|
246
|
+
: "report requires a subcommand");
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case "topic": {
|
|
251
|
+
const topicId = positionals[2];
|
|
252
|
+
switch (subcommand) {
|
|
253
|
+
case "list":
|
|
254
|
+
await topicList(config, flags);
|
|
255
|
+
break;
|
|
256
|
+
case "show":
|
|
257
|
+
if (!topicId)
|
|
258
|
+
usageError("topic show requires an ID");
|
|
259
|
+
await topicShow(config, topicId, flags);
|
|
260
|
+
break;
|
|
261
|
+
case "create":
|
|
262
|
+
await topicCreate(config, flags);
|
|
263
|
+
break;
|
|
264
|
+
case "transition": {
|
|
265
|
+
if (!topicId)
|
|
266
|
+
usageError("topic transition requires an ID");
|
|
267
|
+
const event = positionals[3];
|
|
268
|
+
if (!event)
|
|
269
|
+
usageError("topic transition requires an event (plan|start|complete|decline|reconsider|reopen)");
|
|
270
|
+
await topicTransition(config, topicId, event, flags);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case "note":
|
|
274
|
+
if (!topicId)
|
|
275
|
+
usageError("topic note requires an ID");
|
|
276
|
+
await topicNote(config, topicId, flags);
|
|
277
|
+
break;
|
|
278
|
+
case "edit":
|
|
279
|
+
if (!topicId)
|
|
280
|
+
usageError("topic edit requires an ID");
|
|
281
|
+
await topicEdit(config, topicId, flags);
|
|
282
|
+
break;
|
|
283
|
+
case "link-report":
|
|
284
|
+
if (!topicId)
|
|
285
|
+
usageError("topic link-report requires an ID");
|
|
286
|
+
await topicLinkReport(config, topicId, flags);
|
|
287
|
+
break;
|
|
288
|
+
case "unlink-report":
|
|
289
|
+
if (!topicId)
|
|
290
|
+
usageError("topic unlink-report requires an ID");
|
|
291
|
+
await topicUnlinkReport(config, topicId, flags);
|
|
292
|
+
break;
|
|
293
|
+
case "link-work":
|
|
294
|
+
if (!topicId)
|
|
295
|
+
usageError("topic link-work requires an ID");
|
|
296
|
+
await topicLinkWork(config, topicId, flags);
|
|
297
|
+
break;
|
|
298
|
+
case "unlink-work":
|
|
299
|
+
if (!topicId)
|
|
300
|
+
usageError("topic unlink-work requires an ID");
|
|
301
|
+
await topicUnlinkWork(config, topicId, flags);
|
|
302
|
+
break;
|
|
303
|
+
case "publish":
|
|
304
|
+
if (!topicId)
|
|
305
|
+
usageError("topic publish requires an ID");
|
|
306
|
+
await topicPublish(config, topicId, flags);
|
|
307
|
+
break;
|
|
308
|
+
case "unpublish":
|
|
309
|
+
if (!topicId)
|
|
310
|
+
usageError("topic unpublish requires an ID");
|
|
311
|
+
await topicUnpublish(config, topicId, flags);
|
|
312
|
+
break;
|
|
313
|
+
case "edit-public":
|
|
314
|
+
if (!topicId)
|
|
315
|
+
usageError("topic edit-public requires an ID");
|
|
316
|
+
await topicEditPublic(config, topicId, flags);
|
|
317
|
+
break;
|
|
318
|
+
case "reorder":
|
|
319
|
+
await topicReorder(config, positionals.slice(2), flags);
|
|
320
|
+
break;
|
|
321
|
+
case "announce":
|
|
322
|
+
if (!topicId)
|
|
323
|
+
usageError("topic announce requires an ID");
|
|
324
|
+
await topicAnnounce(config, topicId, flags);
|
|
325
|
+
break;
|
|
326
|
+
case "unannounce":
|
|
327
|
+
if (!topicId)
|
|
328
|
+
usageError("topic unannounce requires an ID");
|
|
329
|
+
await topicUnannounce(config, topicId, flags);
|
|
330
|
+
break;
|
|
331
|
+
default:
|
|
332
|
+
usageError(subcommand
|
|
333
|
+
? `Unknown topic command: ${subcommand}`
|
|
334
|
+
: "topic requires a subcommand");
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
case "--help":
|
|
339
|
+
case "help":
|
|
340
|
+
showHelp();
|
|
341
|
+
default:
|
|
342
|
+
usageError(`Unknown command: ${command}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
catch (err) {
|
|
346
|
+
printError(0, err instanceof Error ? err.message : String(err), 5);
|
|
347
|
+
process.exit(5);
|
|
348
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Config } from "./config.js";
|
|
2
|
+
export interface ApiError {
|
|
3
|
+
status: number;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ApiResult<T = unknown> {
|
|
7
|
+
ok: true;
|
|
8
|
+
status: number;
|
|
9
|
+
data: T;
|
|
10
|
+
}
|
|
11
|
+
export interface ApiFailure {
|
|
12
|
+
ok: false;
|
|
13
|
+
status: number;
|
|
14
|
+
message: string;
|
|
15
|
+
data?: unknown;
|
|
16
|
+
}
|
|
17
|
+
export type ApiResponse<T = unknown> = ApiResult<T> | ApiFailure;
|
|
18
|
+
export declare function api<T = unknown>(config: Config, path: string, options?: {
|
|
19
|
+
method?: string;
|
|
20
|
+
body?: unknown;
|
|
21
|
+
query?: Record<string, string | undefined>;
|
|
22
|
+
}): Promise<ApiResponse<T>>;
|
|
23
|
+
export declare function exitCodeForStatus(status: number): number;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export async function api(config, path, options = {}) {
|
|
2
|
+
const { method = "GET", body, query } = options;
|
|
3
|
+
const url = new URL(path, config.url);
|
|
4
|
+
if (query) {
|
|
5
|
+
for (const [k, v] of Object.entries(query)) {
|
|
6
|
+
if (v !== undefined && v !== "")
|
|
7
|
+
url.searchParams.set(k, v);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const headers = {
|
|
11
|
+
authorization: `Bearer ${config.token}`,
|
|
12
|
+
accept: "application/json",
|
|
13
|
+
};
|
|
14
|
+
if (body !== undefined) {
|
|
15
|
+
headers["content-type"] = "application/json";
|
|
16
|
+
}
|
|
17
|
+
let res;
|
|
18
|
+
try {
|
|
19
|
+
res = await fetch(url, {
|
|
20
|
+
method,
|
|
21
|
+
headers,
|
|
22
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
status: 0,
|
|
29
|
+
message: err instanceof Error ? err.message : "Network error",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const text = await res.text();
|
|
33
|
+
let data;
|
|
34
|
+
try {
|
|
35
|
+
data = JSON.parse(text);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
data = text;
|
|
39
|
+
}
|
|
40
|
+
if (res.ok) {
|
|
41
|
+
return { ok: true, status: res.status, data: data };
|
|
42
|
+
}
|
|
43
|
+
const message = typeof data === "object" &&
|
|
44
|
+
data !== null &&
|
|
45
|
+
"message" in data &&
|
|
46
|
+
typeof data.message === "string"
|
|
47
|
+
? data.message
|
|
48
|
+
: typeof data === "string"
|
|
49
|
+
? data
|
|
50
|
+
: `HTTP ${res.status}`;
|
|
51
|
+
return { ok: false, status: res.status, message, data };
|
|
52
|
+
}
|
|
53
|
+
export function exitCodeForStatus(status) {
|
|
54
|
+
if (status === 401 || status === 403)
|
|
55
|
+
return 2;
|
|
56
|
+
if (status === 409)
|
|
57
|
+
return 3;
|
|
58
|
+
if (status === 404)
|
|
59
|
+
return 4;
|
|
60
|
+
// 400, 415, 429, 5xx, network (0)
|
|
61
|
+
return 5;
|
|
62
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Config } from "./config.js";
|
|
2
|
+
type Flags = Record<string, string | boolean | undefined>;
|
|
3
|
+
export declare function whoami(config: Config, flags: Flags): Promise<void>;
|
|
4
|
+
export declare function projects(config: Config, flags: Flags): Promise<void>;
|
|
5
|
+
export declare function inbox(config: Config, flags: Flags): Promise<void>;
|
|
6
|
+
export declare function next(config: Config, flags: Flags): Promise<void>;
|
|
7
|
+
/** 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: `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";
|
|
9
|
+
export declare function instructions(): void;
|
|
10
|
+
export declare function reportShow(config: Config, id: string, flags: Flags): Promise<void>;
|
|
11
|
+
export declare function reportCapture(config: Config, flags: Flags): Promise<void>;
|
|
12
|
+
export declare function reportAssign(config: Config, id: string, flags: Flags): Promise<void>;
|
|
13
|
+
export declare function reportAck(config: Config, id: string, flags: Flags): Promise<void>;
|
|
14
|
+
export declare function reportResolve(config: Config, id: string, flags: Flags): Promise<void>;
|
|
15
|
+
export declare function reportReopen(config: Config, id: string, flags: Flags): Promise<void>;
|
|
16
|
+
export declare function reportLink(config: Config, id: string, flags: Flags): Promise<void>;
|
|
17
|
+
export declare function reportComment(config: Config, id: string, flags: Flags): Promise<void>;
|
|
18
|
+
export declare function topicList(config: Config, flags: Flags): Promise<void>;
|
|
19
|
+
export declare function topicShow(config: Config, id: string, flags: Flags): Promise<void>;
|
|
20
|
+
export declare function topicCreate(config: Config, flags: Flags): Promise<void>;
|
|
21
|
+
export declare function topicTransition(config: Config, id: string, event: string, flags: Flags): Promise<void>;
|
|
22
|
+
export declare function topicNote(config: Config, id: string, flags: Flags): Promise<void>;
|
|
23
|
+
export declare function topicEdit(config: Config, id: string, flags: Flags): Promise<void>;
|
|
24
|
+
export declare function topicLinkReport(config: Config, id: string, flags: Flags): Promise<void>;
|
|
25
|
+
export declare function topicUnlinkReport(config: Config, id: string, flags: Flags): Promise<void>;
|
|
26
|
+
export declare function topicLinkWork(config: Config, id: string, flags: Flags): Promise<void>;
|
|
27
|
+
export declare function topicUnlinkWork(config: Config, id: string, flags: Flags): Promise<void>;
|
|
28
|
+
export declare function topicPublish(config: Config, id: string, flags: Flags): Promise<void>;
|
|
29
|
+
export declare function topicUnpublish(config: Config, id: string, flags: Flags): Promise<void>;
|
|
30
|
+
export declare function topicEditPublic(config: Config, id: string, flags: Flags): Promise<void>;
|
|
31
|
+
export declare function topicReorder(config: Config, topicIds: string[], flags: Flags): Promise<void>;
|
|
32
|
+
export declare function topicAnnounce(config: Config, id: string, flags: Flags): Promise<void>;
|
|
33
|
+
export declare function topicUnannounce(config: Config, id: string, flags: Flags): Promise<void>;
|
|
34
|
+
export {};
|