agenthud 0.12.4 → 0.13.1

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
@@ -10,9 +10,11 @@ An observability layer for [Claude Code](https://github.com/anthropics/claude-co
10
10
 
11
11
  AgentHUD reads Claude Code's session files from `~/.claude/projects/` and gives you three things:
12
12
 
13
- - **Live monitor** ([`agenthud`](#live-monitor)) — a split-view TUI showing every project, session, sub-agent, and activity as it happens.
14
- - **Structured export** ([`agenthud report`](#report))print activity for any date as Markdown or JSON for piping to scripts, dashboards, or other LLMs.
15
- - **LLM digest** ([`agenthud summary`](#summary))synthesize a day or a date range into an engineering summary via the `claude` CLI, with caching so weekly digests are cheap to regenerate.
13
+ - **Live monitor** (`agenthud`) — a split-view TUI showing every project, session, sub-agent, and activity as it happens
14
+ - **Structured export** (`agenthud report`) — Markdown or JSON for piping to scripts, dashboards, or other LLMs
15
+ - **LLM digest** (`agenthud summary`) — a day or a date range synthesized into an engineering summary via the `claude` CLI
16
+
17
+ → **See [FEATURES.md](./FEATURES.md) for the full surface** — every flag, keybinding, config key, file path, and env var.
16
18
 
17
19
  ## Install
18
20
 
@@ -26,17 +28,28 @@ Run this in a separate terminal while using Claude Code. Press `?` inside the TU
26
28
 
27
29
  > **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.
28
30
 
29
- Pass `--cwd` to scope the view to the Claude project that contains your current directory — useful when you have many projects but only care about the one you're in right now. Exits 1 with a stderr message if no such project is registered.
31
+ ## Quickstart
30
32
 
31
33
  ```bash
32
- agenthud # all Claude projects on the machine
33
- agenthud --cwd # only the project containing the current dir
34
- agenthud --once # print one frame and exit (no alt-screen)
34
+ # Live monitor
35
+ agenthud # all Claude projects
36
+ agenthud --cwd # scope to the project containing $PWD
37
+ agenthud --once # snapshot mode, no alt-screen
38
+
39
+ # Activity report
40
+ agenthud report --date today # today's activity as markdown
41
+ agenthud report --format json # script-readable
42
+ agenthud report --with-git # merge git commits into the timeline
43
+
44
+ # LLM summary
45
+ agenthud summary --date today # daily summary via `claude -p`
46
+ agenthud summary --last 7d # cross-day synthesis of last 7 days
47
+ agenthud summary -oI # open the summary + the summaries index
35
48
  ```
36
49
 
37
- ## Live monitor
50
+ ## What you see
38
51
 
39
- AgentHUD's TUI splits the screen into a project tree and an activity viewer:
52
+ The TUI splits into a project tree (top) and an activity viewer (bottom):
40
53
 
41
54
  ```
42
55
  ┌─ Projects ─────────────────────────────────────────────────┐
@@ -57,257 +70,24 @@ AgentHUD's TUI splits the screen into a project tree and an activity viewer:
57
70
  └────────────────────────────────────────────────────────────┘
58
71
  ```
59
72
 
60
- **Project tree (top pane)**
61
- - Sessions grouped under their project (project name + path at the top).
62
- - Session rows show short ID + first user prompt (the session's "topic"). Long titles truncate with a `…` suffix.
63
- - Right edge of each row shows how long ago it was last touched: `42m`, `17h`, `3d`, `2w`, `1mo`, `1y`. Project rows use the most recent session's mtime.
64
- - Non-interactive sessions (from `claude -p`, SDK, `agenthud summary`) appear in parens and dimmed.
65
- - Sub-agents nest one level deeper under their parent session.
66
- - Cold projects collapse under `... N cold projects` at the bottom (press Enter on the line to expand).
67
- - Press `h` to hide a project, session, or sub-agent (saved to `~/.agenthud/state.yaml`).
68
-
69
- **Activity viewer (bottom pane)**
70
- - Real-time feed for the selected session: file reads, edits, bash, responses, thinking, git commits. Newest at the bottom, like `tail -f`.
71
- - The **newest visible row is rendered "alive"** while in LIVE mode: its icon is replaced with a spinning glyph and the text turns bold. When a new activity arrives, the spinner moves to the new bottom row. Hidden when paused or empty.
72
- - Press `f` to cycle through filter presets (configurable).
73
- - Press `↵` on any row to open a scrollable detail view; on a commit row this shows `git show --stat --patch`.
74
-
75
- ### Session status
76
-
77
- Each session row carries a colored badge derived from when its JSONL file was last touched:
78
-
79
- | Badge | Color | Meaning |
80
- |-------|-------|---------|
81
- | `[hot]` | green | Updated in the last 30 minutes — actively running |
82
- | `[warm]` | yellow | Updated in the last hour |
83
- | `[cool]` | cyan | Updated earlier today |
84
- | `[cold]` | gray | Last updated yesterday or earlier — collapsed under `... N cold projects` at the bottom |
85
-
86
- Sub-agents use the same scheme. Projects inherit the hottest status of their sessions; a project is treated as "cold" only when all its sessions are cold.
87
-
88
- ### Activity types
89
-
90
- | Icon | Type | Description |
91
- |------|------|-------------|
92
- | `○` | Read | File being read |
93
- | `~` | Edit / Write | File being modified |
94
- | `$` | Bash | Shell command |
95
- | `*` | Glob / Grep | File search |
96
- | `@` | WebFetch / WebSearch | Web request |
97
- | `»` | Task | Sub-agent task |
98
- | `<` | Response | Claude's text response |
99
- | `>` | User | Your message |
100
- | `…` | Thinking | Claude's thinking (requires `showThinkingSummaries: true`) |
101
- | `◆` | Commit | Git commit in the project (when `--with-git` or in viewer) |
102
-
103
- ### Keyboard shortcuts
104
-
105
- Full reference is also available inside the app — press `?`.
106
-
107
- #### Project tree focus
108
-
109
- | Key | Action |
110
- |-----|--------|
111
- | `↑` / `k` | Move up |
112
- | `↓` / `j` | Move down |
113
- | `←` | Jump to parent (sub-agent → session, session → project) |
114
- | `↵` | Expand/collapse project, session, or sub-agent summary |
115
- | `h` | Hide selected (project / session / sub-agent) |
116
- | `Tab` | Switch focus to activity viewer |
117
- | `PgUp` / `Ctrl+B` | Page up |
118
- | `PgDn` / `Ctrl+F` | Page down |
119
- | `t` | Track — auto-follow the newest live sub-agent (any nav key turns it off) |
120
- | `r` | Refresh now |
121
- | `?` | Help |
122
- | `q` | Quit |
123
-
124
- When tracking is on, the tree panel's title shows `[LIVE ⠧]` and the status bar replaces `t: track` with `TRK ●`. Any explicit selection-changing key (`↑/k`, `↓/j`, `PgUp/PgDn`, `↵`, `h`, or `t` again) turns tracking off.
125
-
126
- #### Activity viewer focus
127
-
128
- | Key | Action |
129
- |-----|--------|
130
- | `↑` / `k` | Scroll one line up |
131
- | `↓` / `j` | Scroll one line down |
132
- | `PgUp` / `Ctrl+B` | Page up |
133
- | `PgDn` / `Ctrl+F` | Page down |
134
- | `Ctrl+U` / `Ctrl+D` | Half page up / down |
135
- | `g` | Jump to top (oldest) |
136
- | `G` | Jump to live (newest, bottom) |
137
- | `↵` | Open detail view |
138
- | `f` | Cycle filter preset |
139
- | `r` | Refresh now |
140
- | `Tab` | Switch focus to project tree |
141
- | `?` | Help |
142
- | `q` | Quit |
143
-
144
- #### Detail view
145
-
146
- | Key | Action |
147
- |-----|--------|
148
- | `↑` / `k` / `↓` / `j` | Scroll one line |
149
- | `Ctrl+U` / `Ctrl+D` | Half page up / down |
150
- | `↵` / `Esc` / `q` | Close |
151
-
152
- Detail view colors the content based on activity type:
153
-
154
- - **Git commit detail** (`git show --stat --patch`): added lines green (`+`), removed lines red (`-`), hunk headers cyan (`@@ ... @@`), `commit/Author/Date/diff` metadata dimmed.
155
- - **Response / thinking / prompt**: text inside triple-backtick code fences renders in cyan so the boundary between prose and code is obvious. No language-specific syntax highlighting — just code-vs-prose separation.
156
-
157
- ### Behavior
158
-
159
- - **Alternate screen buffer.** Watch mode uses the alt-screen (like `vim`, `htop`, `btop`), so quitting (`q`) restores the pre-launch shell completely. No TUI residue, no "is it still running?" confusion.
160
- - **Minimum terminal size.** 80 cols × 20 rows. Smaller terminals show a one-line hint and redraw automatically when you resize.
161
- - **Help overlay scrolls.** Press `?` for an in-app reference. The overlay scrolls (`j/k`, `PgUp/PgDn`, `Ctrl+B/F`, `Space`, `g/G`) so the full content is reachable on shorter terminals.
162
-
163
- ## Report
164
-
165
- Print activity for a date in Markdown or JSON — suitable for piping to scripts or LLMs:
166
-
167
- ```bash
168
- agenthud report # today (Markdown)
169
- agenthud report --date 2026-05-14 # specific date
170
- agenthud report --date today --include all # all activity types
171
- agenthud report --format json # JSON output
172
- agenthud report --detail-limit 0 # no truncation (full text)
173
- agenthud report --with-git # merge git commits into timeline
174
- ```
175
-
176
- Output:
177
-
178
- ```
179
- # AgentHUD Report: 2026-05-14
180
-
181
- ## myproject (10:23 – 14:45)
182
-
183
- [10:23] $ npm test
184
- [10:35] ~ src/ui/App.tsx
185
- [11:15] < Added spinner hook to make the UI feel alive.
186
- [14:30] ◆ abc1234 feat: fix auth callback
187
- ```
188
-
189
- **Options:**
190
-
191
- | Flag | Default | Description |
192
- |------|---------|-------------|
193
- | `--date` | today | `YYYY-MM-DD`, `today`, `yesterday`, or `-Nd` (N days ago, local date) |
194
- | `--include` | `user,response,bash,edit,thinking` | Comma-separated types or `all` |
195
- | `--format` | `markdown` | `markdown` or `json` |
196
- | `--detail-limit` | `120` | Max chars per detail field; `0` = unlimited |
197
- | `--with-git` | off | Merge git commits from each session's project into the timeline |
198
-
199
- `--include` types: `response`, `bash`, `edit`, `thinking`, `read`, `glob`, `user`
200
-
201
- ## Summary
202
-
203
- Generate an LLM-based summary of one day or a date range using the `claude` CLI:
204
-
205
- ```bash
206
- # Single day
207
- agenthud summary # today (always regenerated)
208
- agenthud summary --date yesterday # natural-language date
209
- agenthud summary --date 2026-05-14 # past date (cached on second run)
210
- agenthud summary --date 2026-05-14 --force # ignore cache
211
- agenthud summary --prompt "Only commits" # override prompt
212
-
213
- # Date range — daily summaries are re-summarized into a meta-summary
214
- agenthud summary --last 7d # last 7 days, ending today
215
- agenthud summary --from 2026-05-10 --to 2026-05-16 # explicit range
216
- agenthud summary --last 7d -y # skip per-day confirmations
217
-
218
- # Tune what gets sent to the LLM — same option shape as `report`
219
- agenthud summary --include all # include every activity type
220
- agenthud summary --detail-limit 200 # truncate long tool outputs
221
- agenthud summary --with-git # include git commits (default in config)
222
- # Defaults are CLI flag → `summary:` config → `report:` config → built-in.
223
-
224
- # Cheaper model — summarization doesn't need Opus-tier reasoning
225
- agenthud summary --date today --model sonnet # ~40% cheaper than Opus
226
- agenthud summary --last 7d --model haiku # ~80% cheaper, 200K context
227
-
228
- # Open the resulting summary in your OS default app (browser, VS Code, etc.)
229
- agenthud summary --last 7d --open # -o is the short form
230
-
231
- # Open the index (~/.agenthud/summaries/index.md) instead — a hub
232
- # listing every daily and range summary, grouped by year/month.
233
- agenthud summary --open-index # -I is the short form
234
- agenthud summary -oI # open today + the index
235
- ```
236
-
237
- The index is auto-regenerated whenever a summary writes, and each summary file gets a one-line backlink footer at the top (`← all summaries · ← prev · next →`) so any markdown viewer is enough to navigate the whole corpus without leaving the file.
238
-
239
- **Daily summaries** are saved to `~/.agenthud/summaries/YYYY-MM-DD.md`. Past dates are cached and returned instantly; today is always regenerated (activity still growing).
73
+ Sessions get colored badges — `[hot]` / `[warm]` / `[cool]` / `[cold]` — based on how recently their JSONL file was touched. Cold projects collapse under a `... N cold projects` sentinel. Press `↵` on any activity to open a scrollable detail view.
240
74
 
241
- **Range summaries** generate any missing daily summaries first, then feed those into a second `claude` call that produces a cross-day synthesis (themes, multi-day workstreams, recurring patterns). Output is cached to `~/.agenthud/summaries/range-FROM_TO.md`. Cached dailies cost nothing to reuse, so weekly summaries are cheap after the first run.
242
-
243
- Each missing daily prompts for confirmation just before generation, so you see concrete context (session/activity/commit counts and report size) before deciding. Pass `-y` / `--yes` to skip all prompts. Press Enter to accept the default (`[Y/n]`).
244
-
245
- **Prompt customization:** The daily template lives at `~/.agenthud/summary-prompt.md` and the range template at `~/.agenthud/summary-range-prompt.md`. Both are auto-created from built-in templates on first run. Edit them freely.
246
-
247
- **`--date` formats:** `YYYY-MM-DD`, `today`, `yesterday`, or `-Nd` (N days ago).
248
-
249
- **Model selection:** Summarization is a low-reasoning task (structured input → structured markdown) — Sonnet or Haiku usually beats Opus on cost-per-summary with no quality loss. Pass `--model sonnet`, `--model haiku`, or a full model id (`--model claude-sonnet-4-6`). With no flag, `claude` uses its default model.
250
-
251
- **Cost warning:** If the day's activity log is large (~300K tokens or more), AgentHUD prints a warning before sending and asks for one more confirmation in interactive mode. `-y` skips the prompt but still prints the warning.
252
-
253
- **Requires:** [`@anthropic-ai/claude-code`](https://www.npmjs.com/package/@anthropic-ai/claude-code) installed and authenticated.
75
+ Full keybinding and badge reference: [FEATURES.md](./FEATURES.md#keybindings).
254
76
 
255
77
  ## Configuration
256
78
 
257
- `~/.agenthud/config.yaml` is auto-created on first run with sensible defaults. Edit it freely:
258
-
259
- ```yaml
260
- # How often to poll for activity updates (Linux fallback when fs.watch isn't used)
261
- refreshInterval: 2s
262
-
263
- # Activity filter presets (cycle with 'f' key in viewer)
264
- # Each list is one preset. Use "all" (or "*") to show everything.
265
- # Types: response, user, bash, edit, thinking, read, glob, commit
266
- filterPresets:
267
- - ["all"]
268
- - ["response", "user"]
269
- - ["commit"]
270
-
271
- # Defaults for `agenthud report` (CLI flags still win per-invocation).
272
- report:
273
- include: [user, response, bash, edit, thinking]
274
- detailLimit: 120
275
- withGit: false
276
- format: markdown
277
-
278
- # Defaults for `agenthud summary`. Any field omitted here is inherited
279
- # from `report` above. `model` is summary-specific.
280
- summary:
281
- withGit: true
282
- detailLimit: 0
283
- # model: sonnet
284
- ```
285
-
286
- `report` / `summary` resolve each option as **CLI flag → `summary:` key → `report:` key → built-in default**. The effective set is printed to stderr at the start of each run (e.g. `agenthud: report → include=[user,response,bash,edit,thinking] detail-limit=120 with-git=off format=markdown`), so the actual values are always visible.
287
-
288
- App-managed state (hidden items) lives separately in `~/.agenthud/state.yaml` so your config file stays clean. You shouldn't need to edit it manually — use `h` in the TUI to hide things.
289
-
290
- ## Files
291
-
292
- | Path | Purpose |
293
- |------|---------|
294
- | `~/.agenthud/config.yaml` | User settings (edit freely) |
295
- | `~/.agenthud/state.yaml` | Hidden projects/sessions/sub-agents (app-managed) |
296
- | `~/.agenthud/summary-prompt.md` | LLM prompt template for daily `summary` |
297
- | `~/.agenthud/summary-range-prompt.md` | LLM prompt template for range `summary --last/--from/--to` |
298
- | `~/.agenthud/summaries/YYYY-MM-DD.md` | Cached daily summaries |
299
- | `~/.agenthud/summaries/range-FROM_TO.md` | Cached range summaries |
300
- | `~/.agenthud/summaries/index.md` | Auto-regenerated hub linking every summary; each summary also gets a backlink footer at the top |
79
+ `~/.agenthud/config.yaml` is auto-created on first run with sensible defaults. CLI flags override config values per-invocation. Resolution order is `CLI flag → summary.<key> → report.<key> → built-in default`, and the effective values print to stderr at the start of every `report` / `summary` run.
301
80
 
302
- ## Environment Variables
81
+ App-managed UI state (hidden projects/sessions/sub-agents toggled by `h`) lives separately in `~/.agenthud/state.yaml`.
303
82
 
304
- | Variable | Default | Description |
305
- |----------|---------|-------------|
306
- | `CLAUDE_PROJECTS_DIR` | `~/.claude/projects` | Path to Claude Code projects directory. Useful for backups or mounted volumes. |
83
+ Full schema, file paths, and env vars: [FEATURES.md → Config](./FEATURES.md#config).
307
84
 
308
- ## Feedback
85
+ ## More
309
86
 
310
- Issues and PRs welcome at [GitHub](https://github.com/neochoon/agenthud).
87
+ - **Reference:** [FEATURES.md](./FEATURES.md) — every flag, keybinding, config key, file path, env var
88
+ - **Release history:** [CHANGELOG.md](./CHANGELOG.md)
89
+ - **Deferred items:** [BACKLOG.md](./BACKLOG.md)
90
+ - **Issues / PRs:** [GitHub](https://github.com/neochoon/agenthud)
311
91
 
312
92
  ## License
313
93
 
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-PNDDQ5WC.js");
18
+ import("./main-GMNV6O4E.js");
@@ -23,7 +23,8 @@ var DEFAULT_INCLUDE_TYPES = [
23
23
  "response",
24
24
  "bash",
25
25
  "edit",
26
- "thinking"
26
+ "thinking",
27
+ "task"
27
28
  ];
28
29
  var ALLOWED_INCLUDE_TYPES = /* @__PURE__ */ new Set([
29
30
  "user",
@@ -33,7 +34,8 @@ var ALLOWED_INCLUDE_TYPES = /* @__PURE__ */ new Set([
33
34
  "thinking",
34
35
  "read",
35
36
  "glob",
36
- "commit"
37
+ "commit",
38
+ "task"
37
39
  ]);
38
40
  var DEFAULT_GLOBAL_CONFIG = {
39
41
  refreshIntervalMs: 2e3,
@@ -944,11 +946,34 @@ function numberLines(content, start) {
944
946
  const width = String(start + lines.length - 1).length;
945
947
  return lines.map((line, i) => `${String(start + i).padStart(width)}: ${line}`).join("\n");
946
948
  }
949
+ function formatBashBody(stdout, stderr, interrupted) {
950
+ const out = stdout?.replace(/\n+$/, "");
951
+ const err = stderr?.replace(/\n+$/, "");
952
+ const sections = [];
953
+ if (out) sections.push(out);
954
+ if (err) sections.push(out ? `--- stderr ---
955
+ ${err}` : err);
956
+ if (interrupted) sections.push("[interrupted]");
957
+ if (sections.length === 0) return null;
958
+ return sections.join("\n\n");
959
+ }
947
960
  function buildToolDetailBody(name, input, result) {
948
961
  if (name === "Write") {
949
962
  const content = result?.content ?? input?.content;
950
963
  if (content) return { text: content, kind: "code" };
951
964
  }
965
+ if (name === "Task") {
966
+ const content = result?.content;
967
+ if (content) return { text: content, kind: "code" };
968
+ }
969
+ if (name === "Bash") {
970
+ const text = formatBashBody(
971
+ result?.stdout,
972
+ result?.stderr,
973
+ result?.interrupted
974
+ );
975
+ if (text) return { text, kind: "code" };
976
+ }
952
977
  if (name === "Read") {
953
978
  const content = result?.file?.content;
954
979
  if (content) {
@@ -1123,6 +1148,7 @@ function activityMatchesInclude(activity, include) {
1123
1148
  return true;
1124
1149
  if (include.includes("glob") && (label === "glob" || label === "grep"))
1125
1150
  return true;
1151
+ if (include.includes("task") && label === "task") return true;
1126
1152
  return false;
1127
1153
  }
1128
1154
  function isSameLocalDay(a, b) {
@@ -1137,6 +1163,14 @@ function formatActivity(activity, limit) {
1137
1163
  const suffix = detail ? `: ${detail}` : "";
1138
1164
  return `[${time}] ${activity.icon} ${activity.label}${suffix}`;
1139
1165
  }
1166
+ function formatTaskBody(activity, limit) {
1167
+ if (activity.label !== "Task") return null;
1168
+ if (!activity.detailBody) return null;
1169
+ const body = truncateRaw(activity.detailBody, limit);
1170
+ return `<task-result>
1171
+ ${body}
1172
+ </task-result>`;
1173
+ }
1140
1174
  function formatDateString2(date) {
1141
1175
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
1142
1176
  }
@@ -1239,6 +1273,8 @@ function generateReport(sessions, options2) {
1239
1273
  lines.push("");
1240
1274
  for (const activity of activities) {
1241
1275
  lines.push(formatActivity(activity, detailLimit));
1276
+ const taskBody = formatTaskBody(activity, detailLimit);
1277
+ if (taskBody) lines.push(taskBody);
1242
1278
  }
1243
1279
  lines.push("");
1244
1280
  }
@@ -2131,6 +2167,16 @@ function resolvePrompt(kind, override) {
2131
2167
  return "Summarize the input below.";
2132
2168
  }
2133
2169
  }
2170
+ function buildRangeMetaInput(dailyMarkdowns) {
2171
+ if (dailyMarkdowns.length === 0) return "";
2172
+ return dailyMarkdowns.map(
2173
+ ({ date, markdown }) => `<day date="${dateKey(date)}">
2174
+
2175
+ ${markdown}
2176
+
2177
+ </day>`
2178
+ ).join("\n\n");
2179
+ }
2134
2180
  function shouldUseRangeCache(force, dates, today, cacheExists) {
2135
2181
  if (force) return false;
2136
2182
  if (!cacheExists) return false;
@@ -2217,7 +2263,12 @@ function spawnClaude(opts) {
2217
2263
  resolve2({ code: 1, text: "", usage: null });
2218
2264
  }
2219
2265
  });
2266
+ let firstChunkFired = false;
2220
2267
  const writeText = (text) => {
2268
+ if (!firstChunkFired) {
2269
+ firstChunkFired = true;
2270
+ opts.onFirstChunk?.();
2271
+ }
2221
2272
  assembledText += text;
2222
2273
  if (opts.streamToStdout) process.stdout.write(text);
2223
2274
  cacheStream?.write(text);
@@ -2410,22 +2461,17 @@ async function generateDailySummary(opts) {
2410
2461
  }
2411
2462
  }
2412
2463
  }
2413
- const showTicker = opts.announce && !opts.streamToStdout;
2414
- if (opts.announce && !showTicker) {
2415
- process.stderr.write(
2416
- `sending to claude (this may take a minute)...
2417
-
2418
- `
2419
- );
2420
- }
2421
- const stopTicker = showTicker ? startStderrTicker("sending to claude") : null;
2464
+ const stopTicker = opts.announce ? startStderrTicker("sending to claude") : null;
2422
2465
  const prompt = resolvePrompt("daily", opts.promptOverride);
2423
2466
  const result = await spawnClaude({
2424
2467
  prompt,
2425
2468
  stdin: reportMarkdown,
2426
2469
  cachePath: cached2,
2427
2470
  streamToStdout: opts.streamToStdout,
2428
- model: opts.model
2471
+ model: opts.model,
2472
+ onFirstChunk: () => {
2473
+ if (stopTicker) stopTicker();
2474
+ }
2429
2475
  });
2430
2476
  if (stopTicker) stopTicker();
2431
2477
  if (opts.announce && result.code === 0) {
@@ -2583,33 +2629,24 @@ async function runRangeSummary(options2) {
2583
2629
  }
2584
2630
  return 0;
2585
2631
  }
2586
- const metaInput = dailyMarkdowns.map(({ date, markdown }) => `# ${dateKey(date)}
2587
-
2588
- ${markdown}`).join("\n\n---\n\n");
2632
+ const metaInput = buildRangeMetaInput(dailyMarkdowns);
2589
2633
  process.stderr.write(
2590
2634
  `
2591
2635
  combining ${dailyMarkdowns.length} daily summaries into range summary...
2592
2636
  `
2593
2637
  );
2594
2638
  const metaStreams = !options2.open;
2595
- if (!metaStreams) {
2596
- } else {
2597
- process.stderr.write(
2598
- `sending to claude (this may take a minute)...
2599
-
2600
- `
2601
- );
2602
- }
2603
- const stopMetaTicker = metaStreams ? null : startStderrTicker("sending to claude");
2639
+ const stopMetaTicker = startStderrTicker("sending to claude");
2604
2640
  const metaPrompt = resolvePrompt("range");
2605
2641
  const metaResult = await spawnClaude({
2606
2642
  prompt: metaPrompt,
2607
2643
  stdin: metaInput,
2608
2644
  cachePath: rangeCache,
2609
2645
  streamToStdout: metaStreams,
2610
- model: options2.model
2646
+ model: options2.model,
2647
+ onFirstChunk: () => stopMetaTicker()
2611
2648
  });
2612
- if (stopMetaTicker) stopMetaTicker();
2649
+ stopMetaTicker();
2613
2650
  if (metaResult.code !== 0) {
2614
2651
  return metaResult.code;
2615
2652
  }
@@ -3829,6 +3866,41 @@ function SessionTreePanel({
3829
3866
  ] });
3830
3867
  }
3831
3868
 
3869
+ // src/ui/viewerCursor.ts
3870
+ function adjustViewerCursorOnNewActivities(args) {
3871
+ const {
3872
+ prevCursorLine,
3873
+ prevActivityCount,
3874
+ newActivityCount,
3875
+ isLive,
3876
+ viewerRows
3877
+ } = args;
3878
+ const noOp = {
3879
+ cursorLine: prevCursorLine,
3880
+ autoPause: false,
3881
+ scrollDelta: 0
3882
+ };
3883
+ if (!isLive) return noOp;
3884
+ if (prevCursorLine === 0)
3885
+ return { cursorLine: 0, autoPause: false, scrollDelta: 0 };
3886
+ if (newActivityCount <= prevActivityCount) return noOp;
3887
+ const delta = newActivityCount - prevActivityCount;
3888
+ const maxCursor = Math.max(0, viewerRows - 1);
3889
+ const upwardRoom = maxCursor - prevCursorLine;
3890
+ if (delta <= upwardRoom) {
3891
+ return {
3892
+ cursorLine: prevCursorLine + delta,
3893
+ autoPause: false,
3894
+ scrollDelta: 0
3895
+ };
3896
+ }
3897
+ return {
3898
+ cursorLine: maxCursor,
3899
+ autoPause: true,
3900
+ scrollDelta: delta - upwardRoom
3901
+ };
3902
+ }
3903
+
3832
3904
  // src/ui/App.tsx
3833
3905
  import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
3834
3906
  var VIEWER_HEIGHT_FRACTION = 0.55;
@@ -4063,12 +4135,12 @@ function App({
4063
4135
  useEffect3(() => {
4064
4136
  allFlatRef.current = allFlat;
4065
4137
  }, [allFlat]);
4066
- const activitiesLengthRef = useRef(0);
4067
4138
  const activitiesRef = useRef(activities);
4068
4139
  useEffect3(() => {
4069
- activitiesLengthRef.current = activities.length;
4070
4140
  activitiesRef.current = activities;
4071
4141
  }, [activities]);
4142
+ const prevMergedCountRef = useRef(0);
4143
+ const prevPausedCountRef = useRef(0);
4072
4144
  const lastLoadedFileRef = useRef(null);
4073
4145
  useEffect3(() => {
4074
4146
  let node = allFlatRef.current.find((s) => s.id === selectedId);
@@ -4163,13 +4235,8 @@ function App({
4163
4235
  setSessionTree(tree);
4164
4236
  if (!node || !node.filePath) return;
4165
4237
  const newActivities = parseSessionHistory(node.filePath);
4166
- const delta = newActivities.length - activitiesLengthRef.current;
4167
4238
  setActivities(newActivities);
4168
- if (!isLive && delta > 0) {
4169
- setScrollOffset((o) => o + delta);
4170
- setNewCount((n) => n + delta);
4171
- }
4172
- }, [selectedId, isLive, expandedIds, tracking, discoverOptions]);
4239
+ }, [selectedId, expandedIds, tracking, discoverOptions]);
4173
4240
  const refreshRef = useRef(refresh);
4174
4241
  useEffect3(() => {
4175
4242
  refreshRef.current = refresh;
@@ -4234,6 +4301,34 @@ function App({
4234
4301
  const naturalTreeRows = allFlat.length;
4235
4302
  const treeRows = Math.max(1, Math.min(naturalTreeRows, maxTreeRows));
4236
4303
  const viewerRows = Math.max(5, height - 7 - treeRows);
4304
+ useEffect3(() => {
4305
+ const prev = prevMergedCountRef.current;
4306
+ prevMergedCountRef.current = mergedActivities.length;
4307
+ const result = adjustViewerCursorOnNewActivities({
4308
+ prevCursorLine: viewerCursorLine,
4309
+ prevActivityCount: prev,
4310
+ newActivityCount: mergedActivities.length,
4311
+ isLive,
4312
+ viewerRows
4313
+ });
4314
+ if (result.cursorLine !== viewerCursorLine) {
4315
+ setViewerCursorLine(result.cursorLine);
4316
+ }
4317
+ if (result.autoPause) {
4318
+ setIsLive(false);
4319
+ setScrollOffset((o) => o + result.scrollDelta);
4320
+ setNewCount((n) => n + result.scrollDelta);
4321
+ }
4322
+ }, [mergedActivities.length, isLive, viewerRows, viewerCursorLine]);
4323
+ useEffect3(() => {
4324
+ const prev = prevPausedCountRef.current;
4325
+ prevPausedCountRef.current = mergedActivities.length;
4326
+ if (!isLive && mergedActivities.length > prev) {
4327
+ const delta = mergedActivities.length - prev;
4328
+ setScrollOffset((o) => o + delta);
4329
+ setNewCount((n) => n + delta);
4330
+ }
4331
+ }, [mergedActivities.length, isLive]);
4237
4332
  const spinner = useSpinner(isWatchMode, 150);
4238
4333
  const tickActive = isWatchMode && isLive && !helpMode && !detailMode && activities.length > 0;
4239
4334
  const liveTick = useTick(tickActive, 150);
@@ -1,8 +1,17 @@
1
- The following is an activity log from a single day of Claude Code sessions.
2
- Each entry: `[HH:MM] <icon> <label>: <detail>`. Icons indicate activity type (◆ = git commit).
1
+ The following is an activity log from a single day of Claude Code sessions,
2
+ possibly spanning multiple projects.
3
3
 
4
- Write a concise but high-signal engineering summary in English.
5
- Aim for roughly 300-500 words total — favor synthesis over completeness.
4
+ Each entry: `[HH:MM] <icon> <label>: <detail>`. Icons:
5
+ Read ~ Edit/Write $ Bash
6
+ * Glob/Grep @ WebFetch » Sub-agent task
7
+ < Response > User message … Thinking
8
+ ◆ Git commit
9
+
10
+ Write a concise, high-signal engineering retro in first-person voice
11
+ ("I implemented X", "I discovered Y") — treat the developer as the author.
12
+
13
+ Target ~300-500 words total; individual sections may be 50-100 words.
14
+ Favor synthesis over completeness.
6
15
 
7
16
  Focus on:
8
17
  - meaningful accomplishments
@@ -10,28 +19,38 @@ Focus on:
10
19
  - architectural decisions
11
20
  - implementation progress
12
21
  - important tradeoffs or realizations
13
- - unfinished work and next steps
22
+ - unfinished work and explicit next steps
23
+
24
+ Do NOT:
25
+ - mechanically summarize every edit, response, or commit
26
+ - list every modified file (cite paths only for important changes)
27
+ - invent follow-ups not present in the log (no "consider refactoring X")
28
+ - include placeholders like "None" or "N/A" — omit empty sections entirely
14
29
 
15
- Do NOT mechanically summarize every edit, response, or commit.
16
- Do NOT list every modified file.
17
- Omit any section that has no substantive content — do not emit placeholders like "None" or "N/A".
18
- Prefer high-level synthesis grouped by theme or workstream.
30
+ If activity spans multiple projects, group findings by project where clarity
31
+ improves. If they share a theme, synthesize.
19
32
 
20
- Use the following format:
33
+ If the day was light (few entries, no meaningful work), output a single short
34
+ paragraph instead of the full template. Do not pad.
35
+
36
+ Format (omit any section without substance):
21
37
 
22
38
  ## Context
23
- What was the primary goal or investigation today?
39
+ The primary goal(s) of the day. If multi-project, briefly note each.
24
40
 
25
41
  ## Key Accomplishments
26
42
  Meaningful progress, completed implementations, resolved issues.
27
43
 
28
44
  ## Technical Insights
29
- Important debugging findings, architectural decisions, tradeoffs, or discoveries.
45
+ Important debugging findings, architectural decisions, tradeoffs, discoveries.
30
46
 
31
47
  ## Major Code Changes
32
- Significant codebase or subsystem changes, grouped by theme. Reference relevant commits inline where useful (e.g. `abc1234`). Do not enumerate every commit.
48
+ Significant codebase changes grouped by theme. Reference commits inline
49
+ (e.g., `abc1234`).
33
50
 
34
51
  ## Open Questions / Next Steps
35
- Only include items explicitly stated in the log or strongly implied by an unfinished workflow. Do not invent generic follow-ups ("consider refactoring X").
52
+ Only items explicitly stated in the log or directly tied to unfinished workflows.
53
+
54
+ After reading the log below, write the summary.
36
55
 
37
56
  Activity log:
@@ -1,5 +1,18 @@
1
- The following are daily engineering summaries from a date range, separated by `---` lines.
2
- Each daily summary uses the structure: Context / Key Accomplishments / Technical Insights / Major Code Changes / Open Questions.
1
+ The input contains daily engineering summaries from a date range.
2
+
3
+ Each day is wrapped in an XML tag with its date as an attribute:
4
+
5
+ <day date="YYYY-MM-DD">
6
+ (that day's summary — markdown using sections like
7
+ Context / Key Accomplishments / Technical Insights /
8
+ Major Code Changes / Open Questions)
9
+ </day>
10
+
11
+ Multiple `<day>` blocks follow in chronological order. Use the
12
+ `date` attribute as the authoritative date for that block — do not
13
+ infer dates from headings or text inside the block. Any `# YYYY-MM-DD`
14
+ heading or `---` line you see inside a `<day>` block is part of the
15
+ day's content, not a structural divider.
3
16
 
4
17
  Write a range-level synthesis in English.
5
18
  Aim for roughly 400-700 words total — the value of a range summary is cross-day pattern extraction, not re-summarization.
@@ -15,6 +28,8 @@ The point of a range summary is to surface what only becomes visible by looking
15
28
  - outcomes that matter at the range level, not at any single day
16
29
  - work that started in one day and continued, completed, or was abandoned later
17
30
 
31
+ When you reference a specific day, use its `date` attribute value verbatim (`2026-06-07`), not a relative term like "Monday" or "yesterday".
32
+
18
33
  Use the following format:
19
34
 
20
35
  ## Range Overview
@@ -32,4 +47,4 @@ Themes that surfaced on more than one day: repeated debugging classes, recurring
32
47
  ## Carried-Over / Unfinished Work
33
48
  Work still in progress at the end of the range, or open questions that persisted across multiple days.
34
49
 
35
- Daily summaries:
50
+ Daily summaries follow:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenthud",
3
- "version": "0.12.4",
3
+ "version": "0.13.1",
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",