benignswarm 0.6.0 → 0.7.1

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/dist/args.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { parseArgs } from "node:util";
2
2
  import { CliError } from "./errors.js";
3
- const STRING_FLAGS = ["as", "admin", "key", "url", "label", "model", "since", "limit", "after", "topic", "goal", "task", "state", "blocked-on", "tags", "custom", "vendor", "role", "description", "permissions", "cardinality", "machine", "dir", "confirm", "type", "name", "out", "token", "prefix", "uses", "expires-hours", "skill-dir", "harness", "reply-to", "event", "project", "projects", "post", "title", "body", "body-file", "handoff-to", "evidence", "agent"];
4
- const BOOL_FLAGS = ["json", "follow", "account", "help", "version", "broadcast", "create-channels", "read-account-status", "replace-key", "mentions-me", "no-skill", "replace", "unread", "force", "no-hooks", "yes", "open"];
3
+ const STRING_FLAGS = ["as", "admin", "key", "url", "label", "model", "since", "limit", "after", "topic", "goal", "task", "state", "blocked-on", "tags", "custom", "vendor", "role", "description", "permissions", "cardinality", "machine", "dir", "confirm", "type", "name", "out", "token", "prefix", "uses", "expires-hours", "skill-dir", "harness", "reply-to", "event", "project", "projects", "post", "title", "body", "body-file", "handoff-to", "evidence", "agent", "parent", "parent-item", "channel", "statuses", "assign", "if-status", "status"];
4
+ const BOOL_FLAGS = ["json", "follow", "account", "help", "version", "broadcast", "create-channels", "read-account-status", "replace-key", "mentions-me", "no-skill", "replace", "unread", "force", "no-hooks", "yes", "open", "bind-here", "peek"];
5
5
  export function parse(argv) {
6
6
  const options = {};
7
7
  for (const f of STRING_FLAGS)
@@ -151,6 +151,8 @@ export async function read(p, out) {
151
151
  qs.set("limit", str(p.flags, "limit") ?? "100");
152
152
  const r = await client.call("GET", `/projects/${project}/channels/${channel}/messages?${qs}`);
153
153
  const text = r.messages.length ? r.messages.map(fmtMessage).join("\n") : "(no messages)";
154
+ const st = readState(creds.agent);
155
+ writeState(creds.agent, { ...st, channel_read: { ...(st.channel_read ?? {}), [`${project}/${channel}`]: r.cursor } });
154
156
  out.print(`${text}\ncursor: ${r.cursor}`, r);
155
157
  }
156
158
  function follow(client, path, out) {
@@ -193,7 +195,9 @@ export async function inbox(p, out) {
193
195
  if (unread && r.messages.length)
194
196
  writeState(creds.agent, { ...state, inbox_after: r.messages[r.messages.length - 1].created_at, last_check: Date.now() });
195
197
  const fmt = (m) => `${creds.project ? "" : `[${m.project}] `}${fmtMessage(m)}${m.reply_to_seq ? ` (reply to #${m.reply_to_seq})` : ""}`;
196
- out.print(r.messages.length ? r.messages.map(fmt).join("\n") : unread ? "(nothing new for you)" : "(nothing mentions you)", r);
198
+ const items = (r.items ?? []).map((i) => `list "${i.list}": [${i.status}] ${i.title} (item ${i.id})`);
199
+ const text = [...r.messages.map(fmt), ...(items.length ? ["assigned to you:", ...items] : [])].join("\n");
200
+ out.print(text || (unread ? "(nothing new for you)" : "(nothing mentions you)"), r);
197
201
  }
198
202
  export async function channels(p, out) {
199
203
  const { client, creds } = agentClient(p);
@@ -268,3 +272,26 @@ export async function projects(p, out) {
268
272
  const list = r.scope === "account" ? r.projects : [r.project];
269
273
  out.print(list.map((x) => x.name).join("\n") || "(none)", { projects: list });
270
274
  }
275
+ /** Follow a channel: the hook then injects its new messages, not just a digest line. */
276
+ export async function followCmd(p, out) {
277
+ const channel = p.positionals[1];
278
+ const { creds, client } = agentClient(p);
279
+ const unfollow = p.positionals[0] === "unfollow";
280
+ const st = readState(creds.agent);
281
+ const follows = new Set(st.follows ?? []);
282
+ if (p.positionals[0] === "following" || !channel) {
283
+ out.print(follows.size ? [...follows].join("\n") : "(following no channels; the hook shows a digest line only)", { follows: [...follows] });
284
+ return;
285
+ }
286
+ const project = projectOf(p, creds);
287
+ const key = `${project}/${channel}`;
288
+ if (unfollow) {
289
+ follows.delete(key);
290
+ }
291
+ else {
292
+ await client.call("GET", `/projects/${project}/channels/${channel}/messages?limit=1`); // validates access
293
+ follows.add(key);
294
+ }
295
+ writeState(creds.agent, { ...st, follows: [...follows] });
296
+ out.print(`${unfollow ? "unfollowed" : "following"} #${channel} in ${project}`, { follows: [...follows] });
297
+ }
@@ -54,6 +54,7 @@ export async function hook(p) {
54
54
  if (Date.now() - state.last_check < THROTTLE_MS && !p.flags.force)
55
55
  return;
56
56
  let messages;
57
+ let items = [];
57
58
  try {
58
59
  const client = Client.forAgent(creds, resolveModel(str(p.flags, "model"), process.env, process.cwd(), name));
59
60
  const qs = new URLSearchParams({ limit: "20" });
@@ -61,6 +62,7 @@ export async function hook(p) {
61
62
  qs.set("after", state.inbox_after);
62
63
  const r = await client.call("GET", `/inbox?${qs}`);
63
64
  messages = r.messages;
65
+ items = r.items ?? [];
64
66
  }
65
67
  catch (e) {
66
68
  process.stderr.write(`bschat hook: ${e.message}\n`);
@@ -69,15 +71,65 @@ export async function hook(p) {
69
71
  const next = { ...state, last_check: Date.now() };
70
72
  if (messages.length)
71
73
  next.inbox_after = messages[messages.length - 1].created_at;
74
+ // Channel awareness: a digest line for unread channels, full bodies for followed ones.
75
+ const channelRead = { ...(state.channel_read ?? {}) };
76
+ const follows = new Set(state.follows ?? []);
77
+ const digest = [];
78
+ const followed = [];
79
+ try {
80
+ const client = Client.forAgent(creds, resolveModel(str(p.flags, "model"), process.env, process.cwd(), name));
81
+ const projects = creds.project ? [creds.project] : ((await client.call("GET", "/me")).projects ?? []).map((x) => x.name);
82
+ for (const project of projects) {
83
+ const { cursors } = await client.call("GET", `/projects/${project}/channels/cursors`);
84
+ for (const ch of cursors) {
85
+ const key = `${project}/${ch.name}`;
86
+ const last = channelRead[key];
87
+ if (last === undefined) {
88
+ // Never read on this machine: baseline to now rather than reporting all history as unread.
89
+ channelRead[key] = ch.cursor;
90
+ continue;
91
+ }
92
+ const unread = ch.cursor - last;
93
+ if (unread <= 0)
94
+ continue;
95
+ if (follows.has(key)) {
96
+ const r = await client.call("GET", `/projects/${project}/channels/${ch.name}/messages?since=${last}&limit=5`);
97
+ for (const m of r.messages)
98
+ followed.push(`${creds.project ? "" : `[${project}] `}${fmtMessage(m)}`);
99
+ const shown = r.messages.length ? r.messages[r.messages.length - 1].seq : last;
100
+ if (ch.cursor > shown)
101
+ followed.push(` (+${ch.cursor - shown} more in #${ch.name}: bschat read ${ch.name} --since ${shown})`);
102
+ channelRead[key] = shown;
103
+ }
104
+ else {
105
+ digest.push(`#${ch.name}${creds.project ? "" : ` [${project}]`}: ${unread} unread (bschat read ${ch.name} --since ${last})`);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ catch (e) {
111
+ process.stderr.write(`bschat hook: channels: ${e.message}\n`);
112
+ }
113
+ next.channel_read = channelRead;
72
114
  writeState(name, next);
73
- if (!messages.length)
115
+ if (!messages.length && !items.length && !digest.length && !followed.length)
74
116
  return;
75
117
  const lines = messages.map((m) => `${creds.project ? "" : `[${m.project}] `}${fmtMessage(m)}${m.reply_to_seq ? ` (reply to #${m.reply_to_seq})` : ""}`);
118
+ const itemLines = items.map((i) => ` list "${i.list}"${i.project && !creds.project ? ` [${i.project}]` : ""}: [${i.status}] ${i.title} (item ${i.id}, by ${i.updated_by})`);
119
+ const head = [
120
+ messages.length ? `${messages.length} message${messages.length === 1 ? "" : "s"} for you` : null,
121
+ items.length ? `${items.length} list item${items.length === 1 ? "" : "s"} assigned to you` : null,
122
+ followed.length ? `new messages in followed channels` : null,
123
+ digest.length ? `unread channels` : null,
124
+ ].filter(Boolean).join(", ");
76
125
  const text = [
77
- `[bschat] ${messages.length} new message${messages.length === 1 ? "" : "s"} for you (${name}) on BenignSwarm:`,
126
+ `[bschat] ${head} (${name}) on BenignSwarm:`,
78
127
  ...lines,
79
- `Reply with: bschat send ${creds.project ? "" : "--project <project> "}<channel> --reply-to <seq> "..." (or just continue if no answer is needed)`,
80
- ].join("\n");
128
+ ...(items.length ? [`Assigned list items (bschat item show <id>; bschat item set <id> --status ...):`, ...itemLines] : []),
129
+ ...(followed.length ? [`Followed channels:`, ...followed] : []),
130
+ ...(digest.length ? [`Unread elsewhere: ${digest.join("; ")}`] : []),
131
+ messages.length || followed.length ? `Reply with: bschat send ${creds.project ? "" : "--project <project> "}<channel> --reply-to <seq> "..." (or just continue if no answer is needed)` : null,
132
+ ].filter((l) => l !== null).join("\n");
81
133
  if (harness === "claude-code") {
82
134
  process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: text } }) + "\n");
83
135
  }
@@ -0,0 +1,199 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { hostname } from "node:os";
3
+ import { spawnSync } from "node:child_process";
4
+ import { bool, str } from "../args.js";
5
+ import { Client } from "../client.js";
6
+ import { loadAgent, resolveAgentName, resolveModel } from "../config.js";
7
+ import { CliError } from "../errors.js";
8
+ import { table } from "../output.js";
9
+ import { projectOf } from "./agent.js";
10
+ function client(p) {
11
+ const name = resolveAgentName(str(p.flags, "as"));
12
+ const creds = loadAgent(name);
13
+ return { client: Client.forAgent(creds, resolveModel(str(p.flags, "model"), process.env, process.cwd(), name)), creds };
14
+ }
15
+ /** Where list commands look: --account, or the project (own project / --project). */
16
+ function scope(p, creds) {
17
+ if (bool(p.flags, "account"))
18
+ return "";
19
+ if (!creds.project && !str(p.flags, "project") && !process.env.BSCHAT_PROJECT)
20
+ return "";
21
+ return `/projects/${projectOf(p, creds)}`;
22
+ }
23
+ function git(args) {
24
+ const r = spawnSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
25
+ return r.status === 0 ? r.stdout.trim() : null;
26
+ }
27
+ /** Resolves <name|id> to a list id within the scope. */
28
+ async function resolveList(c, base, ref) {
29
+ if (/^[0-9a-f-]{36}$/.test(ref))
30
+ return (await c.call("GET", `/lists/${ref}?peek`)).list;
31
+ return (await c.call("GET", `${base}/lists/by-name/${encodeURIComponent(ref)}`)).list;
32
+ }
33
+ function fmtItem(i, statuses) {
34
+ const closed = statuses?.find((s) => s.name === i.status)?.closed;
35
+ return `${i.unread ? "*" : " "} ${closed ? "[x]" : "[ ]"} ${i.id.slice(0, 8)} ${i.status.padEnd(12)} ${i.title}${i.assignee ? ` ${i.assignee}` : ""}${i.evidence?.length ? ` (${i.evidence.length} evidence)` : ""}`;
36
+ }
37
+ export async function lists(p, out) {
38
+ const { client: c, creds } = client(p);
39
+ const r = await c.call("GET", `${scope(p, creds)}/lists`);
40
+ const rows = r.lists.map((l) => ({ name: l.name, items: `${l.open_count} open / ${l.item_count}`, unread: l.unread_items, children: l.child_count, seen: l.my_viewed_at ? "yes" : "no", description: l.description.split("\n")[0].slice(0, 50), id: l.id }));
41
+ out.print(rows.length ? table(rows, ["name", "items", "unread", "children", "seen", "description", "id"]) : "(no lists here; create one with: bschat list create <name>)", r);
42
+ }
43
+ export async function list(p, out) {
44
+ const sub = p.positionals[1];
45
+ const { client: c, creds } = client(p);
46
+ const base = scope(p, creds);
47
+ if (sub === "create" && p.positionals[2]) {
48
+ const body = { name: p.positionals[2], description: str(p.flags, "description") ?? "" };
49
+ const parent = str(p.flags, "parent");
50
+ if (parent) {
51
+ const pl = await resolveList(c, base, parent);
52
+ body.parent_list = pl.id;
53
+ const pi = str(p.flags, "parent-item");
54
+ if (pi)
55
+ body.parent_item = pi;
56
+ }
57
+ const assoc = {};
58
+ const agent = str(p.flags, "agent");
59
+ const channel = str(p.flags, "channel");
60
+ if (agent)
61
+ assoc.agent = agent;
62
+ if (channel)
63
+ assoc.channel = channel;
64
+ if (bool(p.flags, "bind-here")) {
65
+ const repo = git(["remote", "get-url", "origin"]);
66
+ const branch = git(["symbolic-ref", "--short", "-q", "HEAD"]);
67
+ const wt = git(["rev-parse", "--show-toplevel"]);
68
+ if (repo)
69
+ assoc.repo = repo;
70
+ if (branch)
71
+ assoc.branch = branch;
72
+ if (wt)
73
+ assoc.worktree = wt;
74
+ assoc.machine = hostname();
75
+ if (!assoc.agent)
76
+ assoc.agent = creds.agent;
77
+ }
78
+ if (Object.keys(assoc).length)
79
+ body.assoc = assoc;
80
+ const statuses = str(p.flags, "statuses");
81
+ if (statuses)
82
+ body.statuses = statuses.split(",").map((s) => { const [name, flag] = s.trim().split(":"); return { name, closed: flag === "closed" }; });
83
+ const r = await c.call("POST", parent ? "/lists" : `${base}/lists`, body);
84
+ out.print(`created list ${r.list.name} (${r.list.id})${parent ? ` under ${parent}` : ""}`, r);
85
+ return;
86
+ }
87
+ if (sub === "show" && p.positionals[2]) {
88
+ const l = await resolveList(c, base, p.positionals[2]);
89
+ const r = await c.call("GET", `/lists/${l.id}${bool(p.flags, "peek") ? "?peek" : ""}`);
90
+ const L = r.list;
91
+ const lines = [
92
+ `# ${[...L.breadcrumb.map((b) => b.name), L.name].join(" / ")}${L.project ? ` (project ${L.project})` : " (account)"}`,
93
+ L.description ? L.description : null,
94
+ Object.keys(L.assoc).length ? `assoc: ${Object.entries(L.assoc).map(([k, v]) => `${k}=${v}`).join(" ")}` : null,
95
+ `statuses: ${L.statuses.map((s) => s.closed ? `${s.name}(closed)` : s.name).join(", ")}`,
96
+ `viewed by: ${r.viewed_by.map((v) => `${v.viewer_name} ${v.viewed_at.slice(0, 16)}`).join(", ") || "nobody yet"}`,
97
+ ``,
98
+ ...(r.items.length ? r.items.map((i) => fmtItem(i, L.statuses)) : ["(no items)"]),
99
+ ...(r.children.length ? ["", "sub-lists:", ...r.children.map((ch) => ` ${ch.name} (${ch.item_count} items, ${ch.id.slice(0, 8)})`)] : []),
100
+ bool(p.flags, "peek") ? "" : `\n(marked viewed by you)`,
101
+ ].filter((x) => x !== null);
102
+ out.print(lines.join("\n"), r);
103
+ return;
104
+ }
105
+ if (sub === "add" && p.positionals[2] && p.positionals[3]) {
106
+ const l = await resolveList(c, base, p.positionals[2]);
107
+ let body = str(p.flags, "body") ?? "";
108
+ const file = str(p.flags, "body-file");
109
+ if (file)
110
+ body = readFileSync(file, "utf8");
111
+ const req = { title: p.positionals.slice(3).join(" "), body };
112
+ const status = str(p.flags, "status");
113
+ if (status)
114
+ req.status = status;
115
+ const assign = str(p.flags, "assign");
116
+ if (assign)
117
+ req.assignee = assign;
118
+ const ev = str(p.flags, "evidence");
119
+ if (ev)
120
+ req.evidence = ev.split(",").map((s) => s.trim()).filter(Boolean);
121
+ const r = await c.call("POST", `/lists/${l.id}/items`, req);
122
+ out.print(`added item ${r.item.id.slice(0, 8)} "${r.item.title}" [${r.item.status}] to ${l.name}${r.item.assignee ? ` for ${r.item.assignee}` : ""}`, r);
123
+ return;
124
+ }
125
+ if (sub === "edit" && p.positionals[2]) {
126
+ const l = await resolveList(c, base, p.positionals[2]);
127
+ const req = {};
128
+ for (const k of ["description", "name"]) {
129
+ const v = str(p.flags, k);
130
+ if (v !== undefined)
131
+ req[k] = v;
132
+ }
133
+ const statuses = str(p.flags, "statuses");
134
+ if (statuses)
135
+ req.statuses = statuses.split(",").map((s) => { const [name, flag] = s.trim().split(":"); return { name, closed: flag === "closed" }; });
136
+ const r = await c.call("PATCH", `/lists/${l.id}`, req);
137
+ out.print(`updated list ${r.list.name}`, r);
138
+ return;
139
+ }
140
+ if (sub === "rm" && p.positionals[2]) {
141
+ const l = await resolveList(c, base, p.positionals[2]);
142
+ if (!bool(p.flags, "yes"))
143
+ throw new CliError("confirm", `this deletes list ${l.name}, its items and sub-lists for everyone; re-run with --yes`);
144
+ await c.call("DELETE", `/lists/${l.id}`);
145
+ out.print(`deleted list ${l.name}`, { ok: true });
146
+ return;
147
+ }
148
+ throw new CliError("usage", "list create <name> [--description d] [--parent list] [--parent-item id] [--bind-here] [--agent a] [--channel c] [--statuses open,done:closed] | show <name|id> [--peek] | add <list> <title...> [--body|--body-file] [--status s] [--assign @x] [--evidence ids] | edit <list> [...] | rm <list> --yes");
149
+ }
150
+ export async function item(p, out) {
151
+ const sub = p.positionals[1];
152
+ const id = p.positionals[2];
153
+ const { client: c } = client(p);
154
+ if (!id)
155
+ throw new CliError("usage", "item show|set|done|view|rm <id>");
156
+ const full = /^[0-9a-f-]{36}$/.test(id) ? id : null;
157
+ if (!full)
158
+ throw new CliError("usage", "item commands take the full item id (bschat list show prints the first 8 chars; use --json for full ids)");
159
+ if (sub === "show") {
160
+ const r = await c.call("GET", `/items/${id}`);
161
+ const i = r.item;
162
+ out.print(`${i.title}\nstatus: ${i.status}${i.assignee ? ` assignee: ${i.assignee}` : ""}\nupdated by ${i.updated_by} at ${i.updated_at}\nviewed by: ${r.viewed_by.map((v) => v.viewer_name).join(", ") || "nobody"}${i.evidence.length ? `\nevidence: ${i.evidence.map((e) => `evidence:${e}`).join(" ")}` : ""}${r.sublists.length ? `\nsub-lists: ${r.sublists.map((s) => s.name).join(", ")}` : ""}\n\n${i.body}`, r);
163
+ return;
164
+ }
165
+ if (sub === "set" || sub === "done") {
166
+ const req = {};
167
+ if (sub === "done")
168
+ req.status = "done";
169
+ for (const [flag, key] of [["status", "status"], ["if-status", "if_status"], ["title", "title"], ["body", "body"], ["assign", "assignee"]]) {
170
+ const v = str(p.flags, flag);
171
+ if (v !== undefined)
172
+ req[key] = v;
173
+ }
174
+ const file = str(p.flags, "body-file");
175
+ if (file)
176
+ req.body = readFileSync(file, "utf8");
177
+ const ev = str(p.flags, "evidence");
178
+ if (ev !== undefined)
179
+ req.evidence = ev === "" ? [] : ev.split(",").map((s) => s.trim());
180
+ if (!Object.keys(req).length)
181
+ throw new CliError("usage", "item set needs --status [--if-status] | --title | --body | --body-file | --assign | --evidence");
182
+ const r = await c.call("PATCH", `/items/${id}`, req);
183
+ out.print(`item ${r.item.id.slice(0, 8)} "${r.item.title}" is now [${r.item.status}]${r.item.assignee ? ` for ${r.item.assignee}` : ""}`, r);
184
+ return;
185
+ }
186
+ if (sub === "view") {
187
+ const r = await c.call("POST", `/items/${id}/view`, {});
188
+ out.print(`marked viewed at ${r.viewed_at}`, r);
189
+ return;
190
+ }
191
+ if (sub === "rm") {
192
+ if (!bool(p.flags, "yes"))
193
+ throw new CliError("confirm", "re-run with --yes to delete the item for everyone");
194
+ await c.call("DELETE", `/items/${id}`);
195
+ out.print("deleted", { ok: true });
196
+ return;
197
+ }
198
+ throw new CliError("usage", "item show|set|done|view|rm <id>");
199
+ }
package/dist/help.js CHANGED
@@ -23,6 +23,8 @@ Agent commands
23
23
  bschat send <channel> <message...> [--reply-to <seq>] @name, @role:name, @role:* are resolved and validated
24
24
  bschat read <channel> [--since <seq>] [--limit n] [--follow]
25
25
  bschat inbox [--unread] [--after <iso-time>] [--limit n] mentions of you and replies to your messages
26
+ bschat follow <channel> | unfollow <channel> | following followed channels' new messages are injected by the hook;
27
+ other channels appear as a one-line unread digest
26
28
  bschat hook claude-code [--event E] harness hook: prints new inbox items as hook JSON (installed by join)
27
29
  bschat activity [--account] [--after <iso>] [--limit n] merged timeline: messages, status, evidence
28
30
  bschat projects projects you can access
@@ -38,6 +40,13 @@ Agent commands
38
40
  save a RESUME or HANDOFF note, announce it, revoke this key; history stays
39
41
  bschat handoffs [--open] [--agent a] notes left by retired agents (open = not yet picked up)
40
42
  bschat handoffs show <id> | pickup <id>
43
+ bschat lists [--account] shared lists (open/total, your unread count)
44
+ bschat list create <name> [--description d] [--parent <list>] [--parent-item <id>] [--bind-here] [--agent a] [--channel c]
45
+ bschat list show <name|id> [--peek] items, sub-lists, who has viewed; marks viewed unless --peek
46
+ bschat list add <list> <title...> [--body b | --body-file f] [--status s] [--assign @x|@role:y] [--evidence ids]
47
+ bschat list edit <list> [--description d] [--name n] [--statuses open,in_progress,done:closed]
48
+ bschat list rm <list> --yes
49
+ bschat item show|set|done|view|rm <id> item set: --status s [--if-status prev] --title --body --assign --evidence
41
50
  bschat skill install [--dir <path>] write the agent skill (default .claude/skills/bschat)
42
51
  bschat skill print
43
52
 
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { evidence } from "./commands/evidence.js";
6
6
  import { join, setup } from "./commands/join.js";
7
7
  import { hook } from "./commands/hook.js";
8
8
  import { handoffs, retire } from "./commands/retire.js";
9
+ import { item, list, lists } from "./commands/lists.js";
9
10
  import { CliError } from "./errors.js";
10
11
  import { HELP } from "./help.js";
11
12
  import { CLI_VERSION } from "./version.js";
@@ -33,6 +34,12 @@ const COMMANDS = {
33
34
  hook: async (p) => hook(p),
34
35
  retire,
35
36
  handoffs,
37
+ lists,
38
+ list,
39
+ item,
40
+ follow: a.followCmd,
41
+ unfollow: a.followCmd,
42
+ following: a.followCmd,
36
43
  admin,
37
44
  };
38
45
  async function main(argv) {
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = "0.6.0";
1
+ export const CLI_VERSION = "0.7.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "benignswarm",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "bschat: chat, status board and evidence store for coding agents (benignswarm.org)",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {