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.
- package/dist/index.js +519 -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,126 @@ 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 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__ */
|
|
766
|
+
return /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
635
767
|
BOX.v,
|
|
636
|
-
/* @__PURE__ */
|
|
768
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: padLine(" No data", width) }),
|
|
637
769
|
BOX.v
|
|
638
770
|
] });
|
|
639
771
|
}
|
|
640
|
-
return /* @__PURE__ */
|
|
641
|
-
data.summary && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
658
|
-
data.summary && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
804
|
+
items.length === 0 && !data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
673
805
|
BOX.v,
|
|
674
|
-
/* @__PURE__ */
|
|
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__ */
|
|
693
|
-
data.summary && /* @__PURE__ */
|
|
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__ */
|
|
830
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
699
831
|
BOX.v,
|
|
700
832
|
" ",
|
|
701
|
-
/* @__PURE__ */
|
|
833
|
+
/* @__PURE__ */ jsxs6(Text6, { color: "green", children: [
|
|
702
834
|
"\u2713 ",
|
|
703
835
|
stats.passed,
|
|
704
836
|
" passed"
|
|
705
837
|
] }),
|
|
706
|
-
stats.failed > 0 && /* @__PURE__ */
|
|
838
|
+
stats.failed > 0 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
707
839
|
" ",
|
|
708
|
-
/* @__PURE__ */
|
|
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__ */
|
|
846
|
+
stats.skipped && stats.skipped > 0 && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
715
847
|
" ",
|
|
716
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
747
|
-
/* @__PURE__ */
|
|
748
|
-
/* @__PURE__ */
|
|
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__ */
|
|
882
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: padLine(" " + error, width) }),
|
|
751
883
|
BOX.v
|
|
752
884
|
] }),
|
|
753
|
-
/* @__PURE__ */
|
|
885
|
+
/* @__PURE__ */ jsx6(Text6, { children: createBottomLine(width) })
|
|
754
886
|
] });
|
|
755
887
|
}
|
|
756
888
|
if (renderer === "progress") {
|
|
757
|
-
return /* @__PURE__ */
|
|
758
|
-
/* @__PURE__ */
|
|
759
|
-
/* @__PURE__ */
|
|
760
|
-
/* @__PURE__ */
|
|
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__ */
|
|
764
|
-
/* @__PURE__ */
|
|
765
|
-
renderer === "status" ? /* @__PURE__ */
|
|
766
|
-
/* @__PURE__ */
|
|
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
|
|
772
|
-
import { jsx as
|
|
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__ */
|
|
775
|
-
/* @__PURE__ */
|
|
776
|
-
/* @__PURE__ */
|
|
777
|
-
/* @__PURE__ */
|
|
778
|
-
/* @__PURE__ */
|
|
779
|
-
/* @__PURE__ */
|
|
780
|
-
/* @__PURE__ */
|
|
781
|
-
/* @__PURE__ */
|
|
782
|
-
/* @__PURE__ */
|
|
783
|
-
/* @__PURE__ */
|
|
784
|
-
/* @__PURE__ */
|
|
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
|
|
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) =>
|
|
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
|
|
1796
|
-
readFileSync as
|
|
2129
|
+
existsSync as nodeExistsSync4,
|
|
2130
|
+
readFileSync as nodeReadFileSync6
|
|
1797
2131
|
} from "fs";
|
|
1798
2132
|
import { parse as parseYaml } from "yaml";
|
|
1799
|
-
var
|
|
1800
|
-
existsSync:
|
|
1801
|
-
readFileSync: (path) =>
|
|
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 (!
|
|
2199
|
+
if (!fs3.existsSync(CONFIG_PATH)) {
|
|
1858
2200
|
return { config: defaultConfig, warnings };
|
|
1859
2201
|
}
|
|
1860
2202
|
let rawConfig;
|
|
1861
2203
|
try {
|
|
1862
|
-
const content =
|
|
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
|
|
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
|
|
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__ */
|
|
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__ */
|
|
2379
|
-
warnings.length > 0 && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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 ?
|
|
2855
|
+
const relativeTime = isManual ? formatRelativeTime3(result.timestamp) : void 0;
|
|
2447
2856
|
const countdown = !isManual && mode === "watch" ? countdowns[panelName] : null;
|
|
2448
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2885
|
+
return /* @__PURE__ */ jsx8(WelcomeApp, {});
|
|
2477
2886
|
}
|
|
2478
|
-
return /* @__PURE__ */
|
|
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
|
|
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(
|
|
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
|
|
2937
|
+
existsSync as nodeExistsSync5,
|
|
2529
2938
|
mkdirSync as nodeMkdirSync,
|
|
2530
2939
|
writeFileSync as nodeWriteFileSync,
|
|
2531
|
-
readFileSync as
|
|
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
|
|
2536
|
-
import { homedir as
|
|
2537
|
-
var
|
|
2538
|
-
existsSync:
|
|
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) =>
|
|
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 =
|
|
2548
|
-
if (!
|
|
2549
|
-
templatePath =
|
|
2956
|
+
let templatePath = join5(__dirname2, "templates", "config.yaml");
|
|
2957
|
+
if (!nodeExistsSync5(templatePath)) {
|
|
2958
|
+
templatePath = join5(__dirname2, "..", "templates", "config.yaml");
|
|
2550
2959
|
}
|
|
2551
|
-
return
|
|
2960
|
+
return nodeReadFileSync7(templatePath, "utf-8");
|
|
2552
2961
|
}
|
|
2553
2962
|
function getClaudeSessionPath2(projectPath) {
|
|
2554
2963
|
const encoded = projectPath.replace(/\//g, "-");
|
|
2555
|
-
return
|
|
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 (!
|
|
2564
|
-
|
|
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 (!
|
|
2570
|
-
|
|
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 (!
|
|
2576
|
-
|
|
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 (!
|
|
2582
|
-
|
|
2990
|
+
if (!fs4.existsSync(".gitignore")) {
|
|
2991
|
+
fs4.writeFileSync(".gitignore", ".agenthud/\n");
|
|
2583
2992
|
result.created.push(".gitignore");
|
|
2584
2993
|
} else {
|
|
2585
|
-
const content =
|
|
2994
|
+
const content = fs4.readFileSync(".gitignore");
|
|
2586
2995
|
if (!content.includes(".agenthud/")) {
|
|
2587
|
-
|
|
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 (!
|
|
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 (!
|
|
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