@klars/agentobs 0.2.0 → 0.2.2
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 +45 -14
- package/dist/adapters/claude-transcript.js +15 -8
- package/dist/server/public/app.js +14 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,16 +36,31 @@ account, no cloud, no telemetry.
|
|
|
36
36
|
```bash
|
|
37
37
|
npm install -g @klars/agentobs
|
|
38
38
|
agentobs init
|
|
39
|
+
agentobs import
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
`
|
|
42
|
-
|
|
42
|
+
`import` reads Claude Code's own session transcripts from `~/.claude/projects/`
|
|
43
|
+
and backfills everything you have already done — no configuration, no hooks.
|
|
44
|
+
Then:
|
|
43
45
|
|
|
44
46
|
```bash
|
|
45
47
|
agentobs dashboard
|
|
46
48
|
```
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
That is the fastest path to real data, and the one to try first.
|
|
51
|
+
|
|
52
|
+
### Live capture (optional)
|
|
53
|
+
|
|
54
|
+
`import` is after-the-fact. To record calls **as they happen** — and to let
|
|
55
|
+
guardrails actually block them — add the hook configuration that
|
|
56
|
+
`agentobs init` prints to `~/.claude/settings.json`, then restart Claude Code.
|
|
57
|
+
|
|
58
|
+
> **If hooks record nothing:** this has been observed on at least one Windows
|
|
59
|
+
> install, where Claude Code did not invoke the configured command at all — a
|
|
60
|
+
> plain two-line `.cmd` file also never fired, so it is not specific to
|
|
61
|
+
> AgentObs. Check by running any tool and then `agentobs stats --today`. If it
|
|
62
|
+
> stays at zero, keep using `agentobs import`, which needs no hooks; only
|
|
63
|
+
> guardrail *blocking* depends on them.
|
|
49
64
|
|
|
50
65
|
---
|
|
51
66
|
|
|
@@ -85,6 +100,7 @@ Everything lives in `~/.agentobs/`. Uninstalling is `rm -rf ~/.agentobs`.
|
|
|
85
100
|
|
|
86
101
|
```
|
|
87
102
|
agentobs init Set up ~/.agentobs and print the hook config
|
|
103
|
+
agentobs import [--days n] [--all] Import Claude Code transcripts (no hooks needed)
|
|
88
104
|
agentobs dashboard [--port] [--host] Serve the dashboard (default 127.0.0.1:4300)
|
|
89
105
|
agentobs stats [--today] [--since] Print totals in the terminal
|
|
90
106
|
agentobs run -- <command...> Observe any command (coarse detail)
|
|
@@ -148,22 +164,37 @@ Two deliberate behaviours worth knowing:
|
|
|
148
164
|
|
|
149
165
|
## Agent support
|
|
150
166
|
|
|
151
|
-
| Agent
|
|
152
|
-
|
|
|
153
|
-
| **Claude Code** |
|
|
154
|
-
|
|
|
155
|
-
|
|
|
167
|
+
| Agent | How | Detail | Needs setup? |
|
|
168
|
+
| --- | --- | --- | --- |
|
|
169
|
+
| **Claude Code** | `agentobs import` | **Rich** — every tool call, tokens, cost | **No** |
|
|
170
|
+
| **Claude Code** | Native hooks | **Rich**, live, and can *block* calls | Yes — hook config |
|
|
171
|
+
| Any CLI agent | `agentobs run -- <cmd>` | **Coarse** — duration and exit code only | No |
|
|
172
|
+
| Custom / in-house | `agentobs watch <file>` | **Rich**, if it writes JSONL | No |
|
|
173
|
+
|
|
174
|
+
`import` and hooks read the same underlying data. The difference is timing:
|
|
175
|
+
hooks see a call *before* it runs, which is what makes blocking possible;
|
|
176
|
+
`import` reads the transcript afterwards. If you only want observability,
|
|
177
|
+
`import` is enough and needs no configuration.
|
|
156
178
|
|
|
157
179
|
The dashboard labels coarse sessions as `coarse` rather than implying detail it
|
|
158
|
-
does not have
|
|
180
|
+
does not have, and `agentobs stats` explains why a coarse-only range shows zero
|
|
181
|
+
tool calls.
|
|
159
182
|
|
|
160
183
|
### A note on cost accuracy
|
|
161
184
|
|
|
162
|
-
Claude Code's `PostToolUse` hook payload carries **no token or cost fields
|
|
163
|
-
|
|
164
|
-
`
|
|
165
|
-
**per-tool-call cost blank
|
|
166
|
-
across calls
|
|
185
|
+
Claude Code's `PostToolUse` hook payload carries **no token or cost fields**,
|
|
186
|
+
so token usage comes from the session transcript — at `SessionEnd` for hooks,
|
|
187
|
+
or directly via `agentobs import`. That makes **session-level cost accurate**
|
|
188
|
+
while leaving **per-tool-call cost blank**: usage is reported per assistant
|
|
189
|
+
message, not per tool call, and dividing a total across calls would be a
|
|
190
|
+
manufactured number.
|
|
191
|
+
|
|
192
|
+
**Cache tokens dominate a long session.** A cached conversation replays its
|
|
193
|
+
whole context on every turn, so `cache_read` can reach hundreds of millions of
|
|
194
|
+
tokens in a single session. AgentObs tracks cache reads (billed at 0.1x input)
|
|
195
|
+
and cache writes (1.25x) separately from fresh tokens, and `agentobs import`
|
|
196
|
+
prints the four lines separately — a single unexplained total looks like a bug
|
|
197
|
+
when the cache line legitimately dwarfs everything else.
|
|
167
198
|
|
|
168
199
|
Model prices live in `~/.agentobs/pricing.json` and are yours to edit. A model
|
|
169
200
|
missing from that file shows cost as `—`, never `$0.00`.
|
|
@@ -135,14 +135,19 @@ export async function importTranscript(db, file) {
|
|
|
135
135
|
const message = (row.message ?? {});
|
|
136
136
|
const usage = message.usage;
|
|
137
137
|
if (usage && typeof usage === 'object') {
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
|
|
138
|
+
// Fresh input = uncached input + cache writes. A cache write is real
|
|
139
|
+
// new content being sent for the first time (just stored for reuse), so
|
|
140
|
+
// excluding it made "tokens in" absurd: 24K in against 4.6M out, when
|
|
141
|
+
// real agent usage is heavily input-weighted. It is still tracked
|
|
142
|
+
// separately for costing, since it bills at 1.25x.
|
|
143
|
+
//
|
|
144
|
+
// Cache *reads* stay out of this total: they replay the entire context
|
|
145
|
+
// on every turn, so counting them would report the same tokens hundreds
|
|
146
|
+
// of times (2.4 billion across three sessions).
|
|
147
|
+
const cacheWrite = usage.cache_creation_input_tokens ?? 0;
|
|
148
|
+
result.tokensIn += (usage.input_tokens ?? 0) + cacheWrite;
|
|
144
149
|
result.tokensOut += usage.output_tokens ?? 0;
|
|
145
|
-
result.cacheWriteTokens +=
|
|
150
|
+
result.cacheWriteTokens += cacheWrite;
|
|
146
151
|
// cache_read is the whole conversation context replayed on every single
|
|
147
152
|
// message, so it re-counts the same tokens on each turn - summing it
|
|
148
153
|
// reported 413 million tokens for one session. It is tracked separately
|
|
@@ -186,6 +191,8 @@ export async function importTranscript(db, file) {
|
|
|
186
191
|
}
|
|
187
192
|
if (!sessionStarted)
|
|
188
193
|
return result;
|
|
194
|
+
// tokensIn already contains the cache-write tokens, so they are costed once
|
|
195
|
+
// at the base rate here and only topped up by the extra 0.25x premium.
|
|
189
196
|
const baseCost = computeCost(result.model, result.tokensIn, result.tokensOut);
|
|
190
197
|
const readCost = computeCost(result.model, result.cacheReadTokens, 0);
|
|
191
198
|
const writeCost = computeCost(result.model, result.cacheWriteTokens, 0);
|
|
@@ -194,7 +201,7 @@ export async function importTranscript(db, file) {
|
|
|
194
201
|
? null
|
|
195
202
|
: baseCost +
|
|
196
203
|
(readCost ?? 0) * CACHE_READ_RATE +
|
|
197
|
-
(writeCost ?? 0) * CACHE_WRITE_RATE;
|
|
204
|
+
(writeCost ?? 0) * (CACHE_WRITE_RATE - 1);
|
|
198
205
|
// Session totals come from the transcript's own usage blocks, which are
|
|
199
206
|
// authoritative - the per-call rows have no tokens to sum.
|
|
200
207
|
db.prepare(`UPDATE sessions
|
|
@@ -191,10 +191,10 @@ function renderTools(rows) {
|
|
|
191
191
|
body.replaceChildren();
|
|
192
192
|
if (rows.length === 0) {
|
|
193
193
|
const tr = document.createElement('tr');
|
|
194
|
-
const msg =
|
|
195
|
-
state.summary && state.summary.coarse_sessions > 0
|
|
196
|
-
? 'Coarse sessions record no tool calls — connect the Claude Code hook for per-tool detail.'
|
|
197
|
-
: 'No tool calls recorded yet.';
|
|
194
|
+
const msg =
|
|
195
|
+
state.summary && state.summary.coarse_sessions > 0
|
|
196
|
+
? 'Coarse sessions record no tool calls — connect the Claude Code hook for per-tool detail.'
|
|
197
|
+
: 'No tool calls recorded yet.';
|
|
198
198
|
tr.append(Object.assign(cell(msg, 'empty'), { colSpan: 5 }));
|
|
199
199
|
body.append(tr);
|
|
200
200
|
return;
|
|
@@ -630,6 +630,16 @@ function renderDelta(el, current, previous, { goodWhenUp = true } = {}) {
|
|
|
630
630
|
return;
|
|
631
631
|
}
|
|
632
632
|
const change = ((current - previous) / previous) * 100;
|
|
633
|
+
// A tiny previous period produces a huge, meaningless percentage - "+2300%"
|
|
634
|
+
// says nothing except that yesterday was nearly empty. Cap the display so
|
|
635
|
+
// the chip stays informative rather than theatrical.
|
|
636
|
+
if (Math.abs(change) > 999) {
|
|
637
|
+
el.className = `delta ${change > 0 === goodWhenUp ? 'delta-good' : 'delta-bad'}`;
|
|
638
|
+
el.textContent = `${change > 0 ? '▲' : '▼'} vs ${previous}`;
|
|
639
|
+
el.title = `Previous period had only ${previous}; a percentage would be misleading`;
|
|
640
|
+
el.removeAttribute('hidden');
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
633
643
|
if (!Number.isFinite(change) || Math.abs(change) < 0.5) {
|
|
634
644
|
// Below half a percent is noise; a chip there implies a signal that
|
|
635
645
|
// isn't real.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klars/agentobs",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Observability and control layer for AI coding agents - see every tool call, token, and dollar your agents spend, and stop them before they do something risky.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Klars AI",
|