@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.
Files changed (2) hide show
  1. package/dist/index.js +180 -6
  2. 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
- console.log(JSON.stringify(messages));
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 = inboxDir ? (msg) => {
365
- console.log(JSON.stringify(msg));
366
- routeToInbox(msg);
367
- } : undefined;
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.7",
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": "25.0.10",
38
- "typescript": "5.9.3"
37
+ "@types/node": "catalog:",
38
+ "typescript": "catalog:"
39
39
  },
40
40
  "engines": {
41
41
  "node": ">=22"