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 CHANGED
@@ -14,4 +14,4 @@ Error: Node.js ${MIN_NODE_VERSION}+ is required (current: ${process.version})
14
14
  console.error(" https://nodejs.org/\n");
15
15
  process.exit(1);
16
16
  }
17
- import("./main-VPGVYRCR.js");
17
+ import("./main-WBZ2KBF2.js");
@@ -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(["--date", "--include", "--format"]);
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/sessionHistory.ts
264
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
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.length > 120 ? activity.detail.slice(0, 120) : 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 formatDateString(date) {
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 { date, include, format = "markdown" } = options2;
454
- const dateStr = formatDateString(date);
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 dayActivities = allActivities.filter((a) => isSameLocalDay(a.timestamp, date)).filter((a) => activityMatchesInclude(a, include));
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
- return JSON.stringify(
476
- {
477
- date: dateStr,
478
- sessions: blocks.map(({ session, activities }) => ({
479
- project: session.projectName,
480
- start: formatTime(activities[0].timestamp),
481
- end: formatTime(activities[activities.length - 1].timestamp),
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.length > 120 ? a.detail.slice(0, 120) : 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 truncateDetail(detail, maxWidth) {
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 = truncateDetail(detail, Math.max(0, detailMaxWidth));
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 words = text.split(" ");
898
- const lines = [];
899
- let current = "";
900
- for (const word of words) {
901
- if (!current) {
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
- if (current) lines.push(current);
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: top",
1103
- "G: live",
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
- activities,
1921
+ mergedActivities,
1737
1922
  isLive,
1738
1923
  scrollOffset,
1739
1924
  viewerRows,
1740
1925
  viewerCursorLine
1741
1926
  );
1742
1927
  if (act) {
1743
- setDetailActivity(act);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenthud",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "CLI tool to monitor agent status in real-time. Works with Claude Code, multi-agent workflows, and any AI agent system.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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로 작성해주세요."