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.
- package/dist/index.js +556 -110
- 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
|
|
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
|
|
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",
|
|
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/
|
|
617
|
+
// src/ui/OtherSessionsPanel.tsx
|
|
603
618
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
604
|
-
import {
|
|
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__ */
|
|
801
|
+
return /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
635
802
|
BOX.v,
|
|
636
|
-
/* @__PURE__ */
|
|
803
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: padLine(" No data", width) }),
|
|
637
804
|
BOX.v
|
|
638
805
|
] });
|
|
639
806
|
}
|
|
640
|
-
return /* @__PURE__ */
|
|
641
|
-
data.summary && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
658
|
-
data.summary && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
839
|
+
items.length === 0 && !data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
673
840
|
BOX.v,
|
|
674
|
-
/* @__PURE__ */
|
|
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__ */
|
|
693
|
-
data.summary && /* @__PURE__ */
|
|
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__ */
|
|
865
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
699
866
|
BOX.v,
|
|
700
867
|
" ",
|
|
701
|
-
/* @__PURE__ */
|
|
868
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "green", children: [
|
|
702
869
|
"\u2713 ",
|
|
703
870
|
stats.passed,
|
|
704
871
|
" passed"
|
|
705
872
|
] }),
|
|
706
|
-
stats.failed > 0 && /* @__PURE__ */
|
|
873
|
+
stats.failed > 0 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
707
874
|
" ",
|
|
708
|
-
/* @__PURE__ */
|
|
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__ */
|
|
881
|
+
stats.skipped && stats.skipped > 0 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
715
882
|
" ",
|
|
716
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
747
|
-
/* @__PURE__ */
|
|
748
|
-
/* @__PURE__ */
|
|
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__ */
|
|
917
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: padLine(" " + error, width) }),
|
|
751
918
|
BOX.v
|
|
752
919
|
] }),
|
|
753
|
-
/* @__PURE__ */
|
|
920
|
+
/* @__PURE__ */ jsx6(Text6, { children: createBottomLine(width) })
|
|
754
921
|
] });
|
|
755
922
|
}
|
|
756
923
|
if (renderer === "progress") {
|
|
757
|
-
return /* @__PURE__ */
|
|
758
|
-
/* @__PURE__ */
|
|
759
|
-
/* @__PURE__ */
|
|
760
|
-
/* @__PURE__ */
|
|
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__ */
|
|
764
|
-
/* @__PURE__ */
|
|
765
|
-
renderer === "status" ? /* @__PURE__ */
|
|
766
|
-
/* @__PURE__ */
|
|
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
|
|
772
|
-
import { jsx as
|
|
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__ */
|
|
775
|
-
/* @__PURE__ */
|
|
776
|
-
/* @__PURE__ */
|
|
777
|
-
/* @__PURE__ */
|
|
778
|
-
/* @__PURE__ */
|
|
779
|
-
/* @__PURE__ */
|
|
780
|
-
/* @__PURE__ */
|
|
781
|
-
/* @__PURE__ */
|
|
782
|
-
/* @__PURE__ */
|
|
783
|
-
/* @__PURE__ */
|
|
784
|
-
/* @__PURE__ */
|
|
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
|
|
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) =>
|
|
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
|
|
1796
|
-
readFileSync as
|
|
2166
|
+
existsSync as nodeExistsSync4,
|
|
2167
|
+
readFileSync as nodeReadFileSync6
|
|
1797
2168
|
} from "fs";
|
|
1798
2169
|
import { parse as parseYaml } from "yaml";
|
|
1799
|
-
var
|
|
1800
|
-
existsSync:
|
|
1801
|
-
readFileSync: (path) =>
|
|
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 (!
|
|
2236
|
+
if (!fs3.existsSync(CONFIG_PATH)) {
|
|
1858
2237
|
return { config: defaultConfig, warnings };
|
|
1859
2238
|
}
|
|
1860
2239
|
let rawConfig;
|
|
1861
2240
|
try {
|
|
1862
|
-
const content =
|
|
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
|
|
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
|
|
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__ */
|
|
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__ */
|
|
2379
|
-
warnings.length > 0 && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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 ?
|
|
2892
|
+
const relativeTime = isManual ? formatRelativeTime3(result.timestamp) : void 0;
|
|
2447
2893
|
const countdown = !isManual && mode === "watch" ? countdowns[panelName] : null;
|
|
2448
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2922
|
+
return /* @__PURE__ */ jsx8(WelcomeApp, {});
|
|
2477
2923
|
}
|
|
2478
|
-
return /* @__PURE__ */
|
|
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
|
|
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(
|
|
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
|
|
2974
|
+
existsSync as nodeExistsSync5,
|
|
2529
2975
|
mkdirSync as nodeMkdirSync,
|
|
2530
2976
|
writeFileSync as nodeWriteFileSync,
|
|
2531
|
-
readFileSync as
|
|
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
|
|
2536
|
-
import { homedir as
|
|
2537
|
-
var
|
|
2538
|
-
existsSync:
|
|
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) =>
|
|
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 =
|
|
2548
|
-
if (!
|
|
2549
|
-
templatePath =
|
|
2993
|
+
let templatePath = join5(__dirname2, "templates", "config.yaml");
|
|
2994
|
+
if (!nodeExistsSync5(templatePath)) {
|
|
2995
|
+
templatePath = join5(__dirname2, "..", "templates", "config.yaml");
|
|
2550
2996
|
}
|
|
2551
|
-
return
|
|
2997
|
+
return nodeReadFileSync7(templatePath, "utf-8");
|
|
2552
2998
|
}
|
|
2553
2999
|
function getClaudeSessionPath2(projectPath) {
|
|
2554
3000
|
const encoded = projectPath.replace(/\//g, "-");
|
|
2555
|
-
return
|
|
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 (!
|
|
2564
|
-
|
|
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 (!
|
|
2570
|
-
|
|
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 (!
|
|
2576
|
-
|
|
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 (!
|
|
2582
|
-
|
|
3027
|
+
if (!fs4.existsSync(".gitignore")) {
|
|
3028
|
+
fs4.writeFileSync(".gitignore", ".agenthud/\n");
|
|
2583
3029
|
result.created.push(".gitignore");
|
|
2584
3030
|
} else {
|
|
2585
|
-
const content =
|
|
3031
|
+
const content = fs4.readFileSync(".gitignore");
|
|
2586
3032
|
if (!content.includes(".agenthud/")) {
|
|
2587
|
-
|
|
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 (!
|
|
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 (!
|
|
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