agenthud 0.8.3 → 0.8.4
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 +1 -1
- package/dist/{main-VPGVYRCR.js → main-WBZ2KBF2.js} +259 -57
- package/package.json +1 -1
- package/scripts/daily-summary.sh +10 -0
package/dist/index.js
CHANGED
|
@@ -28,7 +28,13 @@ var KNOWN_WATCH_FLAGS = /* @__PURE__ */ new Set([
|
|
|
28
28
|
"-h",
|
|
29
29
|
"--help"
|
|
30
30
|
]);
|
|
31
|
-
var KNOWN_REPORT_FLAGS = /* @__PURE__ */ new Set([
|
|
31
|
+
var KNOWN_REPORT_FLAGS = /* @__PURE__ */ new Set([
|
|
32
|
+
"--date",
|
|
33
|
+
"--include",
|
|
34
|
+
"--format",
|
|
35
|
+
"--detail-limit",
|
|
36
|
+
"--with-git"
|
|
37
|
+
]);
|
|
32
38
|
var KNOWN_SUBCOMMANDS = /* @__PURE__ */ new Set(["report"]);
|
|
33
39
|
function getHelp() {
|
|
34
40
|
return `Usage: agenthud [options]
|
|
@@ -49,6 +55,8 @@ Commands:
|
|
|
49
55
|
Types: response,bash,edit,thinking,read,glob,user
|
|
50
56
|
Default: response,bash,edit,thinking
|
|
51
57
|
--format FORMAT Output format: markdown (default) or json
|
|
58
|
+
--detail-limit N Max chars per activity detail (default: 120, 0 = unlimited)
|
|
59
|
+
--with-git Append today's git commits from cwd to report
|
|
52
60
|
|
|
53
61
|
Environment:
|
|
54
62
|
CLAUDE_PROJECTS_DIR Path to Claude projects directory
|
|
@@ -140,11 +148,25 @@ function parseArgs(args) {
|
|
|
140
148
|
reportError = "Invalid format: missing value for --format.";
|
|
141
149
|
}
|
|
142
150
|
}
|
|
151
|
+
let reportDetailLimit;
|
|
152
|
+
const detailLimitIdx = rest.indexOf("--detail-limit");
|
|
153
|
+
if (detailLimitIdx !== -1) {
|
|
154
|
+
const val = rest[detailLimitIdx + 1];
|
|
155
|
+
const n = Number(val);
|
|
156
|
+
if (!val || Number.isNaN(n) || n < 0 || !Number.isInteger(n)) {
|
|
157
|
+
reportError = `Invalid --detail-limit: "${val}". Must be a non-negative integer.`;
|
|
158
|
+
} else {
|
|
159
|
+
reportDetailLimit = n;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const reportWithGit = rest.includes("--with-git");
|
|
143
163
|
return {
|
|
144
164
|
mode: "report",
|
|
145
165
|
reportDate,
|
|
146
166
|
reportInclude,
|
|
147
167
|
reportFormat,
|
|
168
|
+
reportDetailLimit,
|
|
169
|
+
reportWithGit,
|
|
148
170
|
reportError
|
|
149
171
|
};
|
|
150
172
|
}
|
|
@@ -175,7 +197,8 @@ var DEFAULT_GLOBAL_CONFIG = {
|
|
|
175
197
|
refreshIntervalMs: 2e3,
|
|
176
198
|
logDir: join2(homedir(), ".agenthud", "logs"),
|
|
177
199
|
hiddenSessions: [],
|
|
178
|
-
hiddenSubAgents: []
|
|
200
|
+
hiddenSubAgents: [],
|
|
201
|
+
filterPresets: [[], ["response"], ["commit"]]
|
|
179
202
|
};
|
|
180
203
|
function parseInterval(value) {
|
|
181
204
|
const match = value.match(/^(\d+)(s|m)$/);
|
|
@@ -217,6 +240,12 @@ function loadGlobalConfig() {
|
|
|
217
240
|
(s) => typeof s === "string"
|
|
218
241
|
);
|
|
219
242
|
}
|
|
243
|
+
if (Array.isArray(parsed.filterPresets)) {
|
|
244
|
+
const presets = parsed.filterPresets.filter(Array.isArray).map(
|
|
245
|
+
(p) => p.filter((t) => typeof t === "string")
|
|
246
|
+
);
|
|
247
|
+
if (presets.length > 0) config.filterPresets = presets;
|
|
248
|
+
}
|
|
220
249
|
return config;
|
|
221
250
|
}
|
|
222
251
|
function writeConfig(updates) {
|
|
@@ -260,11 +289,8 @@ function hasProjectLevelConfig() {
|
|
|
260
289
|
// src/data/reportGenerator.ts
|
|
261
290
|
import { statSync } from "fs";
|
|
262
291
|
|
|
263
|
-
// src/data/
|
|
264
|
-
import {
|
|
265
|
-
|
|
266
|
-
// src/data/activityParser.ts
|
|
267
|
-
import { basename } from "path";
|
|
292
|
+
// src/data/gitCommits.ts
|
|
293
|
+
import { execSync } from "child_process";
|
|
268
294
|
|
|
269
295
|
// src/types/index.ts
|
|
270
296
|
var ICONS = {
|
|
@@ -282,10 +308,61 @@ var ICONS = {
|
|
|
282
308
|
Task: "\xBB",
|
|
283
309
|
TodoWrite: "~",
|
|
284
310
|
AskUserQuestion: "?",
|
|
311
|
+
Commit: "\u25C6",
|
|
285
312
|
Default: "$"
|
|
286
313
|
};
|
|
287
314
|
|
|
315
|
+
// src/data/gitCommits.ts
|
|
316
|
+
function formatDateString(date) {
|
|
317
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
318
|
+
}
|
|
319
|
+
function getCommitDetail(projectPath, hash) {
|
|
320
|
+
try {
|
|
321
|
+
return execSync(`git show --stat --no-color ${hash}`, {
|
|
322
|
+
cwd: projectPath,
|
|
323
|
+
encoding: "utf-8"
|
|
324
|
+
}).trim();
|
|
325
|
+
} catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function parseGitCommits(projectPath, startDate, endDate) {
|
|
330
|
+
if (!projectPath) return [];
|
|
331
|
+
const start = formatDateString(startDate);
|
|
332
|
+
const end = formatDateString(endDate ?? startDate);
|
|
333
|
+
let raw;
|
|
334
|
+
try {
|
|
335
|
+
raw = execSync(
|
|
336
|
+
`git log --format="%ct|%h|%s" --after="${start} 00:00:00" --before="${end} 23:59:59"`,
|
|
337
|
+
{ cwd: projectPath, encoding: "utf-8" }
|
|
338
|
+
).trim();
|
|
339
|
+
} catch {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
if (!raw) return [];
|
|
343
|
+
const entries = [];
|
|
344
|
+
for (const line of raw.split("\n")) {
|
|
345
|
+
const parts = line.trim().split("|");
|
|
346
|
+
if (parts.length < 3) continue;
|
|
347
|
+
const [tsStr, hash, ...rest] = parts;
|
|
348
|
+
const ts = Number(tsStr);
|
|
349
|
+
if (Number.isNaN(ts)) continue;
|
|
350
|
+
entries.push({
|
|
351
|
+
timestamp: new Date(ts * 1e3),
|
|
352
|
+
type: "commit",
|
|
353
|
+
icon: ICONS.Commit,
|
|
354
|
+
label: hash,
|
|
355
|
+
detail: rest.join("|")
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return entries.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/data/sessionHistory.ts
|
|
362
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
363
|
+
|
|
288
364
|
// src/data/activityParser.ts
|
|
365
|
+
import { basename } from "path";
|
|
289
366
|
function stripAnsi(text) {
|
|
290
367
|
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
291
368
|
}
|
|
@@ -432,13 +509,13 @@ function isSameLocalDay(a, b) {
|
|
|
432
509
|
function formatTime(date) {
|
|
433
510
|
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
434
511
|
}
|
|
435
|
-
function formatActivity(activity) {
|
|
512
|
+
function formatActivity(activity, limit) {
|
|
436
513
|
const time = formatTime(activity.timestamp);
|
|
437
|
-
const detail = activity.detail
|
|
514
|
+
const detail = truncateDetail(activity.detail, limit);
|
|
438
515
|
const suffix = detail ? `: ${detail}` : "";
|
|
439
516
|
return `[${time}] ${activity.icon} ${activity.label}${suffix}`;
|
|
440
517
|
}
|
|
441
|
-
function
|
|
518
|
+
function formatDateString2(date) {
|
|
442
519
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
443
520
|
}
|
|
444
521
|
function sessionIsOnDate(session, date, activities) {
|
|
@@ -449,14 +526,28 @@ function sessionIsOnDate(session, date, activities) {
|
|
|
449
526
|
}
|
|
450
527
|
return activities.some((a) => isSameLocalDay(a.timestamp, date));
|
|
451
528
|
}
|
|
529
|
+
function truncateDetail(detail, limit) {
|
|
530
|
+
if (limit === 0 || detail.length <= limit) return detail;
|
|
531
|
+
return detail.slice(0, limit);
|
|
532
|
+
}
|
|
452
533
|
function generateReport(sessions, options2) {
|
|
453
|
-
const {
|
|
454
|
-
|
|
534
|
+
const {
|
|
535
|
+
date,
|
|
536
|
+
include,
|
|
537
|
+
format = "markdown",
|
|
538
|
+
detailLimit = 120,
|
|
539
|
+
withGit = false
|
|
540
|
+
} = options2;
|
|
541
|
+
const dateStr = formatDateString2(date);
|
|
455
542
|
const blocks = [];
|
|
456
543
|
for (const session of sessions) {
|
|
457
544
|
const allActivities = parseSessionHistory(session.filePath);
|
|
458
545
|
if (!sessionIsOnDate(session, date, allActivities)) continue;
|
|
459
|
-
const
|
|
546
|
+
const commits = withGit ? parseGitCommits(session.projectPath, date) : [];
|
|
547
|
+
const dayActivities = [
|
|
548
|
+
...allActivities.filter((a) => isSameLocalDay(a.timestamp, date)).filter((a) => activityMatchesInclude(a, include)),
|
|
549
|
+
...commits
|
|
550
|
+
].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
460
551
|
if (dayActivities.length === 0) continue;
|
|
461
552
|
blocks.push({
|
|
462
553
|
session,
|
|
@@ -472,20 +563,39 @@ function generateReport(sessions, options2) {
|
|
|
472
563
|
}
|
|
473
564
|
blocks.sort((a, b) => a.firstTime - b.firstTime);
|
|
474
565
|
if (format === "json") {
|
|
475
|
-
|
|
476
|
-
{
|
|
477
|
-
date
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
activities: activities.map((a) => ({
|
|
566
|
+
const buildJsonSession = (session, acts) => {
|
|
567
|
+
const subAgentBlocks = session.subAgents.map((sa) => {
|
|
568
|
+
const saActivities = parseSessionHistory(sa.filePath).filter((a) => isSameLocalDay(a.timestamp, date)).filter((a) => activityMatchesInclude(a, include));
|
|
569
|
+
return {
|
|
570
|
+
agentId: sa.agentId,
|
|
571
|
+
taskDescription: sa.taskDescription,
|
|
572
|
+
activities: saActivities.map((a) => ({
|
|
483
573
|
time: formatTime(a.timestamp),
|
|
484
574
|
icon: a.icon,
|
|
485
575
|
label: a.label,
|
|
486
|
-
detail: a.detail
|
|
576
|
+
detail: truncateDetail(a.detail, detailLimit)
|
|
487
577
|
}))
|
|
488
|
-
}
|
|
578
|
+
};
|
|
579
|
+
});
|
|
580
|
+
return {
|
|
581
|
+
project: session.projectName,
|
|
582
|
+
start: formatTime(acts[0].timestamp),
|
|
583
|
+
end: formatTime(acts[acts.length - 1].timestamp),
|
|
584
|
+
activities: acts.map((a) => ({
|
|
585
|
+
time: formatTime(a.timestamp),
|
|
586
|
+
icon: a.icon,
|
|
587
|
+
label: a.label,
|
|
588
|
+
detail: truncateDetail(a.detail, detailLimit)
|
|
589
|
+
})),
|
|
590
|
+
subAgents: subAgentBlocks
|
|
591
|
+
};
|
|
592
|
+
};
|
|
593
|
+
return JSON.stringify(
|
|
594
|
+
{
|
|
595
|
+
date: dateStr,
|
|
596
|
+
sessions: blocks.map(
|
|
597
|
+
({ session, activities }) => buildJsonSession(session, activities)
|
|
598
|
+
)
|
|
489
599
|
},
|
|
490
600
|
null,
|
|
491
601
|
2
|
|
@@ -498,7 +608,7 @@ function generateReport(sessions, options2) {
|
|
|
498
608
|
lines.push(`## ${session.projectName} (${first} \u2013 ${last})`);
|
|
499
609
|
lines.push("");
|
|
500
610
|
for (const activity of activities) {
|
|
501
|
-
lines.push(formatActivity(activity));
|
|
611
|
+
lines.push(formatActivity(activity, detailLimit));
|
|
502
612
|
}
|
|
503
613
|
lines.push("");
|
|
504
614
|
}
|
|
@@ -743,6 +853,9 @@ function getActivityStyle(activity) {
|
|
|
743
853
|
if (activity.type === "thinking") {
|
|
744
854
|
return { color: "magenta", dimColor: true };
|
|
745
855
|
}
|
|
856
|
+
if (activity.type === "commit") {
|
|
857
|
+
return { color: "yellow", dimColor: false };
|
|
858
|
+
}
|
|
746
859
|
if (activity.type === "tool") {
|
|
747
860
|
if (activity.label === "Bash") {
|
|
748
861
|
return { color: "gray", dimColor: false };
|
|
@@ -762,7 +875,7 @@ function formatActivityTime(date, now) {
|
|
|
762
875
|
const day = String(date.getDate()).padStart(2, "0");
|
|
763
876
|
return `${month}/${day} ${time}`;
|
|
764
877
|
}
|
|
765
|
-
function
|
|
878
|
+
function truncateDetail2(detail, maxWidth) {
|
|
766
879
|
if (getDisplayWidth(detail) <= maxWidth) return detail;
|
|
767
880
|
let truncated = "";
|
|
768
881
|
let currentWidth = 0;
|
|
@@ -787,16 +900,18 @@ function ActivityViewerPanel({
|
|
|
787
900
|
width,
|
|
788
901
|
cursorLine,
|
|
789
902
|
hasFocus,
|
|
790
|
-
spinner = ""
|
|
903
|
+
spinner = "",
|
|
904
|
+
filterLabel
|
|
791
905
|
}) {
|
|
792
906
|
const innerWidth = getInnerWidth(width);
|
|
793
907
|
const contentWidth = innerWidth - 1;
|
|
908
|
+
const filterSuffix = filterLabel && filterLabel !== "all" ? ` \xB7 ${filterLabel}` : "";
|
|
794
909
|
let titleSuffix;
|
|
795
910
|
if (isLive) {
|
|
796
|
-
titleSuffix = `[LIVE ${spinner || "\u25BC"}]`;
|
|
911
|
+
titleSuffix = `[LIVE ${spinner || "\u25BC"}${filterSuffix}]`;
|
|
797
912
|
} else {
|
|
798
913
|
const badge = newCount > 0 ? ` +${newCount}\u2191` : "";
|
|
799
|
-
titleSuffix = `[PAUSED \u2193${scrollOffset}${badge}]`;
|
|
914
|
+
titleSuffix = `[PAUSED \u2193${scrollOffset}${badge}${filterSuffix}]`;
|
|
800
915
|
}
|
|
801
916
|
let visibleActivities;
|
|
802
917
|
if (activities.length === 0) {
|
|
@@ -846,7 +961,7 @@ function ActivityViewerPanel({
|
|
|
846
961
|
let labelContent;
|
|
847
962
|
let _displayWidth;
|
|
848
963
|
if (detail) {
|
|
849
|
-
const truncated =
|
|
964
|
+
const truncated = truncateDetail2(detail, Math.max(0, detailMaxWidth));
|
|
850
965
|
labelContent = `${labelPart}${truncated}${countSuffix}`;
|
|
851
966
|
_displayWidth = prefixWidth - 1 + labelWidth + getDisplayWidth(truncated) + countSuffixWidth;
|
|
852
967
|
} else {
|
|
@@ -894,21 +1009,27 @@ import { Box as Box2, Text as Text2 } from "ink";
|
|
|
894
1009
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
895
1010
|
function wrapText(text, maxWidth) {
|
|
896
1011
|
if (!text) return ["(empty)"];
|
|
897
|
-
const
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
current = word;
|
|
903
|
-
} else if (getDisplayWidth(`${current} ${word}`) <= maxWidth) {
|
|
904
|
-
current += ` ${word}`;
|
|
905
|
-
} else {
|
|
906
|
-
lines.push(current);
|
|
907
|
-
current = word;
|
|
1012
|
+
const result = [];
|
|
1013
|
+
for (const rawLine of text.split("\n")) {
|
|
1014
|
+
if (!rawLine) {
|
|
1015
|
+
result.push("");
|
|
1016
|
+
continue;
|
|
908
1017
|
}
|
|
1018
|
+
const words = rawLine.split(" ");
|
|
1019
|
+
let current = "";
|
|
1020
|
+
for (const word of words) {
|
|
1021
|
+
if (!current) {
|
|
1022
|
+
current = word;
|
|
1023
|
+
} else if (getDisplayWidth(`${current} ${word}`) <= maxWidth) {
|
|
1024
|
+
current += ` ${word}`;
|
|
1025
|
+
} else {
|
|
1026
|
+
result.push(current);
|
|
1027
|
+
current = word;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
if (current) result.push(current);
|
|
909
1031
|
}
|
|
910
|
-
|
|
911
|
-
return lines.length > 0 ? lines : ["(empty)"];
|
|
1032
|
+
return result.length > 0 ? result : ["(empty)"];
|
|
912
1033
|
}
|
|
913
1034
|
function DetailViewPanel({
|
|
914
1035
|
activity,
|
|
@@ -990,7 +1111,9 @@ function useHotkeys({
|
|
|
990
1111
|
onHide,
|
|
991
1112
|
onDetailClose,
|
|
992
1113
|
onDetailScrollUp,
|
|
993
|
-
onDetailScrollDown
|
|
1114
|
+
onDetailScrollDown,
|
|
1115
|
+
onFilter,
|
|
1116
|
+
filterLabel
|
|
994
1117
|
}) {
|
|
995
1118
|
const handleInput = (input, key) => {
|
|
996
1119
|
if (detailMode) {
|
|
@@ -1024,6 +1147,10 @@ function useHotkeys({
|
|
|
1024
1147
|
onRefresh();
|
|
1025
1148
|
return;
|
|
1026
1149
|
}
|
|
1150
|
+
if (input === "f" && !key.ctrl && focus === "viewer") {
|
|
1151
|
+
onFilter();
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1027
1154
|
if (key.pageUp) {
|
|
1028
1155
|
onScrollPageUp();
|
|
1029
1156
|
return;
|
|
@@ -1099,9 +1226,10 @@ function useHotkeys({
|
|
|
1099
1226
|
"Tab: sessions",
|
|
1100
1227
|
"\u2191\u2193/jk: scroll",
|
|
1101
1228
|
"PgUp/Dn: page",
|
|
1102
|
-
"g:
|
|
1103
|
-
"G:
|
|
1229
|
+
"g: live",
|
|
1230
|
+
"G: oldest",
|
|
1104
1231
|
"\u21B5: detail",
|
|
1232
|
+
`f: ${filterLabel}`,
|
|
1105
1233
|
"q: quit"
|
|
1106
1234
|
];
|
|
1107
1235
|
return { handleInput, statusBarItems };
|
|
@@ -1504,6 +1632,7 @@ function App({ mode }) {
|
|
|
1504
1632
|
const [scrollOffset, setScrollOffset] = useState2(0);
|
|
1505
1633
|
const [isLive, setIsLive] = useState2(true);
|
|
1506
1634
|
const [activities, setActivities] = useState2([]);
|
|
1635
|
+
const [gitActivities, setGitActivities] = useState2([]);
|
|
1507
1636
|
const [newCount, setNewCount] = useState2(0);
|
|
1508
1637
|
const [expandedIds, setExpandedIds] = useState2(/* @__PURE__ */ new Set());
|
|
1509
1638
|
const [viewerCursorLine, setViewerCursorLine] = useState2(0);
|
|
@@ -1512,6 +1641,7 @@ function App({ mode }) {
|
|
|
1512
1641
|
null
|
|
1513
1642
|
);
|
|
1514
1643
|
const [detailScrollOffset, setDetailScrollOffset] = useState2(0);
|
|
1644
|
+
const [filterIndex, setFilterIndex] = useState2(0);
|
|
1515
1645
|
const allFlat = useMemo(
|
|
1516
1646
|
() => flattenSessions2(sessionTree, expandedIds),
|
|
1517
1647
|
[sessionTree, expandedIds]
|
|
@@ -1535,13 +1665,54 @@ function App({ mode }) {
|
|
|
1535
1665
|
} else {
|
|
1536
1666
|
setActivities([]);
|
|
1537
1667
|
}
|
|
1668
|
+
setGitActivities([]);
|
|
1538
1669
|
}, [selectedId]);
|
|
1670
|
+
useEffect2(() => {
|
|
1671
|
+
setScrollOffset(0);
|
|
1672
|
+
setIsLive(true);
|
|
1673
|
+
setViewerCursorLine(0);
|
|
1674
|
+
}, [filterIndex]);
|
|
1675
|
+
useEffect2(() => {
|
|
1676
|
+
if (!isWatchMode) return;
|
|
1677
|
+
const node = allFlatRef.current.find((s) => s.id === selectedId);
|
|
1678
|
+
if (!node?.projectPath) return;
|
|
1679
|
+
const load = () => {
|
|
1680
|
+
const acts = node.filePath ? parseSessionHistory(node.filePath) : [];
|
|
1681
|
+
const today = /* @__PURE__ */ new Date();
|
|
1682
|
+
const todayMidnight = new Date(
|
|
1683
|
+
today.getFullYear(),
|
|
1684
|
+
today.getMonth(),
|
|
1685
|
+
today.getDate()
|
|
1686
|
+
);
|
|
1687
|
+
const startDate = acts.length > 0 ? new Date(
|
|
1688
|
+
acts[0].timestamp.getFullYear(),
|
|
1689
|
+
acts[0].timestamp.getMonth(),
|
|
1690
|
+
acts[0].timestamp.getDate()
|
|
1691
|
+
) : todayMidnight;
|
|
1692
|
+
const endDate = acts.length > 0 ? new Date(
|
|
1693
|
+
acts[acts.length - 1].timestamp.getFullYear(),
|
|
1694
|
+
acts[acts.length - 1].timestamp.getMonth(),
|
|
1695
|
+
acts[acts.length - 1].timestamp.getDate()
|
|
1696
|
+
) : todayMidnight;
|
|
1697
|
+
const commits = parseGitCommits(node.projectPath, startDate, endDate);
|
|
1698
|
+
setGitActivities(commits);
|
|
1699
|
+
};
|
|
1700
|
+
load();
|
|
1701
|
+
const timer = setInterval(load, 3e4);
|
|
1702
|
+
return () => clearInterval(timer);
|
|
1703
|
+
}, [selectedId, isWatchMode]);
|
|
1539
1704
|
const refresh = useCallback(() => {
|
|
1540
1705
|
const freshConfig = loadGlobalConfig();
|
|
1541
1706
|
const tree = discoverSessions(freshConfig);
|
|
1542
|
-
setSessionTree(tree);
|
|
1543
1707
|
const updatedFlat = flattenSessions2(tree, expandedIds);
|
|
1544
1708
|
const node = updatedFlat.find((s) => s.id === selectedId);
|
|
1709
|
+
if (!node) {
|
|
1710
|
+
const parentSession = tree.sessions.find(
|
|
1711
|
+
(s) => s.subAgents.some((sa) => sa.id === selectedId)
|
|
1712
|
+
);
|
|
1713
|
+
if (parentSession) setSelectedId(parentSession.id);
|
|
1714
|
+
}
|
|
1715
|
+
setSessionTree(tree);
|
|
1545
1716
|
if (!node || !node.filePath) return;
|
|
1546
1717
|
const newActivities = parseSessionHistory(node.filePath);
|
|
1547
1718
|
const delta = newActivities.length - activitiesLengthRef.current;
|
|
@@ -1585,6 +1756,18 @@ function App({ mode }) {
|
|
|
1585
1756
|
if (debounce) clearTimeout(debounce);
|
|
1586
1757
|
};
|
|
1587
1758
|
}, [isWatchMode, config.refreshIntervalMs]);
|
|
1759
|
+
const filterPresets = config.filterPresets;
|
|
1760
|
+
const activePreset = filterPresets[filterIndex % filterPresets.length] ?? [];
|
|
1761
|
+
const filterLabel = activePreset.length === 0 ? "all" : activePreset.join("+");
|
|
1762
|
+
const mergedActivities = useMemo(() => {
|
|
1763
|
+
const merged = [...activities, ...gitActivities].sort(
|
|
1764
|
+
(a, b) => a.timestamp.getTime() - b.timestamp.getTime()
|
|
1765
|
+
);
|
|
1766
|
+
if (activePreset.length === 0) return merged;
|
|
1767
|
+
return merged.filter(
|
|
1768
|
+
(a) => activePreset.includes(a.type) || a.type === "tool" && activePreset.some((p) => a.label.toLowerCase() === p)
|
|
1769
|
+
);
|
|
1770
|
+
}, [activities, gitActivities, activePreset]);
|
|
1588
1771
|
const selectedIndex = allFlat.findIndex((s) => s.id === selectedId);
|
|
1589
1772
|
const height = (stdout?.rows ?? 41) - 1;
|
|
1590
1773
|
const width = stdout?.columns ?? 80;
|
|
@@ -1616,6 +1799,7 @@ function App({ mode }) {
|
|
|
1616
1799
|
onSwitchFocus: () => setFocus((f) => f === "tree" ? "viewer" : "tree"),
|
|
1617
1800
|
onScrollUp: () => {
|
|
1618
1801
|
if (focus === "tree") {
|
|
1802
|
+
if (selectedIndex === -1) return;
|
|
1619
1803
|
const prev = Math.max(0, selectedIndex - 1);
|
|
1620
1804
|
setSelectedId(allFlat[prev]?.id ?? selectedId);
|
|
1621
1805
|
} else {
|
|
@@ -1635,6 +1819,7 @@ function App({ mode }) {
|
|
|
1635
1819
|
},
|
|
1636
1820
|
onScrollDown: () => {
|
|
1637
1821
|
if (focus === "tree") {
|
|
1822
|
+
if (selectedIndex === -1) return;
|
|
1638
1823
|
const next = Math.min(allFlat.length - 1, selectedIndex + 1);
|
|
1639
1824
|
setSelectedId(allFlat[next]?.id ?? selectedId);
|
|
1640
1825
|
} else {
|
|
@@ -1711,16 +1896,16 @@ function App({ mode }) {
|
|
|
1711
1896
|
}
|
|
1712
1897
|
},
|
|
1713
1898
|
onScrollTop: () => {
|
|
1714
|
-
setViewerCursorLine(0);
|
|
1715
|
-
setIsLive(false);
|
|
1716
|
-
setScrollOffset(Math.max(0, activities.length - viewerRows));
|
|
1717
|
-
},
|
|
1718
|
-
onScrollBottom: () => {
|
|
1719
1899
|
setViewerCursorLine(0);
|
|
1720
1900
|
setIsLive(true);
|
|
1721
1901
|
setScrollOffset(0);
|
|
1722
1902
|
setNewCount(0);
|
|
1723
1903
|
},
|
|
1904
|
+
onScrollBottom: () => {
|
|
1905
|
+
setViewerCursorLine(0);
|
|
1906
|
+
setIsLive(false);
|
|
1907
|
+
setScrollOffset(Math.max(0, mergedActivities.length - viewerRows));
|
|
1908
|
+
},
|
|
1724
1909
|
onDetailClose: () => {
|
|
1725
1910
|
setDetailMode(false);
|
|
1726
1911
|
},
|
|
@@ -1733,14 +1918,20 @@ function App({ mode }) {
|
|
|
1733
1918
|
onEnter: () => {
|
|
1734
1919
|
if (focus === "viewer") {
|
|
1735
1920
|
const act = getSelectedActivity(
|
|
1736
|
-
|
|
1921
|
+
mergedActivities,
|
|
1737
1922
|
isLive,
|
|
1738
1923
|
scrollOffset,
|
|
1739
1924
|
viewerRows,
|
|
1740
1925
|
viewerCursorLine
|
|
1741
1926
|
);
|
|
1742
1927
|
if (act) {
|
|
1743
|
-
|
|
1928
|
+
if (act.type === "commit") {
|
|
1929
|
+
const node = allFlatRef.current.find((s) => s.id === selectedId);
|
|
1930
|
+
const detail = node?.projectPath ? getCommitDetail(node.projectPath, act.label) ?? act.detail : act.detail;
|
|
1931
|
+
setDetailActivity({ ...act, detail });
|
|
1932
|
+
} else {
|
|
1933
|
+
setDetailActivity(act);
|
|
1934
|
+
}
|
|
1744
1935
|
setDetailMode(true);
|
|
1745
1936
|
setDetailScrollOffset(0);
|
|
1746
1937
|
}
|
|
@@ -1765,8 +1956,14 @@ function App({ mode }) {
|
|
|
1765
1956
|
const next = new Set(prev);
|
|
1766
1957
|
if (next.has(parentId)) {
|
|
1767
1958
|
next.delete(parentId);
|
|
1959
|
+
setSelectedId(parentId);
|
|
1768
1960
|
} else {
|
|
1769
1961
|
next.add(parentId);
|
|
1962
|
+
const parent = sessionTree.sessions.find((s) => s.id === parentId);
|
|
1963
|
+
const firstNew = parent?.subAgents.find(
|
|
1964
|
+
(sa) => sa.status === "cool" || sa.status === "cold"
|
|
1965
|
+
);
|
|
1966
|
+
if (firstNew) setSelectedId(firstNew.id);
|
|
1770
1967
|
}
|
|
1771
1968
|
return next;
|
|
1772
1969
|
});
|
|
@@ -1824,7 +2021,9 @@ function App({ mode }) {
|
|
|
1824
2021
|
},
|
|
1825
2022
|
onSaveLog: saveLog,
|
|
1826
2023
|
onRefresh: refresh,
|
|
1827
|
-
onQuit: exit
|
|
2024
|
+
onQuit: exit,
|
|
2025
|
+
onFilter: () => setFilterIndex((i) => (i + 1) % filterPresets.length),
|
|
2026
|
+
filterLabel
|
|
1828
2027
|
});
|
|
1829
2028
|
useInput((input, key) => handleInput(input, key), { isActive: isWatchMode });
|
|
1830
2029
|
const selectedSession = allFlat.find((s) => s.id === selectedId);
|
|
@@ -1863,7 +2062,7 @@ function App({ mode }) {
|
|
|
1863
2062
|
) : /* @__PURE__ */ jsx4(
|
|
1864
2063
|
ActivityViewerPanel,
|
|
1865
2064
|
{
|
|
1866
|
-
activities,
|
|
2065
|
+
activities: mergedActivities,
|
|
1867
2066
|
sessionName: sessionDisplayName,
|
|
1868
2067
|
scrollOffset,
|
|
1869
2068
|
isLive,
|
|
@@ -1872,7 +2071,8 @@ function App({ mode }) {
|
|
|
1872
2071
|
width,
|
|
1873
2072
|
cursorLine: viewerCursorLine,
|
|
1874
2073
|
hasFocus: focus === "viewer",
|
|
1875
|
-
spinner
|
|
2074
|
+
spinner,
|
|
2075
|
+
filterLabel
|
|
1876
2076
|
}
|
|
1877
2077
|
) })
|
|
1878
2078
|
] });
|
|
@@ -1925,7 +2125,9 @@ if (options.mode === "report") {
|
|
|
1925
2125
|
const markdown = generateReport(tree.sessions, {
|
|
1926
2126
|
date: options.reportDate,
|
|
1927
2127
|
include: options.reportInclude,
|
|
1928
|
-
format: options.reportFormat
|
|
2128
|
+
format: options.reportFormat,
|
|
2129
|
+
detailLimit: options.reportDetailLimit,
|
|
2130
|
+
withGit: options.reportWithGit
|
|
1929
2131
|
});
|
|
1930
2132
|
process.stdout.write(`${markdown}
|
|
1931
2133
|
`);
|
package/package.json
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/zsh
|
|
2
|
+
# Daily summary: pipe today's agenthud report to claude for summarization
|
|
3
|
+
# Usage: daily-summary.sh [--date YYYY-MM-DD]
|
|
4
|
+
|
|
5
|
+
DATE_ARG=""
|
|
6
|
+
if [[ "$1" == "--date" && -n "$2" ]]; then
|
|
7
|
+
DATE_ARG="--date $2"
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
agenthud report ${DATE_ARG} --detail-limit 0 --with-git | claude -p "다음은 오늘 Claude Code로 작업한 활동 로그입니다. 이를 바탕으로 오늘 작업 내용을 한국어로 간결하게 정리해주세요. 완료한 작업, 주요 변경사항, 커밋 내역 순으로 bullet point로 작성해주세요."
|