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 +24 -10
- package/dist/index.js +1 -1
- package/dist/{main-S27FZ2BJ.js → main-26QL33AJ.js} +77 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,10 +4,16 @@
|
|
|
4
4
|
[](https://github.com/neochoon/agenthud/actions/workflows/ci.yml)
|
|
5
5
|
[](https://codecov.io/gh/neochoon/agenthud)
|
|
6
6
|
|
|
7
|
-
|
|
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
|

|
|
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
|
-
##
|
|
27
|
+
## Live monitor
|
|
22
28
|
|
|
23
|
-
AgentHUD
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
+
### Keyboard shortcuts
|
|
89
95
|
|
|
90
96
|
Full reference is also available inside the app — press `?`.
|
|
91
97
|
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
|
177
|
+
if (!includeStr) {
|
|
178
|
+
reportError = "Invalid --include: missing value.";
|
|
179
|
+
} else if (includeStr === "all") {
|
|
176
180
|
reportInclude = ALL_TYPES;
|
|
177
|
-
} else
|
|
178
|
-
|
|
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 = (
|
|
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