@superbased/observer 1.4.5 → 1.4.7

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.
Files changed (2) hide show
  1. package/README.md +800 -48
  2. package/package.json +6 -6
package/README.md CHANGED
@@ -2,13 +2,50 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@superbased/observer.svg)](https://www.npmjs.com/package/@superbased/observer)
4
4
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
5
+ [![Platforms: Linux • macOS • Windows](https://img.shields.io/badge/platforms-linux%20%7C%20macos%20%7C%20windows-blue.svg)](https://github.com/marmutapp/superbased-observer)
5
6
 
6
- SuperBased Observer — capture, normalize, compress, and analyze AI
7
- coding tool activity across **Claude Code**, **Codex**, **Cursor**,
8
- **Cline/Roo Code**, and **GitHub Copilot**.
7
+ **Capture, normalize, compress, and analyze every AI coding tool call you
8
+ run** across Claude Code, Codex, Cursor, Cline / Roo Code, GitHub
9
+ Copilot, OpenCode, OpenClaw, and Pi — in one local single-binary
10
+ tool. No telemetry, no cloud, no data leaves your machine.
11
+
12
+ ```
13
+ ┌──────────────┐ ┌──────────────────────────────────┐
14
+ │ AI clients │ ─▶ │ observer proxy :8820 (local) │ ─▶ Anthropic / OpenAI
15
+ │ (Claude │ │ • intercepts every API call │
16
+ │ Code, │ │ • compresses conversations │
17
+ │ Cursor, │ │ • records api_turns + tokens │
18
+ │ Codex …) │ └──────────────┬───────────────────┘
19
+ │ │ ▼
20
+ │ │ ◀── observer mcp (stdio JSON-RPC) ◀── 12 cross-client tools
21
+ └──────┬───────┘
22
+ │ JSONL session logs
23
+
24
+ ┌──────────────────────────────────────────────────────────────┐
25
+ │ observer watch SQLite store :8081 dashboard │
26
+ │ • normalizes tool calls • cost over time │
27
+ │ • freshness, redundancy detection • compression savings │
28
+ │ • patterns, suggestions • discovery / waste │
29
+ └──────────────────────────────────────────────────────────────┘
30
+ ```
31
+
32
+ # Table of contents
33
+
34
+ - [Install](#install)
35
+ - [Five-minute quickstart](#five-minute-quickstart)
36
+ - [Per-AI-client setup](#per-ai-client-setup)
37
+ - [Architecture in detail](#architecture-in-detail)
38
+ - [Dashboard tour](#dashboard-tour)
39
+ - [MCP tools reference](#mcp-tools-reference)
40
+ - [Compression mechanisms](#compression-mechanisms)
41
+ - [Cost and token math](#cost-and-token-math)
42
+ - [Terminology and glossary](#terminology-and-glossary)
43
+ - [CLI reference](#cli-reference)
44
+ - [Configuration](#configuration)
45
+ - [Troubleshooting](#troubleshooting)
46
+ - [Security and privacy](#security-and-privacy)
47
+ - [Source, contributing, license](#source-contributing-license)
9
48
 
10
- Single binary. Local-only (no network calls from the observer
11
- itself). Pure-Go, no CGO.
12
49
 
13
50
  ## Install
14
51
 
@@ -17,66 +54,781 @@ npm install -g @superbased/observer
17
54
  observer --version
18
55
  ```
19
56
 
20
- The package ships pre-built binaries for:
57
+ Pre-built binaries ship for:
21
58
 
22
59
  | Platform | Architecture |
23
- | --------------------- | ------------ |
60
+ |-----------------------|--------------|
24
61
  | Linux | x64, arm64 |
25
62
  | macOS (Intel) | x64 |
26
63
  | macOS (Apple Silicon) | arm64 |
27
64
  | Windows | x64 |
28
65
 
29
- If you're on a platform that isn't listed, file an issue or build the
30
- binary from sourcesee the
31
- [main repo](https://github.com/marmutapp/superbased-observer).
66
+ The package uses the `optionalDependencies`-per-platform pattern (same
67
+ shape as `esbuild` / `swc` / `@biomejs/biome`) only the binary
68
+ matching your machine downloads. No postinstall network calls, no
69
+ compile step.
70
+
71
+ If your platform isn't listed, build from source — instructions in
72
+ the [main repo](https://github.com/marmutapp/superbased-observer).
32
73
 
33
- ## Quick start
74
+
75
+ ## Five-minute quickstart
34
76
 
35
77
  ```bash
36
- # 1. Install the package + register hooks with the AI clients you use.
78
+ # 1) Install + register hooks with whichever AI clients you use.
37
79
  npm install -g @superbased/observer
38
- observer init
80
+ observer init # interactive: pick clients
81
+
82
+ # 2) Start the long-running services (watcher + dashboard + proxy).
83
+ observer start & # all three in one process
84
+ # (or run them separately, see CLI ref)
85
+
86
+ # 3) Engage the proxy by pointing your AI client at the local URL.
87
+ # See "Per-AI-client setup" for the matching env var.
88
+ export ANTHROPIC_BASE_URL=http://127.0.0.1:8820 # Claude Code
89
+ export OPENAI_BASE_URL=http://127.0.0.1:8820/v1 # Codex / OpenAI
90
+
91
+ # 4) Open the dashboard.
92
+ open http://127.0.0.1:8081/ # macOS
93
+ xdg-open http://127.0.0.1:8081/ # Linux
94
+ start http://127.0.0.1:8081/ # Windows
95
+ ```
96
+
97
+ After ten minutes of normal AI-coding usage, the dashboard will be
98
+ populated with cost over time, per-tool activity, compression
99
+ savings, and stale-reread waste signals.
100
+
101
+
102
+ ## Per-AI-client setup
103
+
104
+ Different clients send to different upstreams. The local proxy on
105
+ `127.0.0.1:8820` handles all of them — routes by URL path. Set the
106
+ env var that matches your client(s); both can coexist on one machine.
107
+
108
+ | AI client | Env var | Capture mode |
109
+ |---------------------------------|-------------------------------------------------------|--------------|
110
+ | **Claude Code** | `ANTHROPIC_BASE_URL=http://127.0.0.1:8820` | proxy + JSONL |
111
+ | **Cursor** (Anthropic mode) | `ANTHROPIC_BASE_URL=http://127.0.0.1:8820` | proxy + JSONL |
112
+ | **Codex** | `OPENAI_BASE_URL=http://127.0.0.1:8820/v1` (note `/v1`) | proxy + JSONL |
113
+ | **Cursor** (OpenAI mode) | `OPENAI_BASE_URL=http://127.0.0.1:8820/v1` | proxy + JSONL |
114
+ | **Cline / Roo Code** | `ANTHROPIC_BASE_URL=...` or `OPENAI_BASE_URL=...` per provider | proxy + JSONL |
115
+ | **GitHub Copilot** | (no proxy yet) | JSONL only |
116
+ | **OpenCode** | (no proxy yet) | JSONL only — `~/.local/share/ai.opencode.desktop/` |
117
+ | **OpenClaw** ([openclaw.ai](https://openclaw.ai/)) | (no proxy yet) | JSONL + sqlite — `~/.openclaw/tasks/runs.sqlite` + `~/.openclaw/agents/<agent>/sessions/sessions.json` |
118
+ | **Pi** ([pi.dev](https://pi.dev/)) | (no proxy yet) | JSONL only — `~/.pi/agent/sessions/*.jsonl` |
119
+
120
+ **JSONL-only** clients are captured passively by the watcher (always-on,
121
+ no setup beyond `observer init`). You won't see real-time cost numbers
122
+ for them on the Compression tab (those need the proxy), but every tool
123
+ call shows up on Sessions / Actions / Discovery / Tools / Patterns and
124
+ the JSONL-derived token counts feed the Cost tab with `Source=jsonl`
125
+ + `Reliability=unreliable` tags.
126
+
127
+ ### Persistent setups
128
+
129
+ **Claude Code** (`~/.claude/settings.json`):
130
+
131
+ ```json
132
+ {
133
+ "env": { "ANTHROPIC_BASE_URL": "http://127.0.0.1:8820" },
134
+ "hooks": { /* `observer init` writes these */ }
135
+ }
136
+ ```
137
+
138
+ **Codex** (`~/.codex/config.toml`):
139
+
140
+ ```toml
141
+ [env]
142
+ OPENAI_BASE_URL = "http://127.0.0.1:8820/v1"
143
+ ```
39
144
 
40
- # 2. Start the long-running watcher + dashboard + proxy in one process.
41
- observer start &
145
+ **Shell rc** (`~/.bashrc` / `~/.zshrc`) affects every program:
42
146
 
43
- # 3. Engage the proxy by pointing your AI client at the local URL.
44
- # Claude Code / Cursor (Anthropic mode):
147
+ ```bash
45
148
  export ANTHROPIC_BASE_URL=http://127.0.0.1:8820
46
- # Codex / Cursor (OpenAI mode):
47
149
  export OPENAI_BASE_URL=http://127.0.0.1:8820/v1
150
+ ```
151
+
152
+
153
+ ## Architecture in detail
154
+
155
+ Five components running side by side:
156
+
157
+ ### 1. JSONL adapters (passive ingest)
158
+
159
+ Watch `~/.claude/projects/`, `~/.codex/sessions/`, etc. for new
160
+ session log lines. Normalize per-client tool names to a shared
161
+ taxonomy (`read_file`, `run_command`, `spawn_subagent`, …) and write
162
+ them into the actions table. **Always-on**, no setup required —
163
+ `observer init` registers the hooks once.
164
+
165
+ What this gets you: every tool call you've ever run, queryable.
166
+
167
+ ### 2. API reverse proxy (active capture)
168
+
169
+ A localhost HTTP server (`127.0.0.1:8820`) you point your AI client
170
+ at via `ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL`. Intercepts every
171
+ request before it hits Anthropic / OpenAI and:
172
+
173
+ - Records exact token usage from the upstream `usage` envelope (the
174
+ most accurate cost source — proxy beats JSONL parsing here).
175
+ - Runs the **conversation compression pipeline** to trim large
176
+ `tool_result` blocks and drop low-importance messages before
177
+ forwarding upstream.
178
+ - Captures the `cost_usd` the upstream reports (when present).
179
+
180
+ What this gets you: ground-truth cost numbers and conversation
181
+ compression savings you can measure.
182
+
183
+ ### 3. SQLite store
184
+
185
+ A single file at `~/.observer/observer.db`. Tables include:
186
+
187
+ - `projects`, `sessions`, `actions` — the taxonomy
188
+ - `api_turns` — one row per proxy-intercepted upstream request
189
+ - `token_usage` — JSONL-derived token-row events (deduped via spec §A1)
190
+ - `file_state` — content hashes for freshness classification
191
+ - `compression_events` — per-event compression detail (post-migration 010)
192
+ - `project_patterns` — derived patterns from `observer patterns`
193
+ - `failure_context`, `action_excerpts` — diagnostic data
194
+
195
+ Pure-Go via `modernc.org/sqlite`, no CGO. WAL mode by default.
196
+
197
+ ### 4. Local dashboard (`:8081`)
198
+
199
+ Eight tabs covering: Overview, Cost, Sessions, Actions, Tools,
200
+ Compression, Discovery, Patterns. See [Dashboard tour](#dashboard-tour).
201
+
202
+ Static HTML + Chart.js. No analytics, no external requests.
203
+
204
+ ### 5. MCP server (stdio)
205
+
206
+ 12 read-only tools the AI client itself can call mid-conversation —
207
+ `check_file_freshness`, `get_last_test_result`, `search_past_outputs`,
208
+ etc. Powers cross-client tool sharing: if Claude Code ran `go test`,
209
+ Cursor's MCP query for the latest test result will return Claude
210
+ Code's run. See [MCP tools reference](#mcp-tools-reference).
211
+
212
+
213
+ ## Dashboard tour
214
+
215
+ Open `http://127.0.0.1:8081/` after `observer start`. Eight tabs.
216
+
217
+ ### Overview tab
218
+
219
+ High-level snapshot of the selected window:
220
+
221
+ - **KPI tiles**: Sessions count, API turns (proxy-captured), Token
222
+ rows (JSONL-recovered), Failures (24h)
223
+ - **Cost over time** chart — daily token volume, split into the four
224
+ billable buckets (net input / cache read / cache write / output)
225
+ - **Actions over time** chart — total actions vs failures
226
+ - **Top models (by tokens)** chart — top-8 models stacked by net
227
+ input / cache read / output
228
+ - **Top tools (actions over time)** — per-AI-client stacked-area
229
+ showing when each client is active
230
+
231
+ ### Cost tab
232
+
233
+ Per-model breakdown over the selected window. Tokens split into the
234
+ four billing buckets, with computed dollar cost and a reliability
235
+ flag (`high` = upstream-reported, `medium` = computed, `low` =
236
+ estimated, `unreliable` = no pricing entry). Daily stacked-bar chart
237
+ of token volume per day.
238
+
239
+ Hover any column header for tooltip; click for the full definition
240
+ in the help drawer.
241
+
242
+ ### Sessions tab
243
+
244
+ One row per AI-coding session. Each session has a stable ID, a tool
245
+ (claude-code / cursor / codex / cline / copilot), a working-directory
246
+ project, action count, sub-agent action count (when the session
247
+ spawned sub-agents via the `Agent` tool), and — if `observer score`
248
+ has run — quality / errors / redundancy ratios.
249
+
250
+ Click a row to open the session-detail modal: action breakdown,
251
+ token buckets, cost, recent actions.
252
+
253
+ ### Actions tab
254
+
255
+ The flat firehose: every recorded tool call, normalized across
256
+ adapters. Filter by action type (`read_file`, `write_file`,
257
+ `run_command`, `spawn_subagent`, `todo_update`, `mcp_call`, …).
258
+ Pagination caps at 50 rows per page; total count is shown next to
259
+ the heading.
260
+
261
+ ### Tools tab
262
+
263
+ Per-AI-client (the *client* — claude-code / cursor / codex / etc., not
264
+ the per-tool name) aggregates plus three views:
265
+
266
+ - **KPI tiles**: Total actions, Distinct tools, Overall success rate,
267
+ Busiest tool
268
+ - **Activity over time** stacked-area showing per-tool action volume
269
+ per day
270
+ - **Action-type mix per tool** horizontal stacked bar — what each
271
+ tool actually does (read_file vs edit_file vs run_command vs
272
+ search_text vs spawn_subagent)
273
+ - The full per-tool aggregate table with first/last seen
274
+
275
+ ### Compression tab
276
+
277
+ How many tokens and dollars the conversation-compression pipeline
278
+ saved by trimming requests before forwarding upstream:
279
+
280
+ - **KPI tiles**: Tokens saved (est.), Dollars saved (est.), Bytes
281
+ saved, Turns compressed
282
+ - **Savings per day** chart — daily tokens-saved (left axis) and
283
+ bytes-saved (right axis)
284
+ - **Savings by mechanism** stacked bar — segments per mechanism
285
+ (json / code / logs / text / diff / html / drop). **Toggle the
286
+ y-axis between tokens and bytes** with the chart-header switch.
287
+ - **Per-model breakdown table** — tokens saved ~, $ saved ~, bytes
288
+ saved, saved %, turns, tool-results compressed, dropped, markers
289
+ - **Recent compression events** — paginated per-event detail with
290
+ mechanism, original / compressed / saved bytes, message slot, importance
291
+ score (for drops), and a **Source** column showing whether the
292
+ event came from a main-thread or sub-agent runtime call
293
+
294
+ ### Discovery tab
295
+
296
+ Wasted-effort signals:
297
+
298
+ - **Stale rereads** — files re-read after they changed inside the
299
+ same session. KPI tiles show count, ~Tokens wasted, ~$ wasted (at
300
+ your blended input rate), affected files. The **CROSS-THREAD**
301
+ column flags re-reads that crossed the parent ↔ sub-agent
302
+ boundary — these are the "pass content via Agent's prompt
303
+ parameter" candidates.
304
+ - **Repeated commands** — commands run multiple times with no
305
+ relevant inputs changed in between (e.g. you ran `go test` three
306
+ times without editing anything between runs).
307
+ - **Cross-tool overlap** — files touched by ≥2 AI clients in the
308
+ window (e.g. claude-code AND cursor both edited `auth.ts`). This
309
+ is the visible side of cross-platform tool-call sharing via the
310
+ MCP server.
311
+
312
+ ### Patterns tab
313
+
314
+ Repeatable behaviours the observer noticed across your sessions —
315
+ "after running `go test`, you almost always run `go vet`", "when
316
+ working on `auth.ts`, you also touch `login.tsx`", etc. Each pattern
317
+ has a confidence score (decay-weighted: more observations + recent
318
+ observations push it higher) and an observation count.
319
+
320
+ `observer patterns` derives them; `observer suggest` writes the
321
+ high-confidence ones into `CLAUDE.md` / `AGENTS.md` / `.cursorrules`
322
+ so new sessions inherit your habits.
323
+
324
+ ### Help drawer
325
+
326
+ Press `?` anywhere on the dashboard or click the **? Help** button in
327
+ the topbar. Every column header, KPI tile, chart label, and filter
328
+ control on every tab is annotated — hover any element to see a
329
+ one-liner tooltip; click to open the drawer at the matching glossary
330
+ entry.
331
+
332
+ The drawer has full descriptions, formulas, data sources, examples,
333
+ "why it matters", "what to do", and cross-links. Search at the top.
334
+ Deep-linkable via URL fragment — `#help/metric.stale_count` opens
335
+ the drawer at that entry.
336
+
337
+ Each compression mechanism (json / code / logs / text / diff / html
338
+ / drop) has a "Full methodology · see more" expandable section
339
+ explaining the actual algorithm.
340
+
341
+
342
+ ## MCP tools reference
343
+
344
+ Every AI client running through `observer init` gets these 12 tools
345
+ registered as an MCP server. They're **read-only** queries against
346
+ the unified database, so any agent can read any other agent's
347
+ recorded work — true cross-platform tool-call sharing.
348
+
349
+ | Tool | Purpose |
350
+ |-----------------------------------|---------|
351
+ | `check_file_freshness` | Has this file been read in the current session? Has it changed since? |
352
+ | `get_file_history` | Full read/edit history of a file across all sessions and clients |
353
+ | `get_session_summary` | Roll-up stats for a session: action count, success rate, cost, token buckets |
354
+ | `search_past_outputs` | Full-text search across recorded tool outputs (FTS5-indexed) |
355
+ | `get_last_test_result` | Most recent `go test` / `npm test` / `pytest` etc. output |
356
+ | `get_failure_context` | Recent failures: which command, which file, which session |
357
+ | `get_action_details` | One specific action's full record (target, args, output excerpt) |
358
+ | `check_command_freshness` | Has this command been run in the current session? With what result? |
359
+ | `get_session_recovery_context` | Recent activity for resuming a paused session |
360
+ | `get_project_patterns` | High-confidence patterns derived from this project's history |
361
+ | `get_cost_summary` | Daily / per-model / per-session cost rollups |
362
+ | `get_redundancy_report` | Stale rereads, repeated commands, cross-tool overlap for the project |
363
+
364
+ **Cross-tool sharing**: when `observer init` registers the MCP server
365
+ with Claude Code AND Cursor (and Codex…), all of them call the same
366
+ 12 tools against the same database. Cursor's `get_last_test_result`
367
+ returns Claude Code's last test run; Codex's `check_file_freshness`
368
+ reflects edits made by Cursor.
369
+
370
+
371
+ ## Compression mechanisms
372
+
373
+ The conversation-compression pipeline runs inside the proxy on every
374
+ upstream request. It tries to fit the request body within
375
+ `target_ratio × original_bytes` (default 0.85) without breaking the
376
+ conversation's referential integrity.
377
+
378
+ Two passes, in order:
379
+
380
+ ### Pass 1 — per-content-type compression
381
+
382
+ Each `tool_result` block is sniffed for content type, then routed to
383
+ a content-aware compressor. Six compressors:
384
+
385
+ | Mechanism | What it does | When it fires |
386
+ |-----------|--------------|---------------|
387
+ | **json** | Replaces every scalar value with a type sentinel (`"<string>"`, `"<number>"`, …) preserving structure (keys, arrays, nesting). Arrays of length > 1 collapse to one element with `_len: N`. | API responses, structured logs, telemetry exports. JSON tool_results are usually the biggest savings target. |
388
+ | **code** | Heuristic skeleton: keeps top-of-file imports + signature lines (function / method / class / struct / interface / type), drops bodies. | Source code files. **Opt-in only** (excluded from default `compress_types`) because the model often needs to read full code. |
389
+ | **logs** | Two-pass: collapses adjacent identical lines to `<line> [×N]`, then head+tail-truncates to 200 lines if still long. | Log-shaped output — `go test ./...`, `npm run build`, polling/retry loops. Lossless on distinct lines; only the truncate pass is lossy. |
390
+ | **text** | Catch-all for content not classified as code/json/logs/diff/html. Head+tail truncation: keeps 40 + 40 lines on inputs over 80 lines. | Markdown bodies, README excerpts, narrative descriptions. |
391
+ | **diff** | Strips unified-diff context beyond ±1 line of each change. Keeps every header, every `+`/`-` line, drops the rest with elision markers. | `git diff`, patch tool outputs. Lossless on changes; lossy only on the cheap-to-rebuild context. |
392
+ | **html** | Three regex passes: strips `<script>`, `<style>`, and HTML comments. Tag attributes + visible text + structural elements survive. | `web_fetch` results pulling whole HTML pages — usually 80%+ scripts/styles. |
393
+
394
+ ### Pass 2 — drop with marker
395
+
396
+ If Pass 1's compressed body is still over budget, the budget enforcer
397
+ ranks remaining messages by **importance score** (a deterministic
398
+ weighted sum) and drops the lowest-scored non-preserved ones until
399
+ the budget is met. Each dropped message is replaced by a single
400
+ marker block (a placeholder text) so the conversation flow stays
401
+ intact for the model.
402
+
403
+ **Importance score** = `0.4 × recency + 0.3 × reference + 0.15 × density + 0.15 × role`
404
+
405
+ - **Recency** = `(i+1) / n` — newest message scores 1.0, oldest scores 1/n
406
+ - **Reference** = `1.0` if any of the message's `tool_use_ids` is
407
+ cited by a later `tool_result`, OR any of its `referenced_ids` points
408
+ to a live tool_use; `0.0` otherwise. Tool-pair-live messages always
409
+ get full weight regardless of position.
410
+ - **Density** = fraction of non-whitespace runes (whitespace-padded
411
+ outputs get dropped first)
412
+ - **Role** = `system 1.0`, `user 0.9`, `assistant 0.7`, `tool 0.5`
413
+ (tool outputs are the most-compressible by policy)
414
+
415
+ **Preserved messages** (never droppable):
416
+ - The last `PreserveLastN` messages (default 4)
417
+ - Any `system` role message
418
+ - **Tool-pair-live messages**: any message whose `tool_use_id` is
419
+ referenced by a later tool_result (parent side), AND any
420
+ `tool_result` message whose `referenced_id` points to a live
421
+ tool_use (consumer side)
422
+
423
+ Tool-pair preservation is symmetric — dropping either side leaves an
424
+ orphan that Anthropic rejects with 400.
425
+
426
+ ### Per-event detail
48
427
 
49
- # 4. Open the dashboard.
50
- open http://127.0.0.1:8081/
428
+ Every drop and every per-type compression is recorded as a row in
429
+ the `compression_events` table (post migration 010). The Compression
430
+ tab's "Recent compression events" view surfaces these with
431
+ mechanism, original / compressed / saved bytes, message slot, and
432
+ importance score (for drops).
433
+
434
+ ### Tuning
435
+
436
+ `observer config` settings (in `~/.observer/config.toml`):
437
+
438
+ ```toml
439
+ [compression.conversation]
440
+ enabled = true
441
+ target_ratio = 0.85
442
+ mode = "token" # or "cache" — restricts drops to tail half
443
+ preserve_last_n = 4
444
+ prefix_bytes = 8192
445
+ compress_types = ["json", "logs", "text"] # add "code", "diff", "html" to opt in
446
+ min_bytes_to_compress = 1024
447
+
448
+ [compression.conversation.weights]
449
+ recency = 0.4
450
+ reference = 0.3
451
+ density = 0.15
452
+ role = 0.15
453
+ ```
454
+
455
+ High importance scores on dropped events (≥0.5) suggest the threshold
456
+ is too aggressive — raise `target_ratio` (e.g. 0.9 or 0.95).
457
+
458
+
459
+ ## Cost and token math
460
+
461
+ Anthropic's usage envelope reports four token buckets per request,
462
+ each at a different rate:
463
+
464
+ | Bucket | What it is | Bills at |
465
+ |--------------------|------------------------------------------------------------------------------|-----------------------------|
466
+ | `net_input` | Fresh prompt tokens not served from cache | model's standard input rate |
467
+ | `cache_read` | Prompt tokens served from Anthropic's ephemeral cache | ~10% of input rate |
468
+ | `cache_creation` | Tokens written to ephemeral cache | 1.25× input (5m tier) or 2× input (1h tier) |
469
+ | `output` | What the model generated | typically 5× input rate |
470
+
471
+ `prompt_context = net_input + cache_read + cache_creation`
472
+ `total_tokens = prompt_context + output`
473
+
474
+ The cost engine (`internal/intelligence/cost`) computes USD via:
475
+
476
+ ```
477
+ cost_usd = (net_input × p.input + cache_read × p.cache_read +
478
+ cache_creation_5m × p.cache_creation +
479
+ cache_creation_1h × p.cache_creation_1h +
480
+ output × p.output) ÷ 1,000,000
51
481
  ```
52
482
 
53
- ## What you get
483
+ When the upstream API returns `cost_usd` in the response envelope
484
+ (proxy-sourced rows), that value is preferred over the computed one —
485
+ ground truth, reliability=high.
486
+
487
+ ### Blended input rate
488
+
489
+ The Discovery tab's "~$ wasted" tile uses a **blended input rate**
490
+ computed from your last-30d api_turns mix: each model's input rate
491
+ weighted by the prompt-token volume it consumed. Example: if you
492
+ spent 70% of prompt tokens on opus-4-7 ($15/1M) and 30% on
493
+ haiku-4-5 ($1/1M), the blended rate is `0.7 × 15 + 0.3 × 1 = $10.80/1M`.
494
+
495
+ Falls back to $3/1M (claude-sonnet-4 input) on fresh installs with
496
+ no proxy data.
497
+
498
+ ### JSONL dedup
499
+
500
+ When the proxy isn't engaged, observer falls back to parsing the AI
501
+ client's on-disk session log. Clients echo the same cumulative usage
502
+ on every content block of a multi-block response, so naive parsing
503
+ counts one API call 2-4×. Two layers of dedup catch this:
504
+
505
+ 1. **Adapter-level**: dedupes on Anthropic `message.id` at write time
506
+ 2. **Cost-engine-level**: dedupes on `(source_file, model, timestamp-bucketed-to-minute, tokens)` at read time
507
+
508
+ Migration 007 ran a one-time pass collapsing pre-fix duplicates.
509
+
510
+
511
+ ## Terminology and glossary
512
+
513
+ Quick reference; the in-platform help drawer (press `?` on the
514
+ dashboard) has the full versions with cross-links.
54
515
 
55
- - **Real-time dashboard** at `http://127.0.0.1:8081/` cost over
56
- time, tool activity, compression savings, stale rereads, patterns,
57
- per-tool action breakdowns, and full-workbook Excel export.
516
+ - **Action** one normalized tool call recorded by an adapter. Action
517
+ types are taxonomic and cross-client: `read_file`, `write_file`,
518
+ `edit_file`, `run_command`, `search_text`, `search_files`,
519
+ `web_search`, `web_fetch`, `mcp_call`, `spawn_subagent`,
520
+ `todo_update`, `ask_user`, `task_complete`, `user_prompt`,
521
+ `unknown`.
522
+ - **API turn** — one HTTP request captured by the local proxy. Records
523
+ one row in `api_turns` per request, with the upstream usage envelope
524
+ intact.
525
+ - **Cache 5m vs 1h tier** — Anthropic's prompt cache has two TTLs.
526
+ Default is 5 minutes; `cache_control: {type: ephemeral, ttl: 3600}`
527
+ extends to 1 hour at 2× the write cost. Reads bill the same rate
528
+ regardless of tier.
529
+ - **Compression event** — one individual compression decision (one
530
+ per-type compress, or one drop) recorded post migration 010.
58
531
  - **Conversation compression** — pre-forward trimming of API request
59
- bodies. Per-mechanism breakdown (json / code / logs / text / diff /
60
- html / drop) so you can see exactly what saved how many tokens.
61
- - **MCP server** with 12 tools every connected AI client can use to
62
- query each other's recorded work `get_last_test_result`,
63
- `check_file_freshness`, `get_failure_context`, etc.
64
- - **Patterns engine** `observer patterns` derives repeatable
65
- behaviours from session history; `observer suggest` writes them
66
- into `CLAUDE.md` / `AGENTS.md` / `.cursorrules` so new sessions
67
- inherit them.
68
-
69
- Full feature reference + walkthrough:
70
- [main repo README](https://github.com/marmutapp/superbased-observer).
71
-
72
- ## How the npm package works
73
-
74
- This package is a thin Node.js shim that locates the right pre-built
75
- binary (shipped via `optionalDependencies`) and spawns it. Same shape
76
- as `esbuild` / `swc` / `@biomejs/biome`. No postinstall network calls,
77
- no compile step at install time.
78
-
79
- ## License
80
-
81
- Apache 2.0 see
82
- [LICENSE](https://github.com/marmutapp/superbased-observer/blob/main/LICENSE).
532
+ bodies. Pass 1 = per-content-type compression, Pass 2 = drop with
533
+ marker. See [Compression mechanisms](#compression-mechanisms).
534
+ - **Cross-platform tool calling** every AI client connected via
535
+ `observer init` can call the 12 MCP tools against the unified
536
+ database. So Cursor's `get_last_test_result` can return a `go test`
537
+ Claude Code ran an hour earlier.
538
+ - **Cross-thread reread** the parent thread re-reads a file the
539
+ sub-agent already saw (or vice versa) within the same session. Fix:
540
+ pass content via the Agent tool's `prompt` parameter rather than
541
+ letting the child re-read.
542
+ - **Freshness state** per-read tag from the freshness engine: `fresh`
543
+ (first read in this session, OR re-read with same content),
544
+ `stale` (re-read after change in same session), `missing` (file no
545
+ longer exists), `modified-elsewhere` (file changed by something
546
+ other than an observable AI action).
547
+ - **Mechanism** one of `json`, `code`, `logs`, `text`, `diff`,
548
+ `html` (per-content-type compressor) or `drop` (low-importance
549
+ message replaced by a marker).
550
+ - **Pattern** a derived behaviour: `command_pair` (X often
551
+ followed by Y), `cross_tool_file` (file touched by multiple
552
+ clients), `knowledge_snippet` (consistent topic-specific habit),
553
+ `failure_correlation` (X often precedes a failure of Y),
554
+ `session_summary`. Each has a decay-weighted confidence score 0-1.
555
+ - **Project** — working-directory root that owns sessions and
556
+ actions. Derived from cwd at session start; `/.git/worktrees/...`
557
+ paths fold back to the working-tree root.
558
+ - **Proxy vs JSONL** — proxy intercepts upstream HTTP calls (ground
559
+ truth, reliability=high). JSONL parses the AI client's on-disk
560
+ session log (works without configuring a base URL, but client
561
+ echoes cumulative usage on every block, requiring dedup —
562
+ reliability=unreliable for token counts on Claude Code).
563
+ - **Reliability** — cost-engine confidence: `high` (upstream-
564
+ reported), `medium` (computed from known pricing), `low` (some
565
+ buckets estimated), `unreliable` (no pricing entry).
566
+ - **Session** — one continuous AI-coding conversation in a single
567
+ tool, scoped to one working directory. Has a stable ID (Claude
568
+ Code's UUID, Codex's rollout ID, …).
569
+ - **Sidechain** — actions emitted inside a sub-agent runtime spawned
570
+ via the parent's `Agent` tool. Sub-agents share the parent's
571
+ session_id; the `is_sidechain` column distinguishes them. The
572
+ Discovery tab's CROSS-THREAD column counts stale rereads that
573
+ crossed this boundary.
574
+ - **Stale reread** — same-session re-read of a file whose content
575
+ changed between reads. Cross-session reads are excluded (a fresh
576
+ session has no memory of a prior session's read).
577
+ - **Tool** — in this dashboard, "tool" means the *AI client*
578
+ (claude-code, cursor, codex, cline, copilot), not the per-tool
579
+ name (`read_file`, `run_command`). The latter is "Tool name" on
580
+ the Actions tab.
581
+ - **Tool-pair integrity** — Anthropic requires every `tool_result`
582
+ block to have a corresponding `tool_use` block in a preceding
583
+ message. The compression pipeline preserves both sides of every
584
+ live pair to satisfy this constraint.
585
+
586
+
587
+ ## CLI reference
588
+
589
+ Every command supports `--help` for the full surface.
590
+
591
+ | Subcommand | Purpose |
592
+ |------------------------------------|---------|
593
+ | `observer init` | Register hooks + MCP server with installed AI clients |
594
+ | `observer uninstall` | Reverse `observer init` |
595
+ | `observer start` | Run watcher + dashboard + proxy in one process (recommended) |
596
+ | `observer watch` | Long-running JSONL watcher only |
597
+ | `observer dashboard --addr ADDR` | HTTP dashboard only |
598
+ | `observer proxy start` | Reverse proxy only |
599
+ | `observer scan` | One-shot ingest of existing JSONL files (catch-up after install) |
600
+ | `observer status` | DB stats + recent activity |
601
+ | `observer doctor` | Diagnostic — checks paths, schemas, hook registration |
602
+ | `observer tail` | Live tail of incoming events |
603
+ | `observer cost` | Per-model cost summary CLI |
604
+ | `observer score` | Compute quality_score / error_rate / redundancy_ratio for sessions |
605
+ | `observer discover` | Stale rereads + repeated commands report (CLI version of the Discovery tab) |
606
+ | `observer patterns` | Derive patterns from session history |
607
+ | `observer learn` | Adapter for ingesting external JSONL exports |
608
+ | `observer suggest` | Write high-confidence patterns into CLAUDE.md / AGENTS.md / .cursorrules |
609
+ | `observer summarize` | Roll-up summary across sessions |
610
+ | `observer export` | Export DB to xlsx / json |
611
+ | `observer prune` | Manual retention pass (delete old data) |
612
+ | `observer backfill --is-sidechain` | Re-walk JSONL to populate is_sidechain on pre-migration rows |
613
+ | `observer metrics` | Prometheus-format metrics endpoint |
614
+ | `observer serve` | MCP server (stdio JSON-RPC) — usually invoked by `observer init` registration |
615
+ | `observer tail` | Live event stream |
616
+
617
+
618
+ ## Configuration
619
+
620
+ `~/.observer/config.toml` — created with defaults on first run.
621
+
622
+ ```toml
623
+ [paths]
624
+ db_path = "~/.observer/observer.db"
625
+ log_dir = "~/.observer/logs"
626
+
627
+ [proxy]
628
+ listen_addr = "127.0.0.1"
629
+ port = 8820
630
+ anthropic_upstream = "https://api.anthropic.com"
631
+ openai_upstream = "https://api.openai.com"
632
+
633
+ [dashboard]
634
+ listen_addr = "127.0.0.1"
635
+ port = 8081
636
+
637
+ [compression.conversation]
638
+ enabled = true
639
+ target_ratio = 0.85
640
+ mode = "token"
641
+ preserve_last_n = 4
642
+ prefix_bytes = 8192
643
+ compress_types = ["json", "logs", "text"]
644
+ min_bytes_to_compress = 1024
645
+
646
+ [compression.conversation.weights]
647
+ recency = 0.4
648
+ reference = 0.3
649
+ density = 0.15
650
+ role = 0.15
651
+
652
+ [compression.shell]
653
+ enabled = true
654
+ # per-command filters configured under [compression.shell.filters]
655
+
656
+ [retention]
657
+ prune_on_startup = true
658
+ max_actions = 5_000_000
659
+ max_age_days = 365
660
+
661
+ [pricing]
662
+ # Per-model overrides if the baked-in pricing is wrong for you.
663
+ # [pricing.models."claude-opus-4-7"]
664
+ # input = 15
665
+ # output = 75
666
+ # cache_read = 1.5
667
+ # cache_creation = 18.75
668
+ ```
669
+
670
+
671
+ ## Troubleshooting
672
+
673
+ ### `npm install -g` fails with `EACCES: permission denied`
674
+
675
+ Default npm puts globals under `/usr/local/lib/node_modules` which
676
+ Homebrew-managed Node owns as root on macOS. Three fixes; pick one:
677
+
678
+ ```bash
679
+ # 1) RECOMMENDED — point npm at a user-writable prefix.
680
+ mkdir -p ~/.npm-global
681
+ npm config set prefix '~/.npm-global'
682
+ echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.zshrc
683
+ source ~/.zshrc
684
+ npm install -g @superbased/observer
685
+
686
+ # 2) Use a Node version manager — fnm / nvm install Node into your
687
+ # home directory and dodge the permission issue entirely.
688
+ brew install fnm
689
+ fnm install --lts
690
+ npm install -g @superbased/observer
691
+
692
+ # 3) sudo (works but you'll fight permissions on every update).
693
+ sudo npm install -g @superbased/observer
694
+ ```
695
+
696
+ ### `observer: command not found` after install
697
+
698
+ The shim binary is at `~/.npm-global/bin/observer` (or wherever your
699
+ npm prefix points). Make sure that directory is on `$PATH`:
700
+
701
+ ```bash
702
+ echo $PATH | tr ':' '\n' | grep -E 'npm|node'
703
+ # add the prefix's bin/ to PATH if missing — see fix above
704
+ ```
705
+
706
+ If you installed only a platform package (e.g. `@superbased/observer-darwin-x64`)
707
+ without the main `@superbased/observer`, the shim doesn't get created
708
+ — there's no `bin` field. Install the main package; npm picks up the
709
+ right platform binary automatically via `optionalDependencies`.
710
+
711
+ ### `observer init` says "no tools selected and none auto-detected"
712
+
713
+ Auto-detection looks for the AI clients' default session-log dirs
714
+ (`~/.claude/projects/`, `~/.codex/sessions/`, `~/.cursor/`, etc.).
715
+ On a fresh machine where no client has run yet, those dirs don't
716
+ exist. Pass the flag explicitly:
717
+
718
+ ```bash
719
+ observer init --claude-code # or --codex / --cursor / --cline / --all
720
+ ```
721
+
722
+ This registers hooks regardless — the next time the client runs,
723
+ its dirs get created and the watcher picks them up.
724
+
725
+ ### Empty dashboard / "No proxy traffic"
726
+
727
+ The JSONL adapter populates passively after `observer init`, but
728
+ ground-truth cost / compression numbers require the proxy. Set
729
+ `ANTHROPIC_BASE_URL=http://127.0.0.1:8820` (Claude Code) or
730
+ `OPENAI_BASE_URL=http://127.0.0.1:8820/v1` (Codex) in the shell
731
+ that launches your AI client.
732
+
733
+ Verify with `observer status | grep api_turns` — count should
734
+ climb during AI-client activity.
735
+
736
+ ### `observer --version` says `dev`
737
+
738
+ You're on a non-released build. Reinstall with `npm install -g @superbased/observer` or rebuild with the workflow's `-X main.version=$VERSION` ldflag.
739
+
740
+ ### `tool_result block must have a corresponding tool_use block`
741
+
742
+ Anthropic 400. Means the conversation-compression pipeline dropped
743
+ a `tool_use` while keeping its matching `tool_result`. Versions
744
+ prior to 1.3.2 had this bug; upgrade. If you're on 1.3.2+ and still
745
+ see it, file an issue with the conversation prefix.
746
+
747
+ ### `tool use concurrency issues`
748
+
749
+ Anthropic 400 surfaced in Claude Code as this message. Means the
750
+ parallel-tool-use case (multiple `tool_use` blocks in one assistant
751
+ message) isn't paired correctly with the multi-block tool_result
752
+ that follows. Versions prior to 1.3.2 had this bug; upgrade.
753
+
754
+ ### Cross-thread numbers are 0
755
+
756
+ Pre-migration data was ingested without the `is_sidechain` flag.
757
+ Run `observer backfill --is-sidechain` once to re-walk JSONL and
758
+ populate the flag on existing rows.
759
+
760
+ ### Migration error: `duplicate column name`
761
+
762
+ Race condition between concurrent daemon startups, fixed in 1.4.1.
763
+ Upgrade. If you still see it, run daemons serially: `observer
764
+ watch`, wait, then `observer dashboard`, then `observer proxy
765
+ start` (or just use `observer start` which runs all three in one
766
+ process — proxy + watcher + dashboard).
767
+
768
+ ### `observer start` log says only `proxy + observer` — no `:8081`
769
+
770
+ You're on a pre-1.4.7 build. Earlier versions ran only proxy +
771
+ watcher under `observer start`; the dashboard had to be started
772
+ separately via `observer dashboard --addr 127.0.0.1:8081`. Upgrade
773
+ to 1.4.7+ — the dashboard goroutine is now part of `observer start`
774
+ and the log line confirms all three: `proxy <addr> + watcher +
775
+ dashboard http://127.0.0.1:8081`. Pass `--no-dashboard` to opt out.
776
+
777
+ ### "address already in use" on port 8820
778
+
779
+ Another `observer proxy start` or `observer start` is still running.
780
+ Find it with `pgrep -af 'observer (proxy|start)'` and `kill <pid>`.
781
+ On macOS:
782
+
783
+ ```bash
784
+ lsof -nP -iTCP:8820 -sTCP:LISTEN
785
+ kill <pid>
786
+ ```
787
+
788
+ ### Dashboard port already in use
789
+
790
+ ```bash
791
+ observer dashboard --addr 127.0.0.1:8082 # pick a different port
792
+ # or
793
+ [dashboard]
794
+ port = 8082 # in config.toml
795
+ ```
796
+
797
+
798
+ ## Security and privacy
799
+
800
+ - **No telemetry**. The observer never makes outbound network calls
801
+ except: (a) the proxy forwarding to the upstream API on your
802
+ behalf, (b) optional codegraph-MCP integration if you've configured
803
+ it. Both are explicit.
804
+ - **Local-only HTTP**. The proxy and dashboard bind to
805
+ `127.0.0.1` by default. Don't bind to `0.0.0.0` unless you've
806
+ thought about it — there's no auth.
807
+ - **Secrets scrubbing**. Tool inputs and outputs are passed through
808
+ a configurable scrubber (`internal/scrub`) that redacts API keys,
809
+ bearer tokens, and other obvious secrets before persistence. Not a
810
+ guarantee — review the rules in `~/.observer/config.toml` for your
811
+ own secret formats.
812
+ - **Database**. `~/.observer/observer.db` is a SQLite file. Owns
813
+ the same security as your `~/.claude/` and `~/.codex/` session
814
+ logs (which already contain the same content). Encrypt the disk
815
+ if your threat model needs that.
816
+ - **Logs**. `~/.observer/logs/` carries operational logs (start /
817
+ stop, errors, retention). Not request bodies — those are in the
818
+ DB only.
819
+
820
+
821
+ ## Source, contributing, license
822
+
823
+ - **Source**: https://github.com/marmutapp/superbased-observer
824
+ - **Specification**: `superbased-final-spec-v2.md` in the repo
825
+ - **Issues**: https://github.com/marmutapp/superbased-observer/issues
826
+ - **License**: [Apache 2.0](https://github.com/marmutapp/superbased-observer/blob/main/LICENSE)
827
+ - **Author**: Santosh Kathira <contact@marmut.app>
828
+
829
+ This npm package is a thin Node.js shim that resolves the right
830
+ pre-built binary at runtime and spawns it. Same shape as `esbuild` /
831
+ `swc` / `@biomejs/biome`. The Go source lives in the main repo;
832
+ binaries are cross-compiled per release tag via GitHub Actions and
833
+ published as `@superbased/observer-<platform>-<arch>` per-platform
834
+ packages.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superbased/observer",
3
- "version": "1.4.5",
3
+ "version": "1.4.7",
4
4
  "description": "SuperBased Observer — capture, normalize, compress, and analyze AI coding tool activity across Claude Code, Codex, Cursor, Cline/Roo, and Copilot.",
5
5
  "keywords": [
6
6
  "ai",
@@ -36,11 +36,11 @@
36
36
  "LICENSE"
37
37
  ],
38
38
  "optionalDependencies": {
39
- "@superbased/observer-linux-x64": "1.4.5",
40
- "@superbased/observer-linux-arm64": "1.4.5",
41
- "@superbased/observer-darwin-x64": "1.4.5",
42
- "@superbased/observer-darwin-arm64": "1.4.5",
43
- "@superbased/observer-win32-x64": "1.4.5"
39
+ "@superbased/observer-linux-x64": "1.4.7",
40
+ "@superbased/observer-linux-arm64": "1.4.7",
41
+ "@superbased/observer-darwin-x64": "1.4.7",
42
+ "@superbased/observer-darwin-arm64": "1.4.7",
43
+ "@superbased/observer-win32-x64": "1.4.7"
44
44
  },
45
45
  "scripts": {
46
46
  "test": "node bin/observer.js --version"