@meet-ai/cli 0.0.9 → 0.0.11

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 +282 -44
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -19,22 +19,22 @@ async function withRetry(fn, options) {
19
19
  const maxRetries = options?.maxRetries ?? 3;
20
20
  const baseDelay = options?.baseDelay ?? 1000;
21
21
  const shouldRetry = options?.shouldRetry ?? isRetryable;
22
- let lastError;
22
+ let lastError = new Error("withRetry: no attempts made");
23
23
  for (let attempt = 0;attempt <= maxRetries; attempt++) {
24
24
  try {
25
25
  return await fn();
26
- } catch (err) {
27
- lastError = err;
28
- if (attempt >= maxRetries || !shouldRetry(err))
29
- throw err;
26
+ } catch (error) {
27
+ lastError = error instanceof Error ? error : new Error(String(error));
28
+ if (attempt >= maxRetries || !shouldRetry(error))
29
+ throw lastError;
30
30
  const delay = baseDelay * 2 ** attempt;
31
31
  console.error(JSON.stringify({
32
32
  event: "retry",
33
33
  attempt: attempt + 1,
34
34
  delay_ms: delay,
35
- error: err instanceof Error ? err.message : String(err)
35
+ error: lastError.message
36
36
  }));
37
- await new Promise((r) => setTimeout(r, delay));
37
+ await new Promise((resolve) => setTimeout(resolve, delay));
38
38
  }
39
39
  }
40
40
  throw lastError;
@@ -43,7 +43,7 @@ var ATTACHMENTS_DIR = "/tmp/meet-ai-attachments";
43
43
  var MAX_AGE_MS = 5 * 60 * 1000;
44
44
  function cleanupOldAttachments() {
45
45
  try {
46
- const { readdirSync, statSync, unlinkSync } = __require("fs");
46
+ const { readdirSync, statSync, unlinkSync } = __require("node:fs");
47
47
  const now = Date.now();
48
48
  for (const entry of readdirSync(ATTACHMENTS_DIR)) {
49
49
  try {
@@ -263,7 +263,7 @@ function createClient(baseUrl, apiKey) {
263
263
  const err = await res.json().catch(() => ({}));
264
264
  throw new Error(err.error ?? `HTTP ${res.status}`);
265
265
  }
266
- const { mkdirSync, writeFileSync } = await import("fs");
266
+ const { mkdirSync, writeFileSync } = await import("node:fs");
267
267
  const dir = "/tmp/meet-ai-attachments";
268
268
  mkdirSync(dir, { recursive: true });
269
269
  const localPath = `${dir}/${attachmentId}-${filename}`;
@@ -281,13 +281,23 @@ function createClient(baseUrl, apiKey) {
281
281
  throw new Error(err.error ?? `HTTP ${res.status}`);
282
282
  }
283
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
+ }
284
294
  }
285
295
  };
286
296
  }
287
297
 
288
298
  // src/inbox-router.ts
289
- import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
290
- import { dirname } from "path";
299
+ import { readFileSync, writeFileSync, mkdirSync, statSync } from "node:fs";
300
+ import { dirname } from "node:path";
291
301
  var IDLE_CHECK_INTERVAL_MS = 60000;
292
302
  var IDLE_THRESHOLD_MS = 5 * 60 * 1000;
293
303
  function appendToInbox(path, entry) {
@@ -338,9 +348,115 @@ function checkIdleAgents(inboxDir, members, excludeAgent, notified, now = Date.n
338
348
  return newlyIdle;
339
349
  }
340
350
 
351
+ // src/config.ts
352
+ import { readFileSync as readFileSync2, existsSync } from "node:fs";
353
+ import { join, resolve } from "node:path";
354
+ import { homedir } from "node:os";
355
+ function loadSettingsFromPath(path) {
356
+ try {
357
+ if (!existsSync(path)) {
358
+ return null;
359
+ }
360
+ const content = readFileSync2(path, "utf-8");
361
+ return JSON.parse(content);
362
+ } catch {
363
+ return null;
364
+ }
365
+ }
366
+ function getMeetAiConfig() {
367
+ if (process.env.MEET_AI_URL) {
368
+ return {
369
+ url: process.env.MEET_AI_URL,
370
+ key: process.env.MEET_AI_KEY
371
+ };
372
+ }
373
+ const projectSettingsPath = resolve("./.claude/settings.json");
374
+ const projectSettings = loadSettingsFromPath(projectSettingsPath);
375
+ if (projectSettings?.env?.MEET_AI_URL) {
376
+ return {
377
+ url: projectSettings.env.MEET_AI_URL,
378
+ key: projectSettings.env.MEET_AI_KEY
379
+ };
380
+ }
381
+ const userSettingsPath = join(homedir(), ".claude/settings.json");
382
+ const userSettings = loadSettingsFromPath(userSettingsPath);
383
+ if (userSettings?.env?.MEET_AI_URL) {
384
+ return {
385
+ url: userSettings.env.MEET_AI_URL,
386
+ key: userSettings.env.MEET_AI_KEY
387
+ };
388
+ }
389
+ return {
390
+ url: "https://meet-ai.cc",
391
+ key: undefined
392
+ };
393
+ }
394
+
395
+ // src/spawner.ts
396
+ import { spawn, execSync } from "node:child_process";
397
+ import { existsSync as existsSync2 } from "node:fs";
398
+ import { join as join2 } from "node:path";
399
+ import { homedir as homedir2, platform } from "node:os";
400
+ function findClaudeCli() {
401
+ try {
402
+ const command = platform() === "win32" ? "where claude" : "which claude";
403
+ const result = execSync(command, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim();
404
+ const claudePath = result.split(`
405
+ `)[0].trim();
406
+ if (claudePath && existsSync2(claudePath)) {
407
+ return claudePath;
408
+ }
409
+ } catch {}
410
+ const envPath = process.env.MEET_AI_CLAUDE_PATH;
411
+ if (envPath && existsSync2(envPath)) {
412
+ return envPath;
413
+ }
414
+ const home = homedir2();
415
+ const commonPaths = [
416
+ join2(home, ".bun", "bin", "claude"),
417
+ "/opt/homebrew/bin/claude",
418
+ "/usr/local/bin/claude",
419
+ join2(home, ".local", "bin", "claude")
420
+ ];
421
+ for (const path of commonPaths) {
422
+ if (existsSync2(path)) {
423
+ return path;
424
+ }
425
+ }
426
+ throw new Error(`
427
+ Claude Code is not installed
428
+
429
+ Please install Claude Code:
430
+ npm install -g @anthropic-ai/claude-code
431
+
432
+ Or set MEET_AI_CLAUDE_PATH to the Claude Code CLI path.
433
+ `.trim());
434
+ }
435
+ async function spawnInteractive() {
436
+ const claudePath = findClaudeCli();
437
+ return new Promise((resolve2, reject) => {
438
+ const child = spawn(claudePath, [], {
439
+ stdio: "inherit",
440
+ env: {
441
+ ...process.env,
442
+ DISABLE_AUTOUPDATER: "1"
443
+ }
444
+ });
445
+ child.on("exit", (code) => {
446
+ if (code === 0 || code === null) {
447
+ resolve2();
448
+ } else {
449
+ reject(new Error(`Claude exited with code ${code}`));
450
+ }
451
+ });
452
+ child.on("error", reject);
453
+ });
454
+ }
455
+
341
456
  // src/index.ts
342
- var API_URL = process.env.MEET_AI_URL || "https://meet-ai.cc";
343
- var API_KEY = process.env.MEET_AI_KEY;
457
+ var config = getMeetAiConfig();
458
+ var API_URL = config.url;
459
+ var API_KEY = config.key;
344
460
  var client = createClient(API_URL, API_KEY);
345
461
  var [command, ...args] = process.argv.slice(2);
346
462
  async function downloadMessageAttachments(roomId, messageId) {
@@ -353,8 +469,8 @@ async function downloadMessageAttachments(roomId, messageId) {
353
469
  try {
354
470
  const localPath = await client.downloadAttachment(att.id, att.filename);
355
471
  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) }));
472
+ } catch (error) {
473
+ console.error(JSON.stringify({ event: "attachment_download_error", attachmentId: att.id, error: error instanceof Error ? error.message : String(error) }));
358
474
  }
359
475
  }
360
476
  return paths;
@@ -375,24 +491,65 @@ function parseFlags(args2) {
375
491
  }
376
492
  return { positional, flags };
377
493
  }
494
+ function rejectFlagLikeArgs(positional, usage) {
495
+ for (const arg of positional) {
496
+ if (arg.startsWith("--")) {
497
+ console.error(`Unknown flag: ${arg}`);
498
+ console.error(`Usage: ${usage}`);
499
+ process.exit(1);
500
+ }
501
+ }
502
+ }
378
503
  switch (command) {
379
504
  case "create-room": {
380
- const name = args[0];
505
+ if (args.includes("--help")) {
506
+ console.log("Usage: meet-ai create-room <room-name>");
507
+ process.exit(0);
508
+ }
509
+ const { positional, flags } = parseFlags(args);
510
+ const unknownFlags = Object.keys(flags);
511
+ if (unknownFlags.length > 0) {
512
+ console.error(`Unknown flag: --${unknownFlags[0]}`);
513
+ console.error("Usage: meet-ai create-room <room-name>");
514
+ process.exit(1);
515
+ }
516
+ rejectFlagLikeArgs(positional, "meet-ai create-room <room-name>");
517
+ const name = positional[0];
381
518
  if (!name) {
382
- console.error("Usage: cli create-room <name>");
519
+ console.error("Usage: meet-ai create-room <room-name>");
383
520
  process.exit(1);
384
521
  }
385
522
  const room = await client.createRoom(name);
386
523
  console.log(`Room created: ${room.id} (${room.name})`);
387
524
  break;
388
525
  }
526
+ case "delete-room": {
527
+ if (args.includes("--help")) {
528
+ console.log("Usage: meet-ai delete-room <roomId>");
529
+ process.exit(0);
530
+ }
531
+ rejectFlagLikeArgs(args, "meet-ai delete-room <roomId>");
532
+ const roomId = args[0];
533
+ if (!roomId) {
534
+ console.error("Usage: meet-ai delete-room <roomId>");
535
+ process.exit(1);
536
+ }
537
+ await client.deleteRoom(roomId);
538
+ console.log(`Room deleted: ${roomId}`);
539
+ break;
540
+ }
389
541
  case "send-message": {
542
+ if (args.includes("--help")) {
543
+ console.log("Usage: meet-ai send-message <roomId> <sender> <content> [--color <color>]");
544
+ process.exit(0);
545
+ }
390
546
  const { positional: smPos, flags: smFlags } = parseFlags(args);
547
+ rejectFlagLikeArgs(smPos, "meet-ai send-message <roomId> <sender> <content> [--color <color>]");
391
548
  const [roomId, sender, ...rest] = smPos;
392
549
  const content = rest.join(" ").replace(/\\n/g, `
393
550
  `);
394
551
  if (!roomId || !sender || !content) {
395
- console.error("Usage: cli send-message <roomId> <sender> <content> [--color <color>]");
552
+ console.error("Usage: meet-ai send-message <roomId> <sender> <content> [--color <color>]");
396
553
  process.exit(1);
397
554
  }
398
555
  const msg = await client.sendMessage(roomId, sender, content, smFlags.color);
@@ -400,10 +557,15 @@ switch (command) {
400
557
  break;
401
558
  }
402
559
  case "poll": {
560
+ if (args.includes("--help")) {
561
+ console.log("Usage: meet-ai poll <roomId> [--after <messageId>] [--exclude <sender>] [--sender-type <type>]");
562
+ process.exit(0);
563
+ }
403
564
  const { positional, flags } = parseFlags(args);
565
+ rejectFlagLikeArgs(positional, "meet-ai poll <roomId> [--after <messageId>] [--exclude <sender>] [--sender-type <type>]");
404
566
  const roomId = positional[0];
405
567
  if (!roomId) {
406
- console.error("Usage: cli poll <roomId> [--after <messageId>] [--exclude <sender>]");
568
+ console.error("Usage: meet-ai poll <roomId> [--after <messageId>] [--exclude <sender>] [--sender-type <type>]");
407
569
  process.exit(1);
408
570
  }
409
571
  const messages = await client.getMessages(roomId, {
@@ -423,7 +585,7 @@ switch (command) {
423
585
  if (!inboxDir)
424
586
  return;
425
587
  const entry = {
426
- from: "meet-ai:" + msg.sender,
588
+ from: `meet-ai:${msg.sender}`,
427
589
  text: msg.content,
428
590
  timestamp: new Date().toISOString(),
429
591
  read: false
@@ -448,10 +610,15 @@ switch (command) {
448
610
  }
449
611
  process.exit(0);
450
612
  };
613
+ if (args.includes("--help")) {
614
+ console.log("Usage: meet-ai listen <roomId> [--exclude <sender>] [--sender-type <type>] [--team <name> --inbox <agent>]");
615
+ process.exit(0);
616
+ }
451
617
  const { positional, flags } = parseFlags(args);
618
+ rejectFlagLikeArgs(positional, "meet-ai listen <roomId> [--exclude <sender>] [--sender-type <type>] [--team <name> --inbox <agent>]");
452
619
  const roomId = positional[0];
453
620
  if (!roomId) {
454
- console.error("Usage: cli listen <roomId> [--exclude <sender>] [--sender-type <type>] [--team <name> --inbox <agent>]");
621
+ console.error("Usage: meet-ai listen <roomId> [--exclude <sender>] [--sender-type <type>] [--team <name> --inbox <agent>]");
455
622
  process.exit(1);
456
623
  }
457
624
  const team = flags.team;
@@ -502,12 +669,17 @@ switch (command) {
502
669
  break;
503
670
  }
504
671
  case "send-log": {
672
+ if (args.includes("--help")) {
673
+ console.log("Usage: meet-ai send-log <roomId> <sender> <content> [--color <color>] [--message-id <id>]");
674
+ process.exit(0);
675
+ }
505
676
  const { positional: slPos, flags: slFlags } = parseFlags(args);
677
+ rejectFlagLikeArgs(slPos, "meet-ai send-log <roomId> <sender> <content> [--color <color>] [--message-id <id>]");
506
678
  const [slRoomId, slSender, ...slRest] = slPos;
507
679
  const slContent = slRest.join(" ").replace(/\\n/g, `
508
680
  `);
509
681
  if (!slRoomId || !slSender || !slContent) {
510
- console.error("Usage: cli send-log <roomId> <sender> <content> [--color <color>] [--message-id <id>]");
682
+ console.error("Usage: meet-ai send-log <roomId> <sender> <content> [--color <color>] [--message-id <id>]");
511
683
  process.exit(1);
512
684
  }
513
685
  const log = await client.sendLog(slRoomId, slSender, slContent, slFlags.color, slFlags["message-id"]);
@@ -515,25 +687,70 @@ switch (command) {
515
687
  break;
516
688
  }
517
689
  case "send-team-info": {
518
- const [tiRoomId, tiPayload] = args;
690
+ if (args.includes("--help")) {
691
+ console.log(`Usage: meet-ai send-team-info <roomId> '<json>' [--team-name <name>]
692
+
693
+ Accepts two JSON formats:
694
+
695
+ 1. Full format (passed through as-is):
696
+ '{"team_name":"my-team","members":[{"name":"agent1","role":"dev","status":"active","color":"#fff","model":"opus","joinedAt":1234567890}]}'
697
+
698
+ 2. Members-only array (requires --team-name):
699
+ '[{"name":"agent1","role":"dev","status":"active","color":"#fff"}]' --team-name my-team
700
+
701
+ Missing fields get defaults: model="claude-opus-4-6", joinedAt=<now>`);
702
+ process.exit(0);
703
+ }
704
+ const { positional: tiPos, flags: tiFlags } = parseFlags(args);
705
+ rejectFlagLikeArgs(tiPos, "meet-ai send-team-info <roomId> '<json>' [--team-name <name>]");
706
+ const [tiRoomId, tiPayload] = tiPos;
519
707
  if (!tiRoomId || !tiPayload) {
520
- console.error("Usage: cli send-team-info <roomId> '<json-payload>'");
708
+ console.error("Usage: meet-ai send-team-info <roomId> '<json>' [--team-name <name>]");
521
709
  process.exit(1);
522
710
  }
711
+ let tiParsed;
523
712
  try {
524
- JSON.parse(tiPayload);
713
+ tiParsed = JSON.parse(tiPayload);
525
714
  } catch {
526
715
  console.error("Error: payload must be valid JSON");
527
716
  process.exit(1);
528
717
  }
529
- await client.sendTeamInfo(tiRoomId, tiPayload);
718
+ let tiBody;
719
+ if (Array.isArray(tiParsed)) {
720
+ const teamName = tiFlags["team-name"];
721
+ if (!teamName) {
722
+ console.error("Error: when passing a members array, --team-name is required");
723
+ console.error("Usage: meet-ai send-team-info <roomId> '[...]' --team-name <name>");
724
+ process.exit(1);
725
+ }
726
+ const now = Date.now();
727
+ tiBody = {
728
+ team_name: teamName,
729
+ members: tiParsed.map((m) => ({
730
+ model: "claude-opus-4-6",
731
+ joinedAt: now,
732
+ ...m
733
+ }))
734
+ };
735
+ } else if (tiParsed && typeof tiParsed === "object" && "team_name" in tiParsed && "members" in tiParsed) {
736
+ tiBody = tiParsed;
737
+ } else {
738
+ console.error("Error: JSON must be either a members array or an object with { team_name, members }");
739
+ process.exit(1);
740
+ }
741
+ await client.sendTeamInfo(tiRoomId, JSON.stringify(tiBody));
530
742
  console.log("Team info sent");
531
743
  break;
532
744
  }
533
745
  case "send-tasks": {
746
+ if (args.includes("--help")) {
747
+ console.log("Usage: meet-ai send-tasks <roomId> '<json-payload>'");
748
+ process.exit(0);
749
+ }
750
+ rejectFlagLikeArgs(args, "meet-ai send-tasks <roomId> '<json-payload>'");
534
751
  const [stRoomId, stPayload] = args;
535
752
  if (!stRoomId || !stPayload) {
536
- console.error("Usage: cli send-tasks <roomId> '<json-payload>'");
753
+ console.error("Usage: meet-ai send-tasks <roomId> '<json-payload>'");
537
754
  process.exit(1);
538
755
  }
539
756
  try {
@@ -547,9 +764,14 @@ switch (command) {
547
764
  break;
548
765
  }
549
766
  case "download-attachment": {
767
+ if (args.includes("--help")) {
768
+ console.log("Usage: meet-ai download-attachment <attachmentId>");
769
+ process.exit(0);
770
+ }
771
+ rejectFlagLikeArgs(args, "meet-ai download-attachment <attachmentId>");
550
772
  const attachmentId = args[0];
551
773
  if (!attachmentId) {
552
- console.error("Usage: cli download-attachment <attachmentId>");
774
+ console.error("Usage: meet-ai download-attachment <attachmentId>");
553
775
  process.exit(1);
554
776
  }
555
777
  try {
@@ -565,15 +787,15 @@ switch (command) {
565
787
  const disposition = res.headers.get("Content-Disposition") || "";
566
788
  const filenameMatch = disposition.match(/filename="(.+?)"/);
567
789
  const filename = filenameMatch?.[1] || attachmentId;
568
- const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
790
+ const { mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("node:fs");
569
791
  const dir = "/tmp/meet-ai-attachments";
570
792
  mkdirSync2(dir, { recursive: true });
571
793
  const localPath = `${dir}/${attachmentId}-${filename}`;
572
794
  const buffer = Buffer.from(await res.arrayBuffer());
573
795
  writeFileSync2(localPath, buffer);
574
796
  console.log(localPath);
575
- } catch (err) {
576
- console.error(err instanceof Error ? err.message : String(err));
797
+ } catch (error) {
798
+ console.error(error instanceof Error ? error.message : String(error));
577
799
  process.exit(1);
578
800
  }
579
801
  break;
@@ -584,31 +806,47 @@ switch (command) {
584
806
  console.log(`Prefix: ${result.prefix}`);
585
807
  break;
586
808
  }
587
- default:
809
+ default: {
810
+ if (!command) {
811
+ console.log(`Starting Claude Code... (Press Ctrl+C to exit)
812
+ `);
813
+ await spawnInteractive();
814
+ break;
815
+ }
588
816
  console.log(`meet-ai CLI
589
817
 
590
- Environment variables:
591
- MEET_AI_URL Server URL (default: https://meet-ai.cc)
592
- MEET_AI_KEY API key for authentication
818
+ Usage:
819
+ meet-ai Start Claude Code interactively (default)
820
+ meet-ai <command> [options] Run a specific command
821
+
822
+ Configuration (in order of priority):
823
+ 1. Environment variables:
824
+ MEET_AI_URL Server URL (default: https://meet-ai.cc)
825
+ MEET_AI_KEY API key for authentication
826
+ 2. Project settings: ./.claude/settings.json
827
+ 3. User settings: ~/.claude/settings.json
593
828
 
594
829
  Commands:
595
- create-room <name> Create a new chat room
596
- send-message <roomId> <sender> <content> Send a message to a room
830
+ create-room <name> Create a new chat room
831
+ delete-room <roomId> Delete a room and all its messages
832
+ send-message <roomId> <sender> <content> Send a message to a room
597
833
  --color <color> Set sender name color (e.g. #ff0000, red)
598
- send-log <roomId> <sender> <content> Send a log entry to a room
834
+ send-log <roomId> <sender> <content> Send a log entry to a room
599
835
  --color <color> Set sender name color (e.g. #ff0000, red)
600
836
  --message-id <id> Associate log with a parent message
601
- poll <roomId> [options] Fetch messages from a room
837
+ poll <roomId> [options] Fetch messages from a room
602
838
  --after <id> Only messages after this ID
603
839
  --exclude <sender> Exclude messages from sender
604
840
  --sender-type <type> Filter by sender_type (human|agent)
605
- listen <roomId> [options] Stream messages via WebSocket
841
+ listen <roomId> [options] Stream messages via WebSocket
606
842
  --exclude <sender> Exclude messages from sender
607
843
  --sender-type <type> Filter by sender_type (human|agent)
608
844
  --team <name> Write to Claude Code team inbox
609
845
  --inbox <agent> Target agent inbox (requires --team)
610
- download-attachment <attachmentId> Download an attachment to /tmp
611
- send-team-info <roomId> '<json>' Send team info to a room
612
- send-tasks <roomId> '<json>' Send tasks info to a room
613
- generate-key Generate a new API key`);
846
+ download-attachment <attachmentId> Download an attachment to /tmp
847
+ send-team-info <roomId> '<json>' [options] Send team info to a room
848
+ --team-name <name> Team name (required when passing members array)
849
+ send-tasks <roomId> '<json>' Send tasks info to a room
850
+ generate-key Generate a new API key`);
851
+ }
614
852
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meet-ai/cli",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "CLI for meet-ai chat rooms — create rooms, send messages, and stream via WebSocket",
5
5
  "keywords": [
6
6
  "chat",