@meet-ai/cli 0.0.7 → 0.0.9
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/index.js +180 -6
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
2
4
|
|
|
3
5
|
// src/client.ts
|
|
4
6
|
function wsLog(data) {
|
|
@@ -37,6 +39,23 @@ async function withRetry(fn, options) {
|
|
|
37
39
|
}
|
|
38
40
|
throw lastError;
|
|
39
41
|
}
|
|
42
|
+
var ATTACHMENTS_DIR = "/tmp/meet-ai-attachments";
|
|
43
|
+
var MAX_AGE_MS = 5 * 60 * 1000;
|
|
44
|
+
function cleanupOldAttachments() {
|
|
45
|
+
try {
|
|
46
|
+
const { readdirSync, statSync, unlinkSync } = __require("fs");
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
for (const entry of readdirSync(ATTACHMENTS_DIR)) {
|
|
49
|
+
try {
|
|
50
|
+
const filePath = `${ATTACHMENTS_DIR}/${entry}`;
|
|
51
|
+
const mtime = statSync(filePath).mtimeMs;
|
|
52
|
+
if (now - mtime > MAX_AGE_MS) {
|
|
53
|
+
unlinkSync(filePath);
|
|
54
|
+
}
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
} catch {}
|
|
58
|
+
}
|
|
40
59
|
function createClient(baseUrl, apiKey) {
|
|
41
60
|
function headers(extra) {
|
|
42
61
|
const h = { "Content-Type": "application/json", ...extra };
|
|
@@ -185,6 +204,20 @@ function createClient(baseUrl, apiKey) {
|
|
|
185
204
|
}
|
|
186
205
|
return connect();
|
|
187
206
|
},
|
|
207
|
+
async sendLog(roomId, sender, content, color, messageId) {
|
|
208
|
+
return withRetry(async () => {
|
|
209
|
+
const res = await fetch(`${baseUrl}/api/rooms/${roomId}/logs`, {
|
|
210
|
+
method: "POST",
|
|
211
|
+
headers: headers(),
|
|
212
|
+
body: JSON.stringify({ sender, content, ...color && { color }, ...messageId && { message_id: messageId } })
|
|
213
|
+
});
|
|
214
|
+
if (!res.ok) {
|
|
215
|
+
const err = await res.json().catch(() => ({}));
|
|
216
|
+
throw new Error(err.error ?? `HTTP ${res.status}`);
|
|
217
|
+
}
|
|
218
|
+
return res.json();
|
|
219
|
+
});
|
|
220
|
+
},
|
|
188
221
|
async sendTeamInfo(roomId, payload) {
|
|
189
222
|
return withRetry(async () => {
|
|
190
223
|
const res = await fetch(`${baseUrl}/api/rooms/${roomId}/team-info`, {
|
|
@@ -199,6 +232,45 @@ function createClient(baseUrl, apiKey) {
|
|
|
199
232
|
return res.text();
|
|
200
233
|
});
|
|
201
234
|
},
|
|
235
|
+
async sendTasks(roomId, payload) {
|
|
236
|
+
return withRetry(async () => {
|
|
237
|
+
const res = await fetch(`${baseUrl}/api/rooms/${roomId}/tasks`, {
|
|
238
|
+
method: "POST",
|
|
239
|
+
headers: headers(),
|
|
240
|
+
body: payload
|
|
241
|
+
});
|
|
242
|
+
if (!res.ok) {
|
|
243
|
+
const err = await res.json().catch(() => ({}));
|
|
244
|
+
throw new Error(err.error ?? `HTTP ${res.status}`);
|
|
245
|
+
}
|
|
246
|
+
return res.text();
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
async getMessageAttachments(roomId, messageId) {
|
|
250
|
+
const res = await fetch(`${baseUrl}/api/rooms/${roomId}/messages/${messageId}/attachments`, { headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined });
|
|
251
|
+
if (!res.ok) {
|
|
252
|
+
const err = await res.json().catch(() => ({}));
|
|
253
|
+
throw new Error(err.error ?? `HTTP ${res.status}`);
|
|
254
|
+
}
|
|
255
|
+
return res.json();
|
|
256
|
+
},
|
|
257
|
+
async downloadAttachment(attachmentId, filename) {
|
|
258
|
+
cleanupOldAttachments();
|
|
259
|
+
const res = await fetch(`${baseUrl}/api/attachments/${attachmentId}`, {
|
|
260
|
+
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined
|
|
261
|
+
});
|
|
262
|
+
if (!res.ok) {
|
|
263
|
+
const err = await res.json().catch(() => ({}));
|
|
264
|
+
throw new Error(err.error ?? `HTTP ${res.status}`);
|
|
265
|
+
}
|
|
266
|
+
const { mkdirSync, writeFileSync } = await import("fs");
|
|
267
|
+
const dir = "/tmp/meet-ai-attachments";
|
|
268
|
+
mkdirSync(dir, { recursive: true });
|
|
269
|
+
const localPath = `${dir}/${attachmentId}-${filename}`;
|
|
270
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
271
|
+
writeFileSync(localPath, buffer);
|
|
272
|
+
return localPath;
|
|
273
|
+
},
|
|
202
274
|
async generateKey() {
|
|
203
275
|
const res = await fetch(`${baseUrl}/api/keys`, {
|
|
204
276
|
method: "POST",
|
|
@@ -271,6 +343,25 @@ var API_URL = process.env.MEET_AI_URL || "https://meet-ai.cc";
|
|
|
271
343
|
var API_KEY = process.env.MEET_AI_KEY;
|
|
272
344
|
var client = createClient(API_URL, API_KEY);
|
|
273
345
|
var [command, ...args] = process.argv.slice(2);
|
|
346
|
+
async function downloadMessageAttachments(roomId, messageId) {
|
|
347
|
+
try {
|
|
348
|
+
const attachments = await client.getMessageAttachments(roomId, messageId);
|
|
349
|
+
if (!attachments.length)
|
|
350
|
+
return [];
|
|
351
|
+
const paths = [];
|
|
352
|
+
for (const att of attachments) {
|
|
353
|
+
try {
|
|
354
|
+
const localPath = await client.downloadAttachment(att.id, att.filename);
|
|
355
|
+
paths.push(localPath);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
console.error(JSON.stringify({ event: "attachment_download_error", attachmentId: att.id, error: err instanceof Error ? err.message : String(err) }));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return paths;
|
|
361
|
+
} catch {
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
}
|
|
274
365
|
function parseFlags(args2) {
|
|
275
366
|
const positional = [];
|
|
276
367
|
const flags = {};
|
|
@@ -320,11 +411,15 @@ switch (command) {
|
|
|
320
411
|
exclude: flags.exclude,
|
|
321
412
|
senderType: flags["sender-type"]
|
|
322
413
|
});
|
|
323
|
-
|
|
414
|
+
const enriched = await Promise.all(messages.map(async (msg) => {
|
|
415
|
+
const paths = await downloadMessageAttachments(roomId, msg.id);
|
|
416
|
+
return paths.length ? { ...msg, attachments: paths } : msg;
|
|
417
|
+
}));
|
|
418
|
+
console.log(JSON.stringify(enriched));
|
|
324
419
|
break;
|
|
325
420
|
}
|
|
326
421
|
case "listen": {
|
|
327
|
-
let routeToInbox = function(msg) {
|
|
422
|
+
let routeToInbox = function(msg, attachmentPaths) {
|
|
328
423
|
if (!inboxDir)
|
|
329
424
|
return;
|
|
330
425
|
const entry = {
|
|
@@ -333,6 +428,9 @@ switch (command) {
|
|
|
333
428
|
timestamp: new Date().toISOString(),
|
|
334
429
|
read: false
|
|
335
430
|
};
|
|
431
|
+
if (attachmentPaths?.length) {
|
|
432
|
+
entry.attachments = attachmentPaths;
|
|
433
|
+
}
|
|
336
434
|
const members = teamDir ? getTeamMembers(teamDir) : new Set;
|
|
337
435
|
const targets = resolveInboxTargets(msg.content, members);
|
|
338
436
|
if (targets) {
|
|
@@ -361,10 +459,20 @@ switch (command) {
|
|
|
361
459
|
const inboxDir = team ? `${process.env.HOME}/.claude/teams/${team}/inboxes` : null;
|
|
362
460
|
const defaultInboxPath = inboxDir && inbox ? `${inboxDir}/${inbox}.json` : null;
|
|
363
461
|
const teamDir = team ? `${process.env.HOME}/.claude/teams/${team}` : null;
|
|
364
|
-
const onMessage =
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
462
|
+
const onMessage = (msg) => {
|
|
463
|
+
if (msg.id && msg.room_id && msg.attachment_count > 0) {
|
|
464
|
+
downloadMessageAttachments(msg.room_id, msg.id).then((paths) => {
|
|
465
|
+
const output = paths.length ? { ...msg, attachments: paths } : msg;
|
|
466
|
+
console.log(JSON.stringify(output));
|
|
467
|
+
if (inboxDir)
|
|
468
|
+
routeToInbox(msg, paths);
|
|
469
|
+
});
|
|
470
|
+
} else {
|
|
471
|
+
console.log(JSON.stringify(msg));
|
|
472
|
+
if (inboxDir)
|
|
473
|
+
routeToInbox(msg);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
368
476
|
const ws = client.listen(roomId, { exclude: flags.exclude, senderType: flags["sender-type"], onMessage });
|
|
369
477
|
let idleCheckTimeout = null;
|
|
370
478
|
const idleNotified = new Set;
|
|
@@ -393,6 +501,19 @@ switch (command) {
|
|
|
393
501
|
process.on("SIGTERM", shutdown);
|
|
394
502
|
break;
|
|
395
503
|
}
|
|
504
|
+
case "send-log": {
|
|
505
|
+
const { positional: slPos, flags: slFlags } = parseFlags(args);
|
|
506
|
+
const [slRoomId, slSender, ...slRest] = slPos;
|
|
507
|
+
const slContent = slRest.join(" ").replace(/\\n/g, `
|
|
508
|
+
`);
|
|
509
|
+
if (!slRoomId || !slSender || !slContent) {
|
|
510
|
+
console.error("Usage: cli send-log <roomId> <sender> <content> [--color <color>] [--message-id <id>]");
|
|
511
|
+
process.exit(1);
|
|
512
|
+
}
|
|
513
|
+
const log = await client.sendLog(slRoomId, slSender, slContent, slFlags.color, slFlags["message-id"]);
|
|
514
|
+
console.log(`Log sent: ${log.id}`);
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
396
517
|
case "send-team-info": {
|
|
397
518
|
const [tiRoomId, tiPayload] = args;
|
|
398
519
|
if (!tiRoomId || !tiPayload) {
|
|
@@ -409,6 +530,54 @@ switch (command) {
|
|
|
409
530
|
console.log("Team info sent");
|
|
410
531
|
break;
|
|
411
532
|
}
|
|
533
|
+
case "send-tasks": {
|
|
534
|
+
const [stRoomId, stPayload] = args;
|
|
535
|
+
if (!stRoomId || !stPayload) {
|
|
536
|
+
console.error("Usage: cli send-tasks <roomId> '<json-payload>'");
|
|
537
|
+
process.exit(1);
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
JSON.parse(stPayload);
|
|
541
|
+
} catch {
|
|
542
|
+
console.error("Error: payload must be valid JSON");
|
|
543
|
+
process.exit(1);
|
|
544
|
+
}
|
|
545
|
+
await client.sendTasks(stRoomId, stPayload);
|
|
546
|
+
console.log("Tasks info sent");
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
case "download-attachment": {
|
|
550
|
+
const attachmentId = args[0];
|
|
551
|
+
if (!attachmentId) {
|
|
552
|
+
console.error("Usage: cli download-attachment <attachmentId>");
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
cleanupOldAttachments();
|
|
557
|
+
const res = await fetch(`${API_URL}/api/attachments/${attachmentId}`, {
|
|
558
|
+
headers: API_KEY ? { Authorization: `Bearer ${API_KEY}` } : undefined
|
|
559
|
+
});
|
|
560
|
+
if (!res.ok) {
|
|
561
|
+
const err = await res.json().catch(() => ({}));
|
|
562
|
+
console.error(err.error ?? `HTTP ${res.status}`);
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
const disposition = res.headers.get("Content-Disposition") || "";
|
|
566
|
+
const filenameMatch = disposition.match(/filename="(.+?)"/);
|
|
567
|
+
const filename = filenameMatch?.[1] || attachmentId;
|
|
568
|
+
const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
|
|
569
|
+
const dir = "/tmp/meet-ai-attachments";
|
|
570
|
+
mkdirSync2(dir, { recursive: true });
|
|
571
|
+
const localPath = `${dir}/${attachmentId}-${filename}`;
|
|
572
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
573
|
+
writeFileSync2(localPath, buffer);
|
|
574
|
+
console.log(localPath);
|
|
575
|
+
} catch (err) {
|
|
576
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
577
|
+
process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
412
581
|
case "generate-key": {
|
|
413
582
|
const result = await client.generateKey();
|
|
414
583
|
console.log(`API Key: ${result.key}`);
|
|
@@ -426,6 +595,9 @@ Commands:
|
|
|
426
595
|
create-room <name> Create a new chat room
|
|
427
596
|
send-message <roomId> <sender> <content> Send a message to a room
|
|
428
597
|
--color <color> Set sender name color (e.g. #ff0000, red)
|
|
598
|
+
send-log <roomId> <sender> <content> Send a log entry to a room
|
|
599
|
+
--color <color> Set sender name color (e.g. #ff0000, red)
|
|
600
|
+
--message-id <id> Associate log with a parent message
|
|
429
601
|
poll <roomId> [options] Fetch messages from a room
|
|
430
602
|
--after <id> Only messages after this ID
|
|
431
603
|
--exclude <sender> Exclude messages from sender
|
|
@@ -435,6 +607,8 @@ Commands:
|
|
|
435
607
|
--sender-type <type> Filter by sender_type (human|agent)
|
|
436
608
|
--team <name> Write to Claude Code team inbox
|
|
437
609
|
--inbox <agent> Target agent inbox (requires --team)
|
|
610
|
+
download-attachment <attachmentId> Download an attachment to /tmp
|
|
438
611
|
send-team-info <roomId> '<json>' Send team info to a room
|
|
612
|
+
send-tasks <roomId> '<json>' Send tasks info to a room
|
|
439
613
|
generate-key Generate a new API key`);
|
|
440
614
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meet-ai/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "CLI for meet-ai chat rooms — create rooms, send messages, and stream via WebSocket",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chat",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"typecheck": "tsc --noEmit"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@types/node": "
|
|
38
|
-
"typescript": "
|
|
37
|
+
"@types/node": "catalog:",
|
|
38
|
+
"typescript": "catalog:"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=22"
|