agenthud 0.13.2 → 0.14.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 CHANGED
@@ -16,15 +16,21 @@ AgentHUD reads Claude Code's session files from `~/.claude/projects/` and gives
16
16
 
17
17
  → **See [FEATURES.md](./FEATURES.md) for the full surface** — every flag, keybinding, config key, file path, and env var.
18
18
 
19
- ## Install
19
+ Requires Node.js 20+. Open agenthud in a separate terminal while using Claude Code; press `?` inside the TUI for in-app help.
20
20
 
21
- Requires Node.js 20+.
21
+ ## Try without installing
22
22
 
23
23
  ```bash
24
24
  npx agenthud
25
+ # or: bunx agenthud
25
26
  ```
26
27
 
27
- Run this in a separate terminal while using Claude Code. Press `?` inside the TUI any time for in-app help.
28
+ ## Install for daily use
29
+
30
+ ```bash
31
+ npm i -g agenthud
32
+ # or: bun i -g agenthud
33
+ ```
28
34
 
29
35
  > **Platform support.** Primary development is on macOS and Linux; the full test suite runs on all three platforms in CI (including Windows). Windows runtime behavior is exercised by a manual smoke job but isn't daily-driven — issues there are valued bug reports.
30
36
 
package/dist/index.js CHANGED
@@ -15,4 +15,4 @@ Error: Node.js ${MIN_NODE_VERSION}+ is required (current: ${process.version})
15
15
  process.exit(1);
16
16
  }
17
17
  if (!process.env.NODE_ENV) process.env.NODE_ENV = "production";
18
- import("./main-GMNV6O4E.js");
18
+ import("./main-S5INN7N3.js");
@@ -179,7 +179,8 @@ function loadGlobalConfig() {
179
179
  config.report.detailLimit = r.detailLimit;
180
180
  }
181
181
  if (typeof r.withGit === "boolean") config.report.withGit = r.withGit;
182
- if (r.format === "markdown" || r.format === "json") config.report.format = r.format;
182
+ if (r.format === "markdown" || r.format === "json")
183
+ config.report.format = r.format;
183
184
  }
184
185
  if (configRaw.summary && typeof configRaw.summary === "object") {
185
186
  const s = configRaw.summary;
@@ -194,7 +195,8 @@ function loadGlobalConfig() {
194
195
  config.summary.detailLimit = s.detailLimit;
195
196
  }
196
197
  if (typeof s.withGit === "boolean") config.summary.withGit = s.withGit;
197
- if (s.format === "markdown" || s.format === "json") config.summary.format = s.format;
198
+ if (s.format === "markdown" || s.format === "json")
199
+ config.summary.format = s.format;
198
200
  if (typeof s.model === "string" && s.model.trim().length > 0) {
199
201
  config.summary.model = s.model.trim();
200
202
  }
@@ -247,7 +249,7 @@ function loadGlobalConfig() {
247
249
  return config;
248
250
  }
249
251
  function updateState(updates) {
250
- let state = {
252
+ const state = {
251
253
  hiddenSessions: [],
252
254
  hiddenSubAgents: [],
253
255
  hiddenProjects: []
@@ -296,6 +298,27 @@ function hideProject(name) {
296
298
  if (config.hiddenProjects.includes(name)) return;
297
299
  updateState({ hiddenProjects: [...config.hiddenProjects, name] });
298
300
  }
301
+ function unhideSession(id) {
302
+ const config = loadGlobalConfig();
303
+ if (!config.hiddenSessions.includes(id)) return;
304
+ updateState({
305
+ hiddenSessions: config.hiddenSessions.filter((s) => s !== id)
306
+ });
307
+ }
308
+ function unhideSubAgent(id) {
309
+ const config = loadGlobalConfig();
310
+ if (!config.hiddenSubAgents.includes(id)) return;
311
+ updateState({
312
+ hiddenSubAgents: config.hiddenSubAgents.filter((s) => s !== id)
313
+ });
314
+ }
315
+ function unhideProject(name) {
316
+ const config = loadGlobalConfig();
317
+ if (!config.hiddenProjects.includes(name)) return;
318
+ updateState({
319
+ hiddenProjects: config.hiddenProjects.filter((p) => p !== name)
320
+ });
321
+ }
299
322
  function hasProjectLevelConfig() {
300
323
  const candidate = join(process.cwd(), ".agenthud", "config.yaml");
301
324
  if (candidate === join(homedir(), ".agenthud", "config.yaml")) return false;
@@ -978,7 +1001,11 @@ function buildToolDetailBody(name, input, result) {
978
1001
  const content = result?.file?.content;
979
1002
  if (content) {
980
1003
  const start = result?.file?.startLine ?? input?.offset ?? 1;
981
- return { text: numberLines(content, start), kind: "code", numbered: true };
1004
+ return {
1005
+ text: numberLines(content, start),
1006
+ kind: "code",
1007
+ numbered: true
1008
+ };
982
1009
  }
983
1010
  }
984
1011
  if (name === "Edit" || name === "Write") {
@@ -1470,6 +1497,15 @@ function readFirstUserPrompt(filePath) {
1470
1497
  } catch {
1471
1498
  return null;
1472
1499
  }
1500
+ const MIN_SUBSTANTIAL_LEN = 10;
1501
+ const isSubstantial = (text) => {
1502
+ const trimmed = text.trim();
1503
+ if (trimmed.length < MIN_SUBSTANTIAL_LEN) return false;
1504
+ if (trimmed.startsWith("/")) return false;
1505
+ return true;
1506
+ };
1507
+ let first = null;
1508
+ let latestSubstantial = null;
1473
1509
  for (const line of content.split("\n")) {
1474
1510
  if (!line.trim()) continue;
1475
1511
  let entry;
@@ -1495,9 +1531,11 @@ function readFirstUserPrompt(filePath) {
1495
1531
  if (!text || isSystemNoise(text)) continue;
1496
1532
  const firstLine = text.split("\n").find((l) => l.trim()) ?? "";
1497
1533
  if (!firstLine || isSystemNoise(firstLine)) continue;
1498
- return capWithEllipsis(firstLine);
1534
+ const capped = capWithEllipsis(firstLine);
1535
+ if (first === null) first = capped;
1536
+ if (isSubstantial(firstLine)) latestSubstantial = capped;
1499
1537
  }
1500
- return null;
1538
+ return latestSubstantial ?? first;
1501
1539
  }
1502
1540
  function readEntrypoint(filePath) {
1503
1541
  if (!existsSync3(filePath)) return null;
@@ -1552,9 +1590,10 @@ function buildSubAgents(parentId, projectDir, config, projectName) {
1552
1590
  } catch {
1553
1591
  return null;
1554
1592
  }
1555
- }).filter(
1556
- (n) => n !== null && !config.hiddenSubAgents.includes(n.hideKey)
1557
- ).sort((a, b) => b.lastModifiedMs - a.lastModifiedMs);
1593
+ }).filter((n) => n !== null).map((n) => {
1594
+ if (config.hiddenSubAgents.includes(n.hideKey)) n.hidden = true;
1595
+ return n;
1596
+ }).sort((a, b) => b.lastModifiedMs - a.lastModifiedMs);
1558
1597
  }
1559
1598
  function findContainingProject(cwd, projectPaths, options2) {
1560
1599
  const resolve2 = options2?.realpath ?? ((p) => p);
@@ -1592,7 +1631,8 @@ function discoverSessions(config, options2) {
1592
1631
  projects: [],
1593
1632
  coldProjects: [],
1594
1633
  totalCount: 0,
1595
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1634
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1635
+ hiddenStats: { total: 0, active: 0 }
1596
1636
  };
1597
1637
  }
1598
1638
  let projectDirs;
@@ -1609,7 +1649,8 @@ function discoverSessions(config, options2) {
1609
1649
  projects: [],
1610
1650
  coldProjects: [],
1611
1651
  totalCount: 0,
1612
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1652
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1653
+ hiddenStats: { total: 0, active: 0 }
1613
1654
  };
1614
1655
  }
1615
1656
  const allSessions = [];
@@ -1659,8 +1700,23 @@ function discoverSessions(config, options2) {
1659
1700
  }
1660
1701
  }
1661
1702
  const byProject = /* @__PURE__ */ new Map();
1703
+ let hiddenTotal = 0;
1704
+ let hiddenActive = 0;
1662
1705
  for (const s of allSessions) {
1663
- if (config.hiddenSessions.includes(s.hideKey)) continue;
1706
+ const sessionHidden = config.hiddenSessions.includes(s.hideKey);
1707
+ const projectHidden = config.hiddenProjects.includes(s.projectName);
1708
+ if (sessionHidden || projectHidden) {
1709
+ hiddenTotal++;
1710
+ if (s.status === "hot" || s.status === "warm") hiddenActive++;
1711
+ s.hidden = true;
1712
+ }
1713
+ for (const sub of s.subAgents) {
1714
+ if (sub.hidden || projectHidden) {
1715
+ if (projectHidden) sub.hidden = true;
1716
+ hiddenTotal++;
1717
+ if (sub.status === "hot" || sub.status === "warm") hiddenActive++;
1718
+ }
1719
+ }
1664
1720
  const arr = byProject.get(s.projectPath) ?? [];
1665
1721
  arr.push(s);
1666
1722
  byProject.set(s.projectPath, arr);
@@ -1675,7 +1731,7 @@ function discoverSessions(config, options2) {
1675
1731
  for (const [projectPath, sessions] of byProject) {
1676
1732
  if (sessions.length === 0) continue;
1677
1733
  const projectName = sessions[0].projectName;
1678
- if (config.hiddenProjects.includes(projectName)) continue;
1734
+ const projectHidden = config.hiddenProjects.includes(projectName);
1679
1735
  sessions.sort((a, b) => {
1680
1736
  if (a.nonInteractive !== b.nonInteractive) {
1681
1737
  return a.nonInteractive ? 1 : -1;
@@ -1685,7 +1741,13 @@ function discoverSessions(config, options2) {
1685
1741
  return b.lastModifiedMs - a.lastModifiedMs;
1686
1742
  });
1687
1743
  const hotness = sessions[0].status;
1688
- allProjects.push({ name: projectName, projectPath, sessions, hotness });
1744
+ allProjects.push({
1745
+ name: projectName,
1746
+ projectPath,
1747
+ sessions,
1748
+ hotness,
1749
+ hidden: projectHidden || void 0
1750
+ });
1689
1751
  }
1690
1752
  const activeProjects = allProjects.filter((p) => p.hotness !== "cold");
1691
1753
  const coldProjects = allProjects.filter((p) => p.hotness === "cold");
@@ -1703,7 +1765,8 @@ function discoverSessions(config, options2) {
1703
1765
  projects: activeProjects,
1704
1766
  coldProjects,
1705
1767
  totalCount,
1706
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1768
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1769
+ hiddenStats: { total: hiddenTotal, active: hiddenActive }
1707
1770
  };
1708
1771
  }
1709
1772
 
@@ -1842,6 +1905,27 @@ async function openInDefaultApp(path) {
1842
1905
  });
1843
1906
  }
1844
1907
 
1908
+ // src/utils/stderrTicker.ts
1909
+ function formatTickerLine(label, elapsedSeconds, dots) {
1910
+ const tail = ".".repeat(dots % 4).padEnd(3, " ");
1911
+ return `${label}${tail} ${elapsedSeconds}s`;
1912
+ }
1913
+ function startStderrTicker(label, options2 = {}) {
1914
+ const intervalMs = options2.intervalMs ?? 500;
1915
+ const stream = options2.stream ?? process.stderr;
1916
+ const start = Date.now();
1917
+ let ticks = 0;
1918
+ const id = setInterval(() => {
1919
+ ticks++;
1920
+ const elapsed = Math.floor((Date.now() - start) / 1e3);
1921
+ stream.write(`\r${formatTickerLine(label, elapsed, ticks)}`);
1922
+ }, intervalMs);
1923
+ return function stop() {
1924
+ clearInterval(id);
1925
+ if (ticks > 0) stream.write("\r\x1B[K");
1926
+ };
1927
+ }
1928
+
1845
1929
  // src/data/summariesIndex.ts
1846
1930
  import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
1847
1931
  import { join as join5 } from "path";
@@ -2001,15 +2085,11 @@ function buildHeaderBlock(currentFilename, entries) {
2001
2085
  const idx = dailies.findIndex((e) => e.filename === currentFilename);
2002
2086
  if (idx > 0) {
2003
2087
  const prev = dailies[idx - 1];
2004
- parts.push(
2005
- `[\u2190 ${formatDateLabel(prev.date)}](./${prev.filename})`
2006
- );
2088
+ parts.push(`[\u2190 ${formatDateLabel(prev.date)}](./${prev.filename})`);
2007
2089
  }
2008
2090
  if (idx >= 0 && idx < dailies.length - 1) {
2009
2091
  const next = dailies[idx + 1];
2010
- parts.push(
2011
- `[${formatDateLabel(next.date)} \u2192](./${next.filename})`
2012
- );
2092
+ parts.push(`[${formatDateLabel(next.date)} \u2192](./${next.filename})`);
2013
2093
  }
2014
2094
  }
2015
2095
  const backlinkLine = parts.join(" \xB7 ");
@@ -2066,11 +2146,7 @@ function regenerateIndex(summariesDir2) {
2066
2146
  }
2067
2147
  const indexPath = join5(summariesDir2, "index.md");
2068
2148
  try {
2069
- writeFileSync2(
2070
- indexPath,
2071
- buildIndexMarkdown(entries, snippets),
2072
- "utf-8"
2073
- );
2149
+ writeFileSync2(indexPath, buildIndexMarkdown(entries, snippets), "utf-8");
2074
2150
  } catch (err) {
2075
2151
  process.stderr.write(
2076
2152
  `warning: cannot write summaries index (${err.message})
@@ -2079,27 +2155,6 @@ function regenerateIndex(summariesDir2) {
2079
2155
  }
2080
2156
  }
2081
2157
 
2082
- // src/utils/stderrTicker.ts
2083
- function formatTickerLine(label, elapsedSeconds, dots) {
2084
- const tail = ".".repeat(dots % 4).padEnd(3, " ");
2085
- return `${label}${tail} ${elapsedSeconds}s`;
2086
- }
2087
- function startStderrTicker(label, options2 = {}) {
2088
- const intervalMs = options2.intervalMs ?? 500;
2089
- const stream = options2.stream ?? process.stderr;
2090
- const start = Date.now();
2091
- let ticks = 0;
2092
- const id = setInterval(() => {
2093
- ticks++;
2094
- const elapsed = Math.floor((Date.now() - start) / 1e3);
2095
- stream.write(`\r${formatTickerLine(label, elapsed, ticks)}`);
2096
- }, intervalMs);
2097
- return function stop() {
2098
- clearInterval(id);
2099
- if (ticks > 0) stream.write("\r\x1B[K");
2100
- };
2101
- }
2102
-
2103
2158
  // src/data/summaryRunner.ts
2104
2159
  function agenthudHomeDir() {
2105
2160
  const dir = join6(homedir3(), ".agenthud");
@@ -2227,14 +2282,10 @@ function spawnClaude(opts) {
2227
2282
  ];
2228
2283
  if (opts.model) args.push("--model", opts.model);
2229
2284
  args.push(opts.prompt);
2230
- const proc = spawn2(
2231
- "claude",
2232
- args,
2233
- {
2234
- stdio: ["pipe", "pipe", "pipe"],
2235
- cwd: agenthudHomeDir()
2236
- }
2237
- );
2285
+ const proc = spawn2("claude", args, {
2286
+ stdio: ["pipe", "pipe", "pipe"],
2287
+ cwd: agenthudHomeDir()
2288
+ });
2238
2289
  let cacheStream = null;
2239
2290
  if (opts.cachePath) {
2240
2291
  cacheStream = createWriteStream(opts.cachePath, { encoding: "utf-8" });
@@ -2534,10 +2585,8 @@ async function runRangeSummary(options2) {
2534
2585
  )) {
2535
2586
  try {
2536
2587
  const content = readFileSync7(rangeCache, "utf-8");
2537
- process.stderr.write(
2538
- `cached range summary from ${rangeCache}
2539
- `
2540
- );
2588
+ process.stderr.write(`cached range summary from ${rangeCache}
2589
+ `);
2541
2590
  if (!options2.open) {
2542
2591
  process.stdout.write(content);
2543
2592
  if (!content.endsWith("\n")) process.stdout.write("\n");
@@ -2565,10 +2614,8 @@ async function runRangeSummary(options2) {
2565
2614
  `range ${fromLabel} \u2192 ${toLabel} (${dates.length} days)
2566
2615
  `
2567
2616
  );
2568
- process.stderr.write(
2569
- `${cachedCount} cached, ${missingCount} to generate
2570
- `
2571
- );
2617
+ process.stderr.write(`${cachedCount} cached, ${missingCount} to generate
2618
+ `);
2572
2619
  const dailyMarkdowns = [];
2573
2620
  let skippedCount = 0;
2574
2621
  for (const d of dates) {
@@ -3106,12 +3153,19 @@ var SECTIONS = [
3106
3153
  title: "Project tree",
3107
3154
  rows: [
3108
3155
  ["\u2191 \u2193 / k j", "Move selection"],
3109
- ["\u2190", "Jump to parent (sub-agent \u2192 session, session \u2192 project)"],
3156
+ ["\u2190 / h", "Jump to parent (sub-agent \u2192 session, session \u2192 project)"],
3110
3157
  ["PgUp / Ctrl+B", "Page up"],
3111
3158
  ["PgDn / Ctrl+F", "Page down"],
3112
3159
  ["\u21B5", "Expand/collapse project, session, or summary"],
3113
- ["h", "Hide selected (project/session/sub-agent)"],
3114
- ["t", "Track: auto-follow the newest live sub-agent (any nav key turns it off)"],
3160
+ [
3161
+ "H",
3162
+ "Toggle hide on selected \u2014 hides if visible, unhides if hidden (Shift+H)"
3163
+ ],
3164
+ ["a", "Toggle show hidden items in the tree (dim \u2298 marker)"],
3165
+ [
3166
+ "t",
3167
+ "Track: auto-follow the newest live sub-agent (any nav key turns it off)"
3168
+ ],
3115
3169
  ["Tab", "Switch focus to activity viewer"],
3116
3170
  ["r", "Refresh now"]
3117
3171
  ]
@@ -3257,6 +3311,7 @@ function useHotkeys({
3257
3311
  onHelpScrollToTop,
3258
3312
  onToggleTracking,
3259
3313
  onJumpToParent,
3314
+ onToggleShowHidden,
3260
3315
  filterLabel,
3261
3316
  trackingOn = false
3262
3317
  }) {
@@ -3341,7 +3396,7 @@ function useHotkeys({
3341
3396
  onFilter();
3342
3397
  return;
3343
3398
  }
3344
- if (input === "t" && !key.ctrl && onToggleTracking) {
3399
+ if (input === "t" && !key.ctrl && focus === "tree" && onToggleTracking) {
3345
3400
  onToggleTracking();
3346
3401
  return;
3347
3402
  }
@@ -3372,11 +3427,15 @@ function useHotkeys({
3372
3427
  }
3373
3428
  }
3374
3429
  if (focus === "tree") {
3375
- if (input === "h") {
3430
+ if (input === "H") {
3376
3431
  onHide();
3377
3432
  return;
3378
3433
  }
3379
- if (key.leftArrow && onJumpToParent) {
3434
+ if (input === "a" && onToggleShowHidden) {
3435
+ onToggleShowHidden();
3436
+ return;
3437
+ }
3438
+ if ((input === "h" || key.leftArrow) && onJumpToParent) {
3380
3439
  onJumpToParent();
3381
3440
  return;
3382
3441
  }
@@ -3408,16 +3467,16 @@ function useHotkeys({
3408
3467
  }
3409
3468
  }
3410
3469
  };
3411
- const trackingItems = trackingOn ? ["TRK \u25CF"] : ["t: track"];
3470
+ const trackingItems = trackingOn ? ["TRK \u25CF"] : [];
3412
3471
  const statusBarItems = helpMode ? ["\u2191\u2193/jk: scroll", "PgDn/Space: page", "\u21B5/Esc/q/?: close"] : detailMode ? ["\u2191\u2193/jk: scroll", "C-u/d: \xBDpage", "\u21B5/Esc: close", "?: help"] : focus === "tree" ? [
3413
3472
  ...trackingItems,
3414
3473
  "Tab: viewer",
3415
3474
  "\u2191\u2193/jk: select",
3416
- "\u2190: parent",
3475
+ "h/\u2190: parent",
3417
3476
  "PgUp/Dn: page",
3418
3477
  "\u21B5: expand",
3419
- "h: hide",
3420
- "r: refresh",
3478
+ "H: hide",
3479
+ "a: show hidden",
3421
3480
  "?: help",
3422
3481
  "q: quit"
3423
3482
  ] : [
@@ -3525,7 +3584,8 @@ function SessionRow({
3525
3584
  const rightParts = [elapsed];
3526
3585
  if (model) rightParts.push(model);
3527
3586
  const rightSide = rightParts.join(" ");
3528
- const leftCoreBase = `${prefix}${rawName}${shortIdDisplay} ${badge}`;
3587
+ const hiddenMarker = session.hidden ? "\u2298 " : "";
3588
+ const leftCoreBase = `${prefix}${rawName}${shortIdDisplay} ${hiddenMarker}${badge}`;
3529
3589
  const leftCoreWidth = getDisplayWidth(leftCoreBase);
3530
3590
  const rightWidth = getDisplayWidth(rightSide);
3531
3591
  const RIGHT_GAP = 3;
@@ -3550,7 +3610,7 @@ function SessionRow({
3550
3610
  const focused = isSelected && hasFocus;
3551
3611
  const muted = isSelected && !hasFocus;
3552
3612
  const showBg = focused || muted;
3553
- const shouldDim = isNonInteractive || muted;
3613
+ const shouldDim = isNonInteractive || muted || !!session.hidden || session.status === "cold";
3554
3614
  return /* @__PURE__ */ jsxs4(Text4, { children: [
3555
3615
  BOX.v,
3556
3616
  " ",
@@ -3565,6 +3625,7 @@ function SessionRow({
3565
3625
  /* @__PURE__ */ jsx4(Text4, { bold: !shouldDim, children: rawName }),
3566
3626
  shortIdDisplay ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: shortIdDisplay }) : null,
3567
3627
  /* @__PURE__ */ jsx4(Text4, { children: " " }),
3628
+ session.hidden ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2298 " }) : null,
3568
3629
  /* @__PURE__ */ jsx4(Text4, { color: badgeColor, children: badge }),
3569
3630
  middleText ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: middleSection }) : null,
3570
3631
  /* @__PURE__ */ jsx4(Text4, { children: gap }),
@@ -3626,10 +3687,28 @@ function flattenSessions(projects, coldProjects, expandedIds) {
3626
3687
  result.push({ kind: "project", project, sentinelId });
3627
3688
  const collapsed = expandedIds.has(`__collapsed-${sentinelId}`);
3628
3689
  if (!collapsed) {
3629
- for (const session of project.sessions) {
3690
+ const activeSessions = project.sessions.filter(
3691
+ (s) => s.status !== "cold"
3692
+ );
3693
+ const coldSessions = project.sessions.filter((s) => s.status === "cold");
3694
+ for (const session of activeSessions) {
3630
3695
  result.push({ kind: "session", session, prefix: " " });
3631
3696
  appendSessionRows(result, session, expandedIds);
3632
3697
  }
3698
+ if (coldSessions.length > 0) {
3699
+ const coldKey = `__cold-sessions-${project.name}__`;
3700
+ result.push({
3701
+ kind: "cold-sessions-summary",
3702
+ projectName: project.name,
3703
+ count: coldSessions.length
3704
+ });
3705
+ if (expandedIds.has(coldKey)) {
3706
+ for (const session of coldSessions) {
3707
+ result.push({ kind: "session", session, prefix: " " });
3708
+ appendSessionRows(result, session, expandedIds);
3709
+ }
3710
+ }
3711
+ }
3633
3712
  }
3634
3713
  }
3635
3714
  if (coldProjects.length > 0) {
@@ -3656,28 +3735,61 @@ function ProjectRow({
3656
3735
  hasFocus,
3657
3736
  contentWidth
3658
3737
  }) {
3659
- const nameText = `> ${project.name}`;
3738
+ const nameText = `> ${project.hidden ? "\u2298 " : ""}${project.name}`;
3660
3739
  const pathText = project.projectPath ? formatProjectPath(project.projectPath) : "";
3661
3740
  const latestMtime = project.sessions.reduce(
3662
3741
  (acc, s) => Math.max(acc, s.lastModifiedMs),
3663
3742
  0
3664
3743
  );
3665
3744
  const elapsed = latestMtime > 0 ? formatElapsed(latestMtime) : "";
3745
+ const sessionCount = project.sessions.length;
3746
+ const subAgentCount = project.sessions.reduce(
3747
+ (n, s) => n + s.subAgents.length,
3748
+ 0
3749
+ );
3750
+ const isActive = (status) => status === "hot" || status === "warm";
3751
+ const activeSessionCount = project.sessions.filter(
3752
+ (s) => isActive(s.status)
3753
+ ).length;
3754
+ const activeSubAgentCount = project.sessions.reduce(
3755
+ (n, s) => n + s.subAgents.filter((sa) => isActive(sa.status)).length,
3756
+ 0
3757
+ );
3758
+ const buildCountsSegments = (short) => [
3759
+ {
3760
+ text: short ? `${sessionCount}s` : `${sessionCount} sessions`,
3761
+ dim: true
3762
+ },
3763
+ ...activeParen(activeSessionCount, "green", { bold: false }),
3764
+ {
3765
+ text: short ? ` \xB7 ${subAgentCount}a` : ` \xB7 ${subAgentCount} sub-agents`,
3766
+ dim: true
3767
+ },
3768
+ ...activeParen(activeSubAgentCount, "green", { bold: false })
3769
+ ];
3770
+ const longSegs = buildCountsSegments(false);
3771
+ const shortSegs = buildCountsSegments(true);
3772
+ const segWidth = (segs) => segs.reduce((n, s) => n + getDisplayWidth(s.text), 0);
3666
3773
  const nameWidth = getDisplayWidth(nameText);
3667
3774
  const pathWidth = pathText ? getDisplayWidth(pathText) : 0;
3668
3775
  const elapsedWidth = elapsed ? getDisplayWidth(elapsed) : 0;
3669
3776
  const middleGap = pathText ? 2 : 0;
3670
3777
  const leftWidth = nameWidth + middleGap + pathWidth;
3671
3778
  const PROJECT_RIGHT_GAP = 3;
3779
+ const COUNTS_GAP = 2;
3780
+ const fitsCounts = (segs) => leftWidth + COUNTS_GAP + segWidth(segs) + PROJECT_RIGHT_GAP + elapsedWidth <= contentWidth;
3781
+ const countsSegs = fitsCounts(longSegs) ? longSegs : fitsCounts(shortSegs) ? shortSegs : null;
3782
+ const countsWidth = countsSegs ? COUNTS_GAP + segWidth(countsSegs) : 0;
3672
3783
  const rightGap = Math.max(
3673
3784
  PROJECT_RIGHT_GAP,
3674
- contentWidth - leftWidth - elapsedWidth
3785
+ contentWidth - leftWidth - countsWidth - elapsedWidth
3675
3786
  );
3676
- const totalWidth = leftWidth + rightGap + elapsedWidth;
3787
+ const totalWidth = leftWidth + countsWidth + rightGap + elapsedWidth;
3677
3788
  const padding = Math.max(0, contentWidth - totalWidth);
3678
3789
  const focused = isSelected && hasFocus;
3679
3790
  const muted = isSelected && !hasFocus;
3680
3791
  const showBg = focused || muted;
3792
+ const dim = muted || !!project.hidden;
3681
3793
  return /* @__PURE__ */ jsxs4(Text4, { children: [
3682
3794
  BOX.v,
3683
3795
  " ",
@@ -3685,14 +3797,27 @@ function ProjectRow({
3685
3797
  Text4,
3686
3798
  {
3687
3799
  backgroundColor: showBg ? "blue" : void 0,
3688
- bold: !showBg,
3689
- dimColor: muted,
3800
+ bold: !showBg && !project.hidden,
3801
+ dimColor: dim,
3690
3802
  children: [
3691
3803
  nameText,
3692
3804
  pathText ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
3693
3805
  " ",
3694
3806
  /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: pathText })
3695
3807
  ] }) : null,
3808
+ countsSegs ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
3809
+ " ".repeat(COUNTS_GAP),
3810
+ countsSegs.map((seg, i) => /* @__PURE__ */ jsx4(
3811
+ Text4,
3812
+ {
3813
+ color: seg.color,
3814
+ dimColor: seg.dim,
3815
+ bold: seg.bold,
3816
+ children: seg.text
3817
+ },
3818
+ `pcnt-${i}`
3819
+ ))
3820
+ ] }) : null,
3696
3821
  " ".repeat(rightGap),
3697
3822
  elapsed ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: elapsed }) : null,
3698
3823
  " ".repeat(padding)
@@ -3712,21 +3837,19 @@ function SubagentSummaryRow({
3712
3837
  const parts = [];
3713
3838
  if (coolCount > 0) parts.push(`${coolCount} cool`);
3714
3839
  if (coldCount > 0) parts.push(`${coldCount} cold`);
3840
+ const baseText = ` \u2514\u2500 ... ${parts.join(" ")}`;
3715
3841
  const hint = " +";
3716
- const text = ` \u2514\u2500 ... ${parts.join(" ")}`;
3717
- const padding = Math.max(
3718
- 0,
3719
- contentWidth - getDisplayWidth(text) - getDisplayWidth(hint)
3720
- );
3721
3842
  const focused = isSelected && hasFocus;
3722
3843
  const muted = isSelected && !hasFocus;
3844
+ const showHint = focused && getDisplayWidth(baseText) + getDisplayWidth(hint) <= contentWidth;
3845
+ const text = showHint ? `${baseText}${hint}` : baseText;
3846
+ const padding = Math.max(0, contentWidth - getDisplayWidth(text));
3723
3847
  return /* @__PURE__ */ jsxs4(Text4, { children: [
3724
3848
  BOX.v,
3725
3849
  " ",
3726
3850
  /* @__PURE__ */ jsxs4(Text4, { dimColor: !focused, inverse: focused || muted, children: [
3727
3851
  text,
3728
- " ".repeat(padding),
3729
- hint
3852
+ " ".repeat(padding)
3730
3853
  ] }),
3731
3854
  BOX.v
3732
3855
  ] });
@@ -3757,6 +3880,121 @@ function ColdProjectsSummaryRow({
3757
3880
  }
3758
3881
  );
3759
3882
  }
3883
+ function ColdSessionsSummaryRow({
3884
+ count,
3885
+ isSelected,
3886
+ hasFocus,
3887
+ contentWidth
3888
+ }) {
3889
+ const prefix = " ";
3890
+ const baseLabel = `... ${count} cold`;
3891
+ const hint = " +";
3892
+ const baseText = `${prefix}${baseLabel}`;
3893
+ const showHint = isSelected && hasFocus && getDisplayWidth(baseText) + getDisplayWidth(hint) <= contentWidth;
3894
+ const text = showHint ? `${baseText}${hint}` : baseText;
3895
+ const padding = Math.max(0, contentWidth - getDisplayWidth(text));
3896
+ const focused = isSelected && hasFocus;
3897
+ const muted = isSelected && !hasFocus;
3898
+ return /* @__PURE__ */ jsxs4(Text4, { children: [
3899
+ BOX.v,
3900
+ " ",
3901
+ /* @__PURE__ */ jsx4(
3902
+ Text4,
3903
+ {
3904
+ backgroundColor: focused || muted ? "blue" : void 0,
3905
+ bold: focused,
3906
+ dimColor: !focused,
3907
+ children: text
3908
+ }
3909
+ ),
3910
+ " ".repeat(padding),
3911
+ BOX.v
3912
+ ] });
3913
+ }
3914
+ function activeParen(count, color, opts = {}) {
3915
+ if (count === 0) return [];
3916
+ const { bold = true, dim = false } = opts;
3917
+ return [
3918
+ { text: " (", dim: true },
3919
+ { text: `${count}`, color, bold, dim },
3920
+ { text: ")", dim: true }
3921
+ ];
3922
+ }
3923
+ function hiddenSeg(hidden, short) {
3924
+ if (hidden.total === 0) return [];
3925
+ const segs = [{ text: ` \xB7 \u2298 ${hidden.total}`, dim: true }];
3926
+ if (!short) segs.push({ text: " hidden", dim: true });
3927
+ if (hidden.active > 0) {
3928
+ segs.push(...activeParen(hidden.active, "yellow"));
3929
+ }
3930
+ return segs;
3931
+ }
3932
+ function buildTitleSegments(label, census, availableWidth) {
3933
+ const labelSeg = { text: label, dim: true };
3934
+ if (!census) return [labelSeg];
3935
+ const widthOf = (segs) => segs.reduce((w, s) => w + getDisplayWidth(s.text), 0);
3936
+ const c = census;
3937
+ const LEVELS = [
3938
+ // L0: full long form
3939
+ {
3940
+ projectsSuffix: " projects",
3941
+ sessionsSuffix: " sessions",
3942
+ subAgentsSuffix: " sub-agents"
3943
+ },
3944
+ // L1: short form (single-letter abbreviations)
3945
+ {
3946
+ projectsSuffix: "p",
3947
+ sessionsSuffix: "s",
3948
+ subAgentsSuffix: "a",
3949
+ hiddenShort: true
3950
+ },
3951
+ // L2: drop sub-agents
3952
+ { projectsSuffix: "p", sessionsSuffix: "s", hiddenShort: true },
3953
+ // L3: drop sessions
3954
+ { projectsSuffix: "p", hiddenShort: true },
3955
+ // L4: drop project active counter
3956
+ { projectsSuffix: "p", showProjectActive: false, hiddenShort: true },
3957
+ // L5: just hidden alert
3958
+ { hiddenShort: true },
3959
+ // L6: just the label
3960
+ { hiddenShort: true, omitHidden: true }
3961
+ ];
3962
+ const buildLevel = (level) => {
3963
+ const segs = [labelSeg];
3964
+ if (level.projectsSuffix !== void 0) {
3965
+ segs.push({
3966
+ text: ` ${c.projects.total}${level.projectsSuffix}`,
3967
+ dim: true
3968
+ });
3969
+ if (level.showProjectActive !== false) {
3970
+ segs.push(...activeParen(c.projects.active, "green"));
3971
+ }
3972
+ }
3973
+ if (level.sessionsSuffix !== void 0) {
3974
+ segs.push({
3975
+ text: ` \xB7 ${c.sessions.total}${level.sessionsSuffix}`,
3976
+ dim: true
3977
+ });
3978
+ segs.push(...activeParen(c.sessions.active, "green"));
3979
+ }
3980
+ if (level.subAgentsSuffix !== void 0) {
3981
+ segs.push({
3982
+ text: ` \xB7 ${c.subAgents.total}${level.subAgentsSuffix}`,
3983
+ dim: true
3984
+ });
3985
+ segs.push(...activeParen(c.subAgents.active, "green"));
3986
+ }
3987
+ if (!level.omitHidden) {
3988
+ segs.push(...hiddenSeg(c.hidden, level.hiddenShort ?? false));
3989
+ }
3990
+ return segs;
3991
+ };
3992
+ const candidates = LEVELS.map(buildLevel);
3993
+ for (const cand of candidates) {
3994
+ if (widthOf(cand) <= availableWidth) return cand;
3995
+ }
3996
+ return candidates[candidates.length - 1];
3997
+ }
3760
3998
  function SessionTreePanel({
3761
3999
  projects,
3762
4000
  coldProjects,
@@ -3767,7 +4005,8 @@ function SessionTreePanel({
3767
4005
  expandedIds = /* @__PURE__ */ new Set(),
3768
4006
  trackingOn = false,
3769
4007
  spinner = "",
3770
- scopeLabel
4008
+ scopeLabel,
4009
+ census
3771
4010
  }) {
3772
4011
  const innerWidth = getInnerWidth(width);
3773
4012
  const contentWidth = innerWidth - 1;
@@ -3775,12 +4014,38 @@ function SessionTreePanel({
3775
4014
  const titleText = scopeLabel ? `Projects [${scopeLabel}]` : "Projects";
3776
4015
  const titleLine = createTitleLine(titleText, titleSuffix, width);
3777
4016
  const bottomLine = createBottomLine(width);
4017
+ const censusSegments = census ? (() => {
4018
+ const segs = buildTitleSegments("", census, contentWidth);
4019
+ const withoutLabel = segs[0]?.text === "" ? segs.slice(1) : segs;
4020
+ return withoutLabel.map(
4021
+ (seg, i) => i === 0 ? { ...seg, text: seg.text.replace(/^ /, "") } : seg
4022
+ );
4023
+ })() : null;
4024
+ const censusWidth = censusSegments ? censusSegments.reduce((w, s) => w + getDisplayWidth(s.text), 0) : 0;
4025
+ const censusPadding = censusSegments ? Math.max(0, contentWidth - censusWidth) : 0;
4026
+ const renderCensusRow = () => censusSegments ? /* @__PURE__ */ jsxs4(Text4, { children: [
4027
+ BOX.v,
4028
+ " ",
4029
+ censusSegments.map((seg, i) => /* @__PURE__ */ jsx4(
4030
+ Text4,
4031
+ {
4032
+ color: seg.color,
4033
+ dimColor: seg.dim,
4034
+ bold: seg.bold,
4035
+ children: seg.text
4036
+ },
4037
+ `census-${i}`
4038
+ )),
4039
+ " ".repeat(censusPadding),
4040
+ BOX.v
4041
+ ] }) : null;
3778
4042
  const totalProjectCount = projects.length + coldProjects.length;
3779
4043
  if (totalProjectCount === 0) {
3780
4044
  const emptyText = "No Claude sessions";
3781
4045
  const emptyPadding = Math.max(0, contentWidth - emptyText.length);
3782
4046
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, children: [
3783
4047
  /* @__PURE__ */ jsx4(Text4, { children: titleLine }),
4048
+ renderCensusRow(),
3784
4049
  /* @__PURE__ */ jsxs4(Text4, { children: [
3785
4050
  BOX.v,
3786
4051
  " ",
@@ -3799,10 +4064,14 @@ function SessionTreePanel({
3799
4064
  if (row.kind === "subagent-summary")
3800
4065
  return selectedId === `__sub-${row.parentId}__`;
3801
4066
  if (row.kind === "cold-projects-summary") return selectedId === "__cold__";
4067
+ if (row.kind === "cold-sessions-summary")
4068
+ return selectedId === `__cold-sessions-${row.projectName}__`;
3802
4069
  return false;
3803
4070
  });
3804
- const needsOverflow = maxRows !== void 0 && totalRows > maxRows;
3805
- const visibleCount = needsOverflow ? maxRows - 1 : totalRows;
4071
+ const censusRowCost = censusSegments ? 1 : 0;
4072
+ const effectiveMaxRows = maxRows !== void 0 ? Math.max(1, maxRows - censusRowCost) : void 0;
4073
+ const needsOverflow = effectiveMaxRows !== void 0 && totalRows > effectiveMaxRows;
4074
+ const visibleCount = needsOverflow ? effectiveMaxRows - 1 : totalRows;
3806
4075
  let scrollTop = 0;
3807
4076
  if (needsOverflow && selectedFlatIndex >= 0) {
3808
4077
  scrollTop = Math.max(0, selectedFlatIndex - visibleCount + 1);
@@ -3812,6 +4081,7 @@ function SessionTreePanel({
3812
4081
  const hiddenBelow = totalRows - (scrollTop + displayRows.length);
3813
4082
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width, children: [
3814
4083
  /* @__PURE__ */ jsx4(Text4, { children: titleLine }),
4084
+ renderCensusRow(),
3815
4085
  displayRows.map(
3816
4086
  (row, idx) => row.kind === "project" ? /* @__PURE__ */ jsx4(
3817
4087
  ProjectRow,
@@ -3842,7 +4112,7 @@ function SessionTreePanel({
3842
4112
  hasFocus
3843
4113
  },
3844
4114
  `subagent-summary-${idx}`
3845
- ) : /* @__PURE__ */ jsx4(
4115
+ ) : row.kind === "cold-projects-summary" ? /* @__PURE__ */ jsx4(
3846
4116
  ColdProjectsSummaryRow,
3847
4117
  {
3848
4118
  count: row.count,
@@ -3851,6 +4121,16 @@ function SessionTreePanel({
3851
4121
  width
3852
4122
  },
3853
4123
  "cold-summary"
4124
+ ) : /* @__PURE__ */ jsx4(
4125
+ ColdSessionsSummaryRow,
4126
+ {
4127
+ count: row.count,
4128
+ projectName: row.projectName,
4129
+ isSelected: selectedId === `__cold-sessions-${row.projectName}__`,
4130
+ hasFocus,
4131
+ contentWidth
4132
+ },
4133
+ `cold-sessions-${row.projectName}`
3854
4134
  )
3855
4135
  ),
3856
4136
  hiddenBelow > 0 && /* @__PURE__ */ jsxs4(Text4, { children: [
@@ -3904,9 +4184,9 @@ function adjustViewerCursorOnNewActivities(args) {
3904
4184
  // src/ui/App.tsx
3905
4185
  import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
3906
4186
  var VIEWER_HEIGHT_FRACTION = 0.55;
3907
- function subSummarySentinel(parentId) {
4187
+ function makeSentinel(id) {
3908
4188
  return {
3909
- id: `__sub-${parentId}__`,
4189
+ id,
3910
4190
  hideKey: "",
3911
4191
  filePath: "",
3912
4192
  projectPath: "",
@@ -3920,6 +4200,8 @@ function subSummarySentinel(parentId) {
3920
4200
  liveState: null
3921
4201
  };
3922
4202
  }
4203
+ var subSummarySentinel = (parentId) => makeSentinel(`__sub-${parentId}__`);
4204
+ var coldSessionsSummarySentinel = (projectName) => makeSentinel(`__cold-sessions-${projectName}__`);
3923
4205
  function appendSubAgentRows(result, session, expandedIds) {
3924
4206
  const isCold = session.status === "cold";
3925
4207
  const sessionCollapsedKey = `__collapsed-session-${session.id}`;
@@ -3942,6 +4224,66 @@ function appendSubAgentRows(result, session, expandedIds) {
3942
4224
  }
3943
4225
  }
3944
4226
  }
4227
+ function filterTreeByHidden(tree) {
4228
+ const visibleSession = (s) => ({
4229
+ ...s,
4230
+ subAgents: s.subAgents.filter((sa) => !sa.hidden)
4231
+ });
4232
+ const visibleProject = (p) => ({
4233
+ ...p,
4234
+ sessions: p.sessions.filter((s) => !s.hidden).map(visibleSession)
4235
+ });
4236
+ return {
4237
+ ...tree,
4238
+ projects: tree.projects.filter((p) => !p.hidden).map(visibleProject),
4239
+ coldProjects: tree.coldProjects.filter((p) => !p.hidden).map(visibleProject)
4240
+ };
4241
+ }
4242
+ function computeCensus(tree) {
4243
+ let projectsTotal = 0;
4244
+ let projectsActive = 0;
4245
+ let sessionsTotal = 0;
4246
+ let sessionsActive = 0;
4247
+ let subAgentsTotal = 0;
4248
+ let subAgentsActive = 0;
4249
+ let hiddenTotal = 0;
4250
+ let hiddenActive = 0;
4251
+ const isActive = (n) => n.status === "hot" || n.status === "warm";
4252
+ for (const p of [...tree.projects, ...tree.coldProjects]) {
4253
+ projectsTotal++;
4254
+ let projectHasVisibleActive = false;
4255
+ for (const s of p.sessions) {
4256
+ sessionsTotal++;
4257
+ const sessionVisible = !s.hidden && !p.hidden;
4258
+ if (sessionVisible) {
4259
+ if (isActive(s)) {
4260
+ sessionsActive++;
4261
+ projectHasVisibleActive = true;
4262
+ }
4263
+ } else {
4264
+ hiddenTotal++;
4265
+ if (isActive(s)) hiddenActive++;
4266
+ }
4267
+ for (const sa of s.subAgents) {
4268
+ subAgentsTotal++;
4269
+ const saVisible = !sa.hidden && !s.hidden && !p.hidden;
4270
+ if (saVisible) {
4271
+ if (isActive(sa)) subAgentsActive++;
4272
+ } else {
4273
+ hiddenTotal++;
4274
+ if (isActive(sa)) hiddenActive++;
4275
+ }
4276
+ }
4277
+ }
4278
+ if (projectHasVisibleActive) projectsActive++;
4279
+ }
4280
+ return {
4281
+ projects: { total: projectsTotal, active: projectsActive },
4282
+ sessions: { total: sessionsTotal, active: sessionsActive },
4283
+ subAgents: { total: subAgentsTotal, active: subAgentsActive },
4284
+ hidden: { total: hiddenTotal, active: hiddenActive }
4285
+ };
4286
+ }
3945
4287
  function flattenSessions2(tree, expandedIds) {
3946
4288
  const result = [];
3947
4289
  const projectToFlat = (project, isCold) => {
@@ -3962,9 +4304,32 @@ function flattenSessions2(tree, expandedIds) {
3962
4304
  });
3963
4305
  const shouldShowSessions = isCold ? expandedIds.has(`__expanded-${sentinelId}`) : !expandedIds.has(`__collapsed-${sentinelId}`);
3964
4306
  if (shouldShowSessions) {
3965
- for (const session of project.sessions) {
3966
- result.push(session);
3967
- appendSubAgentRows(result, session, expandedIds);
4307
+ if (isCold) {
4308
+ for (const session of project.sessions) {
4309
+ result.push(session);
4310
+ appendSubAgentRows(result, session, expandedIds);
4311
+ }
4312
+ } else {
4313
+ const activeSessions = project.sessions.filter(
4314
+ (s) => s.status !== "cold"
4315
+ );
4316
+ const coldSessions = project.sessions.filter(
4317
+ (s) => s.status === "cold"
4318
+ );
4319
+ for (const session of activeSessions) {
4320
+ result.push(session);
4321
+ appendSubAgentRows(result, session, expandedIds);
4322
+ }
4323
+ if (coldSessions.length > 0) {
4324
+ const coldKey = `__cold-sessions-${project.name}__`;
4325
+ result.push(coldSessionsSummarySentinel(project.name));
4326
+ if (expandedIds.has(coldKey)) {
4327
+ for (const session of coldSessions) {
4328
+ result.push(session);
4329
+ appendSubAgentRows(result, session, expandedIds);
4330
+ }
4331
+ }
4332
+ }
3968
4333
  }
3969
4334
  }
3970
4335
  };
@@ -4127,9 +4492,15 @@ function App({
4127
4492
  const helpTotalLinesRef = useRef(0);
4128
4493
  const [tracking, setTracking] = useState3(false);
4129
4494
  const seenIdsRef = useRef(/* @__PURE__ */ new Set());
4495
+ const [showHidden, setShowHidden] = useState3(false);
4496
+ const displayTree = useMemo(
4497
+ () => showHidden ? sessionTree : filterTreeByHidden(sessionTree),
4498
+ [sessionTree, showHidden]
4499
+ );
4500
+ const census = useMemo(() => computeCensus(sessionTree), [sessionTree]);
4130
4501
  const allFlat = useMemo(
4131
- () => flattenSessions2(sessionTree, expandedIds),
4132
- [sessionTree, expandedIds]
4502
+ () => flattenSessions2(displayTree, expandedIds),
4503
+ [displayTree, expandedIds]
4133
4504
  );
4134
4505
  const allFlatRef = useRef(allFlat);
4135
4506
  useEffect3(() => {
@@ -4210,11 +4581,12 @@ function App({
4210
4581
  const refresh = useCallback(() => {
4211
4582
  const freshConfig = loadGlobalConfig();
4212
4583
  const tree = discoverSessions(freshConfig, discoverOptions);
4213
- const updatedFlat = flattenSessions2(tree, expandedIds);
4584
+ const trackingTree = showHidden ? tree : filterTreeByHidden(tree);
4585
+ const updatedFlat = flattenSessions2(trackingTree, expandedIds);
4214
4586
  let nextSelected = selectedId;
4215
4587
  if (tracking) {
4216
4588
  const { target, ids } = pickTrackingTarget(
4217
- tree,
4589
+ trackingTree,
4218
4590
  selectedId,
4219
4591
  seenIdsRef.current
4220
4592
  );
@@ -4236,7 +4608,7 @@ function App({
4236
4608
  if (!node || !node.filePath) return;
4237
4609
  const newActivities = parseSessionHistory(node.filePath);
4238
4610
  setActivities(newActivities);
4239
- }, [selectedId, expandedIds, tracking, discoverOptions]);
4611
+ }, [selectedId, expandedIds, tracking, discoverOptions, showHidden]);
4240
4612
  const refreshRef = useRef(refresh);
4241
4613
  useEffect3(() => {
4242
4614
  refreshRef.current = refresh;
@@ -4354,9 +4726,9 @@ function App({
4354
4726
  setTracking((on) => {
4355
4727
  const next = !on;
4356
4728
  if (next) {
4357
- seenIdsRef.current = collectAllIds(sessionTree);
4729
+ seenIdsRef.current = collectAllIds(displayTree);
4358
4730
  const { target } = pickTrackingTarget(
4359
- sessionTree,
4731
+ displayTree,
4360
4732
  selectedId,
4361
4733
  seenIdsRef.current
4362
4734
  );
@@ -4370,7 +4742,7 @@ function App({
4370
4742
  if (focus !== "tree") return;
4371
4743
  stopTracking();
4372
4744
  if (!selectedId) return;
4373
- const target = findParentTarget(selectedId, sessionTree, allFlat);
4745
+ const target = findParentTarget(selectedId, displayTree, allFlat);
4374
4746
  if (target && target !== selectedId) setSelectedId(target);
4375
4747
  },
4376
4748
  onSwitchFocus: () => setFocus((f) => f === "tree" ? "viewer" : "tree"),
@@ -4389,10 +4761,7 @@ function App({
4389
4761
  } else {
4390
4762
  setIsLive(false);
4391
4763
  setScrollOffset(
4392
- (o) => Math.min(
4393
- o + 1,
4394
- Math.max(0, mergedActivities.length - viewerRows)
4395
- )
4764
+ (o) => Math.min(o + 1, Math.max(0, mergedActivities.length - viewerRows))
4396
4765
  );
4397
4766
  }
4398
4767
  }
@@ -4544,7 +4913,7 @@ function App({
4544
4913
  stopTracking();
4545
4914
  if (selectedId.startsWith("__proj-") && selectedId.endsWith("__")) {
4546
4915
  const projectName = selectedId.slice(7, -2);
4547
- const isCold = sessionTree.coldProjects.some(
4916
+ const isCold = displayTree.coldProjects.some(
4548
4917
  (p) => p.name === projectName
4549
4918
  );
4550
4919
  const toggleKey = isCold ? `__expanded-${selectedId}` : `__collapsed-${selectedId}`;
@@ -4571,6 +4940,15 @@ function App({
4571
4940
  });
4572
4941
  return;
4573
4942
  }
4943
+ if (selectedId.startsWith("__cold-sessions-") && selectedId.endsWith("__")) {
4944
+ setExpandedIds((prev) => {
4945
+ const next = new Set(prev);
4946
+ if (next.has(selectedId)) next.delete(selectedId);
4947
+ else next.add(selectedId);
4948
+ return next;
4949
+ });
4950
+ return;
4951
+ }
4574
4952
  if (selectedId.startsWith("__sub-") && selectedId.endsWith("__")) {
4575
4953
  const parentId = selectedId.slice(6, -2);
4576
4954
  setExpandedIds((prev) => {
@@ -4580,7 +4958,10 @@ function App({
4580
4958
  setSelectedId(parentId);
4581
4959
  } else {
4582
4960
  next.add(parentId);
4583
- const allSessions2 = sessionTree.projects?.flatMap((p) => p.sessions) ?? [];
4961
+ const allSessions2 = [
4962
+ ...displayTree.projects.flatMap((p) => p.sessions),
4963
+ ...displayTree.coldProjects.flatMap((p) => p.sessions)
4964
+ ];
4584
4965
  const parent = allSessions2.find((s) => s.id === parentId);
4585
4966
  const firstNew = parent?.subAgents.find(
4586
4967
  (sa) => sa.status === "cool" || sa.status === "cold"
@@ -4592,8 +4973,8 @@ function App({
4592
4973
  return;
4593
4974
  }
4594
4975
  const allSessions3 = [
4595
- ...sessionTree.projects.flatMap((p) => p.sessions),
4596
- ...sessionTree.coldProjects.flatMap((p) => p.sessions)
4976
+ ...displayTree.projects.flatMap((p) => p.sessions),
4977
+ ...displayTree.coldProjects.flatMap((p) => p.sessions)
4597
4978
  ];
4598
4979
  const selectedSessionObj = allSessions3.find((s) => s.id === selectedId);
4599
4980
  if (selectedSessionObj && selectedSessionObj.subAgents.length > 0) {
@@ -4627,42 +5008,60 @@ function App({
4627
5008
  onHide: () => {
4628
5009
  if (focus !== "tree" || !selectedId) return;
4629
5010
  stopTracking();
5011
+ const nextAfterHide = () => allFlat[selectedIndex + 1]?.id ?? allFlat[selectedIndex - 1]?.id ?? null;
5012
+ const applyHideToggle = (params) => {
5013
+ if (params.isHidden) {
5014
+ params.unhide();
5015
+ refresh();
5016
+ return;
5017
+ }
5018
+ params.hide();
5019
+ refresh();
5020
+ if (!showHidden) setSelectedId(nextAfterHide());
5021
+ };
4630
5022
  if (selectedId.startsWith("__proj-") && selectedId.endsWith("__")) {
4631
5023
  const projectName = selectedId.slice(7, -2);
4632
- hideProject(projectName);
4633
- refresh();
4634
- const nextId = allFlat[selectedIndex + 1]?.id ?? allFlat[selectedIndex - 1]?.id ?? null;
4635
- setSelectedId(nextId);
5024
+ const proj = sessionTree.projects.find((p) => p.name === projectName) ?? sessionTree.coldProjects.find((p) => p.name === projectName);
5025
+ applyHideToggle({
5026
+ isHidden: !!proj?.hidden,
5027
+ hide: () => hideProject(projectName),
5028
+ unhide: () => unhideProject(projectName)
5029
+ });
4636
5030
  return;
4637
5031
  }
4638
5032
  if (selectedId === "__cold__") {
4639
5033
  const coldSessions = sessionTree.coldProjects?.flatMap((p) => p.sessions) ?? [];
4640
5034
  for (const s of coldSessions) hideSession(s.hideKey);
4641
- const nextId = allFlat[selectedIndex - 1]?.id ?? null;
4642
5035
  refresh();
4643
- setSelectedId(nextId);
5036
+ setSelectedId(nextAfterHide());
4644
5037
  return;
4645
5038
  }
4646
- const allSessions4 = sessionTree.projects?.flatMap((p) => p.sessions) ?? [];
4647
- const selectedSession2 = allSessions4.find((s) => s.id === selectedId);
5039
+ const allSessionsFull = [
5040
+ ...sessionTree.projects.flatMap((p) => p.sessions),
5041
+ ...sessionTree.coldProjects.flatMap((p) => p.sessions)
5042
+ ];
5043
+ const selectedSession2 = allSessionsFull.find((s) => s.id === selectedId);
4648
5044
  if (selectedSession2) {
4649
- hideSession(selectedSession2.hideKey);
4650
- const nextId = allFlat[selectedIndex + 1]?.id ?? allFlat[selectedIndex - 1]?.id ?? null;
4651
- refresh();
4652
- setSelectedId(nextId);
5045
+ applyHideToggle({
5046
+ isHidden: !!selectedSession2.hidden,
5047
+ hide: () => hideSession(selectedSession2.hideKey),
5048
+ unhide: () => unhideSession(selectedSession2.hideKey)
5049
+ });
4653
5050
  return;
4654
5051
  }
4655
- for (const s of allSessions4) {
4656
- const selectedSubAgent = s.subAgents.find((sa) => sa.id === selectedId);
4657
- if (selectedSubAgent) {
4658
- hideSubAgent(selectedSubAgent.hideKey);
4659
- const nextId = allFlat[selectedIndex + 1]?.id ?? allFlat[selectedIndex - 1]?.id ?? null;
4660
- refresh();
4661
- setSelectedId(nextId);
5052
+ for (const s of allSessionsFull) {
5053
+ const sa = s.subAgents.find((x) => x.id === selectedId);
5054
+ if (sa) {
5055
+ applyHideToggle({
5056
+ isHidden: !!sa.hidden,
5057
+ hide: () => hideSubAgent(sa.hideKey),
5058
+ unhide: () => unhideSubAgent(sa.hideKey)
5059
+ });
4662
5060
  return;
4663
5061
  }
4664
5062
  }
4665
5063
  },
5064
+ onToggleShowHidden: () => setShowHidden((on) => !on),
4666
5065
  onRefresh: refresh,
4667
5066
  onQuit: exit,
4668
5067
  onFilter: () => setFilterIndex((i) => (i + 1) % filterPresets.length),
@@ -4674,7 +5073,7 @@ function App({
4674
5073
  let selectedSession = rawSelected;
4675
5074
  if (isProjectSentinel && selectedId) {
4676
5075
  const projectName = selectedId.slice(7, -2);
4677
- const project = sessionTree.projects.find((p) => p.name === projectName) ?? sessionTree.coldProjects.find((p) => p.name === projectName);
5076
+ const project = displayTree.projects.find((p) => p.name === projectName) ?? displayTree.coldProjects.find((p) => p.name === projectName);
4678
5077
  if (project && project.sessions.length > 0) {
4679
5078
  selectedSession = project.sessions[0];
4680
5079
  }
@@ -4732,8 +5131,8 @@ function App({
4732
5131
  /* @__PURE__ */ jsx5(
4733
5132
  SessionTreePanel,
4734
5133
  {
4735
- projects: sessionTree.projects ?? [],
4736
- coldProjects: sessionTree.coldProjects ?? [],
5134
+ projects: displayTree.projects ?? [],
5135
+ coldProjects: displayTree.coldProjects ?? [],
4737
5136
  selectedId,
4738
5137
  hasFocus: focus === "tree",
4739
5138
  width,
@@ -4741,7 +5140,8 @@ function App({
4741
5140
  expandedIds,
4742
5141
  trackingOn: tracking,
4743
5142
  spinner,
4744
- scopeLabel: scopeToProject2 ? basename3(scopeToProject2) : void 0
5143
+ scopeLabel: scopeToProject2 ? basename3(scopeToProject2) : void 0,
5144
+ census: isWatchMode ? census : void 0
4745
5145
  }
4746
5146
  ),
4747
5147
  /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: detailMode && detailActivity ? /* @__PURE__ */ jsx5(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenthud",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "description": "Claude Code TUI dashboard: live monitor for parallel sessions and sub-agents, with LLM-generated daily/weekly digest",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",