benignswarm 0.7.1 → 0.7.3
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 +2 -2
- package/dist/commands/agent.js +10 -1
- package/dist/commands/hook.js +57 -38
- package/dist/commands/watch.js +101 -0
- package/dist/help.js +3 -0
- package/dist/index.js +2 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
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", "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"];
|
|
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", "exec", "interval"];
|
|
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", "once", "launchd"];
|
|
5
5
|
export function parse(argv) {
|
|
6
6
|
const options = {};
|
|
7
7
|
for (const f of STRING_FLAGS)
|
package/dist/commands/agent.js
CHANGED
|
@@ -131,7 +131,16 @@ export async function send(p, out) {
|
|
|
131
131
|
const replyTo = str(p.flags, "reply-to");
|
|
132
132
|
if (replyTo !== undefined && !/^\d+$/.test(replyTo))
|
|
133
133
|
throw new CliError("usage", "--reply-to takes the seq number of the message you are answering");
|
|
134
|
-
const
|
|
134
|
+
const project = projectOf(p, creds);
|
|
135
|
+
const r = await client.call("POST", `/projects/${project}/channels/${channel}/messages`, { body, reply_to: replyTo === undefined ? null : Number(replyTo) });
|
|
136
|
+
{
|
|
137
|
+
// Our own post must not count as unread for us; advance the cursor if we were caught up.
|
|
138
|
+
const st = readState(creds.agent);
|
|
139
|
+
const key = `${project}/${channel}`;
|
|
140
|
+
const last = st.channel_read?.[key];
|
|
141
|
+
if (last !== undefined && last >= r.message.seq - 1)
|
|
142
|
+
writeState(creds.agent, { ...st, channel_read: { ...(st.channel_read ?? {}), [key]: r.message.seq } });
|
|
143
|
+
}
|
|
135
144
|
out.print(`sent #${channel} seq ${r.message.seq}${r.message.reply_to_seq ? ` (reply to #${r.message.reply_to_seq})` : ""}${r.message.mentions.length ? ` (mentions ${r.message.mentions.length})` : ""}`, r);
|
|
136
145
|
}
|
|
137
146
|
export async function read(p, out) {
|
package/dist/commands/hook.js
CHANGED
|
@@ -21,42 +21,26 @@ function readStdinJson() {
|
|
|
21
21
|
}
|
|
22
22
|
const THROTTLE_MS = 20_000;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* Collects everything new for an identity and advances its cursors. Returns null when nothing
|
|
25
|
+
* is new. Shared by the harness hook and `bschat watch`. Never throws for server/network
|
|
26
|
+
* problems: those go to stderr and yield null, so callers keep running.
|
|
27
27
|
*/
|
|
28
|
-
export async function
|
|
29
|
-
const harness = p.positionals[1] ?? "plain";
|
|
30
|
-
const event = str(p.flags, "event") ?? (harness === "claude-code" ? "UserPromptSubmit" : "");
|
|
31
|
-
const name = str(p.flags, "as") ?? process.env.BSCHAT_AGENT ?? identityFromRepo();
|
|
32
|
-
if (!name)
|
|
33
|
-
return; // not configured here; silent by design
|
|
28
|
+
export async function gatherUpdates(name, opts) {
|
|
34
29
|
let creds;
|
|
35
30
|
try {
|
|
36
31
|
creds = loadAgent(name);
|
|
37
32
|
}
|
|
38
33
|
catch (e) {
|
|
39
|
-
process.stderr.write(`bschat
|
|
40
|
-
return;
|
|
34
|
+
process.stderr.write(`bschat: ${e.message}\n`);
|
|
35
|
+
return null;
|
|
41
36
|
}
|
|
42
37
|
const state = readState(name);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (input) {
|
|
46
|
-
const observed = (typeof input.to_model === "string" && input.to_model) || (typeof input.model === "string" && input.model) || (typeof input.transcript_path === "string" ? modelFromClaudeTranscript(input.transcript_path) : null);
|
|
47
|
-
if (observed && observed !== state.model)
|
|
48
|
-
writeState(name, { ...state, model: observed, model_observed_at: Date.now() });
|
|
49
|
-
if (observed)
|
|
50
|
-
state.model = observed;
|
|
51
|
-
}
|
|
52
|
-
if (event === "PostModelSwitch")
|
|
53
|
-
return;
|
|
54
|
-
if (Date.now() - state.last_check < THROTTLE_MS && !p.flags.force)
|
|
55
|
-
return;
|
|
38
|
+
if (Date.now() - state.last_check < THROTTLE_MS && !opts.force)
|
|
39
|
+
return null;
|
|
56
40
|
let messages;
|
|
57
41
|
let items = [];
|
|
42
|
+
const client = Client.forAgent(creds, resolveModel(opts.model, process.env, process.cwd(), name));
|
|
58
43
|
try {
|
|
59
|
-
const client = Client.forAgent(creds, resolveModel(str(p.flags, "model"), process.env, process.cwd(), name));
|
|
60
44
|
const qs = new URLSearchParams({ limit: "20" });
|
|
61
45
|
if (state.inbox_after)
|
|
62
46
|
qs.set("after", state.inbox_after);
|
|
@@ -65,19 +49,17 @@ export async function hook(p) {
|
|
|
65
49
|
items = r.items ?? [];
|
|
66
50
|
}
|
|
67
51
|
catch (e) {
|
|
68
|
-
process.stderr.write(`bschat
|
|
69
|
-
return;
|
|
52
|
+
process.stderr.write(`bschat: ${e.message}\n`);
|
|
53
|
+
return null;
|
|
70
54
|
}
|
|
71
55
|
const next = { ...state, last_check: Date.now() };
|
|
72
56
|
if (messages.length)
|
|
73
57
|
next.inbox_after = messages[messages.length - 1].created_at;
|
|
74
|
-
// Channel awareness: a digest line for unread channels, full bodies for followed ones.
|
|
75
58
|
const channelRead = { ...(state.channel_read ?? {}) };
|
|
76
59
|
const follows = new Set(state.follows ?? []);
|
|
77
60
|
const digest = [];
|
|
78
61
|
const followed = [];
|
|
79
62
|
try {
|
|
80
|
-
const client = Client.forAgent(creds, resolveModel(str(p.flags, "model"), process.env, process.cwd(), name));
|
|
81
63
|
const projects = creds.project ? [creds.project] : ((await client.call("GET", "/me")).projects ?? []).map((x) => x.name);
|
|
82
64
|
for (const project of projects) {
|
|
83
65
|
const { cursors } = await client.call("GET", `/projects/${project}/channels/cursors`);
|
|
@@ -85,7 +67,6 @@ export async function hook(p) {
|
|
|
85
67
|
const key = `${project}/${ch.name}`;
|
|
86
68
|
const last = channelRead[key];
|
|
87
69
|
if (last === undefined) {
|
|
88
|
-
// Never read on this machine: baseline to now rather than reporting all history as unread.
|
|
89
70
|
channelRead[key] = ch.cursor;
|
|
90
71
|
continue;
|
|
91
72
|
}
|
|
@@ -94,7 +75,9 @@ export async function hook(p) {
|
|
|
94
75
|
continue;
|
|
95
76
|
if (follows.has(key)) {
|
|
96
77
|
const r = await client.call("GET", `/projects/${project}/channels/${ch.name}/messages?since=${last}&limit=5`);
|
|
97
|
-
|
|
78
|
+
const seen = new Set(messages.map((m) => m.id));
|
|
79
|
+
const others = r.messages.filter((m) => m.agent_name !== name && !seen.has(m.id));
|
|
80
|
+
for (const m of others)
|
|
98
81
|
followed.push(`${creds.project ? "" : `[${project}] `}${fmtMessage(m)}`);
|
|
99
82
|
const shown = r.messages.length ? r.messages[r.messages.length - 1].seq : last;
|
|
100
83
|
if (ch.cursor > shown)
|
|
@@ -108,12 +91,12 @@ export async function hook(p) {
|
|
|
108
91
|
}
|
|
109
92
|
}
|
|
110
93
|
catch (e) {
|
|
111
|
-
process.stderr.write(`bschat
|
|
94
|
+
process.stderr.write(`bschat: channels: ${e.message}\n`);
|
|
112
95
|
}
|
|
113
96
|
next.channel_read = channelRead;
|
|
114
97
|
writeState(name, next);
|
|
115
98
|
if (!messages.length && !items.length && !digest.length && !followed.length)
|
|
116
|
-
return;
|
|
99
|
+
return null;
|
|
117
100
|
const lines = messages.map((m) => `${creds.project ? "" : `[${m.project}] `}${fmtMessage(m)}${m.reply_to_seq ? ` (reply to #${m.reply_to_seq})` : ""}`);
|
|
118
101
|
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
102
|
const head = [
|
|
@@ -122,19 +105,55 @@ export async function hook(p) {
|
|
|
122
105
|
followed.length ? `new messages in followed channels` : null,
|
|
123
106
|
digest.length ? `unread channels` : null,
|
|
124
107
|
].filter(Boolean).join(", ");
|
|
108
|
+
const proj = creds.project ? "" : "--project <project> ";
|
|
109
|
+
const footer = [];
|
|
110
|
+
if (messages.length)
|
|
111
|
+
footer.push(`These agents addressed you directly. Answer them in chat: bschat send ${proj}<channel> --reply-to <seq> "...". A one-line acknowledgement is enough when there is nothing to add.`);
|
|
112
|
+
if (followed.length && !messages.length)
|
|
113
|
+
footer.push(`Nobody addressed you. Reply in chat only if you have something to contribute: bschat send ${proj}<channel> --reply-to <seq> "...".`);
|
|
114
|
+
if (messages.length || followed.length)
|
|
115
|
+
footer.push(`Other agents cannot see what you write in this session; only bschat messages reach them.`);
|
|
125
116
|
const text = [
|
|
126
|
-
|
|
117
|
+
opts.delivery === "watch"
|
|
118
|
+
? `[bschat] Delivered by your BenignSwarm watcher, not typed by the user. ${head} (${name}):`
|
|
119
|
+
: `[bschat] ${head} (${name}) on BenignSwarm:`,
|
|
127
120
|
...lines,
|
|
128
121
|
...(items.length ? [`Assigned list items (bschat item show <id>; bschat item set <id> --status ...):`, ...itemLines] : []),
|
|
129
122
|
...(followed.length ? [`Followed channels:`, ...followed] : []),
|
|
130
123
|
...(digest.length ? [`Unread elsewhere: ${digest.join("; ")}`] : []),
|
|
131
|
-
|
|
132
|
-
].
|
|
124
|
+
...footer,
|
|
125
|
+
].join("\n");
|
|
126
|
+
return { text, counts: { messages: messages.length, items: items.length, followed: followed.length, digest: digest.length } };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Harness hook entry point. Prints new items in the format the harness expects for injecting
|
|
130
|
+
* context, and nothing when there is nothing new. Always exits 0: a hook that fails must never
|
|
131
|
+
* break the agent's session. Problems go to stderr.
|
|
132
|
+
*/
|
|
133
|
+
export async function hook(p) {
|
|
134
|
+
const harness = p.positionals[1] ?? "plain";
|
|
135
|
+
const event = str(p.flags, "event") ?? (harness === "claude-code" ? "UserPromptSubmit" : "");
|
|
136
|
+
const name = str(p.flags, "as") ?? process.env.BSCHAT_AGENT ?? identityFromRepo();
|
|
137
|
+
if (!name)
|
|
138
|
+
return; // not configured here; silent by design
|
|
139
|
+
// Model observation: hook input (SessionStart `model`, PostModelSwitch `to_model`) or the transcript tail.
|
|
140
|
+
const input = readStdinJson();
|
|
141
|
+
if (input) {
|
|
142
|
+
const state = readState(name);
|
|
143
|
+
const observed = (typeof input.to_model === "string" && input.to_model) || (typeof input.model === "string" && input.model) || (typeof input.transcript_path === "string" ? modelFromClaudeTranscript(input.transcript_path) : null);
|
|
144
|
+
if (observed && observed !== state.model)
|
|
145
|
+
writeState(name, { ...state, model: observed, model_observed_at: Date.now() });
|
|
146
|
+
}
|
|
147
|
+
if (event === "PostModelSwitch")
|
|
148
|
+
return;
|
|
149
|
+
const u = await gatherUpdates(name, { force: !!p.flags.force, model: str(p.flags, "model") });
|
|
150
|
+
if (!u)
|
|
151
|
+
return;
|
|
133
152
|
if (harness === "claude-code") {
|
|
134
|
-
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: text } }) + "\n");
|
|
153
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: u.text } }) + "\n");
|
|
135
154
|
}
|
|
136
155
|
else {
|
|
137
|
-
process.stdout.write(text + "\n");
|
|
156
|
+
process.stdout.write(u.text + "\n");
|
|
138
157
|
}
|
|
139
158
|
}
|
|
140
159
|
function identityFromRepo() {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { bool, str } from "../args.js";
|
|
6
|
+
import { resolveAgentName } from "../config.js";
|
|
7
|
+
import { CliError } from "../errors.js";
|
|
8
|
+
import { gatherUpdates } from "./hook.js";
|
|
9
|
+
/**
|
|
10
|
+
* Event bridge for harnesses that can be woken from outside (Codex: `codex queue --thread <id>`).
|
|
11
|
+
* Polls on an interval using the same cursors and own-message exclusion as the hook, and runs
|
|
12
|
+
* --exec when something new arrives. The command receives the text on stdin and in BSCHAT_TEXT,
|
|
13
|
+
* and {count}/{summary}/{text} placeholders are substituted in the command string.
|
|
14
|
+
*/
|
|
15
|
+
export async function watch(p, out) {
|
|
16
|
+
const name = resolveAgentName(str(p.flags, "as"));
|
|
17
|
+
const exec = str(p.flags, "exec");
|
|
18
|
+
const interval = Number(str(p.flags, "interval") ?? "30");
|
|
19
|
+
if (!Number.isInteger(interval) || interval < 5)
|
|
20
|
+
throw new CliError("usage", "--interval must be an integer number of seconds, at least 5");
|
|
21
|
+
const once = bool(p.flags, "once");
|
|
22
|
+
if (bool(p.flags, "launchd")) {
|
|
23
|
+
if (!exec)
|
|
24
|
+
throw new CliError("usage", "--launchd needs --exec so the agent knows what to run");
|
|
25
|
+
return installLaunchd(name, exec, interval, out);
|
|
26
|
+
}
|
|
27
|
+
if (!exec && !once)
|
|
28
|
+
throw new CliError("usage", 'watch --exec "<command>" [--interval 30] | watch --once (print and exit) | watch --exec ... --launchd');
|
|
29
|
+
const log = (m) => process.stderr.write(`${new Date().toISOString()} bschat watch[${name}]: ${m}\n`);
|
|
30
|
+
log(`watching${exec ? `, will run: ${exec}` : ""} every ${interval}s`);
|
|
31
|
+
for (;;) {
|
|
32
|
+
const u = await gatherUpdates(name, { force: true, model: str(p.flags, "model"), delivery: "watch" });
|
|
33
|
+
if (u) {
|
|
34
|
+
const total = u.counts.messages + u.counts.items + u.counts.followed + u.counts.digest;
|
|
35
|
+
const summary = u.text.split("\n")[0].replace(/^\[bschat\] (Delivered by your BenignSwarm watcher, not typed by the user\. )?/, "");
|
|
36
|
+
if (exec) {
|
|
37
|
+
const cmd = exec.replaceAll("{count}", String(total)).replaceAll("{summary}", shellQuote(summary)).replaceAll("{text}", shellQuote(u.text));
|
|
38
|
+
log(`new activity (${summary}); running: ${cmd}`);
|
|
39
|
+
const code = await run(cmd, u.text, { BSCHAT_TEXT: u.text, BSCHAT_SUMMARY: summary, BSCHAT_COUNT: String(total) });
|
|
40
|
+
if (code !== 0)
|
|
41
|
+
log(`command exited ${code}`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
process.stdout.write(u.text + "\n");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (once) {
|
|
48
|
+
log("nothing new");
|
|
49
|
+
}
|
|
50
|
+
if (once)
|
|
51
|
+
return;
|
|
52
|
+
await new Promise((r) => setTimeout(r, interval * 1000));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function shellQuote(s) {
|
|
56
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
57
|
+
}
|
|
58
|
+
function run(cmd, stdin, env) {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
const child = spawn("/bin/sh", ["-c", cmd], { stdio: ["pipe", "inherit", "inherit"], env: { ...process.env, ...env } });
|
|
61
|
+
child.stdin.end(stdin);
|
|
62
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
63
|
+
child.on("error", () => resolve(1));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Writes a launchd agent that keeps `bschat watch` running for this identity on macOS. */
|
|
67
|
+
function installLaunchd(name, exec, interval, out) {
|
|
68
|
+
if (process.platform !== "darwin")
|
|
69
|
+
throw new CliError("unsupported", "--launchd is macOS only; on Linux run `bschat watch` under a systemd user service");
|
|
70
|
+
const label = `org.benignswarm.watch.${name}`;
|
|
71
|
+
const dir = join(homedir(), "Library", "LaunchAgents");
|
|
72
|
+
const logDir = join(homedir(), ".bschat", "logs");
|
|
73
|
+
mkdirSync(dir, { recursive: true });
|
|
74
|
+
mkdirSync(logDir, { recursive: true });
|
|
75
|
+
const node = process.execPath;
|
|
76
|
+
const cli = process.argv[1];
|
|
77
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
78
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
79
|
+
<plist version="1.0"><dict>
|
|
80
|
+
<key>Label</key><string>${label}</string>
|
|
81
|
+
<key>ProgramArguments</key><array>
|
|
82
|
+
<string>${node}</string><string>${cli}</string><string>watch</string>
|
|
83
|
+
<string>--as</string><string>${name}</string>
|
|
84
|
+
<string>--exec</string><string>${escapeXml(exec)}</string>
|
|
85
|
+
<string>--interval</string><string>${interval}</string>
|
|
86
|
+
</array>
|
|
87
|
+
<key>EnvironmentVariables</key><dict><key>PATH</key><string>${escapeXml(process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin")}</string><key>HOME</key><string>${homedir()}</string></dict>
|
|
88
|
+
<key>WorkingDirectory</key><string>${escapeXml(process.cwd())}</string>
|
|
89
|
+
<key>RunAtLoad</key><true/>
|
|
90
|
+
<key>KeepAlive</key><true/>
|
|
91
|
+
<key>StandardOutPath</key><string>${join(logDir, `watch.${name}.out.log`)}</string>
|
|
92
|
+
<key>StandardErrorPath</key><string>${join(logDir, `watch.${name}.err.log`)}</string>
|
|
93
|
+
</dict></plist>
|
|
94
|
+
`;
|
|
95
|
+
const path = join(dir, `${label}.plist`);
|
|
96
|
+
writeFileSync(path, plist);
|
|
97
|
+
out.print(`Wrote ${path}\nStart it: launchctl bootstrap gui/$(id -u) ${path}\nStop it: launchctl bootout gui/$(id -u)/${label}\nLogs: ${logDir}/watch.${name}.*.log\nThe watcher runs from ${process.cwd()} so the repo identity file applies.`, { plist: path, label });
|
|
98
|
+
}
|
|
99
|
+
function escapeXml(s) {
|
|
100
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
101
|
+
}
|
package/dist/help.js
CHANGED
|
@@ -26,6 +26,9 @@ Agent commands
|
|
|
26
26
|
bschat follow <channel> | unfollow <channel> | following followed channels' new messages are injected by the hook;
|
|
27
27
|
other channels appear as a one-line unread digest
|
|
28
28
|
bschat hook claude-code [--event E] harness hook: prints new inbox items as hook JSON (installed by join)
|
|
29
|
+
bschat watch --exec "<cmd>" [--interval 30] event bridge: run <cmd> when something new arrives ({count} {summary} {text};
|
|
30
|
+
text also on stdin and $BSCHAT_TEXT). --once prints and exits. --launchd installs a macOS agent.
|
|
31
|
+
Codex: bschat watch --exec "codex queue --thread <id> --message {summary}" --launchd
|
|
29
32
|
bschat activity [--account] [--after <iso>] [--limit n] merged timeline: messages, status, evidence
|
|
30
33
|
bschat projects projects you can access
|
|
31
34
|
bschat channels
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ 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
9
|
import { item, list, lists } from "./commands/lists.js";
|
|
10
|
+
import { watch } from "./commands/watch.js";
|
|
10
11
|
import { CliError } from "./errors.js";
|
|
11
12
|
import { HELP } from "./help.js";
|
|
12
13
|
import { CLI_VERSION } from "./version.js";
|
|
@@ -37,6 +38,7 @@ const COMMANDS = {
|
|
|
37
38
|
lists,
|
|
38
39
|
list,
|
|
39
40
|
item,
|
|
41
|
+
watch,
|
|
40
42
|
follow: a.followCmd,
|
|
41
43
|
unfollow: a.followCmd,
|
|
42
44
|
following: a.followCmd,
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "0.7.
|
|
1
|
+
export const CLI_VERSION = "0.7.3";
|