groupchat 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 +426 -242
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -424,9 +424,132 @@ var init_Layout = __esm({
424
424
  }
425
425
  });
426
426
 
427
+ // src/lib/constants.ts
428
+ function getAgentDisplayName(agent) {
429
+ if (!agent) return "";
430
+ return AGENT_CONFIG[agent].displayName;
431
+ }
432
+ function getAgentColor(agent) {
433
+ if (!agent) return void 0;
434
+ return AGENT_CONFIG[agent].color;
435
+ }
436
+ var AGENT_CONFIG;
437
+ var init_constants = __esm({
438
+ "src/lib/constants.ts"() {
439
+ "use strict";
440
+ AGENT_CONFIG = {
441
+ claude: {
442
+ type: "claude",
443
+ displayName: "Claude Code",
444
+ color: "redBright"
445
+ },
446
+ codex: {
447
+ type: "codex",
448
+ displayName: "Codex",
449
+ color: "cyan"
450
+ },
451
+ cursor: {
452
+ type: "cursor",
453
+ displayName: "Cursor",
454
+ color: "blueBright"
455
+ },
456
+ windsurf: {
457
+ type: "windsurf",
458
+ displayName: "Windsurf",
459
+ color: "magenta"
460
+ }
461
+ };
462
+ }
463
+ });
464
+
465
+ // src/components/AtAGlance.tsx
466
+ import { Box as Box5, Text as Text4 } from "ink";
467
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
468
+ function AtAGlance({ presenceState }) {
469
+ const userStats = Object.values(presenceState).reduce(
470
+ (acc, userData) => {
471
+ acc.total++;
472
+ const agent = userData.metas[0]?.current_agent;
473
+ if (agent === "claude") {
474
+ acc.claude++;
475
+ } else if (agent === "codex") {
476
+ acc.codex++;
477
+ } else if (agent === "cursor") {
478
+ acc.cursor++;
479
+ } else if (agent === "windsurf") {
480
+ acc.windsurf++;
481
+ }
482
+ return acc;
483
+ },
484
+ { total: 0, claude: 0, codex: 0, cursor: 0, windsurf: 0 }
485
+ );
486
+ return /* @__PURE__ */ jsxs5(
487
+ Box5,
488
+ {
489
+ flexDirection: "column",
490
+ flexShrink: 0,
491
+ borderStyle: "single",
492
+ borderColor: "gray",
493
+ width: 26,
494
+ paddingX: 1,
495
+ children: [
496
+ /* @__PURE__ */ jsx5(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx5(Text4, { color: "white", children: "At A Glance" }) }),
497
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", gap: 1, children: [
498
+ /* @__PURE__ */ jsxs5(Box5, { children: [
499
+ /* @__PURE__ */ jsx5(Text4, { color: "green", children: "\u25CF " }),
500
+ /* @__PURE__ */ jsxs5(Text4, { color: "white", children: [
501
+ userStats.total,
502
+ " Online"
503
+ ] })
504
+ ] }),
505
+ userStats.claude > 0 && /* @__PURE__ */ jsxs5(Box5, { children: [
506
+ /* @__PURE__ */ jsx5(Text4, { color: AGENT_CONFIG.claude.color, children: "\u25CF " }),
507
+ /* @__PURE__ */ jsxs5(Text4, { color: "white", children: [
508
+ userStats.claude,
509
+ " Using ",
510
+ AGENT_CONFIG.claude.displayName
511
+ ] })
512
+ ] }),
513
+ userStats.codex > 0 && /* @__PURE__ */ jsxs5(Box5, { children: [
514
+ /* @__PURE__ */ jsx5(Text4, { color: AGENT_CONFIG.codex.color, children: "\u25CF " }),
515
+ /* @__PURE__ */ jsxs5(Text4, { color: "white", children: [
516
+ userStats.codex,
517
+ " Using ",
518
+ AGENT_CONFIG.codex.displayName
519
+ ] })
520
+ ] }),
521
+ userStats.cursor > 0 && /* @__PURE__ */ jsxs5(Box5, { children: [
522
+ /* @__PURE__ */ jsx5(Text4, { color: AGENT_CONFIG.cursor.color, children: "\u25CF " }),
523
+ /* @__PURE__ */ jsxs5(Text4, { color: "white", children: [
524
+ userStats.cursor,
525
+ " Using ",
526
+ AGENT_CONFIG.cursor.displayName
527
+ ] })
528
+ ] }),
529
+ userStats.windsurf > 0 && /* @__PURE__ */ jsxs5(Box5, { children: [
530
+ /* @__PURE__ */ jsx5(Text4, { color: AGENT_CONFIG.windsurf.color, children: "\u25CF " }),
531
+ /* @__PURE__ */ jsxs5(Text4, { color: "white", children: [
532
+ userStats.windsurf,
533
+ " Using ",
534
+ AGENT_CONFIG.windsurf.displayName
535
+ ] })
536
+ ] }),
537
+ userStats.total === 0 && /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text4, { color: "gray", children: "No users online" }) })
538
+ ] })
539
+ ]
540
+ }
541
+ );
542
+ }
543
+ var init_AtAGlance = __esm({
544
+ "src/components/AtAGlance.tsx"() {
545
+ "use strict";
546
+ init_constants();
547
+ }
548
+ });
549
+
427
550
  // src/routes/Router.tsx
428
551
  import { createContext, useContext, useState as useState2, useCallback } from "react";
429
- import { jsx as jsx5 } from "react/jsx-runtime";
552
+ import { jsx as jsx6 } from "react/jsx-runtime";
430
553
  function useNavigation() {
431
554
  const context = useContext(NavigationContext);
432
555
  if (!context) {
@@ -450,7 +573,7 @@ function Router({ initialRoute = "menu", children }) {
450
573
  return newHistory;
451
574
  });
452
575
  }, []);
453
- return /* @__PURE__ */ jsx5(NavigationContext.Provider, { value: { route, navigate, goBack }, children });
576
+ return /* @__PURE__ */ jsx6(NavigationContext.Provider, { value: { route, navigate, goBack }, children });
454
577
  }
455
578
  var NavigationContext;
456
579
  var init_Router = __esm({
@@ -462,8 +585,8 @@ var init_Router = __esm({
462
585
 
463
586
  // src/components/Menu.tsx
464
587
  import { useState as useState3, useEffect as useEffect2, useMemo } from "react";
465
- import { Box as Box5, Text as Text4, useInput as useInput3, useStdout as useStdout2 } from "ink";
466
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
588
+ import { Box as Box6, Text as Text5, useInput as useInput3, useStdout as useStdout2 } from "ink";
589
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
467
590
  function Menu({
468
591
  width,
469
592
  height,
@@ -475,7 +598,8 @@ function Menu({
475
598
  topPadding = 0,
476
599
  publicChannels,
477
600
  privateChannels,
478
- unreadCounts
601
+ unreadCounts,
602
+ aggregatedPresence
479
603
  }) {
480
604
  const { stdout } = useStdout2();
481
605
  const { navigate } = useNavigation();
@@ -538,110 +662,114 @@ function Menu({
538
662
  const headerHeight = 3;
539
663
  const contentHeight = height - topPadding - headerHeight;
540
664
  const privateStartIndex = sortedPublicChannels.length;
541
- return /* @__PURE__ */ jsxs5(Layout, { width, height, topPadding, children: [
542
- /* @__PURE__ */ jsx6(Layout.Header, { children: /* @__PURE__ */ jsx6(
665
+ return /* @__PURE__ */ jsxs6(Layout, { width, height, topPadding, children: [
666
+ /* @__PURE__ */ jsx7(Layout.Header, { children: /* @__PURE__ */ jsx7(
543
667
  Header,
544
668
  {
545
669
  username,
546
670
  roomName: "Menu",
547
671
  connectionStatus,
548
672
  onLogout,
549
- title: /* @__PURE__ */ jsx6(Text4, { bold: true, color: "cyan", children: "Menu" }),
673
+ title: /* @__PURE__ */ jsx7(Text5, { bold: true, color: "cyan", children: "Menu" }),
550
674
  showStatus: false
551
675
  }
552
676
  ) }),
553
- /* @__PURE__ */ jsx6(Layout.Content, { children: /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", height: contentHeight, padding: 2, children: [
554
- sortedPublicChannels.length > 0 && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
555
- /* @__PURE__ */ jsx6(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text4, { bold: true, color: "white", children: "Global Channels" }) }),
556
- sortedPublicChannels.map((channel, idx) => {
557
- const isSelected = selectedIndex === idx;
558
- const unreadCount = unreadCounts[channel.slug] || 0;
559
- return /* @__PURE__ */ jsx6(
560
- ChannelItem,
561
- {
562
- channel,
563
- isSelected,
564
- unreadCount
565
- },
566
- channel.id
567
- );
568
- })
569
- ] }),
570
- privateChannels.length > 0 && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
571
- /* @__PURE__ */ jsx6(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text4, { bold: true, color: "white", children: "Private Channels" }) }),
572
- privateChannels.map((channel, idx) => {
573
- const absoluteIndex = privateStartIndex + idx;
574
- const isSelected = selectedIndex === absoluteIndex;
575
- const unreadCount = unreadCounts[channel.slug] || 0;
576
- return /* @__PURE__ */ jsx6(
577
- ChannelItem,
578
- {
579
- channel,
580
- isSelected,
581
- isPrivate: true,
582
- unreadCount
583
- },
584
- channel.id
585
- );
586
- })
587
- ] }),
588
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
589
- privateChannels.length === 0 && /* @__PURE__ */ jsx6(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text4, { bold: true, color: "white", children: "Private Channels" }) }),
590
- /* @__PURE__ */ jsx6(
591
- ActionItem,
592
- {
593
- label: "+ Create New Private Channel",
594
- isSelected: selectedIndex === allChannels.length
595
- }
596
- )
677
+ /* @__PURE__ */ jsx7(Layout.Content, { children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: contentHeight, children: [
678
+ /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", flexGrow: 1, children: [
679
+ /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, padding: 2, children: [
680
+ sortedPublicChannels.length > 0 && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginBottom: 1, children: [
681
+ /* @__PURE__ */ jsx7(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text5, { bold: true, color: "white", children: "Global Channels" }) }),
682
+ sortedPublicChannels.map((channel, idx) => {
683
+ const isSelected = selectedIndex === idx;
684
+ const unreadCount = unreadCounts[channel.slug] || 0;
685
+ return /* @__PURE__ */ jsx7(
686
+ ChannelItem,
687
+ {
688
+ channel,
689
+ isSelected,
690
+ unreadCount
691
+ },
692
+ channel.id
693
+ );
694
+ })
695
+ ] }),
696
+ privateChannels.length > 0 && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginBottom: 1, children: [
697
+ /* @__PURE__ */ jsx7(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text5, { bold: true, color: "white", children: "Private Channels" }) }),
698
+ privateChannels.map((channel, idx) => {
699
+ const absoluteIndex = privateStartIndex + idx;
700
+ const isSelected = selectedIndex === absoluteIndex;
701
+ const unreadCount = unreadCounts[channel.slug] || 0;
702
+ return /* @__PURE__ */ jsx7(
703
+ ChannelItem,
704
+ {
705
+ channel,
706
+ isSelected,
707
+ isPrivate: true,
708
+ unreadCount
709
+ },
710
+ channel.id
711
+ );
712
+ })
713
+ ] }),
714
+ /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginBottom: 1, children: [
715
+ privateChannels.length === 0 && /* @__PURE__ */ jsx7(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx7(Text5, { bold: true, color: "white", children: "Private Channels" }) }),
716
+ /* @__PURE__ */ jsx7(
717
+ ActionItem,
718
+ {
719
+ label: "+ Create New Private Channel",
720
+ isSelected: selectedIndex === allChannels.length
721
+ }
722
+ )
723
+ ] }),
724
+ allChannels.length === 0 && /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsx7(Text5, { color: "gray", children: "No channels available" }) })
725
+ ] }),
726
+ /* @__PURE__ */ jsx7(Box6, { paddingRight: 2, paddingTop: 2, children: /* @__PURE__ */ jsx7(AtAGlance, { presenceState: aggregatedPresence }) })
597
727
  ] }),
598
- allChannels.length === 0 && /* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsx6(Text4, { color: "gray", children: "No channels available" }) }),
599
- /* @__PURE__ */ jsx6(Box5, { flexGrow: 1 }),
600
- /* @__PURE__ */ jsxs5(
601
- Box5,
728
+ /* @__PURE__ */ jsx7(Box6, { paddingX: 2, paddingBottom: 2, children: /* @__PURE__ */ jsxs6(
729
+ Box6,
602
730
  {
603
731
  flexDirection: "column",
604
732
  borderStyle: "single",
605
733
  borderColor: "gray",
606
734
  paddingX: 1,
607
735
  children: [
608
- /* @__PURE__ */ jsxs5(Text4, { color: "gray", children: [
609
- /* @__PURE__ */ jsx6(Text4, { color: "cyan", children: "Up/Down" }),
736
+ /* @__PURE__ */ jsxs6(Text5, { color: "gray", children: [
737
+ /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: "Up/Down" }),
610
738
  " Navigate channels"
611
739
  ] }),
612
- /* @__PURE__ */ jsxs5(Text4, { color: "gray", children: [
613
- /* @__PURE__ */ jsx6(Text4, { color: "cyan", children: "Enter" }),
740
+ /* @__PURE__ */ jsxs6(Text5, { color: "gray", children: [
741
+ /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: "Enter" }),
614
742
  " Join selected channel"
615
743
  ] }),
616
- /* @__PURE__ */ jsxs5(Text4, { color: "gray", children: [
617
- /* @__PURE__ */ jsx6(Text4, { color: "cyan", children: "ESC" }),
744
+ /* @__PURE__ */ jsxs6(Text5, { color: "gray", children: [
745
+ /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: "ESC" }),
618
746
  " Back to chat"
619
747
  ] }),
620
- /* @__PURE__ */ jsxs5(Text4, { color: "gray", children: [
621
- /* @__PURE__ */ jsx6(Text4, { color: "cyan", children: "Ctrl+C" }),
748
+ /* @__PURE__ */ jsxs6(Text5, { color: "gray", children: [
749
+ /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: "Ctrl+C" }),
622
750
  " Exit the app"
623
751
  ] })
624
752
  ]
625
753
  }
626
- )
754
+ ) })
627
755
  ] }) })
628
756
  ] });
629
757
  }
630
758
  function ChannelItem({ channel, isSelected, isPrivate = false, unreadCount = 0 }) {
631
- return /* @__PURE__ */ jsxs5(Box5, { marginLeft: 2, children: [
632
- /* @__PURE__ */ jsxs5(Text4, { color: isSelected ? "green" : "white", bold: isSelected, children: [
759
+ return /* @__PURE__ */ jsxs6(Box6, { marginLeft: 2, children: [
760
+ /* @__PURE__ */ jsxs6(Text5, { color: isSelected ? "green" : "white", bold: isSelected, children: [
633
761
  isSelected ? "> " : " ",
634
- isPrivate && /* @__PURE__ */ jsx6(Text4, { color: "yellow", children: "\u{1F512} " }),
762
+ isPrivate && /* @__PURE__ */ jsx7(Text5, { color: "yellow", children: "\u{1F512} " }),
635
763
  "#",
636
764
  channel.name || channel.slug,
637
- unreadCount > 0 && /* @__PURE__ */ jsxs5(Text4, { color: "green", bold: true, children: [
765
+ unreadCount > 0 && /* @__PURE__ */ jsxs6(Text5, { color: "green", bold: true, children: [
638
766
  " ",
639
767
  "(",
640
768
  unreadCount,
641
769
  ")"
642
770
  ] })
643
771
  ] }),
644
- isSelected && channel.description && /* @__PURE__ */ jsxs5(Text4, { color: "gray", dimColor: true, children: [
772
+ isSelected && channel.description && /* @__PURE__ */ jsxs6(Text5, { color: "gray", dimColor: true, children: [
645
773
  " ",
646
774
  "- ",
647
775
  channel.description
@@ -649,7 +777,7 @@ function ChannelItem({ channel, isSelected, isPrivate = false, unreadCount = 0 }
649
777
  ] });
650
778
  }
651
779
  function ActionItem({ label, isSelected }) {
652
- return /* @__PURE__ */ jsx6(Box5, { marginLeft: 2, children: /* @__PURE__ */ jsxs5(Text4, { color: isSelected ? "green" : "cyan", bold: isSelected, children: [
780
+ return /* @__PURE__ */ jsx7(Box6, { marginLeft: 2, children: /* @__PURE__ */ jsxs6(Text5, { color: isSelected ? "green" : "cyan", bold: isSelected, children: [
653
781
  isSelected ? "> " : " ",
654
782
  label
655
783
  ] }) });
@@ -659,13 +787,14 @@ var init_Menu = __esm({
659
787
  "use strict";
660
788
  init_Header();
661
789
  init_Layout();
790
+ init_AtAGlance();
662
791
  init_Router();
663
792
  }
664
793
  });
665
794
 
666
795
  // src/components/MessageItem.tsx
667
- import { Box as Box6, Text as Text5 } from "ink";
668
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
796
+ import { Box as Box7, Text as Text6 } from "ink";
797
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
669
798
  function getUsernameColor(username) {
670
799
  const colors = [
671
800
  "cyan",
@@ -693,30 +822,30 @@ function MessageItem({ message, isOwnMessage }) {
693
822
  const time = formatTime(message.timestamp);
694
823
  const usernameColor = getUsernameColor(message.username);
695
824
  if (isOwnMessage) {
696
- return /* @__PURE__ */ jsx7(Box6, { justifyContent: "flex-end", paddingY: 0, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", alignItems: "flex-end", children: [
697
- /* @__PURE__ */ jsxs6(Box6, { children: [
698
- /* @__PURE__ */ jsxs6(Text5, { color: "gray", children: [
825
+ return /* @__PURE__ */ jsx8(Box7, { justifyContent: "flex-end", paddingY: 0, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", alignItems: "flex-end", children: [
826
+ /* @__PURE__ */ jsxs7(Box7, { children: [
827
+ /* @__PURE__ */ jsxs7(Text6, { color: "gray", children: [
699
828
  "[",
700
829
  time,
701
830
  "] "
702
831
  ] }),
703
- /* @__PURE__ */ jsx7(Text5, { color: usernameColor, bold: true, children: message.username }),
704
- /* @__PURE__ */ jsx7(Text5, { color: "gray", children: " \u2192" })
832
+ /* @__PURE__ */ jsx8(Text6, { color: usernameColor, bold: true, children: message.username }),
833
+ /* @__PURE__ */ jsx8(Text6, { color: "gray", children: " \u2192" })
705
834
  ] }),
706
- /* @__PURE__ */ jsx7(Box6, { paddingLeft: 2, children: /* @__PURE__ */ jsx7(Text5, { children: message.content }) })
835
+ /* @__PURE__ */ jsx8(Box7, { paddingLeft: 2, children: /* @__PURE__ */ jsx8(Text6, { children: message.content }) })
707
836
  ] }) });
708
837
  }
709
- return /* @__PURE__ */ jsx7(Box6, { justifyContent: "flex-start", paddingY: 0, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
710
- /* @__PURE__ */ jsxs6(Box6, { children: [
711
- /* @__PURE__ */ jsx7(Text5, { color: "gray", children: "\u2190 " }),
712
- /* @__PURE__ */ jsx7(Text5, { color: usernameColor, bold: true, children: message.username }),
713
- /* @__PURE__ */ jsxs6(Text5, { color: "gray", children: [
838
+ return /* @__PURE__ */ jsx8(Box7, { justifyContent: "flex-start", paddingY: 0, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
839
+ /* @__PURE__ */ jsxs7(Box7, { children: [
840
+ /* @__PURE__ */ jsx8(Text6, { color: "gray", children: "\u2190 " }),
841
+ /* @__PURE__ */ jsx8(Text6, { color: usernameColor, bold: true, children: message.username }),
842
+ /* @__PURE__ */ jsxs7(Text6, { color: "gray", children: [
714
843
  " [",
715
844
  time,
716
845
  "]"
717
846
  ] })
718
847
  ] }),
719
- /* @__PURE__ */ jsx7(Box6, { paddingLeft: 2, children: /* @__PURE__ */ jsx7(Text5, { children: message.content }) })
848
+ /* @__PURE__ */ jsx8(Box7, { paddingLeft: 2, children: /* @__PURE__ */ jsx8(Text6, { children: message.content }) })
720
849
  ] }) });
721
850
  }
722
851
  var init_MessageItem = __esm({
@@ -727,8 +856,8 @@ var init_MessageItem = __esm({
727
856
 
728
857
  // src/components/MessageList.tsx
729
858
  import { useMemo as useMemo2 } from "react";
730
- import { Box as Box7, Text as Text6 } from "ink";
731
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
859
+ import { Box as Box8, Text as Text7 } from "ink";
860
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
732
861
  function MessageList({
733
862
  messages,
734
863
  currentUsername,
@@ -745,16 +874,16 @@ function MessageList({
745
874
  const startIndex = Math.max(0, endIndex - maxMessages);
746
875
  return messages.slice(startIndex, endIndex);
747
876
  }, [messages, height, scrollOffset]);
748
- return /* @__PURE__ */ jsxs7(
749
- Box7,
877
+ return /* @__PURE__ */ jsxs8(
878
+ Box8,
750
879
  {
751
880
  flexDirection: "column",
752
881
  height,
753
882
  paddingX: 1,
754
883
  overflow: "hidden",
755
884
  children: [
756
- /* @__PURE__ */ jsx8(Box7, { flexGrow: 1 }),
757
- messages.length === 0 ? /* @__PURE__ */ jsx8(Box7, { justifyContent: "center", paddingY: 2, children: /* @__PURE__ */ jsx8(Text6, { color: "gray", children: "No messages yet. Say hello!" }) }) : visibleMessages.map((message) => /* @__PURE__ */ jsx8(
885
+ /* @__PURE__ */ jsx9(Box8, { flexGrow: 1 }),
886
+ messages.length === 0 ? /* @__PURE__ */ jsx9(Box8, { justifyContent: "center", paddingY: 2, children: /* @__PURE__ */ jsx9(Text7, { color: "gray", children: "No messages yet. Say hello!" }) }) : visibleMessages.map((message) => /* @__PURE__ */ jsx9(
758
887
  MessageItem,
759
888
  {
760
889
  message,
@@ -762,12 +891,12 @@ function MessageList({
762
891
  },
763
892
  message.id
764
893
  )),
765
- isDetached && /* @__PURE__ */ jsx8(Box7, { justifyContent: "center", children: /* @__PURE__ */ jsxs7(Text6, { color: "yellow", bold: true, children: [
894
+ isDetached && /* @__PURE__ */ jsx9(Box8, { justifyContent: "center", children: /* @__PURE__ */ jsxs8(Text7, { color: "yellow", bold: true, children: [
766
895
  "-- ",
767
896
  scrollOffset,
768
897
  " more below (\u2193 to scroll down) --"
769
898
  ] }) }),
770
- othersTyping.length > 0 && !isDetached && /* @__PURE__ */ jsx8(Box7, { paddingTop: 1, children: /* @__PURE__ */ jsx8(Text6, { color: "gray", italic: true, children: othersTyping.length === 1 ? `${othersTyping[0]} is typing...` : `${othersTyping.join(", ")} are typing...` }) })
899
+ othersTyping.length > 0 && !isDetached && /* @__PURE__ */ jsx9(Box8, { paddingTop: 1, children: /* @__PURE__ */ jsx9(Text7, { color: "gray", italic: true, children: othersTyping.length === 1 ? `${othersTyping[0]} is typing...` : `${othersTyping.join(", ")} are typing...` }) })
771
900
  ]
772
901
  }
773
902
  );
@@ -780,8 +909,8 @@ var init_MessageList = __esm({
780
909
  });
781
910
 
782
911
  // src/components/UserList.tsx
783
- import { Box as Box8, Text as Text7 } from "ink";
784
- import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
912
+ import { Box as Box9, Text as Text8 } from "ink";
913
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
785
914
  function UserList({
786
915
  users,
787
916
  currentUsername,
@@ -797,8 +926,8 @@ function UserList({
797
926
  if (!a.isOnline && b.isOnline) return 1;
798
927
  return 0;
799
928
  });
800
- return /* @__PURE__ */ jsxs8(
801
- Box8,
929
+ return /* @__PURE__ */ jsxs9(
930
+ Box9,
802
931
  {
803
932
  flexDirection: "column",
804
933
  flexShrink: 0,
@@ -810,36 +939,36 @@ function UserList({
810
939
  marginBottom: 1,
811
940
  overflow: "hidden",
812
941
  children: [
813
- /* @__PURE__ */ jsx9(Box8, { marginBottom: 1, children: isPrivateChannel ? /* @__PURE__ */ jsx9(Text7, { color: "white", bold: true, children: "MEMBERS" }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
814
- /* @__PURE__ */ jsx9(Text7, { color: "green", bold: true, children: "\u25CF " }),
815
- /* @__PURE__ */ jsx9(Text7, { color: "white", bold: true, children: "ONLINE USERS" })
942
+ /* @__PURE__ */ jsx10(Box9, { marginBottom: 1, children: isPrivateChannel ? /* @__PURE__ */ jsx10(Text8, { color: "white", bold: true, children: "MEMBERS" }) : /* @__PURE__ */ jsxs9(Fragment4, { children: [
943
+ /* @__PURE__ */ jsx10(Text8, { color: "green", bold: true, children: "\u25CF " }),
944
+ /* @__PURE__ */ jsx10(Text8, { color: "white", bold: true, children: "ONLINE USERS" })
816
945
  ] }) }),
817
- /* @__PURE__ */ jsx9(Box8, { marginBottom: 1, children: isPrivateChannel ? /* @__PURE__ */ jsxs8(Text7, { color: "cyan", children: [
946
+ /* @__PURE__ */ jsx10(Box9, { marginBottom: 1, children: isPrivateChannel ? /* @__PURE__ */ jsxs9(Text8, { color: "cyan", children: [
818
947
  "[",
819
948
  onlineCount,
820
949
  " online]"
821
- ] }) : /* @__PURE__ */ jsxs8(Text7, { color: "cyan", children: [
950
+ ] }) : /* @__PURE__ */ jsxs9(Text8, { color: "cyan", children: [
822
951
  "[",
823
952
  onlineCount,
824
953
  " connected]"
825
954
  ] }) }),
826
- /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", children: sortedUsers.map((user) => {
955
+ /* @__PURE__ */ jsx10(Box9, { flexDirection: "column", children: sortedUsers.map((user) => {
827
956
  const isTruncated = user.username.length > 8;
828
957
  const displayName = isTruncated ? user.username.substring(0, 8) : user.username;
829
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
830
- /* @__PURE__ */ jsxs8(Box8, { children: [
831
- /* @__PURE__ */ jsx9(Text7, { color: user.isOnline ? "green" : "gray", children: "\u25CF" }),
832
- /* @__PURE__ */ jsx9(Text7, { children: " " }),
833
- /* @__PURE__ */ jsxs8(Text7, { color: user.username === currentUsername ? "yellow" : "white", children: [
958
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
959
+ /* @__PURE__ */ jsxs9(Box9, { children: [
960
+ /* @__PURE__ */ jsx10(Text8, { color: user.isOnline ? "green" : "gray", children: "\u25CF" }),
961
+ /* @__PURE__ */ jsx10(Text8, { children: " " }),
962
+ /* @__PURE__ */ jsxs9(Text8, { color: user.username === currentUsername ? "yellow" : "white", children: [
834
963
  displayName,
835
964
  isTruncated && "\u2026"
836
965
  ] }),
837
- user.username === currentUsername && /* @__PURE__ */ jsx9(Text7, { color: "gray", children: " (you)" }),
838
- user.role === "admin" && /* @__PURE__ */ jsx9(Text7, { color: "yellow", children: " \u2605" })
966
+ user.username === currentUsername && /* @__PURE__ */ jsx10(Text8, { color: "gray", children: " (you)" }),
967
+ user.role === "admin" && /* @__PURE__ */ jsx10(Text8, { color: "yellow", children: " \u2605" })
839
968
  ] }),
840
- user.currentAgent && /* @__PURE__ */ jsx9(Box8, { marginLeft: 2, children: /* @__PURE__ */ jsxs8(Text7, { color: user.currentAgent === "claude" ? "redBright" : "cyan", children: [
969
+ user.currentAgent && /* @__PURE__ */ jsx10(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsxs9(Text8, { color: getAgentColor(user.currentAgent), children: [
841
970
  "\u2937 Using ",
842
- user.currentAgent === "claude" ? "Claude" : "Codex"
971
+ getAgentDisplayName(user.currentAgent)
843
972
  ] }) })
844
973
  ] }, user.username);
845
974
  }) })
@@ -850,14 +979,15 @@ function UserList({
850
979
  var init_UserList = __esm({
851
980
  "src/components/UserList.tsx"() {
852
981
  "use strict";
982
+ init_constants();
853
983
  }
854
984
  });
855
985
 
856
986
  // src/components/InputBox.tsx
857
987
  import { useState as useState4, useCallback as useCallback2, useRef, useEffect as useEffect3 } from "react";
858
- import { Box as Box9, Text as Text8 } from "ink";
988
+ import { Box as Box10, Text as Text9 } from "ink";
859
989
  import TextInput from "ink-text-input";
860
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
990
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
861
991
  function InputBox({
862
992
  onSend,
863
993
  onTypingStart,
@@ -916,8 +1046,8 @@ function InputBox({
916
1046
  }
917
1047
  };
918
1048
  }, []);
919
- return /* @__PURE__ */ jsxs9(
920
- Box9,
1049
+ return /* @__PURE__ */ jsxs10(
1050
+ Box10,
921
1051
  {
922
1052
  borderStyle: "single",
923
1053
  borderColor: "gray",
@@ -926,9 +1056,9 @@ function InputBox({
926
1056
  width: "100%",
927
1057
  flexShrink: 0,
928
1058
  children: [
929
- /* @__PURE__ */ jsxs9(Box9, { children: [
930
- /* @__PURE__ */ jsx10(Text8, { color: "cyan", children: "$ " }),
931
- /* @__PURE__ */ jsx10(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx10(
1059
+ /* @__PURE__ */ jsxs10(Box10, { children: [
1060
+ /* @__PURE__ */ jsx11(Text9, { color: "cyan", children: "$ " }),
1061
+ /* @__PURE__ */ jsx11(Box10, { flexGrow: 1, children: /* @__PURE__ */ jsx11(
932
1062
  TextInput,
933
1063
  {
934
1064
  value,
@@ -937,12 +1067,12 @@ function InputBox({
937
1067
  placeholder: disabled ? "Connecting..." : "Type a message..."
938
1068
  }
939
1069
  ) }),
940
- /* @__PURE__ */ jsxs9(Text8, { color: disabled || !value.trim() ? "gray" : "green", children: [
1070
+ /* @__PURE__ */ jsxs10(Text9, { color: disabled || !value.trim() ? "gray" : "green", children: [
941
1071
  " ",
942
1072
  "[SEND]"
943
1073
  ] })
944
1074
  ] }),
945
- /* @__PURE__ */ jsx10(Box9, { children: /* @__PURE__ */ jsx10(Text8, { color: "gray", dimColor: true, children: "Enter to send" }) })
1075
+ /* @__PURE__ */ jsx11(Box10, { children: /* @__PURE__ */ jsx11(Text9, { color: "gray", dimColor: true, children: "Enter to send" }) })
946
1076
  ]
947
1077
  }
948
1078
  );
@@ -954,8 +1084,8 @@ var init_InputBox = __esm({
954
1084
  });
955
1085
 
956
1086
  // src/components/StatusBar.tsx
957
- import { Box as Box10, Text as Text9 } from "ink";
958
- import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1087
+ import { Box as Box11, Text as Text10 } from "ink";
1088
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
959
1089
  function StatusBar({
960
1090
  connectionStatus,
961
1091
  error,
@@ -963,8 +1093,8 @@ function StatusBar({
963
1093
  }) {
964
1094
  const presenceText = connectionStatus === "connected" ? "Active" : connectionStatus === "connecting" ? "Connecting" : "Disconnected";
965
1095
  const presenceColor = connectionStatus === "connected" ? "green" : connectionStatus === "connecting" ? "yellow" : "red";
966
- return /* @__PURE__ */ jsxs10(
967
- Box10,
1096
+ return /* @__PURE__ */ jsxs11(
1097
+ Box11,
968
1098
  {
969
1099
  borderStyle: "single",
970
1100
  borderColor: "gray",
@@ -973,18 +1103,18 @@ function StatusBar({
973
1103
  width: "100%",
974
1104
  flexShrink: 0,
975
1105
  children: [
976
- /* @__PURE__ */ jsx11(Box10, { children: error ? /* @__PURE__ */ jsxs10(Text9, { color: "red", children: [
1106
+ /* @__PURE__ */ jsx12(Box11, { children: error ? /* @__PURE__ */ jsxs11(Text10, { color: "red", children: [
977
1107
  "[Error: ",
978
1108
  error,
979
1109
  "]"
980
- ] }) : /* @__PURE__ */ jsxs10(Fragment5, { children: [
981
- /* @__PURE__ */ jsx11(Text9, { color: "gray", children: "\u2192 Presence: " }),
982
- /* @__PURE__ */ jsx11(Text9, { color: presenceColor, children: presenceText })
1110
+ ] }) : /* @__PURE__ */ jsxs11(Fragment5, { children: [
1111
+ /* @__PURE__ */ jsx12(Text10, { color: "gray", children: "\u2192 Presence: " }),
1112
+ /* @__PURE__ */ jsx12(Text10, { color: presenceColor, children: presenceText })
983
1113
  ] }) }),
984
- /* @__PURE__ */ jsxs10(Box10, { children: [
985
- /* @__PURE__ */ jsx11(Text9, { color: "gray", children: "Users: " }),
986
- /* @__PURE__ */ jsx11(Text9, { color: "cyan", children: userCount }),
987
- /* @__PURE__ */ jsx11(Text9, { color: "gray", children: " | \u2191/\u2193 scroll | Ctrl+E users | Ctrl+C exit" })
1114
+ /* @__PURE__ */ jsxs11(Box11, { children: [
1115
+ /* @__PURE__ */ jsx12(Text10, { color: "gray", children: "Users: " }),
1116
+ /* @__PURE__ */ jsx12(Text10, { color: "cyan", children: userCount }),
1117
+ /* @__PURE__ */ jsx12(Text10, { color: "gray", children: " | \u2191/\u2193 scroll | Ctrl+E users | Ctrl+C exit" })
988
1118
  ] })
989
1119
  ]
990
1120
  }
@@ -997,21 +1127,21 @@ var init_StatusBar = __esm({
997
1127
  });
998
1128
 
999
1129
  // src/components/ToolTip.tsx
1000
- import { Box as Box11, Text as Text10 } from "ink";
1001
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1130
+ import { Box as Box12, Text as Text11 } from "ink";
1131
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1002
1132
  var ToolTip;
1003
1133
  var init_ToolTip = __esm({
1004
1134
  "src/components/ToolTip.tsx"() {
1005
1135
  "use strict";
1006
1136
  ToolTip = ({ tips, type }) => {
1007
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, children: [
1008
- /* @__PURE__ */ jsx12(Text10, { children: " " }),
1009
- type === "Command" && tips.map((tip) => /* @__PURE__ */ jsxs11(Text10, { color: "gray", children: [
1010
- /* @__PURE__ */ jsx12(Text10, { color: "cyan", children: tip.syntax }),
1137
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, children: [
1138
+ /* @__PURE__ */ jsx13(Text11, { children: " " }),
1139
+ type === "Command" && tips.map((tip) => /* @__PURE__ */ jsxs12(Text11, { color: "gray", children: [
1140
+ /* @__PURE__ */ jsx13(Text11, { color: "cyan", children: tip.syntax }),
1011
1141
  " - ",
1012
1142
  tip.description
1013
1143
  ] }, tip.name)),
1014
- type === "User" && tips.map((suggestion) => /* @__PURE__ */ jsx12(Text10, { color: "gray", children: /* @__PURE__ */ jsx12(Text10, { color: "cyan", children: suggestion }) }, suggestion))
1144
+ type === "User" && tips.map((suggestion) => /* @__PURE__ */ jsx13(Text11, { color: "gray", children: /* @__PURE__ */ jsx13(Text11, { color: "cyan", children: suggestion }) }, suggestion))
1015
1145
  ] });
1016
1146
  };
1017
1147
  }
@@ -1592,8 +1722,8 @@ var init_use_command_input = __esm({
1592
1722
 
1593
1723
  // src/components/ChatView.tsx
1594
1724
  import { useEffect as useEffect5 } from "react";
1595
- import { Box as Box12, Text as Text11, useStdout as useStdout3 } from "ink";
1596
- import { Fragment as Fragment6, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1725
+ import { Box as Box13, Text as Text12, useStdout as useStdout3 } from "ink";
1726
+ import { Fragment as Fragment6, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
1597
1727
  function ChatView({
1598
1728
  terminalSize,
1599
1729
  currentChannel,
@@ -1638,34 +1768,34 @@ function ChatView({
1638
1768
  const prefix = connectionStatus === "connected" ? "\u2022 " : "";
1639
1769
  stdout.write(`\x1B]0;${prefix}#${displayName}\x07`);
1640
1770
  }, [stdout, connectionStatus, displayName]);
1641
- return /* @__PURE__ */ jsxs12(Layout, { width: terminalSize.columns, height: terminalSize.rows, topPadding, children: [
1642
- /* @__PURE__ */ jsx13(Layout.Header, { children: /* @__PURE__ */ jsx13(
1771
+ return /* @__PURE__ */ jsxs13(Layout, { width: terminalSize.columns, height: terminalSize.rows, topPadding, children: [
1772
+ /* @__PURE__ */ jsx14(Layout.Header, { children: /* @__PURE__ */ jsx14(
1643
1773
  Header,
1644
1774
  {
1645
1775
  username,
1646
1776
  roomName: currentChannel,
1647
1777
  connectionStatus,
1648
1778
  onLogout,
1649
- title: /* @__PURE__ */ jsxs12(Fragment6, { children: [
1650
- /* @__PURE__ */ jsx13(Text11, { color: "gray", children: "\u2190 Menu " }),
1651
- /* @__PURE__ */ jsx13(Text11, { color: "gray", dimColor: true, children: "[CTRL+Q]" }),
1652
- /* @__PURE__ */ jsx13(Text11, { color: "gray", children: " | " }),
1653
- /* @__PURE__ */ jsxs12(Text11, { color: "cyan", bold: true, children: [
1779
+ title: /* @__PURE__ */ jsxs13(Fragment6, { children: [
1780
+ /* @__PURE__ */ jsx14(Text12, { color: "gray", children: "\u2190 Menu " }),
1781
+ /* @__PURE__ */ jsx14(Text12, { color: "gray", dimColor: true, children: "[CTRL+Q]" }),
1782
+ /* @__PURE__ */ jsx14(Text12, { color: "gray", children: " | " }),
1783
+ /* @__PURE__ */ jsxs13(Text12, { color: "cyan", bold: true, children: [
1654
1784
  "#",
1655
1785
  displayText
1656
1786
  ] })
1657
1787
  ] })
1658
1788
  }
1659
1789
  ) }),
1660
- /* @__PURE__ */ jsxs12(Layout.Content, { children: [
1661
- /* @__PURE__ */ jsxs12(
1662
- Box12,
1790
+ /* @__PURE__ */ jsxs13(Layout.Content, { children: [
1791
+ /* @__PURE__ */ jsxs13(
1792
+ Box13,
1663
1793
  {
1664
1794
  flexDirection: "row",
1665
1795
  height: Math.max(1, middleSectionHeight - tooltip.height),
1666
1796
  overflow: "hidden",
1667
1797
  children: [
1668
- /* @__PURE__ */ jsx13(Box12, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: /* @__PURE__ */ jsx13(
1798
+ /* @__PURE__ */ jsx14(Box13, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: /* @__PURE__ */ jsx14(
1669
1799
  MessageList,
1670
1800
  {
1671
1801
  messages,
@@ -1676,7 +1806,7 @@ function ChatView({
1676
1806
  isDetached
1677
1807
  }
1678
1808
  ) }),
1679
- showUserList && /* @__PURE__ */ jsx13(
1809
+ showUserList && /* @__PURE__ */ jsx14(
1680
1810
  UserList,
1681
1811
  {
1682
1812
  users,
@@ -1688,8 +1818,8 @@ function ChatView({
1688
1818
  ]
1689
1819
  }
1690
1820
  ),
1691
- tooltip.show && tooltip.tips.length > 0 && /* @__PURE__ */ jsx13(ToolTip, { tips: tooltip.tips, type: tooltip.type }),
1692
- /* @__PURE__ */ jsx13(
1821
+ tooltip.show && tooltip.tips.length > 0 && /* @__PURE__ */ jsx14(ToolTip, { tips: tooltip.tips, type: tooltip.type }),
1822
+ /* @__PURE__ */ jsx14(
1693
1823
  InputBox,
1694
1824
  {
1695
1825
  onSend: handleSubmit,
@@ -1699,7 +1829,7 @@ function ChatView({
1699
1829
  disabled: isInputDisabled
1700
1830
  }
1701
1831
  ),
1702
- /* @__PURE__ */ jsx13(
1832
+ /* @__PURE__ */ jsx14(
1703
1833
  StatusBar,
1704
1834
  {
1705
1835
  connectionStatus,
@@ -1726,9 +1856,9 @@ var init_ChatView = __esm({
1726
1856
 
1727
1857
  // src/components/CreateChannelScreen.tsx
1728
1858
  import { useState as useState7, useEffect as useEffect6 } from "react";
1729
- import { Box as Box13, Text as Text12, useInput as useInput5, useStdout as useStdout4 } from "ink";
1859
+ import { Box as Box14, Text as Text13, useInput as useInput5, useStdout as useStdout4 } from "ink";
1730
1860
  import TextInput2 from "ink-text-input";
1731
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
1861
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
1732
1862
  function CreateChannelScreen({
1733
1863
  width,
1734
1864
  height,
@@ -1807,65 +1937,65 @@ function CreateChannelScreen({
1807
1937
  };
1808
1938
  const headerHeight = 3;
1809
1939
  const contentHeight = height - topPadding - headerHeight;
1810
- return /* @__PURE__ */ jsxs13(Layout, { width, height, topPadding, children: [
1811
- /* @__PURE__ */ jsx14(Layout.Header, { children: /* @__PURE__ */ jsx14(
1940
+ return /* @__PURE__ */ jsxs14(Layout, { width, height, topPadding, children: [
1941
+ /* @__PURE__ */ jsx15(Layout.Header, { children: /* @__PURE__ */ jsx15(
1812
1942
  Header,
1813
1943
  {
1814
1944
  username,
1815
1945
  roomName: "Create Channel",
1816
1946
  connectionStatus,
1817
1947
  onLogout,
1818
- title: /* @__PURE__ */ jsx14(Text12, { bold: true, color: "cyan", children: "Create New Private Channel" }),
1948
+ title: /* @__PURE__ */ jsx15(Text13, { bold: true, color: "cyan", children: "Create New Private Channel" }),
1819
1949
  showStatus: false
1820
1950
  }
1821
1951
  ) }),
1822
- /* @__PURE__ */ jsx14(Layout.Content, { children: /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", height: contentHeight, padding: 2, children: [
1823
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginBottom: 1, children: [
1824
- /* @__PURE__ */ jsx14(Box13, { marginBottom: 0, children: /* @__PURE__ */ jsxs13(Text12, { bold: true, color: activeField === "name" ? "green" : "white", children: [
1952
+ /* @__PURE__ */ jsx15(Layout.Content, { children: /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", height: contentHeight, padding: 2, children: [
1953
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", marginBottom: 1, children: [
1954
+ /* @__PURE__ */ jsx15(Box14, { marginBottom: 0, children: /* @__PURE__ */ jsxs14(Text13, { bold: true, color: activeField === "name" ? "green" : "white", children: [
1825
1955
  "Channel Name ",
1826
1956
  activeField === "name" ? "(editing)" : ""
1827
1957
  ] }) }),
1828
- /* @__PURE__ */ jsx14(
1829
- Box13,
1958
+ /* @__PURE__ */ jsx15(
1959
+ Box14,
1830
1960
  {
1831
1961
  borderStyle: "single",
1832
1962
  borderColor: activeField === "name" ? "green" : "gray",
1833
1963
  paddingX: 1,
1834
- children: activeField === "name" ? /* @__PURE__ */ jsx14(
1964
+ children: activeField === "name" ? /* @__PURE__ */ jsx15(
1835
1965
  TextInput2,
1836
1966
  {
1837
1967
  value: name,
1838
1968
  onChange: setName,
1839
1969
  placeholder: "Enter channel name..."
1840
1970
  }
1841
- ) : /* @__PURE__ */ jsx14(Text12, { color: name ? "white" : "gray", children: name || "Enter channel name..." })
1971
+ ) : /* @__PURE__ */ jsx15(Text13, { color: name ? "white" : "gray", children: name || "Enter channel name..." })
1842
1972
  }
1843
1973
  )
1844
1974
  ] }),
1845
- /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginBottom: 1, children: [
1846
- /* @__PURE__ */ jsx14(Box13, { marginBottom: 0, children: /* @__PURE__ */ jsxs13(Text12, { bold: true, color: activeField === "description" ? "green" : "white", children: [
1975
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", marginBottom: 1, children: [
1976
+ /* @__PURE__ */ jsx15(Box14, { marginBottom: 0, children: /* @__PURE__ */ jsxs14(Text13, { bold: true, color: activeField === "description" ? "green" : "white", children: [
1847
1977
  "Description (optional) ",
1848
1978
  activeField === "description" ? "(editing)" : ""
1849
1979
  ] }) }),
1850
- /* @__PURE__ */ jsx14(
1851
- Box13,
1980
+ /* @__PURE__ */ jsx15(
1981
+ Box14,
1852
1982
  {
1853
1983
  borderStyle: "single",
1854
1984
  borderColor: activeField === "description" ? "green" : "gray",
1855
1985
  paddingX: 1,
1856
- children: activeField === "description" ? /* @__PURE__ */ jsx14(
1986
+ children: activeField === "description" ? /* @__PURE__ */ jsx15(
1857
1987
  TextInput2,
1858
1988
  {
1859
1989
  value: description,
1860
1990
  onChange: setDescription,
1861
1991
  placeholder: "Enter channel description..."
1862
1992
  }
1863
- ) : /* @__PURE__ */ jsx14(Text12, { color: description ? "white" : "gray", children: description || "Enter channel description..." })
1993
+ ) : /* @__PURE__ */ jsx15(Text13, { color: description ? "white" : "gray", children: description || "Enter channel description..." })
1864
1994
  }
1865
1995
  )
1866
1996
  ] }),
1867
- /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs13(
1868
- Text12,
1997
+ /* @__PURE__ */ jsx15(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs14(
1998
+ Text13,
1869
1999
  {
1870
2000
  color: activeField === "submit" ? "green" : "white",
1871
2001
  bold: activeField === "submit",
@@ -1877,30 +2007,30 @@ function CreateChannelScreen({
1877
2007
  ]
1878
2008
  }
1879
2009
  ) }),
1880
- error && /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { color: "red", children: error }) }),
1881
- /* @__PURE__ */ jsx14(Box13, { flexGrow: 1 }),
1882
- /* @__PURE__ */ jsxs13(
1883
- Box13,
2010
+ error && /* @__PURE__ */ jsx15(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "red", children: error }) }),
2011
+ /* @__PURE__ */ jsx15(Box14, { flexGrow: 1 }),
2012
+ /* @__PURE__ */ jsxs14(
2013
+ Box14,
1884
2014
  {
1885
2015
  flexDirection: "column",
1886
2016
  borderStyle: "single",
1887
2017
  borderColor: "gray",
1888
2018
  paddingX: 1,
1889
2019
  children: [
1890
- /* @__PURE__ */ jsxs13(Text12, { color: "gray", children: [
1891
- /* @__PURE__ */ jsx14(Text12, { color: "cyan", children: "Tab/Down" }),
2020
+ /* @__PURE__ */ jsxs14(Text13, { color: "gray", children: [
2021
+ /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: "Tab/Down" }),
1892
2022
  " Next field"
1893
2023
  ] }),
1894
- /* @__PURE__ */ jsxs13(Text12, { color: "gray", children: [
1895
- /* @__PURE__ */ jsx14(Text12, { color: "cyan", children: "Shift+Tab/Up" }),
2024
+ /* @__PURE__ */ jsxs14(Text13, { color: "gray", children: [
2025
+ /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: "Shift+Tab/Up" }),
1896
2026
  " Previous field"
1897
2027
  ] }),
1898
- /* @__PURE__ */ jsxs13(Text12, { color: "gray", children: [
1899
- /* @__PURE__ */ jsx14(Text12, { color: "cyan", children: "Enter" }),
2028
+ /* @__PURE__ */ jsxs14(Text13, { color: "gray", children: [
2029
+ /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: "Enter" }),
1900
2030
  " Submit (when on button)"
1901
2031
  ] }),
1902
- /* @__PURE__ */ jsxs13(Text12, { color: "gray", children: [
1903
- /* @__PURE__ */ jsx14(Text12, { color: "cyan", children: "ESC" }),
2032
+ /* @__PURE__ */ jsxs14(Text13, { color: "gray", children: [
2033
+ /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: "ESC" }),
1904
2034
  " Back to menu"
1905
2035
  ] })
1906
2036
  ]
@@ -2303,6 +2433,50 @@ var init_channel_manager = __esm({
2303
2433
  const channelState = this.channelStates.get(channelSlug);
2304
2434
  return channelState?.presence || {};
2305
2435
  }
2436
+ /**
2437
+ * Get aggregated presence across all channels, deduplicated by user_id.
2438
+ * When a user appears in multiple channels, we prefer:
2439
+ * 1. Presence with current_agent set (if available)
2440
+ * 2. Most recent online_at timestamp
2441
+ */
2442
+ getAggregatedPresence() {
2443
+ const userMap = /* @__PURE__ */ new Map();
2444
+ this.channelStates.forEach((channelState) => {
2445
+ Object.entries(channelState.presence).forEach(([username, data]) => {
2446
+ const meta = data.metas[0];
2447
+ if (!meta) return;
2448
+ const userId = meta.user_id;
2449
+ const hasAgent = !!meta.current_agent;
2450
+ const onlineAt = meta.online_at;
2451
+ const existing = userMap.get(userId);
2452
+ if (!existing) {
2453
+ userMap.set(userId, {
2454
+ username,
2455
+ metas: data.metas,
2456
+ online_at: onlineAt,
2457
+ has_agent: hasAgent
2458
+ });
2459
+ } else {
2460
+ const shouldReplace = hasAgent && !existing.has_agent || hasAgent === existing.has_agent && onlineAt > existing.online_at;
2461
+ if (shouldReplace) {
2462
+ userMap.set(userId, {
2463
+ username,
2464
+ metas: data.metas,
2465
+ online_at: onlineAt,
2466
+ has_agent: hasAgent
2467
+ });
2468
+ }
2469
+ }
2470
+ });
2471
+ });
2472
+ const aggregated = {};
2473
+ userMap.forEach((data) => {
2474
+ aggregated[data.username] = {
2475
+ metas: data.metas
2476
+ };
2477
+ });
2478
+ return aggregated;
2479
+ }
2306
2480
  /**
2307
2481
  * Get buffered real-time messages for a specific channel.
2308
2482
  * These are messages that arrived while viewing other channels.
@@ -2369,6 +2543,20 @@ var init_channel_manager = __esm({
2369
2543
  });
2370
2544
  });
2371
2545
  }
2546
+ /**
2547
+ * Best-effort mark as read without waiting for an ack.
2548
+ * Useful during shutdown paths to avoid timeouts.
2549
+ */
2550
+ markChannelAsReadBestEffort(channelSlug) {
2551
+ const channelState = this.channelStates.get(channelSlug);
2552
+ if (!channelState || !channelState.channel) {
2553
+ return;
2554
+ }
2555
+ try {
2556
+ channelState.channel.push("mark_as_read", {});
2557
+ } catch {
2558
+ }
2559
+ }
2372
2560
  /**
2373
2561
  * Mark all messages in channel as read (used when first joining).
2374
2562
  * Gracefully handles disconnected channels (returns silently during shutdown).
@@ -2431,6 +2619,7 @@ function useMultiChannelChat(token, currentChannel, onChannelListChanged) {
2431
2619
  const [typingUsers, setTypingUsers] = useState8([]);
2432
2620
  const [presenceState, setPresenceState] = useState8({});
2433
2621
  const [subscribers, setSubscribers] = useState8([]);
2622
+ const [channelsReady, setChannelsReady] = useState8(false);
2434
2623
  const managerRef = useRef3(null);
2435
2624
  const prevChannelRef = useRef3(null);
2436
2625
  const isLoadingHistory = useRef3(false);
@@ -2440,6 +2629,7 @@ function useMultiChannelChat(token, currentChannel, onChannelListChanged) {
2440
2629
  managerRef.current.disconnect();
2441
2630
  managerRef.current = null;
2442
2631
  }
2632
+ setChannelsReady(false);
2443
2633
  return;
2444
2634
  }
2445
2635
  if (managerRef.current) {
@@ -2545,6 +2735,7 @@ function useMultiChannelChat(token, currentChannel, onChannelListChanged) {
2545
2735
  ...channelsResponse.channels.private
2546
2736
  ];
2547
2737
  await manager.subscribeToChannels(allChannels);
2738
+ setChannelsReady(true);
2548
2739
  setError(null);
2549
2740
  } catch (err) {
2550
2741
  setError(err instanceof Error ? err.message : "Connection failed");
@@ -2557,6 +2748,7 @@ function useMultiChannelChat(token, currentChannel, onChannelListChanged) {
2557
2748
  managerRef.current.disconnect();
2558
2749
  managerRef.current = null;
2559
2750
  }
2751
+ setChannelsReady(false);
2560
2752
  };
2561
2753
  }, [token]);
2562
2754
  useEffect7(() => {
@@ -2639,7 +2831,7 @@ function useMultiChannelChat(token, currentChannel, onChannelListChanged) {
2639
2831
  // No-op for backward compatibility
2640
2832
  disconnect,
2641
2833
  // No-op for backward compatibility
2642
- channelManager: managerRef.current
2834
+ channelManager: channelsReady ? managerRef.current : null
2643
2835
  };
2644
2836
  }
2645
2837
  var init_use_multi_channel_chat = __esm({
@@ -2700,19 +2892,21 @@ var init_use_presence = __esm({
2700
2892
  // src/hooks/use-agent-detection.ts
2701
2893
  import { useEffect as useEffect8, useRef as useRef4, useCallback as useCallback4 } from "react";
2702
2894
  import { execSync as execSync2 } from "child_process";
2703
- function isProcessRunning(processName) {
2895
+ function detectCurrentAgent() {
2704
2896
  try {
2705
- execSync2(`pgrep -nx ${processName}`, { stdio: "pipe" });
2706
- return true;
2897
+ const result = execSync2(`ps -p $(pgrep -x -n 'codex|claude|Cursor|Windsurf\\ Helper') -o comm=`, {
2898
+ stdio: "pipe",
2899
+ encoding: "utf-8"
2900
+ }).trim();
2901
+ if (result.includes("@openai/codex")) return "codex";
2902
+ if (result === "claude") return "claude";
2903
+ if (result.includes("Cursor.app")) return "cursor";
2904
+ if (result.includes("Windsurf.app")) return "windsurf";
2905
+ return null;
2707
2906
  } catch {
2708
- return false;
2907
+ return null;
2709
2908
  }
2710
2909
  }
2711
- function detectCurrentAgent() {
2712
- if (isProcessRunning("codex")) return "codex";
2713
- if (isProcessRunning("claude")) return "claude";
2714
- return null;
2715
- }
2716
2910
  function useAgentDetection(channelManager, isConnected) {
2717
2911
  const lastSentAgentRef = useRef4(void 0);
2718
2912
  const broadcastAgentUpdate = useCallback4(
@@ -2818,10 +3012,10 @@ __export(App_exports, {
2818
3012
  App: () => App
2819
3013
  });
2820
3014
  import { useState as useState10, useEffect as useEffect10, useCallback as useCallback6, useRef as useRef5 } from "react";
2821
- import { Box as Box14, useApp as useApp2, useInput as useInput6, useStdout as useStdout5 } from "ink";
2822
- import { jsx as jsx15 } from "react/jsx-runtime";
3015
+ import { Box as Box15, useApp as useApp2, useInput as useInput6, useStdout as useStdout5 } from "ink";
3016
+ import { jsx as jsx16 } from "react/jsx-runtime";
2823
3017
  function App() {
2824
- return /* @__PURE__ */ jsx15(Router, { initialRoute: "menu", children: /* @__PURE__ */ jsx15(AppContent, {}) });
3018
+ return /* @__PURE__ */ jsx16(Router, { initialRoute: "menu", children: /* @__PURE__ */ jsx16(AppContent, {}) });
2825
3019
  }
2826
3020
  function AppContent() {
2827
3021
  const { exit } = useApp2();
@@ -2940,14 +3134,7 @@ function AppContent() {
2940
3134
  useEffect10(() => {
2941
3135
  return () => {
2942
3136
  if (currentChannel && channelManager) {
2943
- const markOnUnmount = async () => {
2944
- try {
2945
- await channelManager.markChannelAsRead(currentChannel);
2946
- } catch (err) {
2947
- console.error("Failed to mark as read on unmount:", err);
2948
- }
2949
- };
2950
- markOnUnmount();
3137
+ channelManager.markChannelAsReadBestEffort(currentChannel);
2951
3138
  }
2952
3139
  };
2953
3140
  }, [currentChannel, channelManager]);
@@ -2974,11 +3161,7 @@ function AppContent() {
2974
3161
  }, []);
2975
3162
  const handleLogout = useCallback6(async () => {
2976
3163
  if (currentChannel && channelManager) {
2977
- try {
2978
- await channelManager.markChannelAsRead(currentChannel);
2979
- } catch (err) {
2980
- console.error("Failed to mark as read on logout:", err);
2981
- }
3164
+ channelManager.markChannelAsReadBestEffort(currentChannel);
2982
3165
  }
2983
3166
  disconnect();
2984
3167
  setToken(null);
@@ -3009,18 +3192,11 @@ function AppContent() {
3009
3192
  const maxVisibleMessages = Math.floor(middleSectionHeight / linesPerMessage);
3010
3193
  useInput6((input, key) => {
3011
3194
  if (input === "c" && key.ctrl) {
3012
- const handleExit = async () => {
3013
- if (currentChannel && channelManager) {
3014
- try {
3015
- await channelManager.markChannelAsRead(currentChannel);
3016
- } catch (err) {
3017
- console.error("Failed to mark as read on exit:", err);
3018
- }
3019
- }
3020
- disconnect();
3021
- exit();
3022
- };
3023
- handleExit();
3195
+ if (currentChannel && channelManager) {
3196
+ channelManager.markChannelAsReadBestEffort(currentChannel);
3197
+ }
3198
+ disconnect();
3199
+ exit();
3024
3200
  }
3025
3201
  if (input === "o" && key.ctrl && authState === "authenticated") {
3026
3202
  handleLogout();
@@ -3054,14 +3230,14 @@ function AppContent() {
3054
3230
  }
3055
3231
  });
3056
3232
  if (authState !== "authenticated") {
3057
- return /* @__PURE__ */ jsx15(
3058
- Box14,
3233
+ return /* @__PURE__ */ jsx16(
3234
+ Box15,
3059
3235
  {
3060
3236
  flexDirection: "column",
3061
3237
  width: terminalSize.columns,
3062
3238
  height: terminalSize.rows,
3063
3239
  overflow: "hidden",
3064
- children: /* @__PURE__ */ jsx15(
3240
+ children: /* @__PURE__ */ jsx16(
3065
3241
  LoginScreen,
3066
3242
  {
3067
3243
  onLogin: handleLogin,
@@ -3073,14 +3249,15 @@ function AppContent() {
3073
3249
  );
3074
3250
  }
3075
3251
  if (route === "menu") {
3076
- return /* @__PURE__ */ jsx15(
3077
- Box14,
3252
+ const aggregatedPresence = channelManager?.getAggregatedPresence() || {};
3253
+ return /* @__PURE__ */ jsx16(
3254
+ Box15,
3078
3255
  {
3079
3256
  flexDirection: "column",
3080
3257
  width: terminalSize.columns,
3081
3258
  height: terminalSize.rows,
3082
3259
  overflow: "hidden",
3083
- children: /* @__PURE__ */ jsx15(
3260
+ children: /* @__PURE__ */ jsx16(
3084
3261
  Menu,
3085
3262
  {
3086
3263
  width: terminalSize.columns,
@@ -3093,21 +3270,22 @@ function AppContent() {
3093
3270
  topPadding,
3094
3271
  publicChannels,
3095
3272
  privateChannels,
3096
- unreadCounts
3273
+ unreadCounts,
3274
+ aggregatedPresence
3097
3275
  }
3098
3276
  )
3099
3277
  }
3100
3278
  );
3101
3279
  }
3102
3280
  if (route === "create-channel") {
3103
- return /* @__PURE__ */ jsx15(
3104
- Box14,
3281
+ return /* @__PURE__ */ jsx16(
3282
+ Box15,
3105
3283
  {
3106
3284
  flexDirection: "column",
3107
3285
  width: terminalSize.columns,
3108
3286
  height: terminalSize.rows,
3109
3287
  overflow: "hidden",
3110
- children: /* @__PURE__ */ jsx15(
3288
+ children: /* @__PURE__ */ jsx16(
3111
3289
  CreateChannelScreen,
3112
3290
  {
3113
3291
  width: terminalSize.columns,
@@ -3122,7 +3300,7 @@ function AppContent() {
3122
3300
  }
3123
3301
  );
3124
3302
  }
3125
- return /* @__PURE__ */ jsx15(
3303
+ return /* @__PURE__ */ jsx16(
3126
3304
  ChatView,
3127
3305
  {
3128
3306
  terminalSize,
@@ -3241,7 +3419,7 @@ function getUpdateCommand() {
3241
3419
  // src/components/UpdatePrompt.tsx
3242
3420
  import { useState } from "react";
3243
3421
  import { Box, Text, useInput, useApp } from "ink";
3244
- import { execSync } from "child_process";
3422
+ import { execSync, spawn } from "child_process";
3245
3423
  import { jsx, jsxs } from "react/jsx-runtime";
3246
3424
  var options = [
3247
3425
  { label: "Update now", value: "update" },
@@ -3276,8 +3454,14 @@ function UpdatePrompt({ updateInfo, onComplete }) {
3276
3454
  const command = getUpdateCommand();
3277
3455
  try {
3278
3456
  execSync(command, { stdio: "inherit" });
3279
- console.log("\n\nUpdate complete! Please restart groupchat.\n");
3280
- exit();
3457
+ console.log("\n\nUpdate complete! Restarting...\n");
3458
+ const args = process.argv.slice(2);
3459
+ const child = spawn("groupchat", args, {
3460
+ detached: true,
3461
+ stdio: "inherit"
3462
+ });
3463
+ child.unref();
3464
+ setTimeout(() => exit(), 100);
3281
3465
  } catch (err) {
3282
3466
  setUpdateError(
3283
3467
  `Update failed. Please run manually: ${command}`
@@ -3333,7 +3517,7 @@ function UpdatePrompt({ updateInfo, onComplete }) {
3333
3517
  // package.json
3334
3518
  var package_default = {
3335
3519
  name: "groupchat",
3336
- version: "0.0.7",
3520
+ version: "0.0.9",
3337
3521
  description: "CLI chat client for Groupchat",
3338
3522
  type: "module",
3339
3523
  main: "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groupchat",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "CLI chat client for Groupchat",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",