@aiaiai-pt/manda-cli 0.1.2 → 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 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) {
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/manda-cli",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "CLI for the Manda feedback service",
5
5
  "license": "MIT",
6
6
  "type": "module",