agenthud 0.5.10 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +519 -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,126 @@ 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 OtherSessionsPanel({
643
+ data,
644
+ countdown,
645
+ width = DEFAULT_PANEL_WIDTH,
646
+ isRunning = false,
647
+ messageMaxLength = 50
648
+ }) {
649
+ const countdownSuffix = isRunning ? "running..." : formatCountdown4(countdown);
650
+ const innerWidth = getInnerWidth(width);
651
+ const contentWidth = innerWidth - 1;
652
+ const { totalProjects, activeCount, recentSession } = data;
653
+ const projectWord = totalProjects === 1 ? "project" : "projects";
654
+ const headerText = `\u{1F4C1} ${totalProjects} ${projectWord} | \u26A1 ${activeCount} active`;
655
+ const headerPadding = Math.max(0, contentWidth - getDisplayWidth(headerText));
656
+ const clearEOL = "\x1B[K";
657
+ const lines = [];
658
+ lines.push(
659
+ /* @__PURE__ */ jsxs5(Text5, { children: [
660
+ BOX.v,
661
+ " ",
662
+ /* @__PURE__ */ jsx5(Text5, { children: headerText }),
663
+ " ".repeat(headerPadding),
664
+ BOX.v,
665
+ clearEOL
666
+ ] }, "header")
667
+ );
668
+ lines.push(
669
+ /* @__PURE__ */ jsxs5(Text5, { children: [
670
+ BOX.v,
671
+ " ",
672
+ " ".repeat(contentWidth),
673
+ BOX.v,
674
+ clearEOL
675
+ ] }, "empty")
676
+ );
677
+ if (recentSession) {
678
+ const statusIcon = recentSession.isActive ? "\u{1F535}" : "\u26AA";
679
+ const sessionLine = `${statusIcon} ${recentSession.projectName} (${recentSession.relativeTime})`;
680
+ const sessionLinePadding = Math.max(0, contentWidth - getDisplayWidth(sessionLine));
681
+ lines.push(
682
+ /* @__PURE__ */ jsxs5(Text5, { children: [
683
+ BOX.v,
684
+ " ",
685
+ /* @__PURE__ */ jsx5(Text5, { children: sessionLine }),
686
+ " ".repeat(sessionLinePadding),
687
+ BOX.v,
688
+ clearEOL
689
+ ] }, "session")
690
+ );
691
+ if (recentSession.lastMessage) {
692
+ const indent = " ";
693
+ const quotePrefix = '"';
694
+ const quoteSuffix = '"';
695
+ const availableWidth = contentWidth - indent.length - 2;
696
+ const truncatedMessage = truncateMessage(
697
+ recentSession.lastMessage,
698
+ Math.min(availableWidth, messageMaxLength)
699
+ );
700
+ const messageText = `${indent}${quotePrefix}${truncatedMessage}${quoteSuffix}`;
701
+ const messagePadding = Math.max(0, contentWidth - getDisplayWidth(messageText));
702
+ lines.push(
703
+ /* @__PURE__ */ jsxs5(Text5, { children: [
704
+ BOX.v,
705
+ " ",
706
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: messageText }),
707
+ " ".repeat(messagePadding),
708
+ BOX.v,
709
+ clearEOL
710
+ ] }, "message")
711
+ );
712
+ }
713
+ } else {
714
+ const noSessionText = "No other active sessions";
715
+ const noSessionPadding = Math.max(0, contentWidth - noSessionText.length);
716
+ lines.push(
717
+ /* @__PURE__ */ jsxs5(Text5, { children: [
718
+ BOX.v,
719
+ " ",
720
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: noSessionText }),
721
+ " ".repeat(noSessionPadding),
722
+ BOX.v,
723
+ clearEOL
724
+ ] }, "no-session")
725
+ );
726
+ }
727
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", width, children: [
728
+ /* @__PURE__ */ jsx5(Text5, { children: createTitleLine("Other Sessions", countdownSuffix, width) }),
729
+ lines,
730
+ /* @__PURE__ */ jsx5(Text5, { children: createBottomLine(width) })
731
+ ] });
732
+ }
733
+
734
+ // src/ui/GenericPanel.tsx
735
+ import { Box as Box6, Text as Text6 } from "ink";
736
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
605
737
  var PROGRESS_BAR_WIDTH = 10;
606
738
  function createProgressBar(done, total) {
607
739
  if (total === 0) return "\u2591".repeat(PROGRESS_BAR_WIDTH);
@@ -631,19 +763,19 @@ function ListRenderer({ data, width }) {
631
763
  const items = data.items || [];
632
764
  const contentWidth = getContentWidth(width);
633
765
  if (items.length === 0 && !data.summary) {
634
- return /* @__PURE__ */ jsxs5(Text5, { children: [
766
+ return /* @__PURE__ */ jsxs6(Text6, { children: [
635
767
  BOX.v,
636
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: padLine(" No data", width) }),
768
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: padLine(" No data", width) }),
637
769
  BOX.v
638
770
  ] });
639
771
  }
640
- return /* @__PURE__ */ jsxs5(Fragment4, { children: [
641
- data.summary && /* @__PURE__ */ jsxs5(Text5, { children: [
772
+ return /* @__PURE__ */ jsxs6(Fragment4, { children: [
773
+ data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
642
774
  BOX.v,
643
775
  padLine(" " + truncate(data.summary, contentWidth), width),
644
776
  BOX.v
645
777
  ] }),
646
- items.map((item, index) => /* @__PURE__ */ jsxs5(Text5, { children: [
778
+ items.map((item, index) => /* @__PURE__ */ jsxs6(Text6, { children: [
647
779
  BOX.v,
648
780
  padLine(" \u2022 " + truncate(item.text, contentWidth - 3), width),
649
781
  BOX.v
@@ -654,8 +786,8 @@ function ListRenderer({ data, width }) {
654
786
  function ProgressRenderer({ data, width }) {
655
787
  const items = data.items || [];
656
788
  const contentWidth = getContentWidth(width);
657
- return /* @__PURE__ */ jsxs5(Fragment4, { children: [
658
- data.summary && /* @__PURE__ */ jsxs5(Text5, { children: [
789
+ return /* @__PURE__ */ jsxs6(Fragment4, { children: [
790
+ data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
659
791
  BOX.v,
660
792
  padLine(" " + truncate(data.summary, contentWidth), width),
661
793
  BOX.v
@@ -663,15 +795,15 @@ function ProgressRenderer({ data, width }) {
663
795
  items.map((item, index) => {
664
796
  const icon = item.status === "done" ? "\u2713" : item.status === "failed" ? "\u2717" : "\u25CB";
665
797
  const line = ` ${icon} ${truncate(item.text, contentWidth - 3)}`;
666
- return /* @__PURE__ */ jsxs5(Text5, { children: [
798
+ return /* @__PURE__ */ jsxs6(Text6, { children: [
667
799
  BOX.v,
668
800
  padLine(line, width),
669
801
  BOX.v
670
802
  ] }, `progress-item-${index}`);
671
803
  }),
672
- items.length === 0 && !data.summary && /* @__PURE__ */ jsxs5(Text5, { children: [
804
+ items.length === 0 && !data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
673
805
  BOX.v,
674
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: padLine(" No data", width) }),
806
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: padLine(" No data", width) }),
675
807
  BOX.v
676
808
  ] })
677
809
  ] });
@@ -689,31 +821,31 @@ function StatusRenderer({ data, width }) {
689
821
  summaryLength += 2 + 2 + String(stats.skipped).length + " skipped".length;
690
822
  }
691
823
  const summaryPadding = Math.max(0, innerWidth - summaryLength);
692
- return /* @__PURE__ */ jsxs5(Fragment4, { children: [
693
- data.summary && /* @__PURE__ */ jsxs5(Text5, { children: [
824
+ return /* @__PURE__ */ jsxs6(Fragment4, { children: [
825
+ data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
694
826
  BOX.v,
695
827
  padLine(" " + truncate(data.summary, contentWidth), width),
696
828
  BOX.v
697
829
  ] }),
698
- /* @__PURE__ */ jsxs5(Text5, { children: [
830
+ /* @__PURE__ */ jsxs6(Text6, { children: [
699
831
  BOX.v,
700
832
  " ",
701
- /* @__PURE__ */ jsxs5(Text5, { color: "green", children: [
833
+ /* @__PURE__ */ jsxs6(Text6, { color: "green", children: [
702
834
  "\u2713 ",
703
835
  stats.passed,
704
836
  " passed"
705
837
  ] }),
706
- stats.failed > 0 && /* @__PURE__ */ jsxs5(Fragment4, { children: [
838
+ stats.failed > 0 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
707
839
  " ",
708
- /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
840
+ /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
709
841
  "\u2717 ",
710
842
  stats.failed,
711
843
  " failed"
712
844
  ] })
713
845
  ] }),
714
- stats.skipped && stats.skipped > 0 && /* @__PURE__ */ jsxs5(Fragment4, { children: [
846
+ stats.skipped && stats.skipped > 0 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
715
847
  " ",
716
- /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
848
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
717
849
  "\u25CB ",
718
850
  stats.skipped,
719
851
  " skipped"
@@ -722,7 +854,7 @@ function StatusRenderer({ data, width }) {
722
854
  " ".repeat(summaryPadding),
723
855
  BOX.v
724
856
  ] }),
725
- items.length > 0 && items.map((item, index) => /* @__PURE__ */ jsxs5(Text5, { children: [
857
+ items.length > 0 && items.map((item, index) => /* @__PURE__ */ jsxs6(Text6, { children: [
726
858
  BOX.v,
727
859
  padLine(" \u2022 " + truncate(item.text, contentWidth - 3), width),
728
860
  BOX.v
@@ -743,45 +875,45 @@ function GenericPanel({
743
875
  const suffixColor = isRunning ? "yellow" : justRefreshed ? "green" : void 0;
744
876
  const progress = data.progress || { done: 0, total: 0 };
745
877
  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: [
878
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width, children: [
879
+ /* @__PURE__ */ jsx6(Text6, { children: createTitleLine(data.title, suffix, width) }),
880
+ /* @__PURE__ */ jsxs6(Text6, { children: [
749
881
  BOX.v,
750
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: padLine(" " + error, width) }),
882
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: padLine(" " + error, width) }),
751
883
  BOX.v
752
884
  ] }),
753
- /* @__PURE__ */ jsx5(Text5, { children: createBottomLine(width) })
885
+ /* @__PURE__ */ jsx6(Text6, { children: createBottomLine(width) })
754
886
  ] });
755
887
  }
756
888
  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) })
889
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width, children: [
890
+ /* @__PURE__ */ jsx6(Text6, { children: createProgressTitleLine(data.title, progress.done, progress.total, width, countdown, relativeTime) }),
891
+ /* @__PURE__ */ jsx6(ProgressRenderer, { data, width }),
892
+ /* @__PURE__ */ jsx6(Text6, { children: createBottomLine(width) })
761
893
  ] });
762
894
  }
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) })
895
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width, children: [
896
+ /* @__PURE__ */ jsx6(Text6, { children: createTitleLine(data.title, suffix, width) }),
897
+ renderer === "status" ? /* @__PURE__ */ jsx6(StatusRenderer, { data, width }) : /* @__PURE__ */ jsx6(ListRenderer, { data, width }),
898
+ /* @__PURE__ */ jsx6(Text6, { children: createBottomLine(width) })
767
899
  ] });
768
900
  }
769
901
 
770
902
  // 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";
903
+ import { Box as Box7, Text as Text7 } from "ink";
904
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
773
905
  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" })
906
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "single", paddingX: 1, width: DEFAULT_PANEL_WIDTH, children: [
907
+ /* @__PURE__ */ jsx7(Box7, { marginTop: -1, children: /* @__PURE__ */ jsx7(Text7, { children: " Welcome to agenthud " }) }),
908
+ /* @__PURE__ */ jsx7(Text7, { children: " " }),
909
+ /* @__PURE__ */ jsx7(Text7, { children: " No .agenthud/ directory found." }),
910
+ /* @__PURE__ */ jsx7(Text7, { children: " " }),
911
+ /* @__PURE__ */ jsx7(Text7, { children: " Quick setup:" }),
912
+ /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: " npx agenthud init" }),
913
+ /* @__PURE__ */ jsx7(Text7, { children: " " }),
914
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " Or visit: github.com/neochoon/agenthud" }),
915
+ /* @__PURE__ */ jsx7(Text7, { children: " " }),
916
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " Press q to quit" })
785
917
  ] });
786
918
  }
787
919
 
@@ -1390,7 +1522,8 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
1390
1522
  const defaultState = {
1391
1523
  status: "none",
1392
1524
  activities: [],
1393
- tokenCount: 0
1525
+ tokenCount: 0,
1526
+ sessionStartTime: null
1394
1527
  };
1395
1528
  if (!fs.existsSync(sessionFile)) {
1396
1529
  return defaultState;
@@ -1405,6 +1538,17 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
1405
1538
  if (lines.length === 0) {
1406
1539
  return defaultState;
1407
1540
  }
1541
+ let sessionStartTime = null;
1542
+ for (let i = 0; i < Math.min(10, lines.length); i++) {
1543
+ try {
1544
+ const entry = JSON.parse(lines[i]);
1545
+ if (entry.timestamp) {
1546
+ sessionStartTime = new Date(entry.timestamp);
1547
+ break;
1548
+ }
1549
+ } catch {
1550
+ }
1551
+ }
1408
1552
  const activities = [];
1409
1553
  let tokenCount = 0;
1410
1554
  let lastTimestamp = null;
@@ -1509,14 +1653,16 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
1509
1653
  return {
1510
1654
  status,
1511
1655
  activities: activities.slice(-maxActivities).reverse(),
1512
- tokenCount
1656
+ tokenCount,
1657
+ sessionStartTime
1513
1658
  };
1514
1659
  }
1515
1660
  function getClaudeData(projectPath, maxActivities) {
1516
1661
  const defaultState = {
1517
1662
  status: "none",
1518
1663
  activities: [],
1519
- tokenCount: 0
1664
+ tokenCount: 0,
1665
+ sessionStartTime: null
1520
1666
  };
1521
1667
  try {
1522
1668
  const sessionDir = getClaudeSessionPath(projectPath);
@@ -1546,13 +1692,201 @@ function getClaudeData(projectPath, maxActivities) {
1546
1692
  }
1547
1693
  }
1548
1694
 
1695
+ // src/data/otherSessions.ts
1696
+ import {
1697
+ existsSync as nodeExistsSync3,
1698
+ readFileSync as nodeReadFileSync4,
1699
+ readdirSync as nodeReaddirSync3,
1700
+ statSync as nodeStatSync2
1701
+ } from "fs";
1702
+ import { homedir as homedir2 } from "os";
1703
+ import { join as join3, basename as basename3 } from "path";
1704
+ var fs2 = {
1705
+ existsSync: nodeExistsSync3,
1706
+ readFileSync: (path) => nodeReadFileSync4(path, "utf-8"),
1707
+ readdirSync: (path) => nodeReaddirSync3(path),
1708
+ statSync: nodeStatSync2
1709
+ };
1710
+ var FIVE_MINUTES_MS2 = 5 * 60 * 1e3;
1711
+ var MAX_LINES_TO_SCAN2 = 100;
1712
+ function getProjectsDir() {
1713
+ return join3(homedir2(), ".claude", "projects");
1714
+ }
1715
+ function decodeProjectPath(encoded) {
1716
+ return encoded.replace(/-/g, "/");
1717
+ }
1718
+ function getAllProjects() {
1719
+ const projectsDir = getProjectsDir();
1720
+ if (!fs2.existsSync(projectsDir)) {
1721
+ return [];
1722
+ }
1723
+ const entries = fs2.readdirSync(projectsDir);
1724
+ const projects = [];
1725
+ for (const entry of entries) {
1726
+ const fullPath = join3(projectsDir, entry);
1727
+ try {
1728
+ const stat = fs2.statSync(fullPath);
1729
+ if (stat.isDirectory?.()) {
1730
+ projects.push({
1731
+ encodedPath: entry,
1732
+ decodedPath: decodeProjectPath(entry)
1733
+ });
1734
+ }
1735
+ } catch {
1736
+ }
1737
+ }
1738
+ return projects;
1739
+ }
1740
+ function parseLastAssistantMessage(sessionFile) {
1741
+ if (!fs2.existsSync(sessionFile)) {
1742
+ return null;
1743
+ }
1744
+ let content;
1745
+ try {
1746
+ content = fs2.readFileSync(sessionFile);
1747
+ } catch {
1748
+ return null;
1749
+ }
1750
+ const lines = content.trim().split("\n").filter(Boolean);
1751
+ if (lines.length === 0) {
1752
+ return null;
1753
+ }
1754
+ const recentLines = lines.slice(-MAX_LINES_TO_SCAN2).reverse();
1755
+ for (const line of recentLines) {
1756
+ try {
1757
+ const entry = JSON.parse(line);
1758
+ if (entry.type === "assistant") {
1759
+ const assistantEntry = entry;
1760
+ const content2 = assistantEntry.message?.content;
1761
+ if (Array.isArray(content2)) {
1762
+ const textBlock = content2.find((c) => c.type === "text" && c.text);
1763
+ if (textBlock?.text) {
1764
+ return textBlock.text.replace(/\n/g, " ");
1765
+ }
1766
+ }
1767
+ }
1768
+ } catch {
1769
+ }
1770
+ }
1771
+ return null;
1772
+ }
1773
+ function formatRelativeTime2(date) {
1774
+ const elapsed = Date.now() - date.getTime();
1775
+ const seconds = Math.floor(elapsed / 1e3);
1776
+ const minutes = Math.floor(seconds / 60);
1777
+ const hours = Math.floor(minutes / 60);
1778
+ const days = Math.floor(hours / 24);
1779
+ if (seconds < 1) {
1780
+ return "just now";
1781
+ }
1782
+ if (seconds < 60) {
1783
+ return `${seconds}s ago`;
1784
+ }
1785
+ if (minutes < 60) {
1786
+ return `${minutes}m ago`;
1787
+ }
1788
+ if (hours < 24) {
1789
+ return `${hours}h ago`;
1790
+ }
1791
+ return `${days}d ago`;
1792
+ }
1793
+ function findMostRecentSession(projectDir) {
1794
+ if (!fs2.existsSync(projectDir)) {
1795
+ return null;
1796
+ }
1797
+ let files;
1798
+ try {
1799
+ files = fs2.readdirSync(projectDir);
1800
+ } catch {
1801
+ return null;
1802
+ }
1803
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
1804
+ if (jsonlFiles.length === 0) {
1805
+ return null;
1806
+ }
1807
+ let latestFile = null;
1808
+ let latestMtime = 0;
1809
+ for (const file of jsonlFiles) {
1810
+ const filePath = join3(projectDir, file);
1811
+ try {
1812
+ const stat = fs2.statSync(filePath);
1813
+ if (stat.mtimeMs && stat.mtimeMs > latestMtime) {
1814
+ latestMtime = stat.mtimeMs;
1815
+ latestFile = filePath;
1816
+ }
1817
+ } catch {
1818
+ }
1819
+ }
1820
+ if (!latestFile) {
1821
+ return null;
1822
+ }
1823
+ return { file: latestFile, mtimeMs: latestMtime };
1824
+ }
1825
+ function getOtherSessionsData(currentProjectPath, options2 = {}) {
1826
+ const activeThresholdMs = options2.activeThresholdMs ?? FIVE_MINUTES_MS2;
1827
+ const projectsDir = getProjectsDir();
1828
+ const defaultResult = {
1829
+ totalProjects: 0,
1830
+ activeCount: 0,
1831
+ recentSession: null,
1832
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1833
+ };
1834
+ if (!fs2.existsSync(projectsDir)) {
1835
+ return defaultResult;
1836
+ }
1837
+ const allProjects = getAllProjects();
1838
+ defaultResult.totalProjects = allProjects.length;
1839
+ const normalizedCurrentPath = currentProjectPath.replace(/\/$/, "");
1840
+ const otherSessions = [];
1841
+ for (const project of allProjects) {
1842
+ if (project.decodedPath === normalizedCurrentPath) {
1843
+ continue;
1844
+ }
1845
+ const projectDir = join3(projectsDir, project.encodedPath);
1846
+ const sessionInfo = findMostRecentSession(projectDir);
1847
+ if (sessionInfo) {
1848
+ otherSessions.push({
1849
+ projectPath: project.decodedPath,
1850
+ projectName: basename3(project.decodedPath),
1851
+ lastModified: new Date(sessionInfo.mtimeMs),
1852
+ mtimeMs: sessionInfo.mtimeMs,
1853
+ sessionFile: sessionInfo.file
1854
+ });
1855
+ }
1856
+ }
1857
+ const now = Date.now();
1858
+ let activeCount = 0;
1859
+ for (const session of otherSessions) {
1860
+ if (now - session.mtimeMs < activeThresholdMs) {
1861
+ activeCount++;
1862
+ }
1863
+ }
1864
+ defaultResult.activeCount = activeCount;
1865
+ if (otherSessions.length === 0) {
1866
+ return defaultResult;
1867
+ }
1868
+ otherSessions.sort((a, b) => b.mtimeMs - a.mtimeMs);
1869
+ const mostRecent = otherSessions[0];
1870
+ const lastMessage = parseLastAssistantMessage(mostRecent.sessionFile);
1871
+ const isActive = now - mostRecent.mtimeMs < activeThresholdMs;
1872
+ defaultResult.recentSession = {
1873
+ projectPath: mostRecent.projectPath,
1874
+ projectName: mostRecent.projectName,
1875
+ lastModified: mostRecent.lastModified,
1876
+ lastMessage,
1877
+ isActive,
1878
+ relativeTime: formatRelativeTime2(mostRecent.lastModified)
1879
+ };
1880
+ return defaultResult;
1881
+ }
1882
+
1549
1883
  // src/data/custom.ts
1550
1884
  import { execSync as nodeExecSync4, exec as nodeExec2 } from "child_process";
1551
- import { readFileSync as nodeReadFileSync4, promises as fsPromises } from "fs";
1885
+ import { readFileSync as nodeReadFileSync5, promises as fsPromises } from "fs";
1552
1886
  import { promisify as promisify2 } from "util";
1553
1887
  var execAsync2 = promisify2(nodeExec2);
1554
1888
  var execFn3 = (cmd, options2) => nodeExecSync4(cmd, options2);
1555
- var readFileFn3 = (path) => nodeReadFileSync4(path, "utf-8");
1889
+ var readFileFn3 = (path) => nodeReadFileSync5(path, "utf-8");
1556
1890
  function capitalizeFirst(str) {
1557
1891
  return str.charAt(0).toUpperCase() + str.slice(1);
1558
1892
  }
@@ -1792,13 +2126,13 @@ function runTestCommand(command) {
1792
2126
 
1793
2127
  // src/config/parser.ts
1794
2128
  import {
1795
- existsSync as nodeExistsSync3,
1796
- readFileSync as nodeReadFileSync5
2129
+ existsSync as nodeExistsSync4,
2130
+ readFileSync as nodeReadFileSync6
1797
2131
  } from "fs";
1798
2132
  import { parse as parseYaml } from "yaml";
1799
- var fs2 = {
1800
- existsSync: nodeExistsSync3,
1801
- readFileSync: (path) => nodeReadFileSync5(path, "utf-8")
2133
+ var fs3 = {
2134
+ existsSync: nodeExistsSync4,
2135
+ readFileSync: (path) => nodeReadFileSync6(path, "utf-8")
1802
2136
  };
1803
2137
  var DEFAULT_WIDTH = 70;
1804
2138
  var MIN_WIDTH = 50;
@@ -1843,23 +2177,31 @@ function getDefaultConfig() {
1843
2177
  enabled: true,
1844
2178
  interval: 1e4
1845
2179
  // 10 seconds default
2180
+ },
2181
+ other_sessions: {
2182
+ enabled: true,
2183
+ interval: 1e4,
2184
+ // 10 seconds default
2185
+ activeThreshold: 5 * 60 * 1e3,
2186
+ // 5 minutes
2187
+ messageMaxLength: 50
1846
2188
  }
1847
2189
  },
1848
- panelOrder: ["project", "git", "tests", "claude"],
2190
+ panelOrder: ["project", "git", "tests", "claude", "other_sessions"],
1849
2191
  width: DEFAULT_WIDTH
1850
2192
  };
1851
2193
  }
1852
- var BUILTIN_PANELS = ["project", "git", "tests", "claude"];
2194
+ var BUILTIN_PANELS = ["project", "git", "tests", "claude", "other_sessions"];
1853
2195
  var VALID_RENDERERS = ["list", "progress", "status"];
1854
2196
  function parseConfig() {
1855
2197
  const warnings = [];
1856
2198
  const defaultConfig = getDefaultConfig();
1857
- if (!fs2.existsSync(CONFIG_PATH)) {
2199
+ if (!fs3.existsSync(CONFIG_PATH)) {
1858
2200
  return { config: defaultConfig, warnings };
1859
2201
  }
1860
2202
  let rawConfig;
1861
2203
  try {
1862
- const content = fs2.readFileSync(CONFIG_PATH);
2204
+ const content = fs3.readFileSync(CONFIG_PATH);
1863
2205
  rawConfig = parseYaml(content);
1864
2206
  } catch (error) {
1865
2207
  const message = error instanceof Error ? error.message : String(error);
@@ -1956,6 +2298,29 @@ function parseConfig() {
1956
2298
  }
1957
2299
  continue;
1958
2300
  }
2301
+ if (panelName === "other_sessions") {
2302
+ if (typeof panelConfig.enabled === "boolean") {
2303
+ config.panels.other_sessions.enabled = panelConfig.enabled;
2304
+ }
2305
+ if (typeof panelConfig.interval === "string") {
2306
+ const interval = parseInterval(panelConfig.interval);
2307
+ if (interval === null && panelConfig.interval !== "manual") {
2308
+ warnings.push(`Invalid interval '${panelConfig.interval}' for other_sessions panel, using default`);
2309
+ } else {
2310
+ config.panels.other_sessions.interval = interval;
2311
+ }
2312
+ }
2313
+ if (typeof panelConfig.active_threshold === "string") {
2314
+ const threshold = parseInterval(panelConfig.active_threshold);
2315
+ if (threshold !== null) {
2316
+ config.panels.other_sessions.activeThreshold = threshold;
2317
+ }
2318
+ }
2319
+ if (typeof panelConfig.message_max_length === "number") {
2320
+ config.panels.other_sessions.messageMaxLength = panelConfig.message_max_length;
2321
+ }
2322
+ continue;
2323
+ }
1959
2324
  const customPanel = {
1960
2325
  enabled: typeof panelConfig.enabled === "boolean" ? panelConfig.enabled : true,
1961
2326
  interval: 3e4,
@@ -1995,7 +2360,7 @@ function parseConfig() {
1995
2360
  }
1996
2361
 
1997
2362
  // src/ui/App.tsx
1998
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2363
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1999
2364
  var DEFAULT_VISUAL_STATE = {
2000
2365
  isRunning: false,
2001
2366
  justRefreshed: false,
@@ -2038,7 +2403,7 @@ function generateHotkeys(config, actions) {
2038
2403
  }
2039
2404
  return hotkeys;
2040
2405
  }
2041
- function formatRelativeTime2(timestamp) {
2406
+ function formatRelativeTime3(timestamp) {
2042
2407
  const now = Date.now();
2043
2408
  const then = new Date(timestamp).getTime();
2044
2409
  const diffMs = now - then;
@@ -2057,7 +2422,7 @@ function WelcomeApp() {
2057
2422
  exit();
2058
2423
  }
2059
2424
  });
2060
- return /* @__PURE__ */ jsx7(WelcomePanel, {});
2425
+ return /* @__PURE__ */ jsx8(WelcomePanel, {});
2061
2426
  }
2062
2427
  function getClampedWidth(columns) {
2063
2428
  if (!columns || columns <= 0) {
@@ -2088,6 +2453,7 @@ function DashboardApp({ mode }) {
2088
2453
  const projectIntervalSeconds = config.panels.project.interval ? config.panels.project.interval / 1e3 : null;
2089
2454
  const gitIntervalSeconds = config.panels.git.interval ? config.panels.git.interval / 1e3 : null;
2090
2455
  const claudeIntervalSeconds = config.panels.claude.interval ? config.panels.claude.interval / 1e3 : null;
2456
+ const otherSessionsIntervalSeconds = config.panels.other_sessions.interval ? config.panels.other_sessions.interval / 1e3 : null;
2091
2457
  const cwd = process.cwd();
2092
2458
  const [projectData, setProjectData] = useState(() => getProjectData());
2093
2459
  const refreshProject = useCallback(() => {
@@ -2111,6 +2477,12 @@ function DashboardApp({ mode }) {
2111
2477
  const refreshClaude = useCallback(() => {
2112
2478
  setClaudeData(getClaudeData(cwd, config.panels.claude.maxActivities));
2113
2479
  }, [cwd, config.panels.claude.maxActivities]);
2480
+ const [otherSessionsData, setOtherSessionsData] = useState(
2481
+ () => getOtherSessionsData(cwd, { activeThresholdMs: config.panels.other_sessions.activeThreshold })
2482
+ );
2483
+ const refreshOtherSessions = useCallback(() => {
2484
+ setOtherSessionsData(getOtherSessionsData(cwd, { activeThresholdMs: config.panels.other_sessions.activeThreshold }));
2485
+ }, [cwd, config.panels.other_sessions.activeThreshold]);
2114
2486
  const customPanelNames = useMemo(
2115
2487
  () => Object.keys(config.customPanels || {}),
2116
2488
  [config.customPanels]
@@ -2141,7 +2513,8 @@ function DashboardApp({ mode }) {
2141
2513
  const countdowns2 = {
2142
2514
  project: projectIntervalSeconds,
2143
2515
  git: gitIntervalSeconds,
2144
- claude: claudeIntervalSeconds
2516
+ claude: claudeIntervalSeconds,
2517
+ other_sessions: otherSessionsIntervalSeconds
2145
2518
  };
2146
2519
  if (config.customPanels) {
2147
2520
  for (const [name, panelConfig] of Object.entries(config.customPanels)) {
@@ -2149,14 +2522,15 @@ function DashboardApp({ mode }) {
2149
2522
  }
2150
2523
  }
2151
2524
  return countdowns2;
2152
- }, [projectIntervalSeconds, gitIntervalSeconds, claudeIntervalSeconds, config.customPanels]);
2525
+ }, [projectIntervalSeconds, gitIntervalSeconds, claudeIntervalSeconds, otherSessionsIntervalSeconds, config.customPanels]);
2153
2526
  const [countdowns, setCountdowns] = useState(initialCountdowns);
2154
2527
  const initialVisualStates = useMemo(() => {
2155
2528
  const states = {
2156
2529
  project: { ...DEFAULT_VISUAL_STATE },
2157
2530
  git: { ...DEFAULT_VISUAL_STATE },
2158
2531
  tests: { ...DEFAULT_VISUAL_STATE },
2159
- claude: { ...DEFAULT_VISUAL_STATE }
2532
+ claude: { ...DEFAULT_VISUAL_STATE },
2533
+ other_sessions: { ...DEFAULT_VISUAL_STATE }
2160
2534
  };
2161
2535
  for (const name of customPanelNames) {
2162
2536
  states[name] = { ...DEFAULT_VISUAL_STATE };
@@ -2227,6 +2601,11 @@ function DashboardApp({ mode }) {
2227
2601
  setVisualState("claude", { justRefreshed: true });
2228
2602
  clearFeedback("claude", "justRefreshed");
2229
2603
  }, [cwd, config.panels.claude.maxActivities, setVisualState, clearFeedback]);
2604
+ const refreshOtherSessionsWithFeedback = useCallback(() => {
2605
+ setOtherSessionsData(getOtherSessionsData(cwd, { activeThresholdMs: config.panels.other_sessions.activeThreshold }));
2606
+ setVisualState("other_sessions", { justRefreshed: true });
2607
+ clearFeedback("other_sessions", "justRefreshed");
2608
+ }, [cwd, config.panels.other_sessions.activeThreshold, setVisualState, clearFeedback]);
2230
2609
  const customPanelActionsAsync = useMemo(() => {
2231
2610
  const actions = {};
2232
2611
  for (const name of customPanelNames) {
@@ -2250,6 +2629,10 @@ function DashboardApp({ mode }) {
2250
2629
  refreshClaudeWithFeedback();
2251
2630
  setCountdowns((prev) => ({ ...prev, claude: claudeIntervalSeconds }));
2252
2631
  }
2632
+ if (config.panels.other_sessions.enabled) {
2633
+ refreshOtherSessionsWithFeedback();
2634
+ setCountdowns((prev) => ({ ...prev, other_sessions: otherSessionsIntervalSeconds }));
2635
+ }
2253
2636
  for (const name of customPanelNames) {
2254
2637
  if (config.customPanels[name].enabled) {
2255
2638
  void refreshCustomPanelAsync(name);
@@ -2265,11 +2648,13 @@ function DashboardApp({ mode }) {
2265
2648
  refreshGitAsync,
2266
2649
  refreshTestAsync,
2267
2650
  refreshClaudeWithFeedback,
2651
+ refreshOtherSessionsWithFeedback,
2268
2652
  refreshCustomPanelAsync,
2269
2653
  config,
2270
2654
  projectIntervalSeconds,
2271
2655
  gitIntervalSeconds,
2272
2656
  claudeIntervalSeconds,
2657
+ otherSessionsIntervalSeconds,
2273
2658
  customPanelNames
2274
2659
  ]);
2275
2660
  const hotkeys = useMemo(
@@ -2309,6 +2694,14 @@ function DashboardApp({ mode }) {
2309
2694
  }, config.panels.claude.interval)
2310
2695
  );
2311
2696
  }
2697
+ if (config.panels.other_sessions.enabled && config.panels.other_sessions.interval !== null) {
2698
+ timers.push(
2699
+ setInterval(() => {
2700
+ refreshOtherSessionsWithFeedback();
2701
+ setCountdowns((prev) => ({ ...prev, other_sessions: otherSessionsIntervalSeconds }));
2702
+ }, config.panels.other_sessions.interval)
2703
+ );
2704
+ }
2312
2705
  if (config.customPanels) {
2313
2706
  for (const [name, panelConfig] of Object.entries(config.customPanels)) {
2314
2707
  if (panelConfig.enabled && panelConfig.interval !== null) {
@@ -2330,10 +2723,12 @@ function DashboardApp({ mode }) {
2330
2723
  refreshGitAsync,
2331
2724
  refreshTestAsync,
2332
2725
  refreshClaudeWithFeedback,
2726
+ refreshOtherSessionsWithFeedback,
2333
2727
  refreshCustomPanelAsync,
2334
2728
  projectIntervalSeconds,
2335
2729
  gitIntervalSeconds,
2336
- claudeIntervalSeconds
2730
+ claudeIntervalSeconds,
2731
+ otherSessionsIntervalSeconds
2337
2732
  ]);
2338
2733
  useEffect(() => {
2339
2734
  if (mode !== "watch") return;
@@ -2342,7 +2737,8 @@ function DashboardApp({ mode }) {
2342
2737
  const next = {
2343
2738
  project: prev.project !== null && prev.project > 1 ? prev.project - 1 : prev.project,
2344
2739
  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
2740
+ claude: prev.claude !== null && prev.claude > 1 ? prev.claude - 1 : prev.claude,
2741
+ other_sessions: prev.other_sessions !== null && prev.other_sessions > 1 ? prev.other_sessions - 1 : prev.other_sessions
2346
2742
  };
2347
2743
  for (const name of customPanelNames) {
2348
2744
  next[name] = prev[name] !== null && prev[name] > 1 ? prev[name] - 1 : prev[name];
@@ -2375,8 +2771,8 @@ function DashboardApp({ mode }) {
2375
2771
  }
2376
2772
  statusBarItems.push("r: refresh all");
2377
2773
  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: [
2774
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2775
+ warnings.length > 0 && /* @__PURE__ */ jsx8(Box8, { marginBottom: 1, children: /* @__PURE__ */ jsxs8(Text8, { color: "yellow", children: [
2380
2776
  "\u26A0 ",
2381
2777
  warnings.join(", ")
2382
2778
  ] }) }),
@@ -2384,7 +2780,7 @@ function DashboardApp({ mode }) {
2384
2780
  const isFirst = index === 0;
2385
2781
  if (panelName === "project" && config.panels.project.enabled) {
2386
2782
  const projectVisual = visualStates.project || DEFAULT_VISUAL_STATE;
2387
- return /* @__PURE__ */ jsx7(Box7, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx7(
2783
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2388
2784
  ProjectPanel,
2389
2785
  {
2390
2786
  data: projectData,
@@ -2396,7 +2792,7 @@ function DashboardApp({ mode }) {
2396
2792
  }
2397
2793
  if (panelName === "git" && config.panels.git.enabled) {
2398
2794
  const gitVisual = visualStates.git || DEFAULT_VISUAL_STATE;
2399
- return /* @__PURE__ */ jsx7(Box7, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx7(
2795
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2400
2796
  GitPanel,
2401
2797
  {
2402
2798
  branch: gitData.branch,
@@ -2412,7 +2808,7 @@ function DashboardApp({ mode }) {
2412
2808
  }
2413
2809
  if (panelName === "tests" && config.panels.tests.enabled) {
2414
2810
  const testsVisual = visualStates.tests || DEFAULT_VISUAL_STATE;
2415
- return /* @__PURE__ */ jsx7(Box7, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx7(
2811
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2416
2812
  TestPanel,
2417
2813
  {
2418
2814
  results: testData.results,
@@ -2427,7 +2823,7 @@ function DashboardApp({ mode }) {
2427
2823
  }
2428
2824
  if (panelName === "claude" && config.panels.claude.enabled) {
2429
2825
  const claudeVisual = visualStates.claude || DEFAULT_VISUAL_STATE;
2430
- return /* @__PURE__ */ jsx7(Box7, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx7(
2826
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2431
2827
  ClaudePanel,
2432
2828
  {
2433
2829
  data: claudeData,
@@ -2437,15 +2833,28 @@ function DashboardApp({ mode }) {
2437
2833
  }
2438
2834
  ) }, `panel-claude-${index}`);
2439
2835
  }
2836
+ if (panelName === "other_sessions" && config.panels.other_sessions.enabled) {
2837
+ const otherSessionsVisual = visualStates.other_sessions || DEFAULT_VISUAL_STATE;
2838
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2839
+ OtherSessionsPanel,
2840
+ {
2841
+ data: otherSessionsData,
2842
+ countdown: mode === "watch" ? countdowns.other_sessions : null,
2843
+ width,
2844
+ isRunning: otherSessionsVisual.isRunning,
2845
+ messageMaxLength: config.panels.other_sessions.messageMaxLength
2846
+ }
2847
+ ) }, `panel-other_sessions-${index}`);
2848
+ }
2440
2849
  const customConfig = config.customPanels?.[panelName];
2441
2850
  if (customConfig && customConfig.enabled) {
2442
2851
  const result = customPanelData[panelName];
2443
2852
  if (!result) return null;
2444
2853
  const customVisual = visualStates[panelName] || DEFAULT_VISUAL_STATE;
2445
2854
  const isManual = customConfig.interval === null;
2446
- const relativeTime = isManual ? formatRelativeTime2(result.timestamp) : void 0;
2855
+ const relativeTime = isManual ? formatRelativeTime3(result.timestamp) : void 0;
2447
2856
  const countdown = !isManual && mode === "watch" ? countdowns[panelName] : null;
2448
- return /* @__PURE__ */ jsx7(Box7, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx7(
2857
+ return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
2449
2858
  GenericPanel,
2450
2859
  {
2451
2860
  data: result.data,
@@ -2461,9 +2870,9 @@ function DashboardApp({ mode }) {
2461
2870
  }
2462
2871
  return null;
2463
2872
  }),
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: [
2873
+ 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
2874
  index > 0 && " \xB7 ",
2466
- /* @__PURE__ */ jsxs7(Text7, { color: "cyan", children: [
2875
+ /* @__PURE__ */ jsxs8(Text8, { color: "cyan", children: [
2467
2876
  item.split(":")[0],
2468
2877
  ":"
2469
2878
  ] }),
@@ -2473,14 +2882,14 @@ function DashboardApp({ mode }) {
2473
2882
  }
2474
2883
  function App({ mode, agentDirExists: agentDirExists2 = true }) {
2475
2884
  if (!agentDirExists2) {
2476
- return /* @__PURE__ */ jsx7(WelcomeApp, {});
2885
+ return /* @__PURE__ */ jsx8(WelcomeApp, {});
2477
2886
  }
2478
- return /* @__PURE__ */ jsx7(DashboardApp, { mode });
2887
+ return /* @__PURE__ */ jsx8(DashboardApp, { mode });
2479
2888
  }
2480
2889
 
2481
2890
  // src/cli.ts
2482
2891
  import { readFileSync } from "fs";
2483
- import { dirname, join as join3 } from "path";
2892
+ import { dirname, join as join4 } from "path";
2484
2893
  import { fileURLToPath } from "url";
2485
2894
  function getHelp() {
2486
2895
  return `Usage: agenthud [command] [options]
@@ -2498,7 +2907,7 @@ Options:
2498
2907
  function getVersion() {
2499
2908
  const __dirname3 = dirname(fileURLToPath(import.meta.url));
2500
2909
  const packageJson = JSON.parse(
2501
- readFileSync(join3(__dirname3, "..", "package.json"), "utf-8")
2910
+ readFileSync(join4(__dirname3, "..", "package.json"), "utf-8")
2502
2911
  );
2503
2912
  return packageJson.version;
2504
2913
  }
@@ -2525,34 +2934,34 @@ function parseArgs(args) {
2525
2934
 
2526
2935
  // src/commands/init.ts
2527
2936
  import {
2528
- existsSync as nodeExistsSync4,
2937
+ existsSync as nodeExistsSync5,
2529
2938
  mkdirSync as nodeMkdirSync,
2530
2939
  writeFileSync as nodeWriteFileSync,
2531
- readFileSync as nodeReadFileSync6,
2940
+ readFileSync as nodeReadFileSync7,
2532
2941
  appendFileSync as nodeAppendFileSync
2533
2942
  } from "fs";
2534
2943
  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,
2944
+ import { dirname as dirname2, join as join5 } from "path";
2945
+ import { homedir as homedir3 } from "os";
2946
+ var fs4 = {
2947
+ existsSync: nodeExistsSync5,
2539
2948
  mkdirSync: nodeMkdirSync,
2540
2949
  writeFileSync: nodeWriteFileSync,
2541
- readFileSync: (path) => nodeReadFileSync6(path, "utf-8"),
2950
+ readFileSync: (path) => nodeReadFileSync7(path, "utf-8"),
2542
2951
  appendFileSync: nodeAppendFileSync
2543
2952
  };
2544
2953
  var __filename2 = fileURLToPath2(import.meta.url);
2545
2954
  var __dirname2 = dirname2(__filename2);
2546
2955
  function getDefaultConfig2() {
2547
- let templatePath = join4(__dirname2, "templates", "config.yaml");
2548
- if (!nodeExistsSync4(templatePath)) {
2549
- templatePath = join4(__dirname2, "..", "templates", "config.yaml");
2956
+ let templatePath = join5(__dirname2, "templates", "config.yaml");
2957
+ if (!nodeExistsSync5(templatePath)) {
2958
+ templatePath = join5(__dirname2, "..", "templates", "config.yaml");
2550
2959
  }
2551
- return nodeReadFileSync6(templatePath, "utf-8");
2960
+ return nodeReadFileSync7(templatePath, "utf-8");
2552
2961
  }
2553
2962
  function getClaudeSessionPath2(projectPath) {
2554
2963
  const encoded = projectPath.replace(/\//g, "-");
2555
- return join4(homedir2(), ".claude", "projects", encoded);
2964
+ return join5(homedir3(), ".claude", "projects", encoded);
2556
2965
  }
2557
2966
  function runInit(cwd = process.cwd()) {
2558
2967
  const result = {
@@ -2560,41 +2969,41 @@ function runInit(cwd = process.cwd()) {
2560
2969
  skipped: [],
2561
2970
  warnings: []
2562
2971
  };
2563
- if (!fs3.existsSync(".agenthud")) {
2564
- fs3.mkdirSync(".agenthud", { recursive: true });
2972
+ if (!fs4.existsSync(".agenthud")) {
2973
+ fs4.mkdirSync(".agenthud", { recursive: true });
2565
2974
  result.created.push(".agenthud/");
2566
2975
  } else {
2567
2976
  result.skipped.push(".agenthud/");
2568
2977
  }
2569
- if (!fs3.existsSync(".agenthud/tests")) {
2570
- fs3.mkdirSync(".agenthud/tests", { recursive: true });
2978
+ if (!fs4.existsSync(".agenthud/tests")) {
2979
+ fs4.mkdirSync(".agenthud/tests", { recursive: true });
2571
2980
  result.created.push(".agenthud/tests/");
2572
2981
  } else {
2573
2982
  result.skipped.push(".agenthud/tests/");
2574
2983
  }
2575
- if (!fs3.existsSync(".agenthud/config.yaml")) {
2576
- fs3.writeFileSync(".agenthud/config.yaml", getDefaultConfig2());
2984
+ if (!fs4.existsSync(".agenthud/config.yaml")) {
2985
+ fs4.writeFileSync(".agenthud/config.yaml", getDefaultConfig2());
2577
2986
  result.created.push(".agenthud/config.yaml");
2578
2987
  } else {
2579
2988
  result.skipped.push(".agenthud/config.yaml");
2580
2989
  }
2581
- if (!fs3.existsSync(".gitignore")) {
2582
- fs3.writeFileSync(".gitignore", ".agenthud/\n");
2990
+ if (!fs4.existsSync(".gitignore")) {
2991
+ fs4.writeFileSync(".gitignore", ".agenthud/\n");
2583
2992
  result.created.push(".gitignore");
2584
2993
  } else {
2585
- const content = fs3.readFileSync(".gitignore");
2994
+ const content = fs4.readFileSync(".gitignore");
2586
2995
  if (!content.includes(".agenthud/")) {
2587
- fs3.appendFileSync(".gitignore", "\n.agenthud/\n");
2996
+ fs4.appendFileSync(".gitignore", "\n.agenthud/\n");
2588
2997
  result.created.push(".gitignore");
2589
2998
  } else {
2590
2999
  result.skipped.push(".gitignore");
2591
3000
  }
2592
3001
  }
2593
- if (!fs3.existsSync(".git")) {
3002
+ if (!fs4.existsSync(".git")) {
2594
3003
  result.warnings.push("Not a git repository - Git panel will show limited info");
2595
3004
  }
2596
3005
  const claudeSessionPath = getClaudeSessionPath2(cwd);
2597
- if (!fs3.existsSync(claudeSessionPath)) {
3006
+ if (!fs4.existsSync(claudeSessionPath)) {
2598
3007
  result.warnings.push("No Claude session found - start Claude to see activity");
2599
3008
  }
2600
3009
  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.11",
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",