@aiaiai-pt/manda-cli 0.1.1 → 0.2.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 +9 -0
- package/dist/bin.js +9 -1
- package/dist/commands.d.ts +24 -1
- package/dist/commands.js +62 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ Credentials are saved to `${XDG_CONFIG_HOME:-~/.config}/manda/config.json` with
|
|
|
37
37
|
| `manda inbox [filters]` | List submissions |
|
|
38
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
39
|
| `manda instructions` | Print a block for CLAUDE.md or AGENTS.md describing the loop and commit trailers |
|
|
40
|
+
| `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
41
|
| `manda report show <id>` | Show a report |
|
|
41
42
|
| `manda report capture --project <id> --type bug\|feature\|other --message <text> [--name] [--email] [--source-label]` | Create a report |
|
|
42
43
|
| `manda report assign <id> --to <email\|me\|none>` | Assign a report |
|
|
@@ -108,3 +109,11 @@ topic=$(manda topic create --project proj-1 --title "Widget focus trap" --json |
|
|
|
108
109
|
manda topic link-report "$topic" --report "$id"
|
|
109
110
|
manda topic transition "$topic" plan
|
|
110
111
|
```
|
|
112
|
+
|
|
113
|
+
# Wake on changes (one JSON line per event; a customer reply on a resolved report reopens it)
|
|
114
|
+
manda watch --since "$cursor" | while read -r event; do
|
|
115
|
+
if [ "$(jq -r .author_kind <<<"$event")" = customer ]; then
|
|
116
|
+
manda report reopen "$(jq -r .report_id <<<"$event")"
|
|
117
|
+
fi
|
|
118
|
+
done
|
|
119
|
+
```
|
package/dist/bin.js
CHANGED
|
@@ -3,7 +3,7 @@ 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";
|
|
6
|
+
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";
|
|
7
7
|
const USAGE = `Usage: manda <command> [options]
|
|
8
8
|
|
|
9
9
|
Commands:
|
|
@@ -12,6 +12,7 @@ Commands:
|
|
|
12
12
|
inbox List submissions (inbox)
|
|
13
13
|
next What to work on next, in priority order (--project)
|
|
14
14
|
instructions Print a CLAUDE.md / AGENTS.md block for this loop
|
|
15
|
+
watch Print changes as they land (--since, --project, --interval, --once)
|
|
15
16
|
report show <id> Show a report
|
|
16
17
|
report capture Create a new report
|
|
17
18
|
report assign <id> Assign a report
|
|
@@ -92,6 +93,10 @@ const parseOptions = {
|
|
|
92
93
|
"public-title": { type: "string" },
|
|
93
94
|
"public-summary": { type: "string" },
|
|
94
95
|
announcement: { type: "string" },
|
|
96
|
+
// watch flags
|
|
97
|
+
since: { type: "string" },
|
|
98
|
+
interval: { type: "string" },
|
|
99
|
+
once: { type: "boolean", default: false },
|
|
95
100
|
},
|
|
96
101
|
// Strict: an unknown or misspelled flag is a usage error, never silently ignored.
|
|
97
102
|
strict: true,
|
|
@@ -199,6 +204,9 @@ try {
|
|
|
199
204
|
case "next":
|
|
200
205
|
await next(config, flags);
|
|
201
206
|
break;
|
|
207
|
+
case "watch":
|
|
208
|
+
await watch(config, flags);
|
|
209
|
+
break;
|
|
202
210
|
case "report": {
|
|
203
211
|
const reportId = positionals[2];
|
|
204
212
|
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
|
|
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
|
|
@@ -183,9 +237,16 @@ The loop:
|
|
|
183
237
|
and \`manda topic transition <topic> plan\` then \`start\` when you begin.
|
|
184
238
|
4. Do the work in the repository. Put \`Manda-Report: <id>\` and \`Manda-Topic: <topic>\` as commit trailers
|
|
185
239
|
and \`manda topic link-work <topic> --tracker github --issue <pr number> --issue-url <pr url>\` once a PR exists.
|
|
186
|
-
5. Tell the customer only when it is true
|
|
240
|
+
5. Tell the customer only when it is true, and "true" means you exercised their path, not the artefact:
|
|
241
|
+
load the page or call the entry point they use (the hosted script, the endpoint, the screen) on the
|
|
242
|
+
deployed version and watch it work. A green suite, a merged PR or a file read back are not that.
|
|
243
|
+
Then \`manda report comment <id> --audience customer --outcome resolved --body "<what changed, in their words>"\`.
|
|
187
244
|
Internal notes use \`--audience internal\`. Never resolve without a customer-facing explanation.
|
|
188
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.
|
|
189
250
|
|
|
190
251
|
Rules: read before you write (every write checks the revision); do not reprioritise by editing reports, reorder topics
|
|
191
252
|
(\`manda topic reorder\`) with the person who owns the roadmap; a 3 exit means someone else changed it, re-read and decide again.
|