agenthud 0.9.3 → 0.9.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/README.md CHANGED
@@ -4,10 +4,16 @@
4
4
  [![CI](https://github.com/neochoon/agenthud/actions/workflows/ci.yml/badge.svg)](https://github.com/neochoon/agenthud/actions/workflows/ci.yml)
5
5
  [![codecov](https://codecov.io/gh/neochoon/agenthud/branch/main/graph/badge.svg)](https://codecov.io/gh/neochoon/agenthud)
6
6
 
7
- When working with AI coding agents like Claude Code, you lose visibility into what's happening across sessions. **AgentHUD** gives you a live session browser in a separate terminal see every session, sub-agent, and activity as it happens.
7
+ An observability layer for [Claude Code](https://github.com/anthropics/claude-code). **See** your live sessions, **export** structured activity logs, and **summarize** a day or a week into an LLM digest all from one CLI.
8
8
 
9
9
  ![demo](./output960.gif)
10
10
 
11
+ AgentHUD reads Claude Code's session files from `~/.claude/projects/` and gives you three things:
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.
16
+
11
17
  ## Install
12
18
 
13
19
  Requires Node.js 20+.
@@ -18,9 +24,9 @@ npx agenthud
18
24
 
19
25
  Run this in a separate terminal while using Claude Code. Press `?` inside the TUI any time for in-app help.
20
26
 
21
- ## What it shows
27
+ ## Live monitor
22
28
 
23
- AgentHUD reads Claude Code's session files from `~/.claude/projects/` and displays them in a split view:
29
+ AgentHUD's TUI splits the screen into a project tree and an activity viewer:
24
30
 
25
31
  ```
26
32
  ┌─ Projects ───────────────────────────────────────────────┐
@@ -57,7 +63,7 @@ AgentHUD reads Claude Code's session files from `~/.claude/projects/` and displa
57
63
  - Press `f` to cycle through filter presets (configurable).
58
64
  - Press `↵` on any row to open a scrollable detail view; on a commit row this shows `git show --stat --patch`.
59
65
 
60
- ## Session status
66
+ ### Session status
61
67
 
62
68
  Each session row carries a colored badge derived from when its JSONL file was last touched:
63
69
 
@@ -70,7 +76,7 @@ Each session row carries a colored badge derived from when its JSONL file was la
70
76
 
71
77
  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.
72
78
 
73
- ## Activity types
79
+ ### Activity types
74
80
 
75
81
  | Icon | Type | Description |
76
82
  |------|------|-------------|
@@ -85,11 +91,11 @@ Sub-agents use the same scheme. Projects inherit the hottest status of their ses
85
91
  | `…` | Thinking | Claude's thinking (requires `showThinkingSummaries: true`) |
86
92
  | `◆` | Commit | Git commit in the project (when `--with-git` or in viewer) |
87
93
 
88
- ## Keyboard shortcuts
94
+ ### Keyboard shortcuts
89
95
 
90
96
  Full reference is also available inside the app — press `?`.
91
97
 
92
- ### Project tree focus
98
+ #### Project tree focus
93
99
 
94
100
  | Key | Action |
95
101
  |-----|--------|
@@ -104,7 +110,7 @@ Full reference is also available inside the app — press `?`.
104
110
  | `?` | Help |
105
111
  | `q` | Quit |
106
112
 
107
- ### Activity viewer focus
113
+ #### Activity viewer focus
108
114
 
109
115
  | Key | Action |
110
116
  |-----|--------|
@@ -122,7 +128,7 @@ Full reference is also available inside the app — press `?`.
122
128
  | `?` | Help |
123
129
  | `q` | Quit |
124
130
 
125
- ### Detail view
131
+ #### Detail view
126
132
 
127
133
  | Key | Action |
128
134
  |-----|--------|
@@ -134,7 +140,7 @@ Detail view colors the content based on activity type:
134
140
  - **Git commit detail** (`git show --stat --patch`): added lines green (`+`), removed lines red (`-`), hunk headers cyan (`@@ ... @@`), `commit/Author/Date/diff` metadata dimmed.
135
141
  - **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.
136
142
 
137
- ## Behavior
143
+ ### Behavior
138
144
 
139
145
  - **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.
140
146
  - **Minimum terminal size.** 80 cols × 20 rows. Smaller terminals show a one-line hint and redraw automatically when you resize.
@@ -194,6 +200,10 @@ agenthud summary --prompt "Only commits" # override prompt
194
200
  agenthud summary --last 7d # last 7 days, ending today
195
201
  agenthud summary --from 2026-05-10 --to 2026-05-16 # explicit range
196
202
  agenthud summary --last 7d -y # skip per-day confirmations
203
+
204
+ # Cheaper model — summarization doesn't need Opus-tier reasoning
205
+ agenthud summary --date today --model sonnet # ~40% cheaper than Opus
206
+ agenthud summary --last 7d --model haiku # ~80% cheaper, 200K context
197
207
  ```
198
208
 
199
209
  **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).
@@ -206,6 +216,10 @@ Each missing daily prompts for confirmation just before generation, so you see c
206
216
 
207
217
  **`--date` formats:** `YYYY-MM-DD`, `today`, `yesterday`, or `-Nd` (N days ago).
208
218
 
219
+ **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.
220
+
221
+ **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.
222
+
209
223
  **Requires:** [`@anthropic-ai/claude-code`](https://www.npmjs.com/package/@anthropic-ai/claude-code) installed and authenticated.
210
224
 
211
225
  ## Configuration
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-S27FZ2BJ.js");
18
+ import("./main-26QL33AJ.js");
@@ -53,6 +53,7 @@ var KNOWN_SUMMARY_FLAGS = /* @__PURE__ */ new Set([
53
53
  "--to",
54
54
  "--prompt",
55
55
  "--force",
56
+ "--model",
56
57
  "-y",
57
58
  "--yes"
58
59
  ]);
@@ -89,6 +90,7 @@ Commands:
89
90
  --to YYYY-MM-DD Date range: end date (use with --from)
90
91
  --prompt TEXT Override prompt for this run (daily only)
91
92
  --force Regenerate even if cached
93
+ --model NAME Pass --model to claude (e.g. "sonnet", "haiku", or a full model ID)
92
94
  -y, --yes Skip confirmation prompts for new daily summaries
93
95
 
94
96
  Environment:
@@ -172,10 +174,18 @@ function parseArgs(args) {
172
174
  const includeIdx = rest.indexOf("--include");
173
175
  if (includeIdx !== -1) {
174
176
  const includeStr = rest[includeIdx + 1];
175
- if (includeStr === "all") {
177
+ if (!includeStr) {
178
+ reportError = "Invalid --include: missing value.";
179
+ } else if (includeStr === "all") {
176
180
  reportInclude = ALL_TYPES;
177
- } else if (includeStr) {
178
- reportInclude = includeStr.split(",").map((s) => s.trim()).filter(Boolean);
181
+ } else {
182
+ const tokens = includeStr.split(",").map((s) => s.trim()).filter(Boolean);
183
+ const unknown = tokens.filter((t) => !ALL_TYPES.includes(t));
184
+ if (unknown.length > 0) {
185
+ reportError = `Unknown --include type${unknown.length > 1 ? "s" : ""}: ${unknown.map((u) => `"${u}"`).join(", ")}. Valid types: ${ALL_TYPES.join(", ")} (or "all").`;
186
+ } else {
187
+ reportInclude = tokens;
188
+ }
179
189
  }
180
190
  }
181
191
  let reportFormat = "markdown";
@@ -220,13 +230,15 @@ function parseArgs(args) {
220
230
  let summaryPrompt;
221
231
  let summaryForce = false;
222
232
  let summaryAssumeYes = false;
233
+ let summaryModel;
223
234
  let summaryError;
224
235
  const FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
225
236
  "--date",
226
237
  "--last",
227
238
  "--from",
228
239
  "--to",
229
- "--prompt"
240
+ "--prompt",
241
+ "--model"
230
242
  ]);
231
243
  for (let i = 0; i < rest.length; i++) {
232
244
  const arg = rest[i];
@@ -318,6 +330,15 @@ function parseArgs(args) {
318
330
  summaryPrompt = val;
319
331
  }
320
332
  }
333
+ const modelIdx = rest.indexOf("--model");
334
+ if (modelIdx !== -1) {
335
+ const val = rest[modelIdx + 1];
336
+ if (!val) {
337
+ summaryError = "Invalid --model: missing value (e.g. --model sonnet).";
338
+ } else {
339
+ summaryModel = val;
340
+ }
341
+ }
321
342
  if (rest.includes("--force")) summaryForce = true;
322
343
  if (rest.includes("-y") || rest.includes("--yes")) summaryAssumeYes = true;
323
344
  return {
@@ -328,6 +349,7 @@ function parseArgs(args) {
328
349
  summaryPrompt,
329
350
  summaryForce,
330
351
  summaryAssumeYes,
352
+ summaryModel,
331
353
  summaryError
332
354
  };
333
355
  }
@@ -1391,16 +1413,18 @@ function formatUsage(u) {
1391
1413
  }
1392
1414
  function spawnClaude(opts) {
1393
1415
  return new Promise((resolve2) => {
1416
+ const args = [
1417
+ "-p",
1418
+ "--no-session-persistence",
1419
+ "--output-format",
1420
+ "stream-json",
1421
+ "--verbose"
1422
+ ];
1423
+ if (opts.model) args.push("--model", opts.model);
1424
+ args.push(opts.prompt);
1394
1425
  const proc = spawn(
1395
1426
  "claude",
1396
- [
1397
- "-p",
1398
- "--no-session-persistence",
1399
- "--output-format",
1400
- "stream-json",
1401
- "--verbose",
1402
- opts.prompt
1403
- ],
1427
+ args,
1404
1428
  {
1405
1429
  stdio: ["pipe", "pipe", "pipe"],
1406
1430
  cwd: agenthudHomeDir()
@@ -1513,6 +1537,7 @@ function spawnClaude(opts) {
1513
1537
  proc.stdin.end(opts.stdin);
1514
1538
  });
1515
1539
  }
1540
+ var REPORT_TOKEN_WARN_THRESHOLD = 3e5;
1516
1541
  async function generateDailySummary(opts) {
1517
1542
  ensureUserPromptFile("daily");
1518
1543
  const isToday = isSameLocalDay2(opts.date, opts.today);
@@ -1556,6 +1581,8 @@ async function generateDailySummary(opts) {
1556
1581
  detailLimit: 0,
1557
1582
  withGit: true
1558
1583
  });
1584
+ const reportBytes = Buffer.byteLength(reportMarkdown, "utf-8");
1585
+ const estimatedTokens = Math.ceil(reportBytes / 4);
1559
1586
  if (opts.announce) {
1560
1587
  const reportLines = reportMarkdown.split("\n");
1561
1588
  const sessionCount = reportLines.filter((l) => l.startsWith("## ")).length;
@@ -1565,11 +1592,9 @@ async function generateDailySummary(opts) {
1565
1592
  const commitCount = reportLines.filter(
1566
1593
  (l) => /^\[\d{2}:\d{2}\] ◆/.test(l)
1567
1594
  ).length;
1568
- const sizeKb = (Buffer.byteLength(reportMarkdown, "utf-8") / 1024).toFixed(
1569
- 1
1570
- );
1595
+ const sizeKb = (reportBytes / 1024).toFixed(1);
1571
1596
  process.stderr.write(
1572
- `agenthud: input: ${sessionCount} sessions, ${activityCount} activities, ${commitCount} commits (${reportLines.length} lines, ${sizeKb}KB)
1597
+ `agenthud: input: ${sessionCount} sessions, ${activityCount} activities, ${commitCount} commits (${reportLines.length} lines, ${sizeKb}KB \u2248 ${estimatedTokens.toLocaleString()} tokens)
1573
1598
  `
1574
1599
  );
1575
1600
  }
@@ -1585,6 +1610,29 @@ async function generateDailySummary(opts) {
1585
1610
  };
1586
1611
  }
1587
1612
  }
1613
+ if (estimatedTokens > REPORT_TOKEN_WARN_THRESHOLD) {
1614
+ const sizeMb = (reportBytes / (1024 * 1024)).toFixed(1);
1615
+ process.stderr.write(
1616
+ `agenthud: \u26A0 report is large (~${estimatedTokens.toLocaleString()} tokens, ${sizeMb}MB). Cost will be high; very long reports may exceed context.
1617
+ `
1618
+ );
1619
+ if (!opts.assumeYes) {
1620
+ const proceed = await ask("Send anyway? [Y/n] ", true);
1621
+ if (!proceed) {
1622
+ process.stderr.write(
1623
+ `agenthud: ${dateLabel} \u2014 aborted (report too large).
1624
+ `
1625
+ );
1626
+ return {
1627
+ code: 0,
1628
+ markdown: "",
1629
+ fromCache: false,
1630
+ skipped: true,
1631
+ usage: null
1632
+ };
1633
+ }
1634
+ }
1635
+ }
1588
1636
  if (opts.announce) {
1589
1637
  process.stderr.write(
1590
1638
  `agenthud: sending to claude (this may take a minute)...
@@ -1597,7 +1645,8 @@ async function generateDailySummary(opts) {
1597
1645
  prompt,
1598
1646
  stdin: reportMarkdown,
1599
1647
  cachePath: cached,
1600
- streamToStdout: opts.streamToStdout
1648
+ streamToStdout: opts.streamToStdout,
1649
+ model: opts.model
1601
1650
  });
1602
1651
  if (opts.announce && result.code === 0) {
1603
1652
  process.stderr.write("\n");
@@ -1623,7 +1672,8 @@ async function runSummary(options2) {
1623
1672
  force: options2.force,
1624
1673
  promptOverride: options2.prompt,
1625
1674
  streamToStdout: true,
1626
- announce: true
1675
+ announce: true,
1676
+ model: options2.model
1627
1677
  });
1628
1678
  return res.code;
1629
1679
  }
@@ -1686,7 +1736,9 @@ agenthud: --- ${label} ---
1686
1736
  force: false,
1687
1737
  streamToStdout: false,
1688
1738
  announce: true,
1689
- confirmBeforeSpawn: confirmer
1739
+ confirmBeforeSpawn: confirmer,
1740
+ assumeYes: options2.assumeYes,
1741
+ model: options2.model
1690
1742
  });
1691
1743
  if (res.skipped) {
1692
1744
  process.stderr.write(`agenthud: ${label} \u2014 skipped by user.
@@ -1731,7 +1783,8 @@ agenthud: combining ${dailyMarkdowns.length} daily summaries into range summary.
1731
1783
  prompt: metaPrompt,
1732
1784
  stdin: metaInput,
1733
1785
  cachePath: rangeCache,
1734
- streamToStdout: true
1786
+ streamToStdout: true,
1787
+ model: options2.model
1735
1788
  });
1736
1789
  if (metaResult.code !== 0) {
1737
1790
  return metaResult.code;
@@ -3591,7 +3644,8 @@ if (options.mode === "summary") {
3591
3644
  to: options.summaryTo,
3592
3645
  today,
3593
3646
  force: options.summaryForce ?? false,
3594
- assumeYes: options.summaryAssumeYes ?? false
3647
+ assumeYes: options.summaryAssumeYes ?? false,
3648
+ model: options.summaryModel
3595
3649
  });
3596
3650
  process.exit(exitCode2);
3597
3651
  }
@@ -3599,7 +3653,8 @@ if (options.mode === "summary") {
3599
3653
  date: options.summaryDate,
3600
3654
  prompt: options.summaryPrompt,
3601
3655
  force: options.summaryForce ?? false,
3602
- today
3656
+ today,
3657
+ model: options.summaryModel
3603
3658
  });
3604
3659
  process.exit(exitCode);
3605
3660
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenthud",
3
- "version": "0.9.3",
3
+ "version": "0.9.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",