agenthud 0.5.10 → 0.5.12

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 +556 -110
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { existsSync } from "fs";
7
7
 
8
8
  // src/ui/App.tsx
9
9
  import React, { useState, useEffect, useCallback, useMemo } from "react";
10
- import { Box as Box7, Text as Text7, useApp, useInput, useStdout } from "ink";
10
+ import { Box as Box8, Text as Text8, useApp, useInput, useStdout } from "ink";
11
11
 
12
12
  // src/ui/GitPanel.tsx
13
13
  import { Box, Text } from "ink";
@@ -443,6 +443,20 @@ function formatCountdown3(seconds) {
443
443
  const padded = String(seconds).padStart(2, " ");
444
444
  return `\u21BB ${padded}s`;
445
445
  }
446
+ function formatElapsedTime(startTime) {
447
+ if (!startTime) return "";
448
+ const elapsed = Date.now() - startTime.getTime();
449
+ const minutes = Math.floor(elapsed / 6e4);
450
+ const hours = Math.floor(minutes / 60);
451
+ const remainingMinutes = minutes % 60;
452
+ if (hours > 0) {
453
+ return `${hours}h ${remainingMinutes}m`;
454
+ }
455
+ if (minutes > 0) {
456
+ return `${minutes}m`;
457
+ }
458
+ return "<1m";
459
+ }
446
460
  function getStatusIcon(status) {
447
461
  switch (status) {
448
462
  case "running":
@@ -513,7 +527,8 @@ function ClaudePanel({
513
527
  const contentWidth = innerWidth - 1;
514
528
  const { state } = data;
515
529
  const statusIcon = getStatusIcon(state.status);
516
- const titleSuffix = countdownSuffix;
530
+ const elapsedTime = formatElapsedTime(state.sessionStartTime);
531
+ const titleSuffix = elapsedTime ? countdownSuffix ? `${elapsedTime} \xB7 ${countdownSuffix}` : elapsedTime : countdownSuffix;
517
532
  if (data.error) {
518
533
  const errorPadding = Math.max(0, contentWidth - data.error.length);
519
534
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, children: [
@@ -547,7 +562,7 @@ function ClaudePanel({
547
562
  const noActiveText = "No active session";
548
563
  const noActivePadding = Math.max(0, contentWidth - noActiveText.length);
549
564
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, children: [
550
- /* @__PURE__ */ jsx4(Text4, { children: createTitleLine("Claude", countdownSuffix, width) }),
565
+ /* @__PURE__ */ jsx4(Text4, { children: createTitleLine("Claude", titleSuffix, width) }),
551
566
  /* @__PURE__ */ jsxs4(Text4, { children: [
552
567
  BOX.v,
553
568
  " ",
@@ -599,9 +614,161 @@ function ClaudePanel({
599
614
  ] });
600
615
  }
601
616
 
602
- // src/ui/GenericPanel.tsx
617
+ // src/ui/OtherSessionsPanel.tsx
603
618
  import { Box as Box5, Text as Text5 } from "ink";
604
- import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
619
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
620
+ function formatCountdown4(seconds) {
621
+ if (seconds == null) return "";
622
+ const padded = String(seconds).padStart(2, " ");
623
+ return `\u21BB ${padded}s`;
624
+ }
625
+ function truncateMessage(message, maxLength) {
626
+ if (getDisplayWidth(message) <= maxLength) {
627
+ return message;
628
+ }
629
+ let truncated = "";
630
+ let currentWidth = 0;
631
+ for (const char of message) {
632
+ const charWidth = getDisplayWidth(char);
633
+ if (currentWidth + charWidth > maxLength - 3) {
634
+ truncated += "...";
635
+ break;
636
+ }
637
+ truncated += char;
638
+ currentWidth += charWidth;
639
+ }
640
+ return truncated;
641
+ }
642
+ function formatProjectNames(projectNames, maxWidth) {
643
+ if (projectNames.length === 0) {
644
+ return "No projects";
645
+ }
646
+ const MAX_NAMES_TO_SHOW = 3;
647
+ const remaining = projectNames.length - MAX_NAMES_TO_SHOW;
648
+ let namesToShow = projectNames.slice(0, MAX_NAMES_TO_SHOW);
649
+ let suffix = remaining > 0 ? ` +${remaining}` : "";
650
+ const emojiWidth = getDisplayWidth("\u{1F4C1}");
651
+ const availableWidth = maxWidth - emojiWidth - 1;
652
+ let text = namesToShow.join(", ") + suffix;
653
+ if (text.length <= availableWidth) {
654
+ return text;
655
+ }
656
+ for (let count = MAX_NAMES_TO_SHOW - 1; count >= 1; count--) {
657
+ namesToShow = projectNames.slice(0, count);
658
+ const newRemaining = projectNames.length - count;
659
+ suffix = newRemaining > 0 ? ` +${newRemaining}` : "";
660
+ text = namesToShow.join(", ") + suffix;
661
+ if (text.length <= availableWidth) {
662
+ return text;
663
+ }
664
+ }
665
+ const firstProject = projectNames[0];
666
+ const remainingCount = projectNames.length - 1;
667
+ suffix = remainingCount > 0 ? ` +${remainingCount}` : "";
668
+ const suffixLen = suffix.length;
669
+ const maxNameLen = availableWidth - suffixLen - 3;
670
+ if (maxNameLen > 0) {
671
+ return firstProject.slice(0, maxNameLen) + "..." + suffix;
672
+ }
673
+ return "...";
674
+ }
675
+ function OtherSessionsPanel({
676
+ data,
677
+ countdown,
678
+ width = DEFAULT_PANEL_WIDTH,
679
+ isRunning = false,
680
+ messageMaxLength = 50
681
+ }) {
682
+ const countdownSuffix = isRunning ? "running..." : formatCountdown4(countdown);
683
+ const innerWidth = getInnerWidth(width);
684
+ const contentWidth = innerWidth - 1;
685
+ const { activeCount, projectNames, recentSession } = data;
686
+ const activeSuffix = ` | \u26A1 ${activeCount} active`;
687
+ const projectsAvailableWidth = contentWidth - getDisplayWidth(activeSuffix);
688
+ const projectsText = formatProjectNames(projectNames, projectsAvailableWidth);
689
+ const headerText = `\u{1F4C1} ${projectsText}${activeSuffix}`;
690
+ const headerPadding = Math.max(0, contentWidth - getDisplayWidth(headerText));
691
+ const clearEOL = "\x1B[K";
692
+ const lines = [];
693
+ lines.push(
694
+ /* @__PURE__ */ jsxs5(Text5, { children: [
695
+ BOX.v,
696
+ " ",
697
+ /* @__PURE__ */ jsx5(Text5, { children: headerText }),
698
+ " ".repeat(headerPadding),
699
+ BOX.v,
700
+ clearEOL
701
+ ] }, "header")
702
+ );
703
+ lines.push(
704
+ /* @__PURE__ */ jsxs5(Text5, { children: [
705
+ BOX.v,
706
+ " ",
707
+ " ".repeat(contentWidth),
708
+ BOX.v,
709
+ clearEOL
710
+ ] }, "empty")
711
+ );
712
+ if (recentSession) {
713
+ const statusIcon = recentSession.isActive ? "\u{1F535}" : "\u26AA";
714
+ const sessionLine = `${statusIcon} ${recentSession.projectName} (${recentSession.relativeTime})`;
715
+ const sessionLinePadding = Math.max(0, contentWidth - getDisplayWidth(sessionLine));
716
+ lines.push(
717
+ /* @__PURE__ */ jsxs5(Text5, { children: [
718
+ BOX.v,
719
+ " ",
720
+ /* @__PURE__ */ jsx5(Text5, { children: sessionLine }),
721
+ " ".repeat(sessionLinePadding),
722
+ BOX.v,
723
+ clearEOL
724
+ ] }, "session")
725
+ );
726
+ if (recentSession.lastMessage) {
727
+ const indent = " ";
728
+ const quotePrefix = '"';
729
+ const quoteSuffix = '"';
730
+ const availableWidth = contentWidth - indent.length - 2;
731
+ const truncatedMessage = truncateMessage(
732
+ recentSession.lastMessage,
733
+ Math.min(availableWidth, messageMaxLength)
734
+ );
735
+ const messageText = `${indent}${quotePrefix}${truncatedMessage}${quoteSuffix}`;
736
+ const messagePadding = Math.max(0, contentWidth - getDisplayWidth(messageText));
737
+ lines.push(
738
+ /* @__PURE__ */ jsxs5(Text5, { children: [
739
+ BOX.v,
740
+ " ",
741
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: messageText }),
742
+ " ".repeat(messagePadding),
743
+ BOX.v,
744
+ clearEOL
745
+ ] }, "message")
746
+ );
747
+ }
748
+ } else {
749
+ const noSessionText = "No other active sessions";
750
+ const noSessionPadding = Math.max(0, contentWidth - noSessionText.length);
751
+ lines.push(
752
+ /* @__PURE__ */ jsxs5(Text5, { children: [
753
+ BOX.v,
754
+ " ",
755
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: noSessionText }),
756
+ " ".repeat(noSessionPadding),
757
+ BOX.v,
758
+ clearEOL
759
+ ] }, "no-session")
760
+ );
761
+ }
762
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", width, children: [
763
+ /* @__PURE__ */ jsx5(Text5, { children: createTitleLine("Other Sessions", countdownSuffix, width) }),
764
+ lines,
765
+ /* @__PURE__ */ jsx5(Text5, { children: createBottomLine(width) })
766
+ ] });
767
+ }
768
+
769
+ // src/ui/GenericPanel.tsx
770
+ import { Box as Box6, Text as Text6 } from "ink";
771
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
605
772
  var PROGRESS_BAR_WIDTH = 10;
606
773
  function createProgressBar(done, total) {
607
774
  if (total === 0) return "\u2591".repeat(PROGRESS_BAR_WIDTH);
@@ -631,19 +798,19 @@ function ListRenderer({ data, width }) {
631
798
  const items = data.items || [];
632
799
  const contentWidth = getContentWidth(width);
633
800
  if (items.length === 0 && !data.summary) {
634
- return /* @__PURE__ */ jsxs5(Text5, { children: [
801
+ return /* @__PURE__ */ jsxs6(Text6, { children: [
635
802
  BOX.v,
636
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: padLine(" No data", width) }),
803
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: padLine(" No data", width) }),
637
804
  BOX.v
638
805
  ] });
639
806
  }
640
- return /* @__PURE__ */ jsxs5(Fragment4, { children: [
641
- data.summary && /* @__PURE__ */ jsxs5(Text5, { children: [
807
+ return /* @__PURE__ */ jsxs6(Fragment4, { children: [
808
+ data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
642
809
  BOX.v,
643
810
  padLine(" " + truncate(data.summary, contentWidth), width),
644
811
  BOX.v
645
812
  ] }),
646
- items.map((item, index) => /* @__PURE__ */ jsxs5(Text5, { children: [
813
+ items.map((item, index) => /* @__PURE__ */ jsxs6(Text6, { children: [
647
814
  BOX.v,
648
815
  padLine(" \u2022 " + truncate(item.text, contentWidth - 3), width),
649
816
  BOX.v
@@ -654,8 +821,8 @@ function ListRenderer({ data, width }) {
654
821
  function ProgressRenderer({ data, width }) {
655
822
  const items = data.items || [];
656
823
  const contentWidth = getContentWidth(width);
657
- return /* @__PURE__ */ jsxs5(Fragment4, { children: [
658
- data.summary && /* @__PURE__ */ jsxs5(Text5, { children: [
824
+ return /* @__PURE__ */ jsxs6(Fragment4, { children: [
825
+ data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
659
826
  BOX.v,
660
827
  padLine(" " + truncate(data.summary, contentWidth), width),
661
828
  BOX.v
@@ -663,15 +830,15 @@ function ProgressRenderer({ data, width }) {
663
830
  items.map((item, index) => {
664
831
  const icon = item.status === "done" ? "\u2713" : item.status === "failed" ? "\u2717" : "\u25CB";
665
832
  const line = ` ${icon} ${truncate(item.text, contentWidth - 3)}`;
666
- return /* @__PURE__ */ jsxs5(Text5, { children: [
833
+ return /* @__PURE__ */ jsxs6(Text6, { children: [
667
834
  BOX.v,
668
835
  padLine(line, width),
669
836
  BOX.v
670
837
  ] }, `progress-item-${index}`);
671
838
  }),
672
- items.length === 0 && !data.summary && /* @__PURE__ */ jsxs5(Text5, { children: [
839
+ items.length === 0 && !data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
673
840
  BOX.v,
674
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: padLine(" No data", width) }),
841
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: padLine(" No data", width) }),
675
842
  BOX.v
676
843
  ] })
677
844
  ] });
@@ -689,31 +856,31 @@ function StatusRenderer({ data, width }) {
689
856
  summaryLength += 2 + 2 + String(stats.skipped).length + " skipped".length;
690
857
  }
691
858
  const summaryPadding = Math.max(0, innerWidth - summaryLength);
692
- return /* @__PURE__ */ jsxs5(Fragment4, { children: [
693
- data.summary && /* @__PURE__ */ jsxs5(Text5, { children: [
859
+ return /* @__PURE__ */ jsxs6(Fragment4, { children: [
860
+ data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
694
861
  BOX.v,
695
862
  padLine(" " + truncate(data.summary, contentWidth), width),
696
863
  BOX.v
697
864
  ] }),
698
- /* @__PURE__ */ jsxs5(Text5, { children: [
865
+ /* @__PURE__ */ jsxs6(Text6, { children: [
699
866
  BOX.v,
700
867
  " ",
701
- /* @__PURE__ */ jsxs5(Text5, { color: "green", children: [
868
+ /* @__PURE__ */ jsxs6(Text6, { color: "green", children: [
702
869
  "\u2713 ",
703
870
  stats.passed,
704
871
  " passed"
705
872
  ] }),
706
- stats.failed > 0 && /* @__PURE__ */ jsxs5(Fragment4, { children: [
873
+ stats.failed > 0 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
707
874
  " ",
708
- /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
875
+ /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
709
876
  "\u2717 ",
710
877
  stats.failed,
711
878
  " failed"
712
879
  ] })
713
880
  ] }),
714
- stats.skipped && stats.skipped > 0 && /* @__PURE__ */ jsxs5(Fragment4, { children: [
881
+ stats.skipped && stats.skipped > 0 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
715
882
  " ",
716
- /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
883
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
717
884
  "\u25CB ",
718
885
  stats.skipped,
719
886
  " skipped"
@@ -722,7 +889,7 @@ function StatusRenderer({ data, width }) {
722
889
  " ".repeat(summaryPadding),
723
890
  BOX.v
724
891
  ] }),
725
- items.length > 0 && items.map((item, index) => /* @__PURE__ */ jsxs5(Text5, { children: [
892
+ items.length > 0 && items.map((item, index) => /* @__PURE__ */ jsxs6(Text6, { children: [
726
893
  BOX.v,
727
894
  padLine(" \u2022 " + truncate(item.text, contentWidth - 3), width),
728
895
  BOX.v
@@ -743,45 +910,45 @@ function GenericPanel({
743
910
  const suffixColor = isRunning ? "yellow" : justRefreshed ? "green" : void 0;
744
911
  const progress = data.progress || { done: 0, total: 0 };
745
912
  if (error) {
746
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", width, children: [
747
- /* @__PURE__ */ jsx5(Text5, { children: createTitleLine(data.title, suffix, width) }),
748
- /* @__PURE__ */ jsxs5(Text5, { children: [
913
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width, children: [
914
+ /* @__PURE__ */ jsx6(Text6, { children: createTitleLine(data.title, suffix, width) }),
915
+ /* @__PURE__ */ jsxs6(Text6, { children: [
749
916
  BOX.v,
750
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: padLine(" " + error, width) }),
917
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: padLine(" " + error, width) }),
751
918
  BOX.v
752
919
  ] }),
753
- /* @__PURE__ */ jsx5(Text5, { children: createBottomLine(width) })
920
+ /* @__PURE__ */ jsx6(Text6, { children: createBottomLine(width) })
754
921
  ] });
755
922
  }
756
923
  if (renderer === "progress") {
757
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", width, children: [
758
- /* @__PURE__ */ jsx5(Text5, { children: createProgressTitleLine(data.title, progress.done, progress.total, width, countdown, relativeTime) }),
759
- /* @__PURE__ */ jsx5(ProgressRenderer, { data, width }),
760
- /* @__PURE__ */ jsx5(Text5, { children: createBottomLine(width) })
924
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width, children: [
925
+ /* @__PURE__ */ jsx6(Text6, { children: createProgressTitleLine(data.title, progress.done, progress.total, width, countdown, relativeTime) }),
926
+ /* @__PURE__ */ jsx6(ProgressRenderer, { data, width }),
927
+ /* @__PURE__ */ jsx6(Text6, { children: createBottomLine(width) })
761
928
  ] });
762
929
  }
763
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", width, children: [
764
- /* @__PURE__ */ jsx5(Text5, { children: createTitleLine(data.title, suffix, width) }),
765
- renderer === "status" ? /* @__PURE__ */ jsx5(StatusRenderer, { data, width }) : /* @__PURE__ */ jsx5(ListRenderer, { data, width }),
766
- /* @__PURE__ */ jsx5(Text5, { children: createBottomLine(width) })
930
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width, children: [
931
+ /* @__PURE__ */ jsx6(Text6, { children: createTitleLine(data.title, suffix, width) }),
932
+ renderer === "status" ? /* @__PURE__ */ jsx6(StatusRenderer, { data, width }) : /* @__PURE__ */ jsx6(ListRenderer, { data, width }),
933
+ /* @__PURE__ */ jsx6(Text6, { children: createBottomLine(width) })
767
934
  ] });
768
935
  }
769
936
 
770
937
  // src/ui/WelcomePanel.tsx
771
- import { Box as Box6, Text as Text6 } from "ink";
772
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
938
+ import { Box as Box7, Text as Text7 } from "ink";
939
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
773
940
  function WelcomePanel() {
774
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "single", paddingX: 1, width: DEFAULT_PANEL_WIDTH, children: [
775
- /* @__PURE__ */ jsx6(Box6, { marginTop: -1, children: /* @__PURE__ */ jsx6(Text6, { children: " Welcome to agenthud " }) }),
776
- /* @__PURE__ */ jsx6(Text6, { children: " " }),
777
- /* @__PURE__ */ jsx6(Text6, { children: " No .agenthud/ directory found." }),
778
- /* @__PURE__ */ jsx6(Text6, { children: " " }),
779
- /* @__PURE__ */ jsx6(Text6, { children: " Quick setup:" }),
780
- /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: " npx agenthud init" }),
781
- /* @__PURE__ */ jsx6(Text6, { children: " " }),
782
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " Or visit: github.com/neochoon/agenthud" }),
783
- /* @__PURE__ */ jsx6(Text6, { children: " " }),
784
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " Press q to quit" })
941
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "single", paddingX: 1, width: DEFAULT_PANEL_WIDTH, children: [
942
+ /* @__PURE__ */ jsx7(Box7, { marginTop: -1, children: /* @__PURE__ */ jsx7(Text7, { children: " Welcome to agenthud " }) }),
943
+ /* @__PURE__ */ jsx7(Text7, { children: " " }),
944
+ /* @__PURE__ */ jsx7(Text7, { children: " No .agenthud/ directory found." }),
945
+ /* @__PURE__ */ jsx7(Text7, { children: " " }),
946
+ /* @__PURE__ */ jsx7(Text7, { children: " Quick setup:" }),
947
+ /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: " npx agenthud init" }),
948
+ /* @__PURE__ */ jsx7(Text7, { children: " " }),
949
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " Or visit: github.com/neochoon/agenthud" }),
950
+ /* @__PURE__ */ jsx7(Text7, { children: " " }),
951
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " Press q to quit" })
785
952
  ] });
786
953
  }
787
954
 
@@ -1390,7 +1557,8 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
1390
1557
  const defaultState = {
1391
1558
  status: "none",
1392
1559
  activities: [],
1393
- tokenCount: 0
1560
+ tokenCount: 0,
1561
+ sessionStartTime: null
1394
1562
  };
1395
1563
  if (!fs.existsSync(sessionFile)) {
1396
1564
  return defaultState;
@@ -1405,6 +1573,17 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
1405
1573
  if (lines.length === 0) {
1406
1574
  return defaultState;
1407
1575
  }
1576
+ let sessionStartTime = null;
1577
+ for (let i = 0; i < Math.min(10, lines.length); i++) {
1578
+ try {
1579
+ const entry = JSON.parse(lines[i]);
1580
+ if (entry.timestamp) {
1581
+ sessionStartTime = new Date(entry.timestamp);
1582
+ break;
1583
+ }
1584
+ } catch {
1585
+ }
1586
+ }
1408
1587
  const activities = [];
1409
1588
  let tokenCount = 0;
1410
1589
  let lastTimestamp = null;
@@ -1509,14 +1688,16 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
1509
1688
  return {
1510
1689
  status,
1511
1690
  activities: activities.slice(-maxActivities).reverse(),
1512
- tokenCount
1691
+ tokenCount,
1692
+ sessionStartTime
1513
1693
  };
1514
1694
  }
1515
1695
  function getClaudeData(projectPath, maxActivities) {
1516
1696
  const defaultState = {
1517
1697
  status: "none",
1518
1698
  activities: [],
1519
- tokenCount: 0
1699
+ tokenCount: 0,
1700
+ sessionStartTime: null
1520
1701
  };
1521
1702
  try {
1522
1703
  const sessionDir = getClaudeSessionPath(projectPath);
@@ -1546,13 +1727,203 @@ function getClaudeData(projectPath, maxActivities) {
1546
1727
  }
1547
1728
  }
1548
1729
 
1730
+ // src/data/otherSessions.ts
1731
+ import {
1732
+ existsSync as nodeExistsSync3,
1733
+ readFileSync as nodeReadFileSync4,
1734
+ readdirSync as nodeReaddirSync3,
1735
+ statSync as nodeStatSync2
1736
+ } from "fs";
1737
+ import { homedir as homedir2 } from "os";
1738
+ import { join as join3, basename as basename3 } from "path";
1739
+ var fs2 = {
1740
+ existsSync: nodeExistsSync3,
1741
+ readFileSync: (path) => nodeReadFileSync4(path, "utf-8"),
1742
+ readdirSync: (path) => nodeReaddirSync3(path),
1743
+ statSync: nodeStatSync2
1744
+ };
1745
+ var FIVE_MINUTES_MS2 = 5 * 60 * 1e3;
1746
+ var MAX_LINES_TO_SCAN2 = 100;
1747
+ function getProjectsDir() {
1748
+ return join3(homedir2(), ".claude", "projects");
1749
+ }
1750
+ function decodeProjectPath(encoded) {
1751
+ return encoded.replace(/-/g, "/");
1752
+ }
1753
+ function getAllProjects() {
1754
+ const projectsDir = getProjectsDir();
1755
+ if (!fs2.existsSync(projectsDir)) {
1756
+ return [];
1757
+ }
1758
+ const entries = fs2.readdirSync(projectsDir);
1759
+ const projects = [];
1760
+ for (const entry of entries) {
1761
+ const fullPath = join3(projectsDir, entry);
1762
+ try {
1763
+ const stat = fs2.statSync(fullPath);
1764
+ if (stat.isDirectory?.()) {
1765
+ projects.push({
1766
+ encodedPath: entry,
1767
+ decodedPath: decodeProjectPath(entry)
1768
+ });
1769
+ }
1770
+ } catch {
1771
+ }
1772
+ }
1773
+ return projects;
1774
+ }
1775
+ function parseLastAssistantMessage(sessionFile) {
1776
+ if (!fs2.existsSync(sessionFile)) {
1777
+ return null;
1778
+ }
1779
+ let content;
1780
+ try {
1781
+ content = fs2.readFileSync(sessionFile);
1782
+ } catch {
1783
+ return null;
1784
+ }
1785
+ const lines = content.trim().split("\n").filter(Boolean);
1786
+ if (lines.length === 0) {
1787
+ return null;
1788
+ }
1789
+ const recentLines = lines.slice(-MAX_LINES_TO_SCAN2).reverse();
1790
+ for (const line of recentLines) {
1791
+ try {
1792
+ const entry = JSON.parse(line);
1793
+ if (entry.type === "assistant") {
1794
+ const assistantEntry = entry;
1795
+ const content2 = assistantEntry.message?.content;
1796
+ if (Array.isArray(content2)) {
1797
+ const textBlock = content2.find((c) => c.type === "text" && c.text);
1798
+ if (textBlock?.text) {
1799
+ return textBlock.text.replace(/\n/g, " ");
1800
+ }
1801
+ }
1802
+ }
1803
+ } catch {
1804
+ }
1805
+ }
1806
+ return null;
1807
+ }
1808
+ function formatRelativeTime2(date) {
1809
+ const elapsed = Date.now() - date.getTime();
1810
+ const seconds = Math.floor(elapsed / 1e3);
1811
+ const minutes = Math.floor(seconds / 60);
1812
+ const hours = Math.floor(minutes / 60);
1813
+ const days = Math.floor(hours / 24);
1814
+ if (seconds < 1) {
1815
+ return "just now";
1816
+ }
1817
+ if (seconds < 60) {
1818
+ return `${seconds}s ago`;
1819
+ }
1820
+ if (minutes < 60) {
1821
+ return `${minutes}m ago`;
1822
+ }
1823
+ if (hours < 24) {
1824
+ return `${hours}h ago`;
1825
+ }
1826
+ return `${days}d ago`;
1827
+ }
1828
+ function findMostRecentSession(projectDir) {
1829
+ if (!fs2.existsSync(projectDir)) {
1830
+ return null;
1831
+ }
1832
+ let files;
1833
+ try {
1834
+ files = fs2.readdirSync(projectDir);
1835
+ } catch {
1836
+ return null;
1837
+ }
1838
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
1839
+ if (jsonlFiles.length === 0) {
1840
+ return null;
1841
+ }
1842
+ let latestFile = null;
1843
+ let latestMtime = 0;
1844
+ for (const file of jsonlFiles) {
1845
+ const filePath = join3(projectDir, file);
1846
+ try {
1847
+ const stat = fs2.statSync(filePath);
1848
+ if (stat.mtimeMs && stat.mtimeMs > latestMtime) {
1849
+ latestMtime = stat.mtimeMs;
1850
+ latestFile = filePath;
1851
+ }
1852
+ } catch {
1853
+ }
1854
+ }
1855
+ if (!latestFile) {
1856
+ return null;
1857
+ }
1858
+ return { file: latestFile, mtimeMs: latestMtime };
1859
+ }
1860
+ function getOtherSessionsData(currentProjectPath, options2 = {}) {
1861
+ const activeThresholdMs = options2.activeThresholdMs ?? FIVE_MINUTES_MS2;
1862
+ const projectsDir = getProjectsDir();
1863
+ const defaultResult = {
1864
+ totalProjects: 0,
1865
+ activeCount: 0,
1866
+ projectNames: [],
1867
+ recentSession: null,
1868
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1869
+ };
1870
+ if (!fs2.existsSync(projectsDir)) {
1871
+ return defaultResult;
1872
+ }
1873
+ const allProjects = getAllProjects();
1874
+ defaultResult.totalProjects = allProjects.length;
1875
+ const normalizedCurrentPath = currentProjectPath.replace(/\/$/, "");
1876
+ const otherSessions = [];
1877
+ for (const project of allProjects) {
1878
+ if (project.decodedPath === normalizedCurrentPath) {
1879
+ continue;
1880
+ }
1881
+ const projectDir = join3(projectsDir, project.encodedPath);
1882
+ const sessionInfo = findMostRecentSession(projectDir);
1883
+ if (sessionInfo) {
1884
+ otherSessions.push({
1885
+ projectPath: project.decodedPath,
1886
+ projectName: basename3(project.decodedPath),
1887
+ lastModified: new Date(sessionInfo.mtimeMs),
1888
+ mtimeMs: sessionInfo.mtimeMs,
1889
+ sessionFile: sessionInfo.file
1890
+ });
1891
+ }
1892
+ }
1893
+ const now = Date.now();
1894
+ let activeCount = 0;
1895
+ for (const session of otherSessions) {
1896
+ if (now - session.mtimeMs < activeThresholdMs) {
1897
+ activeCount++;
1898
+ }
1899
+ }
1900
+ defaultResult.activeCount = activeCount;
1901
+ if (otherSessions.length === 0) {
1902
+ return defaultResult;
1903
+ }
1904
+ otherSessions.sort((a, b) => b.mtimeMs - a.mtimeMs);
1905
+ defaultResult.projectNames = otherSessions.map((s) => s.projectName);
1906
+ const mostRecent = otherSessions[0];
1907
+ const lastMessage = parseLastAssistantMessage(mostRecent.sessionFile);
1908
+ const isActive = now - mostRecent.mtimeMs < activeThresholdMs;
1909
+ defaultResult.recentSession = {
1910
+ projectPath: mostRecent.projectPath,
1911
+ projectName: mostRecent.projectName,
1912
+ lastModified: mostRecent.lastModified,
1913
+ lastMessage,
1914
+ isActive,
1915
+ relativeTime: formatRelativeTime2(mostRecent.lastModified)
1916
+ };
1917
+ return defaultResult;
1918
+ }
1919
+
1549
1920
  // src/data/custom.ts
1550
1921
  import { execSync as nodeExecSync4, exec as nodeExec2 } from "child_process";
1551
- import { readFileSync as nodeReadFileSync4, promises as fsPromises } from "fs";
1922
+ import { readFileSync as nodeReadFileSync5, promises as fsPromises } from "fs";
1552
1923
  import { promisify as promisify2 } from "util";
1553
1924
  var execAsync2 = promisify2(nodeExec2);
1554
1925
  var execFn3 = (cmd, options2) => nodeExecSync4(cmd, options2);
1555
- var readFileFn3 = (path) => nodeReadFileSync4(path, "utf-8");
1926
+ var readFileFn3 = (path) => nodeReadFileSync5(path, "utf-8");
1556
1927
  function capitalizeFirst(str) {
1557
1928
  return str.charAt(0).toUpperCase() + str.slice(1);
1558
1929
  }
@@ -1792,13 +2163,13 @@ function runTestCommand(command) {
1792
2163
 
1793
2164
  // src/config/parser.ts
1794
2165
  import {
1795
- existsSync as nodeExistsSync3,
1796
- readFileSync as nodeReadFileSync5
2166
+ existsSync as nodeExistsSync4,
2167
+ readFileSync as nodeReadFileSync6
1797
2168
  } from "fs";
1798
2169
  import { parse as parseYaml } from "yaml";
1799
- var fs2 = {
1800
- existsSync: nodeExistsSync3,
1801
- readFileSync: (path) => nodeReadFileSync5(path, "utf-8")
2170
+ var fs3 = {
2171
+ existsSync: nodeExistsSync4,
2172
+ readFileSync: (path) => nodeReadFileSync6(path, "utf-8")
1802
2173
  };
1803
2174
  var DEFAULT_WIDTH = 70;
1804
2175
  var MIN_WIDTH = 50;
@@ -1843,23 +2214,31 @@ function getDefaultConfig() {
1843
2214
  enabled: true,
1844
2215
  interval: 1e4
1845
2216
  // 10 seconds default
2217
+ },
2218
+ other_sessions: {
2219
+ enabled: true,
2220
+ interval: 1e4,
2221
+ // 10 seconds default
2222
+ activeThreshold: 5 * 60 * 1e3,
2223
+ // 5 minutes
2224
+ messageMaxLength: 50
1846
2225
  }
1847
2226
  },
1848
- panelOrder: ["project", "git", "tests", "claude"],
2227
+ panelOrder: ["project", "git", "tests", "claude", "other_sessions"],
1849
2228
  width: DEFAULT_WIDTH
1850
2229
  };
1851
2230
  }
1852
- var BUILTIN_PANELS = ["project", "git", "tests", "claude"];
2231
+ var BUILTIN_PANELS = ["project", "git", "tests", "claude", "other_sessions"];
1853
2232
  var VALID_RENDERERS = ["list", "progress", "status"];
1854
2233
  function parseConfig() {
1855
2234
  const warnings = [];
1856
2235
  const defaultConfig = getDefaultConfig();
1857
- if (!fs2.existsSync(CONFIG_PATH)) {
2236
+ if (!fs3.existsSync(CONFIG_PATH)) {
1858
2237
  return { config: defaultConfig, warnings };
1859
2238
  }
1860
2239
  let rawConfig;
1861
2240
  try {
1862
- const content = fs2.readFileSync(CONFIG_PATH);
2241
+ const content = fs3.readFileSync(CONFIG_PATH);
1863
2242
  rawConfig = parseYaml(content);
1864
2243
  } catch (error) {
1865
2244
  const message = error instanceof Error ? error.message : String(error);
@@ -1956,6 +2335,29 @@ function parseConfig() {
1956
2335
  }
1957
2336
  continue;
1958
2337
  }
2338
+ if (panelName === "other_sessions") {
2339
+ if (typeof panelConfig.enabled === "boolean") {
2340
+ config.panels.other_sessions.enabled = panelConfig.enabled;
2341
+ }
2342
+ if (typeof panelConfig.interval === "string") {
2343
+ const interval = parseInterval(panelConfig.interval);
2344
+ if (interval === null && panelConfig.interval !== "manual") {
2345
+ warnings.push(`Invalid interval '${panelConfig.interval}' for other_sessions panel, using default`);
2346
+ } else {
2347
+ config.panels.other_sessions.interval = interval;
2348
+ }
2349
+ }
2350
+ if (typeof panelConfig.active_threshold === "string") {
2351
+ const threshold = parseInterval(panelConfig.active_threshold);
2352
+ if (threshold !== null) {
2353
+ config.panels.other_sessions.activeThreshold = threshold;
2354
+ }
2355
+ }
2356
+ if (typeof panelConfig.message_max_length === "number") {
2357
+ config.panels.other_sessions.messageMaxLength = panelConfig.message_max_length;
2358
+ }
2359
+ continue;
2360
+ }
1959
2361
  const customPanel = {
1960
2362
  enabled: typeof panelConfig.enabled === "boolean" ? panelConfig.enabled : true,
1961
2363
  interval: 3e4,
@@ -1995,7 +2397,7 @@ function parseConfig() {
1995
2397
  }
1996
2398
 
1997
2399
  // src/ui/App.tsx
1998
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2400
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1999
2401
  var DEFAULT_VISUAL_STATE = {
2000
2402
  isRunning: false,
2001
2403
  justRefreshed: false,
@@ -2038,7 +2440,7 @@ function generateHotkeys(config, actions) {
2038
2440
  }
2039
2441
  return hotkeys;
2040
2442
  }
2041
- function formatRelativeTime2(timestamp) {
2443
+ function formatRelativeTime3(timestamp) {
2042
2444
  const now = Date.now();
2043
2445
  const then = new Date(timestamp).getTime();
2044
2446
  const diffMs = now - then;
@@ -2057,7 +2459,7 @@ function WelcomeApp() {
2057
2459
  exit();
2058
2460
  }
2059
2461
  });
2060
- return /* @__PURE__ */ jsx7(WelcomePanel, {});
2462
+ return /* @__PURE__ */ jsx8(WelcomePanel, {});
2061
2463
  }
2062
2464
  function getClampedWidth(columns) {
2063
2465
  if (!columns || columns <= 0) {
@@ -2088,6 +2490,7 @@ function DashboardApp({ mode }) {
2088
2490
  const projectIntervalSeconds = config.panels.project.interval ? config.panels.project.interval / 1e3 : null;
2089
2491
  const gitIntervalSeconds = config.panels.git.interval ? config.panels.git.interval / 1e3 : null;
2090
2492
  const claudeIntervalSeconds = config.panels.claude.interval ? config.panels.claude.interval / 1e3 : null;
2493
+ const otherSessionsIntervalSeconds = config.panels.other_sessions.interval ? config.panels.other_sessions.interval / 1e3 : null;
2091
2494
  const cwd = process.cwd();
2092
2495
  const [projectData, setProjectData] = useState(() => getProjectData());
2093
2496
  const refreshProject = useCallback(() => {
@@ -2111,6 +2514,12 @@ function DashboardApp({ mode }) {
2111
2514
  const refreshClaude = useCallback(() => {
2112
2515
  setClaudeData(getClaudeData(cwd, config.panels.claude.maxActivities));
2113
2516
  }, [cwd, config.panels.claude.maxActivities]);
2517
+ const [otherSessionsData, setOtherSessionsData] = useState(
2518
+ () => getOtherSessionsData(cwd, { activeThresholdMs: config.panels.other_sessions.activeThreshold })
2519
+ );
2520
+ const refreshOtherSessions = useCallback(() => {
2521
+ setOtherSessionsData(getOtherSessionsData(cwd, { activeThresholdMs: config.panels.other_sessions.activeThreshold }));
2522
+ }, [cwd, config.panels.other_sessions.activeThreshold]);
2114
2523
  const customPanelNames = useMemo(
2115
2524
  () => Object.keys(config.customPanels || {}),
2116
2525
  [config.customPanels]
@@ -2141,7 +2550,8 @@ function DashboardApp({ mode }) {
2141
2550
  const countdowns2 = {
2142
2551
  project: projectIntervalSeconds,
2143
2552
  git: gitIntervalSeconds,
2144
- claude: claudeIntervalSeconds
2553
+ claude: claudeIntervalSeconds,
2554
+ other_sessions: otherSessionsIntervalSeconds
2145
2555
  };
2146
2556
  if (config.customPanels) {
2147
2557
  for (const [name, panelConfig] of Object.entries(config.customPanels)) {
@@ -2149,14 +2559,15 @@ function DashboardApp({ mode }) {
2149
2559
  }
2150
2560
  }
2151
2561
  return countdowns2;
2152
- }, [projectIntervalSeconds, gitIntervalSeconds, claudeIntervalSeconds, config.customPanels]);
2562
+ }, [projectIntervalSeconds, gitIntervalSeconds, claudeIntervalSeconds, otherSessionsIntervalSeconds, config.customPanels]);
2153
2563
  const [countdowns, setCountdowns] = useState(initialCountdowns);
2154
2564
  const initialVisualStates = useMemo(() => {
2155
2565
  const states = {
2156
2566
  project: { ...DEFAULT_VISUAL_STATE },
2157
2567
  git: { ...DEFAULT_VISUAL_STATE },
2158
2568
  tests: { ...DEFAULT_VISUAL_STATE },
2159
- claude: { ...DEFAULT_VISUAL_STATE }
2569
+ claude: { ...DEFAULT_VISUAL_STATE },
2570
+ other_sessions: { ...DEFAULT_VISUAL_STATE }
2160
2571
  };
2161
2572
  for (const name of customPanelNames) {
2162
2573
  states[name] = { ...DEFAULT_VISUAL_STATE };
@@ -2227,6 +2638,11 @@ function DashboardApp({ mode }) {
2227
2638
  setVisualState("claude", { justRefreshed: true });
2228
2639
  clearFeedback("claude", "justRefreshed");
2229
2640
  }, [cwd, config.panels.claude.maxActivities, setVisualState, clearFeedback]);
2641
+ const refreshOtherSessionsWithFeedback = useCallback(() => {
2642
+ setOtherSessionsData(getOtherSessionsData(cwd, { activeThresholdMs: config.panels.other_sessions.activeThreshold }));
2643
+ setVisualState("other_sessions", { justRefreshed: true });
2644
+ clearFeedback("other_sessions", "justRefreshed");
2645
+ }, [cwd, config.panels.other_sessions.activeThreshold, setVisualState, clearFeedback]);
2230
2646
  const customPanelActionsAsync = useMemo(() => {
2231
2647
  const actions = {};
2232
2648
  for (const name of customPanelNames) {
@@ -2250,6 +2666,10 @@ function DashboardApp({ mode }) {
2250
2666
  refreshClaudeWithFeedback();
2251
2667
  setCountdowns((prev) => ({ ...prev, claude: claudeIntervalSeconds }));
2252
2668
  }
2669
+ if (config.panels.other_sessions.enabled) {
2670
+ refreshOtherSessionsWithFeedback();
2671
+ setCountdowns((prev) => ({ ...prev, other_sessions: otherSessionsIntervalSeconds }));
2672
+ }
2253
2673
  for (const name of customPanelNames) {
2254
2674
  if (config.customPanels[name].enabled) {
2255
2675
  void refreshCustomPanelAsync(name);
@@ -2265,11 +2685,13 @@ function DashboardApp({ mode }) {
2265
2685
  refreshGitAsync,
2266
2686
  refreshTestAsync,
2267
2687
  refreshClaudeWithFeedback,
2688
+ refreshOtherSessionsWithFeedback,
2268
2689
  refreshCustomPanelAsync,
2269
2690
  config,
2270
2691
  projectIntervalSeconds,
2271
2692
  gitIntervalSeconds,
2272
2693
  claudeIntervalSeconds,
2694
+ otherSessionsIntervalSeconds,
2273
2695
  customPanelNames
2274
2696
  ]);
2275
2697
  const hotkeys = useMemo(
@@ -2309,6 +2731,14 @@ function DashboardApp({ mode }) {
2309
2731
  }, config.panels.claude.interval)
2310
2732
  );
2311
2733
  }
2734
+ if (config.panels.other_sessions.enabled && config.panels.other_sessions.interval !== null) {
2735
+ timers.push(
2736
+ setInterval(() => {
2737
+ refreshOtherSessionsWithFeedback();
2738
+ setCountdowns((prev) => ({ ...prev, other_sessions: otherSessionsIntervalSeconds }));
2739
+ }, config.panels.other_sessions.interval)
2740
+ );
2741
+ }
2312
2742
  if (config.customPanels) {
2313
2743
  for (const [name, panelConfig] of Object.entries(config.customPanels)) {
2314
2744
  if (panelConfig.enabled && panelConfig.interval !== null) {
@@ -2330,10 +2760,12 @@ function DashboardApp({ mode }) {
2330
2760
  refreshGitAsync,
2331
2761
  refreshTestAsync,
2332
2762
  refreshClaudeWithFeedback,
2763
+ refreshOtherSessionsWithFeedback,
2333
2764
  refreshCustomPanelAsync,
2334
2765
  projectIntervalSeconds,
2335
2766
  gitIntervalSeconds,
2336
- claudeIntervalSeconds
2767
+ claudeIntervalSeconds,
2768
+ otherSessionsIntervalSeconds
2337
2769
  ]);
2338
2770
  useEffect(() => {
2339
2771
  if (mode !== "watch") return;
@@ -2342,7 +2774,8 @@ function DashboardApp({ mode }) {
2342
2774
  const next = {
2343
2775
  project: prev.project !== null && prev.project > 1 ? prev.project - 1 : prev.project,
2344
2776
  git: prev.git !== null && prev.git > 1 ? prev.git - 1 : prev.git,
2345
- claude: prev.claude !== null && prev.claude > 1 ? prev.claude - 1 : prev.claude
2777
+ claude: prev.claude !== null && prev.claude > 1 ? prev.claude - 1 : prev.claude,
2778
+ other_sessions: prev.other_sessions !== null && prev.other_sessions > 1 ? prev.other_sessions - 1 : prev.other_sessions
2346
2779
  };
2347
2780
  for (const name of customPanelNames) {
2348
2781
  next[name] = prev[name] !== null && prev[name] > 1 ? prev[name] - 1 : prev[name];
@@ -2375,8 +2808,8 @@ function DashboardApp({ mode }) {
2375
2808
  }
2376
2809
  statusBarItems.push("r: refresh all");
2377
2810
  statusBarItems.push("q: quit");
2378
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2379
- warnings.length > 0 && /* @__PURE__ */ jsx7(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: "yellow", children: [
2811
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2812
+ warnings.length > 0 && /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
2380
2813
  "\u26A0 ",
2381
2814
  warnings.join(", ")
2382
2815
  ] }) }),
@@ -2384,7 +2817,7 @@ function DashboardApp({ mode }) {
2384
2817
  const isFirst = index === 0;
2385
2818
  if (panelName === "project" && config.panels.project.enabled) {
2386
2819
  const projectVisual = visualStates.project || DEFAULT_VISUAL_STATE;
2387
- return /* @__PURE__ */ jsx7(Box7, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx7(
2820
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2388
2821
  ProjectPanel,
2389
2822
  {
2390
2823
  data: projectData,
@@ -2396,7 +2829,7 @@ function DashboardApp({ mode }) {
2396
2829
  }
2397
2830
  if (panelName === "git" && config.panels.git.enabled) {
2398
2831
  const gitVisual = visualStates.git || DEFAULT_VISUAL_STATE;
2399
- return /* @__PURE__ */ jsx7(Box7, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx7(
2832
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2400
2833
  GitPanel,
2401
2834
  {
2402
2835
  branch: gitData.branch,
@@ -2412,7 +2845,7 @@ function DashboardApp({ mode }) {
2412
2845
  }
2413
2846
  if (panelName === "tests" && config.panels.tests.enabled) {
2414
2847
  const testsVisual = visualStates.tests || DEFAULT_VISUAL_STATE;
2415
- return /* @__PURE__ */ jsx7(Box7, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx7(
2848
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2416
2849
  TestPanel,
2417
2850
  {
2418
2851
  results: testData.results,
@@ -2427,7 +2860,7 @@ function DashboardApp({ mode }) {
2427
2860
  }
2428
2861
  if (panelName === "claude" && config.panels.claude.enabled) {
2429
2862
  const claudeVisual = visualStates.claude || DEFAULT_VISUAL_STATE;
2430
- return /* @__PURE__ */ jsx7(Box7, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx7(
2863
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2431
2864
  ClaudePanel,
2432
2865
  {
2433
2866
  data: claudeData,
@@ -2437,15 +2870,28 @@ function DashboardApp({ mode }) {
2437
2870
  }
2438
2871
  ) }, `panel-claude-${index}`);
2439
2872
  }
2873
+ if (panelName === "other_sessions" && config.panels.other_sessions.enabled) {
2874
+ const otherSessionsVisual = visualStates.other_sessions || DEFAULT_VISUAL_STATE;
2875
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2876
+ OtherSessionsPanel,
2877
+ {
2878
+ data: otherSessionsData,
2879
+ countdown: mode === "watch" ? countdowns.other_sessions : null,
2880
+ width,
2881
+ isRunning: otherSessionsVisual.isRunning,
2882
+ messageMaxLength: config.panels.other_sessions.messageMaxLength
2883
+ }
2884
+ ) }, `panel-other_sessions-${index}`);
2885
+ }
2440
2886
  const customConfig = config.customPanels?.[panelName];
2441
2887
  if (customConfig && customConfig.enabled) {
2442
2888
  const result = customPanelData[panelName];
2443
2889
  if (!result) return null;
2444
2890
  const customVisual = visualStates[panelName] || DEFAULT_VISUAL_STATE;
2445
2891
  const isManual = customConfig.interval === null;
2446
- const relativeTime = isManual ? formatRelativeTime2(result.timestamp) : void 0;
2892
+ const relativeTime = isManual ? formatRelativeTime3(result.timestamp) : void 0;
2447
2893
  const countdown = !isManual && mode === "watch" ? countdowns[panelName] : null;
2448
- return /* @__PURE__ */ jsx7(Box7, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx7(
2894
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2449
2895
  GenericPanel,
2450
2896
  {
2451
2897
  data: result.data,
@@ -2461,9 +2907,9 @@ function DashboardApp({ mode }) {
2461
2907
  }
2462
2908
  return null;
2463
2909
  }),
2464
- mode === "watch" && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, width, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: statusBarItems.map((item, index) => /* @__PURE__ */ jsxs7(React.Fragment, { children: [
2910
+ mode === "watch" && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, width, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: statusBarItems.map((item, index) => /* @__PURE__ */ jsxs8(React.Fragment, { children: [
2465
2911
  index > 0 && " \xB7 ",
2466
- /* @__PURE__ */ jsxs7(Text7, { color: "cyan", children: [
2912
+ /* @__PURE__ */ jsxs8(Text8, { color: "cyan", children: [
2467
2913
  item.split(":")[0],
2468
2914
  ":"
2469
2915
  ] }),
@@ -2473,14 +2919,14 @@ function DashboardApp({ mode }) {
2473
2919
  }
2474
2920
  function App({ mode, agentDirExists: agentDirExists2 = true }) {
2475
2921
  if (!agentDirExists2) {
2476
- return /* @__PURE__ */ jsx7(WelcomeApp, {});
2922
+ return /* @__PURE__ */ jsx8(WelcomeApp, {});
2477
2923
  }
2478
- return /* @__PURE__ */ jsx7(DashboardApp, { mode });
2924
+ return /* @__PURE__ */ jsx8(DashboardApp, { mode });
2479
2925
  }
2480
2926
 
2481
2927
  // src/cli.ts
2482
2928
  import { readFileSync } from "fs";
2483
- import { dirname, join as join3 } from "path";
2929
+ import { dirname, join as join4 } from "path";
2484
2930
  import { fileURLToPath } from "url";
2485
2931
  function getHelp() {
2486
2932
  return `Usage: agenthud [command] [options]
@@ -2498,7 +2944,7 @@ Options:
2498
2944
  function getVersion() {
2499
2945
  const __dirname3 = dirname(fileURLToPath(import.meta.url));
2500
2946
  const packageJson = JSON.parse(
2501
- readFileSync(join3(__dirname3, "..", "package.json"), "utf-8")
2947
+ readFileSync(join4(__dirname3, "..", "package.json"), "utf-8")
2502
2948
  );
2503
2949
  return packageJson.version;
2504
2950
  }
@@ -2525,34 +2971,34 @@ function parseArgs(args) {
2525
2971
 
2526
2972
  // src/commands/init.ts
2527
2973
  import {
2528
- existsSync as nodeExistsSync4,
2974
+ existsSync as nodeExistsSync5,
2529
2975
  mkdirSync as nodeMkdirSync,
2530
2976
  writeFileSync as nodeWriteFileSync,
2531
- readFileSync as nodeReadFileSync6,
2977
+ readFileSync as nodeReadFileSync7,
2532
2978
  appendFileSync as nodeAppendFileSync
2533
2979
  } from "fs";
2534
2980
  import { fileURLToPath as fileURLToPath2 } from "url";
2535
- import { dirname as dirname2, join as join4 } from "path";
2536
- import { homedir as homedir2 } from "os";
2537
- var fs3 = {
2538
- existsSync: nodeExistsSync4,
2981
+ import { dirname as dirname2, join as join5 } from "path";
2982
+ import { homedir as homedir3 } from "os";
2983
+ var fs4 = {
2984
+ existsSync: nodeExistsSync5,
2539
2985
  mkdirSync: nodeMkdirSync,
2540
2986
  writeFileSync: nodeWriteFileSync,
2541
- readFileSync: (path) => nodeReadFileSync6(path, "utf-8"),
2987
+ readFileSync: (path) => nodeReadFileSync7(path, "utf-8"),
2542
2988
  appendFileSync: nodeAppendFileSync
2543
2989
  };
2544
2990
  var __filename2 = fileURLToPath2(import.meta.url);
2545
2991
  var __dirname2 = dirname2(__filename2);
2546
2992
  function getDefaultConfig2() {
2547
- let templatePath = join4(__dirname2, "templates", "config.yaml");
2548
- if (!nodeExistsSync4(templatePath)) {
2549
- templatePath = join4(__dirname2, "..", "templates", "config.yaml");
2993
+ let templatePath = join5(__dirname2, "templates", "config.yaml");
2994
+ if (!nodeExistsSync5(templatePath)) {
2995
+ templatePath = join5(__dirname2, "..", "templates", "config.yaml");
2550
2996
  }
2551
- return nodeReadFileSync6(templatePath, "utf-8");
2997
+ return nodeReadFileSync7(templatePath, "utf-8");
2552
2998
  }
2553
2999
  function getClaudeSessionPath2(projectPath) {
2554
3000
  const encoded = projectPath.replace(/\//g, "-");
2555
- return join4(homedir2(), ".claude", "projects", encoded);
3001
+ return join5(homedir3(), ".claude", "projects", encoded);
2556
3002
  }
2557
3003
  function runInit(cwd = process.cwd()) {
2558
3004
  const result = {
@@ -2560,41 +3006,41 @@ function runInit(cwd = process.cwd()) {
2560
3006
  skipped: [],
2561
3007
  warnings: []
2562
3008
  };
2563
- if (!fs3.existsSync(".agenthud")) {
2564
- fs3.mkdirSync(".agenthud", { recursive: true });
3009
+ if (!fs4.existsSync(".agenthud")) {
3010
+ fs4.mkdirSync(".agenthud", { recursive: true });
2565
3011
  result.created.push(".agenthud/");
2566
3012
  } else {
2567
3013
  result.skipped.push(".agenthud/");
2568
3014
  }
2569
- if (!fs3.existsSync(".agenthud/tests")) {
2570
- fs3.mkdirSync(".agenthud/tests", { recursive: true });
3015
+ if (!fs4.existsSync(".agenthud/tests")) {
3016
+ fs4.mkdirSync(".agenthud/tests", { recursive: true });
2571
3017
  result.created.push(".agenthud/tests/");
2572
3018
  } else {
2573
3019
  result.skipped.push(".agenthud/tests/");
2574
3020
  }
2575
- if (!fs3.existsSync(".agenthud/config.yaml")) {
2576
- fs3.writeFileSync(".agenthud/config.yaml", getDefaultConfig2());
3021
+ if (!fs4.existsSync(".agenthud/config.yaml")) {
3022
+ fs4.writeFileSync(".agenthud/config.yaml", getDefaultConfig2());
2577
3023
  result.created.push(".agenthud/config.yaml");
2578
3024
  } else {
2579
3025
  result.skipped.push(".agenthud/config.yaml");
2580
3026
  }
2581
- if (!fs3.existsSync(".gitignore")) {
2582
- fs3.writeFileSync(".gitignore", ".agenthud/\n");
3027
+ if (!fs4.existsSync(".gitignore")) {
3028
+ fs4.writeFileSync(".gitignore", ".agenthud/\n");
2583
3029
  result.created.push(".gitignore");
2584
3030
  } else {
2585
- const content = fs3.readFileSync(".gitignore");
3031
+ const content = fs4.readFileSync(".gitignore");
2586
3032
  if (!content.includes(".agenthud/")) {
2587
- fs3.appendFileSync(".gitignore", "\n.agenthud/\n");
3033
+ fs4.appendFileSync(".gitignore", "\n.agenthud/\n");
2588
3034
  result.created.push(".gitignore");
2589
3035
  } else {
2590
3036
  result.skipped.push(".gitignore");
2591
3037
  }
2592
3038
  }
2593
- if (!fs3.existsSync(".git")) {
3039
+ if (!fs4.existsSync(".git")) {
2594
3040
  result.warnings.push("Not a git repository - Git panel will show limited info");
2595
3041
  }
2596
3042
  const claudeSessionPath = getClaudeSessionPath2(cwd);
2597
- if (!fs3.existsSync(claudeSessionPath)) {
3043
+ if (!fs4.existsSync(claudeSessionPath)) {
2598
3044
  result.warnings.push("No Claude session found - start Claude to see activity");
2599
3045
  }
2600
3046
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenthud",
3
- "version": "0.5.10",
3
+ "version": "0.5.12",
4
4
  "description": "CLI tool to monitor agent status in real-time. Works with Claude Code, multi-agent workflows, and any AI agent system.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",