agenthud 0.5.16 → 0.6.0
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/README.md +86 -3
- package/dist/index.js +353 -61
- package/dist/templates/config.yaml +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,16 +67,99 @@ panels:
|
|
|
67
67
|
interval: 10s
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
## Panels
|
|
71
|
+
|
|
72
|
+
### Claude Panel
|
|
73
|
+
|
|
74
|
+
Shows real-time Claude Code activity:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
┌─ Claude ─────────────────────────────────────────────┐
|
|
78
|
+
│ [10:23:45] ○ Read: src/components/Button.tsx │
|
|
79
|
+
│ [10:23:46] ~ Edit: src/components/Button.tsx │
|
|
80
|
+
│ [10:23:47] $ Bash: npm test │
|
|
81
|
+
│ [10:23:50] < Response: Tests passed successfully... │
|
|
82
|
+
└──────────────────────────────────────────────────────┘
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- **○ Read**: File being read
|
|
86
|
+
- **~ Edit/Write**: File being modified
|
|
87
|
+
- **$ Bash**: Command being executed
|
|
88
|
+
- **< Response**: Claude's text response
|
|
89
|
+
|
|
90
|
+
### Git Panel
|
|
91
|
+
|
|
92
|
+
Shows today's git activity and current state:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
┌─ Git ────────────────────────────────────────────────┐
|
|
96
|
+
│ feat/add-dashboard · +142 -23 · 3 commits · 5 files │
|
|
97
|
+
│ • abc1234 Add dashboard component │
|
|
98
|
+
│ • def5678 Fix styling issues │
|
|
99
|
+
└──────────────────────────────────────────────────────┘
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- **Branch name**: Current working branch (green)
|
|
103
|
+
- **Stats**: Lines added/deleted, commits, files changed
|
|
104
|
+
- **dirty**: Shows uncommitted change count (yellow)
|
|
105
|
+
|
|
106
|
+
### Tests Panel
|
|
107
|
+
|
|
108
|
+
Shows test results with staleness detection:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
┌─ Tests ──────────────────────────────────────────────┐
|
|
112
|
+
│ ✓ 42 passed ✗ 1 failed ○ 2 skipped · abc1234 │
|
|
113
|
+
│ ⚠ Outdated (3 commits behind) │
|
|
114
|
+
│──────────────────────────────────────────────────────│
|
|
115
|
+
│ ✗ Button.test.tsx │
|
|
116
|
+
│ • should render correctly │
|
|
117
|
+
└──────────────────────────────────────────────────────┘
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- **✓ passed** (green), **✗ failed** (red), **○ skipped**
|
|
121
|
+
- **⚠ Outdated**: Warning if tests are behind commits
|
|
122
|
+
- **Failures**: Shows failing test file and name
|
|
123
|
+
|
|
124
|
+
**Auto-detection**: During `agenthud init`, the test framework is automatically detected:
|
|
125
|
+
|
|
126
|
+
| Framework | Detection |
|
|
127
|
+
|-----------|-----------|
|
|
128
|
+
| vitest | package.json devDependencies |
|
|
129
|
+
| jest | package.json devDependencies |
|
|
130
|
+
| mocha | package.json devDependencies |
|
|
131
|
+
| pytest | pytest.ini, conftest.py, pyproject.toml, requirements.txt |
|
|
132
|
+
|
|
133
|
+
If the test command fails, the panel is automatically disabled.
|
|
134
|
+
|
|
135
|
+
### Project Panel
|
|
136
|
+
|
|
137
|
+
Shows project overview and structure:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
┌─ Project ────────────────────────────────────────────┐
|
|
141
|
+
│ agenthud · TypeScript · MIT │
|
|
142
|
+
│ Stack: react, ink, vitest │
|
|
143
|
+
│ Files: 45 .ts · Lines: 3.2k │
|
|
144
|
+
│ Deps: 12 prod · 8 dev │
|
|
145
|
+
└──────────────────────────────────────────────────────┘
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- **Name/Language/License**: Project basics
|
|
149
|
+
- **Stack**: Detected frameworks and tools
|
|
150
|
+
- **Files/Lines**: Source code stats
|
|
151
|
+
- **Deps**: Dependency counts
|
|
152
|
+
|
|
70
153
|
### Other Sessions Panel
|
|
71
154
|
|
|
72
155
|
Shows activity from your other Claude Code projects:
|
|
73
156
|
|
|
74
157
|
```
|
|
75
158
|
┌─ Other Sessions ─────────────────────────────────────┐
|
|
76
|
-
│ 📁 dotfiles, pain-radar, myapp +4 | ⚡ 1 active
|
|
159
|
+
│ 📁 dotfiles, pain-radar, myapp +4 | ⚡ 1 active │
|
|
77
160
|
│ │
|
|
78
|
-
│ 🔵 dotfiles (2m ago)
|
|
79
|
-
│ "Updated the config file as requested..."
|
|
161
|
+
│ 🔵 dotfiles (2m ago) │
|
|
162
|
+
│ "Updated the config file as requested..." │
|
|
80
163
|
└──────────────────────────────────────────────────────┘
|
|
81
164
|
```
|
|
82
165
|
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
4
|
+
import React3 from "react";
|
|
5
5
|
import { render } from "ink";
|
|
6
6
|
import { existsSync } from "fs";
|
|
7
7
|
|
|
8
8
|
// src/ui/App.tsx
|
|
9
|
-
import
|
|
9
|
+
import React2, { useState as useState2, useEffect as useEffect2, useCallback, useMemo } from "react";
|
|
10
10
|
import { Box as Box8, Text as Text8, useApp, useInput, useStdout } from "ink";
|
|
11
11
|
|
|
12
12
|
// src/ui/GitPanel.tsx
|
|
@@ -48,6 +48,13 @@ function createTitleLine(label, suffix = "", panelWidth = DEFAULT_PANEL_WIDTH) {
|
|
|
48
48
|
function createBottomLine(panelWidth = DEFAULT_PANEL_WIDTH) {
|
|
49
49
|
return BOX.bl + BOX.h.repeat(getInnerWidth(panelWidth)) + BOX.br;
|
|
50
50
|
}
|
|
51
|
+
function createSeparatorLine(title, panelWidth = DEFAULT_PANEL_WIDTH) {
|
|
52
|
+
const leftPart = BOX.h + " " + title + " ";
|
|
53
|
+
const leftWidth = leftPart.length;
|
|
54
|
+
const dashCount = panelWidth - 1 - leftWidth - 1;
|
|
55
|
+
const dashes = BOX.h.repeat(Math.max(0, dashCount));
|
|
56
|
+
return BOX.ml + leftPart + dashes + BOX.mr;
|
|
57
|
+
}
|
|
51
58
|
function padLine(content, panelWidth = DEFAULT_PANEL_WIDTH) {
|
|
52
59
|
const innerWidth = getInnerWidth(panelWidth);
|
|
53
60
|
const padding = innerWidth - content.length;
|
|
@@ -421,8 +428,9 @@ function ProjectPanel({
|
|
|
421
428
|
}
|
|
422
429
|
|
|
423
430
|
// src/ui/ClaudePanel.tsx
|
|
431
|
+
import { useState, useEffect } from "react";
|
|
424
432
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
425
|
-
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
433
|
+
import { Fragment as Fragment4, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
426
434
|
function getActivityStyle(activity) {
|
|
427
435
|
if (activity.type === "user") {
|
|
428
436
|
return { color: "white", dimColor: false };
|
|
@@ -481,15 +489,46 @@ function formatActivityParts(activity, maxWidth) {
|
|
|
481
489
|
const icon = activity.icon;
|
|
482
490
|
const label = activity.label;
|
|
483
491
|
const detail = activity.detail;
|
|
492
|
+
const count = activity.count;
|
|
493
|
+
const countSuffix = count && count > 1 ? ` (\xD7${count})` : "";
|
|
494
|
+
const countSuffixWidth = countSuffix.length;
|
|
495
|
+
const skipLabel = label === "User" || label === "Response";
|
|
484
496
|
const timestamp = `[${time}] `;
|
|
485
497
|
const timestampWidth = timestamp.length;
|
|
486
498
|
const iconWidth = getDisplayWidth(icon);
|
|
499
|
+
if (skipLabel && detail) {
|
|
500
|
+
const prefixWidth = timestampWidth + iconWidth + 1;
|
|
501
|
+
const availableWidth = maxWidth - prefixWidth - countSuffixWidth;
|
|
502
|
+
let truncatedDetail = detail;
|
|
503
|
+
let detailDisplayWidth = getDisplayWidth(detail);
|
|
504
|
+
if (detailDisplayWidth > availableWidth) {
|
|
505
|
+
truncatedDetail = "";
|
|
506
|
+
let currentWidth = 0;
|
|
507
|
+
for (const char of detail) {
|
|
508
|
+
const charWidth = getDisplayWidth(char);
|
|
509
|
+
if (currentWidth + charWidth > availableWidth - 3) {
|
|
510
|
+
truncatedDetail += "...";
|
|
511
|
+
currentWidth += 3;
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
truncatedDetail += char;
|
|
515
|
+
currentWidth += charWidth;
|
|
516
|
+
}
|
|
517
|
+
detailDisplayWidth = currentWidth;
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
timestamp,
|
|
521
|
+
icon,
|
|
522
|
+
labelContent: truncatedDetail + countSuffix,
|
|
523
|
+
displayWidth: prefixWidth + detailDisplayWidth + countSuffixWidth
|
|
524
|
+
};
|
|
525
|
+
}
|
|
487
526
|
const labelWidth = label.length;
|
|
488
527
|
const separatorWidth = detail ? 2 : 0;
|
|
489
528
|
const contentPrefixWidth = iconWidth + 1 + labelWidth + separatorWidth;
|
|
490
529
|
const totalPrefixWidth = timestampWidth + contentPrefixWidth;
|
|
491
530
|
if (detail) {
|
|
492
|
-
const availableWidth = maxWidth - totalPrefixWidth;
|
|
531
|
+
const availableWidth = maxWidth - totalPrefixWidth - countSuffixWidth;
|
|
493
532
|
let truncatedDetail = detail;
|
|
494
533
|
let detailDisplayWidth = getDisplayWidth(detail);
|
|
495
534
|
if (detailDisplayWidth > availableWidth) {
|
|
@@ -507,14 +546,80 @@ function formatActivityParts(activity, maxWidth) {
|
|
|
507
546
|
}
|
|
508
547
|
detailDisplayWidth = currentWidth;
|
|
509
548
|
}
|
|
510
|
-
const labelContent2 = `${label}: ${truncatedDetail}`;
|
|
511
|
-
const displayWidth2 = totalPrefixWidth + detailDisplayWidth;
|
|
549
|
+
const labelContent2 = `${label}: ${truncatedDetail}${countSuffix}`;
|
|
550
|
+
const displayWidth2 = totalPrefixWidth + detailDisplayWidth + countSuffixWidth;
|
|
512
551
|
return { timestamp, icon, labelContent: labelContent2, displayWidth: displayWidth2 };
|
|
513
552
|
}
|
|
514
|
-
const labelContent = label;
|
|
515
|
-
const displayWidth = totalPrefixWidth;
|
|
553
|
+
const labelContent = label + countSuffix;
|
|
554
|
+
const displayWidth = totalPrefixWidth + countSuffixWidth;
|
|
516
555
|
return { timestamp, icon, labelContent, displayWidth };
|
|
517
556
|
}
|
|
557
|
+
var TODO_ICONS = {
|
|
558
|
+
completed: "\u2713",
|
|
559
|
+
in_progress_left: "\u25D0",
|
|
560
|
+
in_progress_right: "\u25D1",
|
|
561
|
+
pending: "\u25CB"
|
|
562
|
+
};
|
|
563
|
+
function TodoSection({ todos, width }) {
|
|
564
|
+
const [tick, setTick] = useState(false);
|
|
565
|
+
const innerWidth = getInnerWidth(width);
|
|
566
|
+
const contentWidth = innerWidth - 1;
|
|
567
|
+
useEffect(() => {
|
|
568
|
+
const timer = setInterval(() => setTick((t) => !t), 500);
|
|
569
|
+
return () => clearInterval(timer);
|
|
570
|
+
}, []);
|
|
571
|
+
const completedCount = todos.filter((t) => t.status === "completed").length;
|
|
572
|
+
const totalCount = todos.length;
|
|
573
|
+
const headerTitle = `Todo (${completedCount}/${totalCount})`;
|
|
574
|
+
const inProgressIcon = tick ? TODO_ICONS.in_progress_left : TODO_ICONS.in_progress_right;
|
|
575
|
+
return /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
576
|
+
/* @__PURE__ */ jsx4(Text4, { children: createSeparatorLine(headerTitle, width) }),
|
|
577
|
+
todos.map((todo, i) => {
|
|
578
|
+
let icon;
|
|
579
|
+
let iconColor;
|
|
580
|
+
switch (todo.status) {
|
|
581
|
+
case "completed":
|
|
582
|
+
icon = TODO_ICONS.completed;
|
|
583
|
+
iconColor = "green";
|
|
584
|
+
break;
|
|
585
|
+
case "in_progress":
|
|
586
|
+
icon = inProgressIcon;
|
|
587
|
+
iconColor = "yellow";
|
|
588
|
+
break;
|
|
589
|
+
default:
|
|
590
|
+
icon = TODO_ICONS.pending;
|
|
591
|
+
iconColor = void 0;
|
|
592
|
+
}
|
|
593
|
+
const text = todo.status === "in_progress" ? todo.activeForm : todo.content;
|
|
594
|
+
const maxTextWidth = contentWidth - 3;
|
|
595
|
+
let displayText = text;
|
|
596
|
+
if (getDisplayWidth(text) > maxTextWidth) {
|
|
597
|
+
displayText = "";
|
|
598
|
+
let currentWidth = 0;
|
|
599
|
+
for (const char of text) {
|
|
600
|
+
const charWidth = getDisplayWidth(char);
|
|
601
|
+
if (currentWidth + charWidth > maxTextWidth - 3) {
|
|
602
|
+
displayText += "...";
|
|
603
|
+
currentWidth += 3;
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
displayText += char;
|
|
607
|
+
currentWidth += charWidth;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const padding = Math.max(0, contentWidth - getDisplayWidth(icon) - 1 - getDisplayWidth(displayText));
|
|
611
|
+
return /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
612
|
+
BOX.v,
|
|
613
|
+
" ",
|
|
614
|
+
/* @__PURE__ */ jsx4(Text4, { color: iconColor, children: icon }),
|
|
615
|
+
" ",
|
|
616
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: todo.status === "completed", children: displayText }),
|
|
617
|
+
" ".repeat(padding),
|
|
618
|
+
BOX.v
|
|
619
|
+
] }, `todo-${i}`);
|
|
620
|
+
})
|
|
621
|
+
] });
|
|
622
|
+
}
|
|
518
623
|
function ClaudePanel({
|
|
519
624
|
data,
|
|
520
625
|
countdown,
|
|
@@ -607,9 +712,51 @@ function ClaudePanel({
|
|
|
607
712
|
] }, "tokens")
|
|
608
713
|
);
|
|
609
714
|
}
|
|
715
|
+
const hasTodos = state.todos && state.todos.length > 0;
|
|
716
|
+
const allCompleted = hasTodos && state.todos.every((t) => t.status === "completed");
|
|
717
|
+
if (hasTodos && allCompleted) {
|
|
718
|
+
const todos = state.todos;
|
|
719
|
+
const summaryText = `Todo (${todos.length}/${todos.length} done)`;
|
|
720
|
+
const summaryIcon = "\u2713";
|
|
721
|
+
const timestamp = formatActivityTime(/* @__PURE__ */ new Date());
|
|
722
|
+
const timestampStr = `[${timestamp}] `;
|
|
723
|
+
const timestampWidth = timestampStr.length;
|
|
724
|
+
const iconWidth = getDisplayWidth(summaryIcon);
|
|
725
|
+
const prefixWidth = timestampWidth + iconWidth + 1;
|
|
726
|
+
const maxTextWidth = contentWidth - prefixWidth;
|
|
727
|
+
let displaySummary = summaryText;
|
|
728
|
+
if (getDisplayWidth(summaryText) > maxTextWidth) {
|
|
729
|
+
displaySummary = "";
|
|
730
|
+
let currentWidth = 0;
|
|
731
|
+
for (const char of summaryText) {
|
|
732
|
+
const charWidth = getDisplayWidth(char);
|
|
733
|
+
if (currentWidth + charWidth > maxTextWidth - 3) {
|
|
734
|
+
displaySummary += "...";
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
displaySummary += char;
|
|
738
|
+
currentWidth += charWidth;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const summaryPadding = Math.max(0, contentWidth - prefixWidth - getDisplayWidth(displaySummary));
|
|
742
|
+
lines.push(
|
|
743
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
744
|
+
BOX.v,
|
|
745
|
+
" ",
|
|
746
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: timestampStr }),
|
|
747
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", children: summaryIcon }),
|
|
748
|
+
" ",
|
|
749
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", children: displaySummary }),
|
|
750
|
+
" ".repeat(summaryPadding),
|
|
751
|
+
BOX.v
|
|
752
|
+
] }, "todo-summary")
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
const showTodoSection = hasTodos && !allCompleted;
|
|
610
756
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, children: [
|
|
611
757
|
/* @__PURE__ */ jsx4(Text4, { children: createTitleLine("Claude", titleSuffix, width) }),
|
|
612
758
|
lines,
|
|
759
|
+
showTodoSection && /* @__PURE__ */ jsx4(TodoSection, { todos: state.todos, width }),
|
|
613
760
|
/* @__PURE__ */ jsx4(Text4, { children: createBottomLine(width) })
|
|
614
761
|
] });
|
|
615
762
|
}
|
|
@@ -768,7 +915,7 @@ function OtherSessionsPanel({
|
|
|
768
915
|
|
|
769
916
|
// src/ui/GenericPanel.tsx
|
|
770
917
|
import { Box as Box6, Text as Text6 } from "ink";
|
|
771
|
-
import { Fragment as
|
|
918
|
+
import { Fragment as Fragment5, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
772
919
|
var PROGRESS_BAR_WIDTH = 10;
|
|
773
920
|
function createProgressBar(done, total) {
|
|
774
921
|
if (total === 0) return "\u2591".repeat(PROGRESS_BAR_WIDTH);
|
|
@@ -804,7 +951,7 @@ function ListRenderer({ data, width }) {
|
|
|
804
951
|
BOX.v
|
|
805
952
|
] });
|
|
806
953
|
}
|
|
807
|
-
return /* @__PURE__ */ jsxs6(
|
|
954
|
+
return /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
808
955
|
data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
809
956
|
BOX.v,
|
|
810
957
|
padLine(" " + truncate(data.summary, contentWidth), width),
|
|
@@ -821,7 +968,7 @@ function ListRenderer({ data, width }) {
|
|
|
821
968
|
function ProgressRenderer({ data, width }) {
|
|
822
969
|
const items = data.items || [];
|
|
823
970
|
const contentWidth = getContentWidth(width);
|
|
824
|
-
return /* @__PURE__ */ jsxs6(
|
|
971
|
+
return /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
825
972
|
data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
826
973
|
BOX.v,
|
|
827
974
|
padLine(" " + truncate(data.summary, contentWidth), width),
|
|
@@ -856,7 +1003,7 @@ function StatusRenderer({ data, width }) {
|
|
|
856
1003
|
summaryLength += 2 + 2 + String(stats.skipped).length + " skipped".length;
|
|
857
1004
|
}
|
|
858
1005
|
const summaryPadding = Math.max(0, innerWidth - summaryLength);
|
|
859
|
-
return /* @__PURE__ */ jsxs6(
|
|
1006
|
+
return /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
860
1007
|
data.summary && /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
861
1008
|
BOX.v,
|
|
862
1009
|
padLine(" " + truncate(data.summary, contentWidth), width),
|
|
@@ -870,7 +1017,7 @@ function StatusRenderer({ data, width }) {
|
|
|
870
1017
|
stats.passed,
|
|
871
1018
|
" passed"
|
|
872
1019
|
] }),
|
|
873
|
-
stats.failed > 0 && /* @__PURE__ */ jsxs6(
|
|
1020
|
+
stats.failed > 0 && /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
874
1021
|
" ",
|
|
875
1022
|
/* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
|
|
876
1023
|
"\u2717 ",
|
|
@@ -878,7 +1025,7 @@ function StatusRenderer({ data, width }) {
|
|
|
878
1025
|
" failed"
|
|
879
1026
|
] })
|
|
880
1027
|
] }),
|
|
881
|
-
stats.skipped && stats.skipped > 0 && /* @__PURE__ */ jsxs6(
|
|
1028
|
+
stats.skipped && stats.skipped > 0 && /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
882
1029
|
" ",
|
|
883
1030
|
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
884
1031
|
"\u25CB ",
|
|
@@ -1557,6 +1704,9 @@ function getToolDetail(toolName, input) {
|
|
|
1557
1704
|
if (input.query) {
|
|
1558
1705
|
return input.query;
|
|
1559
1706
|
}
|
|
1707
|
+
if (input.description) {
|
|
1708
|
+
return input.description;
|
|
1709
|
+
}
|
|
1560
1710
|
return "";
|
|
1561
1711
|
}
|
|
1562
1712
|
function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES) {
|
|
@@ -1564,7 +1714,8 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
|
|
|
1564
1714
|
status: "none",
|
|
1565
1715
|
activities: [],
|
|
1566
1716
|
tokenCount: 0,
|
|
1567
|
-
sessionStartTime: null
|
|
1717
|
+
sessionStartTime: null,
|
|
1718
|
+
todos: null
|
|
1568
1719
|
};
|
|
1569
1720
|
if (!fs.existsSync(sessionFile)) {
|
|
1570
1721
|
return defaultState;
|
|
@@ -1594,6 +1745,7 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
|
|
|
1594
1745
|
let tokenCount = 0;
|
|
1595
1746
|
let lastTimestamp = null;
|
|
1596
1747
|
let lastType = null;
|
|
1748
|
+
let todos = null;
|
|
1597
1749
|
const recentLines = lines.slice(-MAX_LINES_TO_SCAN);
|
|
1598
1750
|
for (const line of recentLines) {
|
|
1599
1751
|
try {
|
|
@@ -1624,6 +1776,13 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
|
|
|
1624
1776
|
detail: userText.replace(/\n/g, " ")
|
|
1625
1777
|
});
|
|
1626
1778
|
}
|
|
1779
|
+
if (userEntry.toolUseResult?.newTodos) {
|
|
1780
|
+
todos = userEntry.toolUseResult.newTodos.map((t) => ({
|
|
1781
|
+
content: t.content,
|
|
1782
|
+
status: t.status,
|
|
1783
|
+
activeForm: t.activeForm
|
|
1784
|
+
}));
|
|
1785
|
+
}
|
|
1627
1786
|
lastType = "user";
|
|
1628
1787
|
}
|
|
1629
1788
|
if (entry.type === "assistant") {
|
|
@@ -1636,15 +1795,25 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
|
|
|
1636
1795
|
for (const block of messageContent) {
|
|
1637
1796
|
if (block.type === "tool_use") {
|
|
1638
1797
|
const toolName = block.name || "Tool";
|
|
1798
|
+
if (toolName === "TodoWrite") {
|
|
1799
|
+
lastType = "tool";
|
|
1800
|
+
continue;
|
|
1801
|
+
}
|
|
1639
1802
|
const icon = ICONS[toolName] || ICONS.Default;
|
|
1640
1803
|
const detail = getToolDetail(toolName, block.input);
|
|
1641
|
-
activities.
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1804
|
+
const lastActivity = activities[activities.length - 1];
|
|
1805
|
+
if (lastActivity && lastActivity.type === "tool" && lastActivity.label === toolName && lastActivity.detail === detail) {
|
|
1806
|
+
lastActivity.count = (lastActivity.count || 1) + 1;
|
|
1807
|
+
lastActivity.timestamp = lastTimestamp || /* @__PURE__ */ new Date();
|
|
1808
|
+
} else {
|
|
1809
|
+
activities.push({
|
|
1810
|
+
timestamp: lastTimestamp || /* @__PURE__ */ new Date(),
|
|
1811
|
+
type: "tool",
|
|
1812
|
+
icon,
|
|
1813
|
+
label: toolName,
|
|
1814
|
+
detail
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1648
1817
|
lastType = "tool";
|
|
1649
1818
|
} else if (block.type === "text" && block.text) {
|
|
1650
1819
|
if (block.text.length > 10) {
|
|
@@ -1695,7 +1864,8 @@ function parseSessionState(sessionFile, maxActivities = DEFAULT_MAX_ACTIVITIES)
|
|
|
1695
1864
|
status,
|
|
1696
1865
|
activities: activities.slice(-maxActivities).reverse(),
|
|
1697
1866
|
tokenCount,
|
|
1698
|
-
sessionStartTime
|
|
1867
|
+
sessionStartTime,
|
|
1868
|
+
todos
|
|
1699
1869
|
};
|
|
1700
1870
|
}
|
|
1701
1871
|
function getClaudeData(projectPath, maxActivities) {
|
|
@@ -1703,7 +1873,8 @@ function getClaudeData(projectPath, maxActivities) {
|
|
|
1703
1873
|
status: "none",
|
|
1704
1874
|
activities: [],
|
|
1705
1875
|
tokenCount: 0,
|
|
1706
|
-
sessionStartTime: null
|
|
1876
|
+
sessionStartTime: null,
|
|
1877
|
+
todos: null
|
|
1707
1878
|
};
|
|
1708
1879
|
try {
|
|
1709
1880
|
const sessionDir = getClaudeSessionPath(projectPath);
|
|
@@ -2522,8 +2693,8 @@ function DashboardApp({ mode }) {
|
|
|
2522
2693
|
}
|
|
2523
2694
|
return getClampedWidth(terminalColumns);
|
|
2524
2695
|
};
|
|
2525
|
-
const [width, setWidth] =
|
|
2526
|
-
|
|
2696
|
+
const [width, setWidth] = useState2(() => getEffectiveWidth(stdout?.columns));
|
|
2697
|
+
useEffect2(() => {
|
|
2527
2698
|
if (!config.width) {
|
|
2528
2699
|
const newWidth = getEffectiveWidth(stdout?.columns);
|
|
2529
2700
|
if (newWidth !== width) {
|
|
@@ -2531,7 +2702,7 @@ function DashboardApp({ mode }) {
|
|
|
2531
2702
|
}
|
|
2532
2703
|
}
|
|
2533
2704
|
}, [stdout?.columns, width, config.width]);
|
|
2534
|
-
|
|
2705
|
+
useEffect2(() => {
|
|
2535
2706
|
if (config.width) return;
|
|
2536
2707
|
const handleResize = () => {
|
|
2537
2708
|
setWidth(getEffectiveWidth(stdout?.columns));
|
|
@@ -2546,11 +2717,11 @@ function DashboardApp({ mode }) {
|
|
|
2546
2717
|
const claudeIntervalSeconds = config.panels.claude.interval ? config.panels.claude.interval / 1e3 : null;
|
|
2547
2718
|
const otherSessionsIntervalSeconds = config.panels.other_sessions.interval ? config.panels.other_sessions.interval / 1e3 : null;
|
|
2548
2719
|
const cwd = process.cwd();
|
|
2549
|
-
const [projectData, setProjectData] =
|
|
2720
|
+
const [projectData, setProjectData] = useState2(() => getProjectData());
|
|
2550
2721
|
const refreshProject = useCallback(() => {
|
|
2551
2722
|
setProjectData(getProjectData());
|
|
2552
2723
|
}, []);
|
|
2553
|
-
const [gitData, setGitData] =
|
|
2724
|
+
const [gitData, setGitData] = useState2(() => getGitData(config.panels.git));
|
|
2554
2725
|
const refreshGit = useCallback(() => {
|
|
2555
2726
|
setGitData(getGitData(config.panels.git));
|
|
2556
2727
|
}, [config.panels.git]);
|
|
@@ -2560,15 +2731,22 @@ function DashboardApp({ mode }) {
|
|
|
2560
2731
|
}
|
|
2561
2732
|
return getTestData();
|
|
2562
2733
|
}, [config.panels.tests.command]);
|
|
2563
|
-
const
|
|
2734
|
+
const initialTestData = useMemo(() => getTestDataFromConfig(), [getTestDataFromConfig]);
|
|
2735
|
+
const initialTestsDisabled = !!(initialTestData.error && config.panels.tests.command);
|
|
2736
|
+
const [testsDisabled, setTestsDisabled] = useState2(initialTestsDisabled);
|
|
2737
|
+
const [testData, setTestData] = useState2(initialTestData);
|
|
2564
2738
|
const refreshTest = useCallback(() => {
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2739
|
+
const data = getTestDataFromConfig();
|
|
2740
|
+
if (config.panels.tests.command) {
|
|
2741
|
+
setTestsDisabled(!!data.error);
|
|
2742
|
+
}
|
|
2743
|
+
setTestData(data);
|
|
2744
|
+
}, [getTestDataFromConfig, config.panels.tests.command]);
|
|
2745
|
+
const [claudeData, setClaudeData] = useState2(() => getClaudeData(cwd, config.panels.claude.maxActivities));
|
|
2568
2746
|
const refreshClaude = useCallback(() => {
|
|
2569
2747
|
setClaudeData(getClaudeData(cwd, config.panels.claude.maxActivities));
|
|
2570
2748
|
}, [cwd, config.panels.claude.maxActivities]);
|
|
2571
|
-
const [otherSessionsData, setOtherSessionsData] =
|
|
2749
|
+
const [otherSessionsData, setOtherSessionsData] = useState2(
|
|
2572
2750
|
() => getOtherSessionsData(cwd, { activeThresholdMs: config.panels.other_sessions.activeThreshold })
|
|
2573
2751
|
);
|
|
2574
2752
|
const refreshOtherSessions = useCallback(() => {
|
|
@@ -2578,7 +2756,7 @@ function DashboardApp({ mode }) {
|
|
|
2578
2756
|
() => Object.keys(config.customPanels || {}),
|
|
2579
2757
|
[config.customPanels]
|
|
2580
2758
|
);
|
|
2581
|
-
const [customPanelData, setCustomPanelData] =
|
|
2759
|
+
const [customPanelData, setCustomPanelData] = useState2(() => {
|
|
2582
2760
|
const data = {};
|
|
2583
2761
|
if (config.customPanels) {
|
|
2584
2762
|
for (const [name, panelConfig] of Object.entries(config.customPanels)) {
|
|
@@ -2614,7 +2792,7 @@ function DashboardApp({ mode }) {
|
|
|
2614
2792
|
}
|
|
2615
2793
|
return countdowns2;
|
|
2616
2794
|
}, [projectIntervalSeconds, gitIntervalSeconds, claudeIntervalSeconds, otherSessionsIntervalSeconds, config.customPanels]);
|
|
2617
|
-
const [countdowns, setCountdowns] =
|
|
2795
|
+
const [countdowns, setCountdowns] = useState2(initialCountdowns);
|
|
2618
2796
|
const initialVisualStates = useMemo(() => {
|
|
2619
2797
|
const states = {
|
|
2620
2798
|
project: { ...DEFAULT_VISUAL_STATE },
|
|
@@ -2628,7 +2806,7 @@ function DashboardApp({ mode }) {
|
|
|
2628
2806
|
}
|
|
2629
2807
|
return states;
|
|
2630
2808
|
}, [customPanelNames]);
|
|
2631
|
-
const [visualStates, setVisualStates] =
|
|
2809
|
+
const [visualStates, setVisualStates] = useState2(initialVisualStates);
|
|
2632
2810
|
const setVisualState = useCallback((panel, update) => {
|
|
2633
2811
|
setVisualStates((prev) => ({
|
|
2634
2812
|
...prev,
|
|
@@ -2678,7 +2856,11 @@ function DashboardApp({ mode }) {
|
|
|
2678
2856
|
try {
|
|
2679
2857
|
await new Promise((resolve) => {
|
|
2680
2858
|
setTimeout(() => {
|
|
2681
|
-
|
|
2859
|
+
const data = getTestDataFromConfig();
|
|
2860
|
+
if (config.panels.tests.command) {
|
|
2861
|
+
setTestsDisabled(!!data.error);
|
|
2862
|
+
}
|
|
2863
|
+
setTestData(data);
|
|
2682
2864
|
resolve();
|
|
2683
2865
|
}, 0);
|
|
2684
2866
|
});
|
|
@@ -2686,7 +2868,7 @@ function DashboardApp({ mode }) {
|
|
|
2686
2868
|
setVisualState("tests", { isRunning: false, justCompleted: true });
|
|
2687
2869
|
clearFeedback("tests", "justCompleted");
|
|
2688
2870
|
}
|
|
2689
|
-
}, [getTestDataFromConfig, setVisualState, clearFeedback]);
|
|
2871
|
+
}, [getTestDataFromConfig, setVisualState, clearFeedback, config.panels.tests.command]);
|
|
2690
2872
|
const refreshClaudeWithFeedback = useCallback(() => {
|
|
2691
2873
|
setClaudeData(getClaudeData(cwd, config.panels.claude.maxActivities));
|
|
2692
2874
|
setVisualState("claude", { justRefreshed: true });
|
|
@@ -2755,7 +2937,7 @@ function DashboardApp({ mode }) {
|
|
|
2755
2937
|
}),
|
|
2756
2938
|
[config, refreshTestAsync, customPanelActionsAsync]
|
|
2757
2939
|
);
|
|
2758
|
-
|
|
2940
|
+
useEffect2(() => {
|
|
2759
2941
|
if (mode !== "watch") return;
|
|
2760
2942
|
const timers = [];
|
|
2761
2943
|
if (config.panels.project.enabled && config.panels.project.interval !== null) {
|
|
@@ -2821,7 +3003,7 @@ function DashboardApp({ mode }) {
|
|
|
2821
3003
|
claudeIntervalSeconds,
|
|
2822
3004
|
otherSessionsIntervalSeconds
|
|
2823
3005
|
]);
|
|
2824
|
-
|
|
3006
|
+
useEffect2(() => {
|
|
2825
3007
|
if (mode !== "watch") return;
|
|
2826
3008
|
const tick = setInterval(() => {
|
|
2827
3009
|
setCountdowns((prev) => {
|
|
@@ -2897,7 +3079,7 @@ function DashboardApp({ mode }) {
|
|
|
2897
3079
|
}
|
|
2898
3080
|
) }, `panel-git-${index}`);
|
|
2899
3081
|
}
|
|
2900
|
-
if (panelName === "tests" && config.panels.tests.enabled) {
|
|
3082
|
+
if (panelName === "tests" && config.panels.tests.enabled && !testsDisabled) {
|
|
2901
3083
|
const testsVisual = visualStates.tests || DEFAULT_VISUAL_STATE;
|
|
2902
3084
|
return /* @__PURE__ */ jsx8(Box8, { marginTop: isFirst ? 0 : 1, children: /* @__PURE__ */ jsx8(
|
|
2903
3085
|
TestPanel,
|
|
@@ -2961,7 +3143,7 @@ function DashboardApp({ mode }) {
|
|
|
2961
3143
|
}
|
|
2962
3144
|
return null;
|
|
2963
3145
|
}),
|
|
2964
|
-
mode === "watch" && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, width, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: statusBarItems.map((item, index) => /* @__PURE__ */ jsxs8(
|
|
3146
|
+
mode === "watch" && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, width, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: statusBarItems.map((item, index) => /* @__PURE__ */ jsxs8(React2.Fragment, { children: [
|
|
2965
3147
|
index > 0 && " \xB7 ",
|
|
2966
3148
|
/* @__PURE__ */ jsxs8(Text8, { color: "cyan", children: [
|
|
2967
3149
|
item.split(":")[0],
|
|
@@ -3025,30 +3207,124 @@ function parseArgs(args) {
|
|
|
3025
3207
|
|
|
3026
3208
|
// src/commands/init.ts
|
|
3027
3209
|
import {
|
|
3028
|
-
existsSync as
|
|
3210
|
+
existsSync as nodeExistsSync6,
|
|
3029
3211
|
mkdirSync as nodeMkdirSync,
|
|
3030
3212
|
writeFileSync as nodeWriteFileSync,
|
|
3031
|
-
readFileSync as
|
|
3213
|
+
readFileSync as nodeReadFileSync8,
|
|
3032
3214
|
appendFileSync as nodeAppendFileSync
|
|
3033
3215
|
} from "fs";
|
|
3034
3216
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3035
3217
|
import { dirname as dirname2, join as join5 } from "path";
|
|
3036
3218
|
import { homedir as homedir3 } from "os";
|
|
3219
|
+
|
|
3220
|
+
// src/data/detectTestFramework.ts
|
|
3221
|
+
import {
|
|
3222
|
+
existsSync as nodeExistsSync5,
|
|
3223
|
+
readFileSync as nodeReadFileSync7
|
|
3224
|
+
} from "fs";
|
|
3037
3225
|
var fs4 = {
|
|
3038
3226
|
existsSync: nodeExistsSync5,
|
|
3227
|
+
readFileSync: (path) => nodeReadFileSync7(path, "utf-8")
|
|
3228
|
+
};
|
|
3229
|
+
var FRAMEWORK_COMMANDS = {
|
|
3230
|
+
vitest: "npx vitest run --reporter=json",
|
|
3231
|
+
jest: "npx jest --json",
|
|
3232
|
+
mocha: "npx mocha --reporter=json",
|
|
3233
|
+
pytest: "pytest --json-report --json-report-file=.agenthud/test-results.json"
|
|
3234
|
+
};
|
|
3235
|
+
var JS_FRAMEWORKS = ["vitest", "jest", "mocha"];
|
|
3236
|
+
function detectTestFramework() {
|
|
3237
|
+
const jsFramework = detectJsFramework();
|
|
3238
|
+
if (jsFramework) {
|
|
3239
|
+
return jsFramework;
|
|
3240
|
+
}
|
|
3241
|
+
const pythonFramework = detectPythonFramework();
|
|
3242
|
+
if (pythonFramework) {
|
|
3243
|
+
return pythonFramework;
|
|
3244
|
+
}
|
|
3245
|
+
return null;
|
|
3246
|
+
}
|
|
3247
|
+
function detectJsFramework() {
|
|
3248
|
+
if (!fs4.existsSync("package.json")) {
|
|
3249
|
+
return null;
|
|
3250
|
+
}
|
|
3251
|
+
let packageJson;
|
|
3252
|
+
try {
|
|
3253
|
+
const content = fs4.readFileSync("package.json");
|
|
3254
|
+
packageJson = JSON.parse(content);
|
|
3255
|
+
} catch {
|
|
3256
|
+
return null;
|
|
3257
|
+
}
|
|
3258
|
+
const allDeps = {
|
|
3259
|
+
...packageJson.dependencies,
|
|
3260
|
+
...packageJson.devDependencies
|
|
3261
|
+
};
|
|
3262
|
+
for (const framework of JS_FRAMEWORKS) {
|
|
3263
|
+
if (allDeps[framework]) {
|
|
3264
|
+
return {
|
|
3265
|
+
framework,
|
|
3266
|
+
command: FRAMEWORK_COMMANDS[framework]
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
return null;
|
|
3271
|
+
}
|
|
3272
|
+
function detectPythonFramework() {
|
|
3273
|
+
const pytestIndicators = ["pytest.ini", "conftest.py"];
|
|
3274
|
+
for (const file of pytestIndicators) {
|
|
3275
|
+
if (fs4.existsSync(file)) {
|
|
3276
|
+
return {
|
|
3277
|
+
framework: "pytest",
|
|
3278
|
+
command: FRAMEWORK_COMMANDS.pytest
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
if (fs4.existsSync("pyproject.toml")) {
|
|
3283
|
+
try {
|
|
3284
|
+
const content = fs4.readFileSync("pyproject.toml");
|
|
3285
|
+
if (content.includes("[tool.pytest") || content.includes("[tool.pytest.ini_options]")) {
|
|
3286
|
+
return {
|
|
3287
|
+
framework: "pytest",
|
|
3288
|
+
command: FRAMEWORK_COMMANDS.pytest
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
} catch {
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
const requirementsFiles = ["requirements.txt", "requirements-dev.txt"];
|
|
3295
|
+
for (const file of requirementsFiles) {
|
|
3296
|
+
if (fs4.existsSync(file)) {
|
|
3297
|
+
try {
|
|
3298
|
+
const content = fs4.readFileSync(file);
|
|
3299
|
+
if (content.includes("pytest")) {
|
|
3300
|
+
return {
|
|
3301
|
+
framework: "pytest",
|
|
3302
|
+
command: FRAMEWORK_COMMANDS.pytest
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
} catch {
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
return null;
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
// src/commands/init.ts
|
|
3313
|
+
var fs5 = {
|
|
3314
|
+
existsSync: nodeExistsSync6,
|
|
3039
3315
|
mkdirSync: nodeMkdirSync,
|
|
3040
3316
|
writeFileSync: nodeWriteFileSync,
|
|
3041
|
-
readFileSync: (path) =>
|
|
3317
|
+
readFileSync: (path) => nodeReadFileSync8(path, "utf-8"),
|
|
3042
3318
|
appendFileSync: nodeAppendFileSync
|
|
3043
3319
|
};
|
|
3044
3320
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
3045
3321
|
var __dirname2 = dirname2(__filename2);
|
|
3046
3322
|
function getDefaultConfig2() {
|
|
3047
3323
|
let templatePath = join5(__dirname2, "templates", "config.yaml");
|
|
3048
|
-
if (!
|
|
3324
|
+
if (!nodeExistsSync6(templatePath)) {
|
|
3049
3325
|
templatePath = join5(__dirname2, "..", "templates", "config.yaml");
|
|
3050
3326
|
}
|
|
3051
|
-
return
|
|
3327
|
+
return nodeReadFileSync8(templatePath, "utf-8");
|
|
3052
3328
|
}
|
|
3053
3329
|
function getClaudeSessionPath2(projectPath) {
|
|
3054
3330
|
const encoded = projectPath.replace(/[/\\]/g, "-");
|
|
@@ -3060,41 +3336,57 @@ function runInit(cwd = process.cwd()) {
|
|
|
3060
3336
|
skipped: [],
|
|
3061
3337
|
warnings: []
|
|
3062
3338
|
};
|
|
3063
|
-
if (!
|
|
3064
|
-
|
|
3339
|
+
if (!fs5.existsSync(".agenthud")) {
|
|
3340
|
+
fs5.mkdirSync(".agenthud", { recursive: true });
|
|
3065
3341
|
result.created.push(".agenthud/");
|
|
3066
3342
|
} else {
|
|
3067
3343
|
result.skipped.push(".agenthud/");
|
|
3068
3344
|
}
|
|
3069
|
-
if (!
|
|
3070
|
-
|
|
3345
|
+
if (!fs5.existsSync(".agenthud/tests")) {
|
|
3346
|
+
fs5.mkdirSync(".agenthud/tests", { recursive: true });
|
|
3071
3347
|
result.created.push(".agenthud/tests/");
|
|
3072
3348
|
} else {
|
|
3073
3349
|
result.skipped.push(".agenthud/tests/");
|
|
3074
3350
|
}
|
|
3075
|
-
|
|
3076
|
-
|
|
3351
|
+
const testFramework = detectTestFramework();
|
|
3352
|
+
if (testFramework) {
|
|
3353
|
+
result.detectedTestFramework = testFramework.framework;
|
|
3354
|
+
}
|
|
3355
|
+
if (!fs5.existsSync(".agenthud/config.yaml")) {
|
|
3356
|
+
let configContent = getDefaultConfig2();
|
|
3357
|
+
if (testFramework) {
|
|
3358
|
+
configContent = configContent.replace(
|
|
3359
|
+
/command: npx vitest run --reporter=json/,
|
|
3360
|
+
`command: ${testFramework.command}`
|
|
3361
|
+
);
|
|
3362
|
+
} else {
|
|
3363
|
+
configContent = configContent.replace(
|
|
3364
|
+
/command: npx vitest run --reporter=json/,
|
|
3365
|
+
"# command: (auto-detect failed - configure manually)"
|
|
3366
|
+
);
|
|
3367
|
+
}
|
|
3368
|
+
fs5.writeFileSync(".agenthud/config.yaml", configContent);
|
|
3077
3369
|
result.created.push(".agenthud/config.yaml");
|
|
3078
3370
|
} else {
|
|
3079
3371
|
result.skipped.push(".agenthud/config.yaml");
|
|
3080
3372
|
}
|
|
3081
|
-
if (!
|
|
3082
|
-
|
|
3373
|
+
if (!fs5.existsSync(".gitignore")) {
|
|
3374
|
+
fs5.writeFileSync(".gitignore", ".agenthud/\n");
|
|
3083
3375
|
result.created.push(".gitignore");
|
|
3084
3376
|
} else {
|
|
3085
|
-
const content =
|
|
3377
|
+
const content = fs5.readFileSync(".gitignore");
|
|
3086
3378
|
if (!content.includes(".agenthud/")) {
|
|
3087
|
-
|
|
3379
|
+
fs5.appendFileSync(".gitignore", "\n.agenthud/\n");
|
|
3088
3380
|
result.created.push(".gitignore");
|
|
3089
3381
|
} else {
|
|
3090
3382
|
result.skipped.push(".gitignore");
|
|
3091
3383
|
}
|
|
3092
3384
|
}
|
|
3093
|
-
if (!
|
|
3385
|
+
if (!fs5.existsSync(".git")) {
|
|
3094
3386
|
result.warnings.push("Not a git repository - Git panel will show limited info");
|
|
3095
3387
|
}
|
|
3096
3388
|
const claudeSessionPath = getClaudeSessionPath2(cwd);
|
|
3097
|
-
if (!
|
|
3389
|
+
if (!fs5.existsSync(claudeSessionPath)) {
|
|
3098
3390
|
result.warnings.push("No Claude session found - start Claude to see activity");
|
|
3099
3391
|
}
|
|
3100
3392
|
return result;
|
|
@@ -3161,7 +3453,7 @@ if (options.mode === "watch") {
|
|
|
3161
3453
|
clearScreen();
|
|
3162
3454
|
}
|
|
3163
3455
|
var { waitUntilExit } = render(
|
|
3164
|
-
|
|
3456
|
+
React3.createElement(App, { mode: options.mode, agentDirExists })
|
|
3165
3457
|
);
|
|
3166
3458
|
if (options.mode === "once") {
|
|
3167
3459
|
setTimeout(() => process.exit(0), 100);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# agenthud configuration
|
|
2
2
|
|
|
3
|
-
width:
|
|
3
|
+
width: 100 # between 50~120
|
|
4
4
|
|
|
5
5
|
panels:
|
|
6
6
|
claude:
|
|
@@ -19,6 +19,7 @@ panels:
|
|
|
19
19
|
tests:
|
|
20
20
|
enabled: true
|
|
21
21
|
interval: manual
|
|
22
|
+
# command is auto-detected from: vitest, jest, mocha, pytest
|
|
22
23
|
command: npx vitest run --reporter=json
|
|
23
24
|
|
|
24
25
|
project:
|
package/package.json
CHANGED