agenthud 0.8.2 → 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-QZW2W2BX.js → main-WBZ2KBF2.js} +272 -74
- 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
|
|
@@ -68,25 +76,21 @@ function getVersion() {
|
|
|
68
76
|
function clearScreen() {
|
|
69
77
|
console.clear();
|
|
70
78
|
}
|
|
71
|
-
function
|
|
79
|
+
function parseLocalMidnight(dateStr) {
|
|
72
80
|
if (dateStr === "today") {
|
|
73
81
|
const now = /* @__PURE__ */ new Date();
|
|
74
|
-
return new Date(
|
|
75
|
-
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
|
|
76
|
-
);
|
|
82
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
77
83
|
}
|
|
78
84
|
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
79
85
|
if (!match) return null;
|
|
80
86
|
const [, y, m, d] = match.map(Number);
|
|
81
|
-
const date = new Date(
|
|
87
|
+
const date = new Date(y, m - 1, d);
|
|
82
88
|
if (Number.isNaN(date.getTime())) return null;
|
|
83
89
|
return date;
|
|
84
90
|
}
|
|
85
|
-
function
|
|
91
|
+
function todayLocalMidnight() {
|
|
86
92
|
const now = /* @__PURE__ */ new Date();
|
|
87
|
-
return new Date(
|
|
88
|
-
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
|
|
89
|
-
);
|
|
93
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
90
94
|
}
|
|
91
95
|
function parseArgs(args) {
|
|
92
96
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -100,7 +104,7 @@ function parseArgs(args) {
|
|
|
100
104
|
}
|
|
101
105
|
if (args[0] === "report") {
|
|
102
106
|
const rest = args.slice(1);
|
|
103
|
-
let reportDate =
|
|
107
|
+
let reportDate = todayLocalMidnight();
|
|
104
108
|
let reportInclude = DEFAULT_TYPES;
|
|
105
109
|
let reportError;
|
|
106
110
|
for (const arg of rest) {
|
|
@@ -115,7 +119,7 @@ function parseArgs(args) {
|
|
|
115
119
|
if (!dateStr) {
|
|
116
120
|
reportError = "Invalid date: missing value for --date";
|
|
117
121
|
} else {
|
|
118
|
-
const parsed =
|
|
122
|
+
const parsed = parseLocalMidnight(dateStr);
|
|
119
123
|
if (!parsed) {
|
|
120
124
|
reportError = `Invalid date: "${dateStr}". Use YYYY-MM-DD or "today".`;
|
|
121
125
|
} else {
|
|
@@ -144,11 +148,25 @@ function parseArgs(args) {
|
|
|
144
148
|
reportError = "Invalid format: missing value for --format.";
|
|
145
149
|
}
|
|
146
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");
|
|
147
163
|
return {
|
|
148
164
|
mode: "report",
|
|
149
165
|
reportDate,
|
|
150
166
|
reportInclude,
|
|
151
167
|
reportFormat,
|
|
168
|
+
reportDetailLimit,
|
|
169
|
+
reportWithGit,
|
|
152
170
|
reportError
|
|
153
171
|
};
|
|
154
172
|
}
|
|
@@ -179,7 +197,8 @@ var DEFAULT_GLOBAL_CONFIG = {
|
|
|
179
197
|
refreshIntervalMs: 2e3,
|
|
180
198
|
logDir: join2(homedir(), ".agenthud", "logs"),
|
|
181
199
|
hiddenSessions: [],
|
|
182
|
-
hiddenSubAgents: []
|
|
200
|
+
hiddenSubAgents: [],
|
|
201
|
+
filterPresets: [[], ["response"], ["commit"]]
|
|
183
202
|
};
|
|
184
203
|
function parseInterval(value) {
|
|
185
204
|
const match = value.match(/^(\d+)(s|m)$/);
|
|
@@ -221,6 +240,12 @@ function loadGlobalConfig() {
|
|
|
221
240
|
(s) => typeof s === "string"
|
|
222
241
|
);
|
|
223
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
|
+
}
|
|
224
249
|
return config;
|
|
225
250
|
}
|
|
226
251
|
function writeConfig(updates) {
|
|
@@ -264,11 +289,8 @@ function hasProjectLevelConfig() {
|
|
|
264
289
|
// src/data/reportGenerator.ts
|
|
265
290
|
import { statSync } from "fs";
|
|
266
291
|
|
|
267
|
-
// src/data/
|
|
268
|
-
import {
|
|
269
|
-
|
|
270
|
-
// src/data/activityParser.ts
|
|
271
|
-
import { basename } from "path";
|
|
292
|
+
// src/data/gitCommits.ts
|
|
293
|
+
import { execSync } from "child_process";
|
|
272
294
|
|
|
273
295
|
// src/types/index.ts
|
|
274
296
|
var ICONS = {
|
|
@@ -286,10 +308,61 @@ var ICONS = {
|
|
|
286
308
|
Task: "\xBB",
|
|
287
309
|
TodoWrite: "~",
|
|
288
310
|
AskUserQuestion: "?",
|
|
311
|
+
Commit: "\u25C6",
|
|
289
312
|
Default: "$"
|
|
290
313
|
};
|
|
291
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
|
+
|
|
292
364
|
// src/data/activityParser.ts
|
|
365
|
+
import { basename } from "path";
|
|
293
366
|
function stripAnsi(text) {
|
|
294
367
|
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
295
368
|
}
|
|
@@ -430,37 +503,51 @@ function activityMatchesInclude(activity, include) {
|
|
|
430
503
|
return true;
|
|
431
504
|
return false;
|
|
432
505
|
}
|
|
433
|
-
function
|
|
434
|
-
return a.
|
|
506
|
+
function isSameLocalDay(a, b) {
|
|
507
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
435
508
|
}
|
|
436
509
|
function formatTime(date) {
|
|
437
|
-
return `${String(date.
|
|
510
|
+
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
438
511
|
}
|
|
439
|
-
function formatActivity(activity) {
|
|
512
|
+
function formatActivity(activity, limit) {
|
|
440
513
|
const time = formatTime(activity.timestamp);
|
|
441
|
-
const detail = activity.detail
|
|
514
|
+
const detail = truncateDetail(activity.detail, limit);
|
|
442
515
|
const suffix = detail ? `: ${detail}` : "";
|
|
443
516
|
return `[${time}] ${activity.icon} ${activity.label}${suffix}`;
|
|
444
517
|
}
|
|
445
|
-
function
|
|
446
|
-
return `${date.
|
|
518
|
+
function formatDateString2(date) {
|
|
519
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
447
520
|
}
|
|
448
521
|
function sessionIsOnDate(session, date, activities) {
|
|
449
522
|
try {
|
|
450
523
|
const mtime = new Date(statSync(session.filePath).mtimeMs);
|
|
451
|
-
if (
|
|
524
|
+
if (isSameLocalDay(mtime, date)) return true;
|
|
452
525
|
} catch {
|
|
453
526
|
}
|
|
454
|
-
return activities.some((a) =>
|
|
527
|
+
return activities.some((a) => isSameLocalDay(a.timestamp, date));
|
|
528
|
+
}
|
|
529
|
+
function truncateDetail(detail, limit) {
|
|
530
|
+
if (limit === 0 || detail.length <= limit) return detail;
|
|
531
|
+
return detail.slice(0, limit);
|
|
455
532
|
}
|
|
456
533
|
function generateReport(sessions, options2) {
|
|
457
|
-
const {
|
|
458
|
-
|
|
534
|
+
const {
|
|
535
|
+
date,
|
|
536
|
+
include,
|
|
537
|
+
format = "markdown",
|
|
538
|
+
detailLimit = 120,
|
|
539
|
+
withGit = false
|
|
540
|
+
} = options2;
|
|
541
|
+
const dateStr = formatDateString2(date);
|
|
459
542
|
const blocks = [];
|
|
460
543
|
for (const session of sessions) {
|
|
461
544
|
const allActivities = parseSessionHistory(session.filePath);
|
|
462
545
|
if (!sessionIsOnDate(session, date, allActivities)) continue;
|
|
463
|
-
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());
|
|
464
551
|
if (dayActivities.length === 0) continue;
|
|
465
552
|
blocks.push({
|
|
466
553
|
session,
|
|
@@ -476,20 +563,39 @@ function generateReport(sessions, options2) {
|
|
|
476
563
|
}
|
|
477
564
|
blocks.sort((a, b) => a.firstTime - b.firstTime);
|
|
478
565
|
if (format === "json") {
|
|
479
|
-
|
|
480
|
-
{
|
|
481
|
-
date
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
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) => ({
|
|
487
573
|
time: formatTime(a.timestamp),
|
|
488
574
|
icon: a.icon,
|
|
489
575
|
label: a.label,
|
|
490
|
-
detail: a.detail
|
|
576
|
+
detail: truncateDetail(a.detail, detailLimit)
|
|
491
577
|
}))
|
|
492
|
-
}
|
|
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
|
+
)
|
|
493
599
|
},
|
|
494
600
|
null,
|
|
495
601
|
2
|
|
@@ -502,7 +608,7 @@ function generateReport(sessions, options2) {
|
|
|
502
608
|
lines.push(`## ${session.projectName} (${first} \u2013 ${last})`);
|
|
503
609
|
lines.push("");
|
|
504
610
|
for (const activity of activities) {
|
|
505
|
-
lines.push(formatActivity(activity));
|
|
611
|
+
lines.push(formatActivity(activity, detailLimit));
|
|
506
612
|
}
|
|
507
613
|
lines.push("");
|
|
508
614
|
}
|
|
@@ -747,6 +853,9 @@ function getActivityStyle(activity) {
|
|
|
747
853
|
if (activity.type === "thinking") {
|
|
748
854
|
return { color: "magenta", dimColor: true };
|
|
749
855
|
}
|
|
856
|
+
if (activity.type === "commit") {
|
|
857
|
+
return { color: "yellow", dimColor: false };
|
|
858
|
+
}
|
|
750
859
|
if (activity.type === "tool") {
|
|
751
860
|
if (activity.label === "Bash") {
|
|
752
861
|
return { color: "gray", dimColor: false };
|
|
@@ -766,7 +875,7 @@ function formatActivityTime(date, now) {
|
|
|
766
875
|
const day = String(date.getDate()).padStart(2, "0");
|
|
767
876
|
return `${month}/${day} ${time}`;
|
|
768
877
|
}
|
|
769
|
-
function
|
|
878
|
+
function truncateDetail2(detail, maxWidth) {
|
|
770
879
|
if (getDisplayWidth(detail) <= maxWidth) return detail;
|
|
771
880
|
let truncated = "";
|
|
772
881
|
let currentWidth = 0;
|
|
@@ -791,16 +900,18 @@ function ActivityViewerPanel({
|
|
|
791
900
|
width,
|
|
792
901
|
cursorLine,
|
|
793
902
|
hasFocus,
|
|
794
|
-
spinner = ""
|
|
903
|
+
spinner = "",
|
|
904
|
+
filterLabel
|
|
795
905
|
}) {
|
|
796
906
|
const innerWidth = getInnerWidth(width);
|
|
797
907
|
const contentWidth = innerWidth - 1;
|
|
908
|
+
const filterSuffix = filterLabel && filterLabel !== "all" ? ` \xB7 ${filterLabel}` : "";
|
|
798
909
|
let titleSuffix;
|
|
799
910
|
if (isLive) {
|
|
800
|
-
titleSuffix = `[LIVE ${spinner || "\u25BC"}]`;
|
|
911
|
+
titleSuffix = `[LIVE ${spinner || "\u25BC"}${filterSuffix}]`;
|
|
801
912
|
} else {
|
|
802
913
|
const badge = newCount > 0 ? ` +${newCount}\u2191` : "";
|
|
803
|
-
titleSuffix = `[PAUSED \u2193${scrollOffset}${badge}]`;
|
|
914
|
+
titleSuffix = `[PAUSED \u2193${scrollOffset}${badge}${filterSuffix}]`;
|
|
804
915
|
}
|
|
805
916
|
let visibleActivities;
|
|
806
917
|
if (activities.length === 0) {
|
|
@@ -850,7 +961,7 @@ function ActivityViewerPanel({
|
|
|
850
961
|
let labelContent;
|
|
851
962
|
let _displayWidth;
|
|
852
963
|
if (detail) {
|
|
853
|
-
const truncated =
|
|
964
|
+
const truncated = truncateDetail2(detail, Math.max(0, detailMaxWidth));
|
|
854
965
|
labelContent = `${labelPart}${truncated}${countSuffix}`;
|
|
855
966
|
_displayWidth = prefixWidth - 1 + labelWidth + getDisplayWidth(truncated) + countSuffixWidth;
|
|
856
967
|
} else {
|
|
@@ -898,21 +1009,27 @@ import { Box as Box2, Text as Text2 } from "ink";
|
|
|
898
1009
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
899
1010
|
function wrapText(text, maxWidth) {
|
|
900
1011
|
if (!text) return ["(empty)"];
|
|
901
|
-
const
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
current = word;
|
|
907
|
-
} else if (getDisplayWidth(`${current} ${word}`) <= maxWidth) {
|
|
908
|
-
current += ` ${word}`;
|
|
909
|
-
} else {
|
|
910
|
-
lines.push(current);
|
|
911
|
-
current = word;
|
|
1012
|
+
const result = [];
|
|
1013
|
+
for (const rawLine of text.split("\n")) {
|
|
1014
|
+
if (!rawLine) {
|
|
1015
|
+
result.push("");
|
|
1016
|
+
continue;
|
|
912
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);
|
|
913
1031
|
}
|
|
914
|
-
|
|
915
|
-
return lines.length > 0 ? lines : ["(empty)"];
|
|
1032
|
+
return result.length > 0 ? result : ["(empty)"];
|
|
916
1033
|
}
|
|
917
1034
|
function DetailViewPanel({
|
|
918
1035
|
activity,
|
|
@@ -994,7 +1111,9 @@ function useHotkeys({
|
|
|
994
1111
|
onHide,
|
|
995
1112
|
onDetailClose,
|
|
996
1113
|
onDetailScrollUp,
|
|
997
|
-
onDetailScrollDown
|
|
1114
|
+
onDetailScrollDown,
|
|
1115
|
+
onFilter,
|
|
1116
|
+
filterLabel
|
|
998
1117
|
}) {
|
|
999
1118
|
const handleInput = (input, key) => {
|
|
1000
1119
|
if (detailMode) {
|
|
@@ -1028,6 +1147,10 @@ function useHotkeys({
|
|
|
1028
1147
|
onRefresh();
|
|
1029
1148
|
return;
|
|
1030
1149
|
}
|
|
1150
|
+
if (input === "f" && !key.ctrl && focus === "viewer") {
|
|
1151
|
+
onFilter();
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1031
1154
|
if (key.pageUp) {
|
|
1032
1155
|
onScrollPageUp();
|
|
1033
1156
|
return;
|
|
@@ -1103,9 +1226,10 @@ function useHotkeys({
|
|
|
1103
1226
|
"Tab: sessions",
|
|
1104
1227
|
"\u2191\u2193/jk: scroll",
|
|
1105
1228
|
"PgUp/Dn: page",
|
|
1106
|
-
"g:
|
|
1107
|
-
"G:
|
|
1229
|
+
"g: live",
|
|
1230
|
+
"G: oldest",
|
|
1108
1231
|
"\u21B5: detail",
|
|
1232
|
+
`f: ${filterLabel}`,
|
|
1109
1233
|
"q: quit"
|
|
1110
1234
|
];
|
|
1111
1235
|
return { handleInput, statusBarItems };
|
|
@@ -1508,6 +1632,7 @@ function App({ mode }) {
|
|
|
1508
1632
|
const [scrollOffset, setScrollOffset] = useState2(0);
|
|
1509
1633
|
const [isLive, setIsLive] = useState2(true);
|
|
1510
1634
|
const [activities, setActivities] = useState2([]);
|
|
1635
|
+
const [gitActivities, setGitActivities] = useState2([]);
|
|
1511
1636
|
const [newCount, setNewCount] = useState2(0);
|
|
1512
1637
|
const [expandedIds, setExpandedIds] = useState2(/* @__PURE__ */ new Set());
|
|
1513
1638
|
const [viewerCursorLine, setViewerCursorLine] = useState2(0);
|
|
@@ -1516,6 +1641,7 @@ function App({ mode }) {
|
|
|
1516
1641
|
null
|
|
1517
1642
|
);
|
|
1518
1643
|
const [detailScrollOffset, setDetailScrollOffset] = useState2(0);
|
|
1644
|
+
const [filterIndex, setFilterIndex] = useState2(0);
|
|
1519
1645
|
const allFlat = useMemo(
|
|
1520
1646
|
() => flattenSessions2(sessionTree, expandedIds),
|
|
1521
1647
|
[sessionTree, expandedIds]
|
|
@@ -1539,13 +1665,54 @@ function App({ mode }) {
|
|
|
1539
1665
|
} else {
|
|
1540
1666
|
setActivities([]);
|
|
1541
1667
|
}
|
|
1668
|
+
setGitActivities([]);
|
|
1542
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]);
|
|
1543
1704
|
const refresh = useCallback(() => {
|
|
1544
1705
|
const freshConfig = loadGlobalConfig();
|
|
1545
1706
|
const tree = discoverSessions(freshConfig);
|
|
1546
|
-
setSessionTree(tree);
|
|
1547
1707
|
const updatedFlat = flattenSessions2(tree, expandedIds);
|
|
1548
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);
|
|
1549
1716
|
if (!node || !node.filePath) return;
|
|
1550
1717
|
const newActivities = parseSessionHistory(node.filePath);
|
|
1551
1718
|
const delta = newActivities.length - activitiesLengthRef.current;
|
|
@@ -1589,6 +1756,18 @@ function App({ mode }) {
|
|
|
1589
1756
|
if (debounce) clearTimeout(debounce);
|
|
1590
1757
|
};
|
|
1591
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]);
|
|
1592
1771
|
const selectedIndex = allFlat.findIndex((s) => s.id === selectedId);
|
|
1593
1772
|
const height = (stdout?.rows ?? 41) - 1;
|
|
1594
1773
|
const width = stdout?.columns ?? 80;
|
|
@@ -1620,6 +1799,7 @@ function App({ mode }) {
|
|
|
1620
1799
|
onSwitchFocus: () => setFocus((f) => f === "tree" ? "viewer" : "tree"),
|
|
1621
1800
|
onScrollUp: () => {
|
|
1622
1801
|
if (focus === "tree") {
|
|
1802
|
+
if (selectedIndex === -1) return;
|
|
1623
1803
|
const prev = Math.max(0, selectedIndex - 1);
|
|
1624
1804
|
setSelectedId(allFlat[prev]?.id ?? selectedId);
|
|
1625
1805
|
} else {
|
|
@@ -1639,6 +1819,7 @@ function App({ mode }) {
|
|
|
1639
1819
|
},
|
|
1640
1820
|
onScrollDown: () => {
|
|
1641
1821
|
if (focus === "tree") {
|
|
1822
|
+
if (selectedIndex === -1) return;
|
|
1642
1823
|
const next = Math.min(allFlat.length - 1, selectedIndex + 1);
|
|
1643
1824
|
setSelectedId(allFlat[next]?.id ?? selectedId);
|
|
1644
1825
|
} else {
|
|
@@ -1715,16 +1896,16 @@ function App({ mode }) {
|
|
|
1715
1896
|
}
|
|
1716
1897
|
},
|
|
1717
1898
|
onScrollTop: () => {
|
|
1718
|
-
setViewerCursorLine(0);
|
|
1719
|
-
setIsLive(false);
|
|
1720
|
-
setScrollOffset(Math.max(0, activities.length - viewerRows));
|
|
1721
|
-
},
|
|
1722
|
-
onScrollBottom: () => {
|
|
1723
1899
|
setViewerCursorLine(0);
|
|
1724
1900
|
setIsLive(true);
|
|
1725
1901
|
setScrollOffset(0);
|
|
1726
1902
|
setNewCount(0);
|
|
1727
1903
|
},
|
|
1904
|
+
onScrollBottom: () => {
|
|
1905
|
+
setViewerCursorLine(0);
|
|
1906
|
+
setIsLive(false);
|
|
1907
|
+
setScrollOffset(Math.max(0, mergedActivities.length - viewerRows));
|
|
1908
|
+
},
|
|
1728
1909
|
onDetailClose: () => {
|
|
1729
1910
|
setDetailMode(false);
|
|
1730
1911
|
},
|
|
@@ -1737,14 +1918,20 @@ function App({ mode }) {
|
|
|
1737
1918
|
onEnter: () => {
|
|
1738
1919
|
if (focus === "viewer") {
|
|
1739
1920
|
const act = getSelectedActivity(
|
|
1740
|
-
|
|
1921
|
+
mergedActivities,
|
|
1741
1922
|
isLive,
|
|
1742
1923
|
scrollOffset,
|
|
1743
1924
|
viewerRows,
|
|
1744
1925
|
viewerCursorLine
|
|
1745
1926
|
);
|
|
1746
1927
|
if (act) {
|
|
1747
|
-
|
|
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
|
+
}
|
|
1748
1935
|
setDetailMode(true);
|
|
1749
1936
|
setDetailScrollOffset(0);
|
|
1750
1937
|
}
|
|
@@ -1769,8 +1956,14 @@ function App({ mode }) {
|
|
|
1769
1956
|
const next = new Set(prev);
|
|
1770
1957
|
if (next.has(parentId)) {
|
|
1771
1958
|
next.delete(parentId);
|
|
1959
|
+
setSelectedId(parentId);
|
|
1772
1960
|
} else {
|
|
1773
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);
|
|
1774
1967
|
}
|
|
1775
1968
|
return next;
|
|
1776
1969
|
});
|
|
@@ -1828,7 +2021,9 @@ function App({ mode }) {
|
|
|
1828
2021
|
},
|
|
1829
2022
|
onSaveLog: saveLog,
|
|
1830
2023
|
onRefresh: refresh,
|
|
1831
|
-
onQuit: exit
|
|
2024
|
+
onQuit: exit,
|
|
2025
|
+
onFilter: () => setFilterIndex((i) => (i + 1) % filterPresets.length),
|
|
2026
|
+
filterLabel
|
|
1832
2027
|
});
|
|
1833
2028
|
useInput((input, key) => handleInput(input, key), { isActive: isWatchMode });
|
|
1834
2029
|
const selectedSession = allFlat.find((s) => s.id === selectedId);
|
|
@@ -1867,7 +2062,7 @@ function App({ mode }) {
|
|
|
1867
2062
|
) : /* @__PURE__ */ jsx4(
|
|
1868
2063
|
ActivityViewerPanel,
|
|
1869
2064
|
{
|
|
1870
|
-
activities,
|
|
2065
|
+
activities: mergedActivities,
|
|
1871
2066
|
sessionName: sessionDisplayName,
|
|
1872
2067
|
scrollOffset,
|
|
1873
2068
|
isLive,
|
|
@@ -1876,7 +2071,8 @@ function App({ mode }) {
|
|
|
1876
2071
|
width,
|
|
1877
2072
|
cursorLine: viewerCursorLine,
|
|
1878
2073
|
hasFocus: focus === "viewer",
|
|
1879
|
-
spinner
|
|
2074
|
+
spinner,
|
|
2075
|
+
filterLabel
|
|
1880
2076
|
}
|
|
1881
2077
|
) })
|
|
1882
2078
|
] });
|
|
@@ -1929,7 +2125,9 @@ if (options.mode === "report") {
|
|
|
1929
2125
|
const markdown = generateReport(tree.sessions, {
|
|
1930
2126
|
date: options.reportDate,
|
|
1931
2127
|
include: options.reportInclude,
|
|
1932
|
-
format: options.reportFormat
|
|
2128
|
+
format: options.reportFormat,
|
|
2129
|
+
detailLimit: options.reportDetailLimit,
|
|
2130
|
+
withGit: options.reportWithGit
|
|
1933
2131
|
});
|
|
1934
2132
|
process.stdout.write(`${markdown}
|
|
1935
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로 작성해주세요."
|