@meet-ai/cli 0.0.8 → 0.0.10

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 +251 -24
  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) {
@@ -17,26 +19,43 @@ async function withRetry(fn, options) {
17
19
  const maxRetries = options?.maxRetries ?? 3;
18
20
  const baseDelay = options?.baseDelay ?? 1000;
19
21
  const shouldRetry = options?.shouldRetry ?? isRetryable;
20
- let lastError;
22
+ let lastError = new Error("withRetry: no attempts made");
21
23
  for (let attempt = 0;attempt <= maxRetries; attempt++) {
22
24
  try {
23
25
  return await fn();
24
- } catch (err) {
25
- lastError = err;
26
- if (attempt >= maxRetries || !shouldRetry(err))
27
- throw err;
26
+ } catch (error) {
27
+ lastError = error instanceof Error ? error : new Error(String(error));
28
+ if (attempt >= maxRetries || !shouldRetry(error))
29
+ throw lastError;
28
30
  const delay = baseDelay * 2 ** attempt;
29
31
  console.error(JSON.stringify({
30
32
  event: "retry",
31
33
  attempt: attempt + 1,
32
34
  delay_ms: delay,
33
- error: err instanceof Error ? err.message : String(err)
35
+ error: lastError.message
34
36
  }));
35
- await new Promise((r) => setTimeout(r, delay));
37
+ await new Promise((resolve) => setTimeout(resolve, delay));
36
38
  }
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("node: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 };
@@ -213,6 +232,45 @@ function createClient(baseUrl, apiKey) {
213
232
  return res.text();
214
233
  });
215
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("node: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
+ },
216
274
  async generateKey() {
217
275
  const res = await fetch(`${baseUrl}/api/keys`, {
218
276
  method: "POST",
@@ -223,13 +281,23 @@ function createClient(baseUrl, apiKey) {
223
281
  throw new Error(err.error ?? `HTTP ${res.status}`);
224
282
  }
225
283
  return res.json();
284
+ },
285
+ async deleteRoom(roomId) {
286
+ const res = await fetch(`${baseUrl}/api/rooms/${roomId}`, {
287
+ method: "DELETE",
288
+ headers: headers()
289
+ });
290
+ if (!res.ok) {
291
+ const err = await res.json().catch(() => ({}));
292
+ throw new Error(err.error ?? `HTTP ${res.status}`);
293
+ }
226
294
  }
227
295
  };
228
296
  }
229
297
 
230
298
  // src/inbox-router.ts
231
- import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
232
- import { dirname } from "path";
299
+ import { readFileSync, writeFileSync, mkdirSync, statSync } from "node:fs";
300
+ import { dirname } from "node:path";
233
301
  var IDLE_CHECK_INTERVAL_MS = 60000;
234
302
  var IDLE_THRESHOLD_MS = 5 * 60 * 1000;
235
303
  function appendToInbox(path, entry) {
@@ -285,6 +353,25 @@ var API_URL = process.env.MEET_AI_URL || "https://meet-ai.cc";
285
353
  var API_KEY = process.env.MEET_AI_KEY;
286
354
  var client = createClient(API_URL, API_KEY);
287
355
  var [command, ...args] = process.argv.slice(2);
356
+ async function downloadMessageAttachments(roomId, messageId) {
357
+ try {
358
+ const attachments = await client.getMessageAttachments(roomId, messageId);
359
+ if (!attachments.length)
360
+ return [];
361
+ const paths = [];
362
+ for (const att of attachments) {
363
+ try {
364
+ const localPath = await client.downloadAttachment(att.id, att.filename);
365
+ paths.push(localPath);
366
+ } catch (error) {
367
+ console.error(JSON.stringify({ event: "attachment_download_error", attachmentId: att.id, error: error instanceof Error ? error.message : String(error) }));
368
+ }
369
+ }
370
+ return paths;
371
+ } catch {
372
+ return [];
373
+ }
374
+ }
288
375
  function parseFlags(args2) {
289
376
  const positional = [];
290
377
  const flags = {};
@@ -298,24 +385,65 @@ function parseFlags(args2) {
298
385
  }
299
386
  return { positional, flags };
300
387
  }
388
+ function rejectFlagLikeArgs(positional, usage) {
389
+ for (const arg of positional) {
390
+ if (arg.startsWith("--")) {
391
+ console.error(`Unknown flag: ${arg}`);
392
+ console.error(`Usage: ${usage}`);
393
+ process.exit(1);
394
+ }
395
+ }
396
+ }
301
397
  switch (command) {
302
398
  case "create-room": {
303
- const name = args[0];
399
+ if (args.includes("--help")) {
400
+ console.log("Usage: meet-ai create-room <room-name>");
401
+ process.exit(0);
402
+ }
403
+ const { positional, flags } = parseFlags(args);
404
+ const unknownFlags = Object.keys(flags);
405
+ if (unknownFlags.length > 0) {
406
+ console.error(`Unknown flag: --${unknownFlags[0]}`);
407
+ console.error("Usage: meet-ai create-room <room-name>");
408
+ process.exit(1);
409
+ }
410
+ rejectFlagLikeArgs(positional, "meet-ai create-room <room-name>");
411
+ const name = positional[0];
304
412
  if (!name) {
305
- console.error("Usage: cli create-room <name>");
413
+ console.error("Usage: meet-ai create-room <room-name>");
306
414
  process.exit(1);
307
415
  }
308
416
  const room = await client.createRoom(name);
309
417
  console.log(`Room created: ${room.id} (${room.name})`);
310
418
  break;
311
419
  }
420
+ case "delete-room": {
421
+ if (args.includes("--help")) {
422
+ console.log("Usage: meet-ai delete-room <roomId>");
423
+ process.exit(0);
424
+ }
425
+ rejectFlagLikeArgs(args, "meet-ai delete-room <roomId>");
426
+ const roomId = args[0];
427
+ if (!roomId) {
428
+ console.error("Usage: meet-ai delete-room <roomId>");
429
+ process.exit(1);
430
+ }
431
+ await client.deleteRoom(roomId);
432
+ console.log(`Room deleted: ${roomId}`);
433
+ break;
434
+ }
312
435
  case "send-message": {
436
+ if (args.includes("--help")) {
437
+ console.log("Usage: meet-ai send-message <roomId> <sender> <content> [--color <color>]");
438
+ process.exit(0);
439
+ }
313
440
  const { positional: smPos, flags: smFlags } = parseFlags(args);
441
+ rejectFlagLikeArgs(smPos, "meet-ai send-message <roomId> <sender> <content> [--color <color>]");
314
442
  const [roomId, sender, ...rest] = smPos;
315
443
  const content = rest.join(" ").replace(/\\n/g, `
316
444
  `);
317
445
  if (!roomId || !sender || !content) {
318
- console.error("Usage: cli send-message <roomId> <sender> <content> [--color <color>]");
446
+ console.error("Usage: meet-ai send-message <roomId> <sender> <content> [--color <color>]");
319
447
  process.exit(1);
320
448
  }
321
449
  const msg = await client.sendMessage(roomId, sender, content, smFlags.color);
@@ -323,10 +451,15 @@ switch (command) {
323
451
  break;
324
452
  }
325
453
  case "poll": {
454
+ if (args.includes("--help")) {
455
+ console.log("Usage: meet-ai poll <roomId> [--after <messageId>] [--exclude <sender>] [--sender-type <type>]");
456
+ process.exit(0);
457
+ }
326
458
  const { positional, flags } = parseFlags(args);
459
+ rejectFlagLikeArgs(positional, "meet-ai poll <roomId> [--after <messageId>] [--exclude <sender>] [--sender-type <type>]");
327
460
  const roomId = positional[0];
328
461
  if (!roomId) {
329
- console.error("Usage: cli poll <roomId> [--after <messageId>] [--exclude <sender>]");
462
+ console.error("Usage: meet-ai poll <roomId> [--after <messageId>] [--exclude <sender>] [--sender-type <type>]");
330
463
  process.exit(1);
331
464
  }
332
465
  const messages = await client.getMessages(roomId, {
@@ -334,19 +467,26 @@ switch (command) {
334
467
  exclude: flags.exclude,
335
468
  senderType: flags["sender-type"]
336
469
  });
337
- console.log(JSON.stringify(messages));
470
+ const enriched = await Promise.all(messages.map(async (msg) => {
471
+ const paths = await downloadMessageAttachments(roomId, msg.id);
472
+ return paths.length ? { ...msg, attachments: paths } : msg;
473
+ }));
474
+ console.log(JSON.stringify(enriched));
338
475
  break;
339
476
  }
340
477
  case "listen": {
341
- let routeToInbox = function(msg) {
478
+ let routeToInbox = function(msg, attachmentPaths) {
342
479
  if (!inboxDir)
343
480
  return;
344
481
  const entry = {
345
- from: "meet-ai:" + msg.sender,
482
+ from: `meet-ai:${msg.sender}`,
346
483
  text: msg.content,
347
484
  timestamp: new Date().toISOString(),
348
485
  read: false
349
486
  };
487
+ if (attachmentPaths?.length) {
488
+ entry.attachments = attachmentPaths;
489
+ }
350
490
  const members = teamDir ? getTeamMembers(teamDir) : new Set;
351
491
  const targets = resolveInboxTargets(msg.content, members);
352
492
  if (targets) {
@@ -364,10 +504,15 @@ switch (command) {
364
504
  }
365
505
  process.exit(0);
366
506
  };
507
+ if (args.includes("--help")) {
508
+ console.log("Usage: meet-ai listen <roomId> [--exclude <sender>] [--sender-type <type>] [--team <name> --inbox <agent>]");
509
+ process.exit(0);
510
+ }
367
511
  const { positional, flags } = parseFlags(args);
512
+ rejectFlagLikeArgs(positional, "meet-ai listen <roomId> [--exclude <sender>] [--sender-type <type>] [--team <name> --inbox <agent>]");
368
513
  const roomId = positional[0];
369
514
  if (!roomId) {
370
- console.error("Usage: cli listen <roomId> [--exclude <sender>] [--sender-type <type>] [--team <name> --inbox <agent>]");
515
+ console.error("Usage: meet-ai listen <roomId> [--exclude <sender>] [--sender-type <type>] [--team <name> --inbox <agent>]");
371
516
  process.exit(1);
372
517
  }
373
518
  const team = flags.team;
@@ -375,10 +520,20 @@ switch (command) {
375
520
  const inboxDir = team ? `${process.env.HOME}/.claude/teams/${team}/inboxes` : null;
376
521
  const defaultInboxPath = inboxDir && inbox ? `${inboxDir}/${inbox}.json` : null;
377
522
  const teamDir = team ? `${process.env.HOME}/.claude/teams/${team}` : null;
378
- const onMessage = inboxDir ? (msg) => {
379
- console.log(JSON.stringify(msg));
380
- routeToInbox(msg);
381
- } : undefined;
523
+ const onMessage = (msg) => {
524
+ if (msg.id && msg.room_id && msg.attachment_count > 0) {
525
+ downloadMessageAttachments(msg.room_id, msg.id).then((paths) => {
526
+ const output = paths.length ? { ...msg, attachments: paths } : msg;
527
+ console.log(JSON.stringify(output));
528
+ if (inboxDir)
529
+ routeToInbox(msg, paths);
530
+ });
531
+ } else {
532
+ console.log(JSON.stringify(msg));
533
+ if (inboxDir)
534
+ routeToInbox(msg);
535
+ }
536
+ };
382
537
  const ws = client.listen(roomId, { exclude: flags.exclude, senderType: flags["sender-type"], onMessage });
383
538
  let idleCheckTimeout = null;
384
539
  const idleNotified = new Set;
@@ -408,12 +563,17 @@ switch (command) {
408
563
  break;
409
564
  }
410
565
  case "send-log": {
566
+ if (args.includes("--help")) {
567
+ console.log("Usage: meet-ai send-log <roomId> <sender> <content> [--color <color>] [--message-id <id>]");
568
+ process.exit(0);
569
+ }
411
570
  const { positional: slPos, flags: slFlags } = parseFlags(args);
571
+ rejectFlagLikeArgs(slPos, "meet-ai send-log <roomId> <sender> <content> [--color <color>] [--message-id <id>]");
412
572
  const [slRoomId, slSender, ...slRest] = slPos;
413
573
  const slContent = slRest.join(" ").replace(/\\n/g, `
414
574
  `);
415
575
  if (!slRoomId || !slSender || !slContent) {
416
- console.error("Usage: cli send-log <roomId> <sender> <content> [--color <color>] [--message-id <id>]");
576
+ console.error("Usage: meet-ai send-log <roomId> <sender> <content> [--color <color>] [--message-id <id>]");
417
577
  process.exit(1);
418
578
  }
419
579
  const log = await client.sendLog(slRoomId, slSender, slContent, slFlags.color, slFlags["message-id"]);
@@ -421,9 +581,14 @@ switch (command) {
421
581
  break;
422
582
  }
423
583
  case "send-team-info": {
584
+ if (args.includes("--help")) {
585
+ console.log("Usage: meet-ai send-team-info <roomId> '<json-payload>'");
586
+ process.exit(0);
587
+ }
588
+ rejectFlagLikeArgs(args, "meet-ai send-team-info <roomId> '<json-payload>'");
424
589
  const [tiRoomId, tiPayload] = args;
425
590
  if (!tiRoomId || !tiPayload) {
426
- console.error("Usage: cli send-team-info <roomId> '<json-payload>'");
591
+ console.error("Usage: meet-ai send-team-info <roomId> '<json-payload>'");
427
592
  process.exit(1);
428
593
  }
429
594
  try {
@@ -436,13 +601,71 @@ switch (command) {
436
601
  console.log("Team info sent");
437
602
  break;
438
603
  }
604
+ case "send-tasks": {
605
+ if (args.includes("--help")) {
606
+ console.log("Usage: meet-ai send-tasks <roomId> '<json-payload>'");
607
+ process.exit(0);
608
+ }
609
+ rejectFlagLikeArgs(args, "meet-ai send-tasks <roomId> '<json-payload>'");
610
+ const [stRoomId, stPayload] = args;
611
+ if (!stRoomId || !stPayload) {
612
+ console.error("Usage: meet-ai send-tasks <roomId> '<json-payload>'");
613
+ process.exit(1);
614
+ }
615
+ try {
616
+ JSON.parse(stPayload);
617
+ } catch {
618
+ console.error("Error: payload must be valid JSON");
619
+ process.exit(1);
620
+ }
621
+ await client.sendTasks(stRoomId, stPayload);
622
+ console.log("Tasks info sent");
623
+ break;
624
+ }
625
+ case "download-attachment": {
626
+ if (args.includes("--help")) {
627
+ console.log("Usage: meet-ai download-attachment <attachmentId>");
628
+ process.exit(0);
629
+ }
630
+ rejectFlagLikeArgs(args, "meet-ai download-attachment <attachmentId>");
631
+ const attachmentId = args[0];
632
+ if (!attachmentId) {
633
+ console.error("Usage: meet-ai download-attachment <attachmentId>");
634
+ process.exit(1);
635
+ }
636
+ try {
637
+ cleanupOldAttachments();
638
+ const res = await fetch(`${API_URL}/api/attachments/${attachmentId}`, {
639
+ headers: API_KEY ? { Authorization: `Bearer ${API_KEY}` } : undefined
640
+ });
641
+ if (!res.ok) {
642
+ const err = await res.json().catch(() => ({}));
643
+ console.error(err.error ?? `HTTP ${res.status}`);
644
+ process.exit(1);
645
+ }
646
+ const disposition = res.headers.get("Content-Disposition") || "";
647
+ const filenameMatch = disposition.match(/filename="(.+?)"/);
648
+ const filename = filenameMatch?.[1] || attachmentId;
649
+ const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("node:fs");
650
+ const dir = "/tmp/meet-ai-attachments";
651
+ mkdirSync2(dir, { recursive: true });
652
+ const localPath = `${dir}/${attachmentId}-${filename}`;
653
+ const buffer = Buffer.from(await res.arrayBuffer());
654
+ writeFileSync2(localPath, buffer);
655
+ console.log(localPath);
656
+ } catch (error) {
657
+ console.error(error instanceof Error ? error.message : String(error));
658
+ process.exit(1);
659
+ }
660
+ break;
661
+ }
439
662
  case "generate-key": {
440
663
  const result = await client.generateKey();
441
664
  console.log(`API Key: ${result.key}`);
442
665
  console.log(`Prefix: ${result.prefix}`);
443
666
  break;
444
667
  }
445
- default:
668
+ default: {
446
669
  console.log(`meet-ai CLI
447
670
 
448
671
  Environment variables:
@@ -451,6 +674,7 @@ Environment variables:
451
674
 
452
675
  Commands:
453
676
  create-room <name> Create a new chat room
677
+ delete-room <roomId> Delete a room and all its messages
454
678
  send-message <roomId> <sender> <content> Send a message to a room
455
679
  --color <color> Set sender name color (e.g. #ff0000, red)
456
680
  send-log <roomId> <sender> <content> Send a log entry to a room
@@ -465,6 +689,9 @@ Commands:
465
689
  --sender-type <type> Filter by sender_type (human|agent)
466
690
  --team <name> Write to Claude Code team inbox
467
691
  --inbox <agent> Target agent inbox (requires --team)
692
+ download-attachment <attachmentId> Download an attachment to /tmp
468
693
  send-team-info <roomId> '<json>' Send team info to a room
694
+ send-tasks <roomId> '<json>' Send tasks info to a room
469
695
  generate-key Generate a new API key`);
696
+ }
470
697
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meet-ai/cli",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
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"