@commonlyai/cli 0.1.219 → 0.1.220

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonlyai/cli",
3
- "version": "0.1.219",
3
+ "version": "0.1.220",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The Commonly CLI — connect agents, manage pods, iterate fast",
6
6
  "type": "module",
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { createClient } from '../lib/api.js';
10
10
  import { getToken, resolveInstanceUrl } from '../lib/config.js';
11
+ import { createTailPoller } from '../lib/pod-tail.js';
11
12
 
12
13
  export const registerPod = (program) => {
13
14
  const pod = program.command('pod').description('Manage pods');
@@ -96,28 +97,16 @@ Commonly instance (see: commonly login --help).
96
97
 
97
98
  const client = createClient({ instance: resolveInstanceUrl(opts.instance), token });
98
99
 
99
- let lastId = null;
100
100
  console.log(`Watching pod ${podId}... (Ctrl+C to stop)\n`);
101
101
 
102
+ const pollNew = createTailPoller({
103
+ fetchPage: (limit) => client.get(`/api/messages/${podId}`, { limit }),
104
+ filter: opts.filter,
105
+ print: (line) => console.log(line),
106
+ });
102
107
  const poll = async () => {
103
108
  try {
104
- const data = await client.get(`/api/messages/${podId}`, {
105
- limit: 20,
106
- after: lastId,
107
- });
108
- const messages = Array.isArray(data) ? data : data.messages || [];
109
-
110
- messages.forEach((m) => {
111
- const isBot = m.isBot || m.sender?.isBot;
112
- if (opts.filter === 'agents' && !isBot) return;
113
- if (opts.filter === 'humans' && isBot) return;
114
-
115
- const ts = new Date(m.createdAt || m.timestamp).toTimeString().slice(0, 8);
116
- const who = m.username || m.sender?.username || '?';
117
- const marker = isBot ? '🤖' : '👤';
118
- console.log(`[${ts}] ${marker} ${who}: ${m.content}`);
119
- lastId = m._id || m.id;
120
- });
109
+ await pollNew();
121
110
  } catch (err) {
122
111
  console.error(`[poll error] ${err.message}`);
123
112
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Live pod tail for `commonly pod tail`.
3
+ *
4
+ * GET /api/messages/:podId takes `before` and `limit` only; it ignores `after`
5
+ * and returns the newest `limit` rows. Polling it with a cursor therefore
6
+ * re-serves the same window every tick. The poller keeps the ids it has
7
+ * already printed and prints only the rest, in the order the page returns
8
+ * them (oldest first).
9
+ */
10
+
11
+ // Ids kept for de-duplication. Old entries fall out first, so a long tail
12
+ // stays bounded in memory.
13
+ export const TAIL_SEEN_MAX = 1000;
14
+ export const TAIL_PAGE_LIMIT = 100;
15
+
16
+ // Same bot field the v2 chat reads (frontend/src/v2/utils/threadView.ts
17
+ // participantOf): `user.isBot`, falling back to the mapped `userId.isBot`.
18
+ export const isAgentMessage = (m) => {
19
+ if (typeof m?.user?.isBot === 'boolean') return m.user.isBot;
20
+ return m?.userId?.isBot === true;
21
+ };
22
+
23
+ const authorName = (m) => m?.user?.username || m?.userId?.username || m?.username || '?';
24
+
25
+ const clock = (m) => {
26
+ const raw = m?.createdAt || m?.timestamp;
27
+ const date = raw ? new Date(raw) : null;
28
+ return date && !Number.isNaN(date.getTime()) ? date.toTimeString().slice(0, 8) : '--:--:--';
29
+ };
30
+
31
+ export const formatTailLine = (m) => {
32
+ const marker = isAgentMessage(m) ? '🤖' : '👤';
33
+ return `[${clock(m)}] ${marker} ${authorName(m)}: ${m?.content ?? ''}`;
34
+ };
35
+
36
+ /**
37
+ * @param {object} opts
38
+ * @param {(limit: number) => Promise<object[] | {messages: object[]}>} opts.fetchPage
39
+ * @param {'all'|'agents'|'humans'} [opts.filter='all']
40
+ * @param {(line: string) => void} opts.print
41
+ */
42
+ export const createTailPoller = ({ fetchPage, filter = 'all', print }) => {
43
+ const seen = new Set();
44
+ const remember = (id) => {
45
+ seen.add(id);
46
+ if (seen.size > TAIL_SEEN_MAX) seen.delete(seen.values().next().value);
47
+ };
48
+
49
+ return async () => {
50
+ const data = await fetchPage(TAIL_PAGE_LIMIT);
51
+ const messages = Array.isArray(data) ? data : data?.messages || [];
52
+ let printed = 0;
53
+
54
+ messages.forEach((m) => {
55
+ const id = String(m?._id || m?.id || '');
56
+ if (id) {
57
+ if (seen.has(id)) return;
58
+ remember(id);
59
+ }
60
+ if (filter === 'agents' && !isAgentMessage(m)) return;
61
+ if (filter === 'humans' && isAgentMessage(m)) return;
62
+ print(formatTailLine(m));
63
+ printed += 1;
64
+ });
65
+
66
+ return printed;
67
+ };
68
+ };