11 KiB
TokenTracker als Auswertungs-Backend für die Token-Analytics
Datum: 2026-08-24 Status: freigegeben, Implementierung offen
Problem
ClaudeDo pflegt zwei eigene Token-Datenpfade:
ClaudeOAuthUsageClient(170 Zeilen) — pollthttps://api.anthropic.com/api/oauth/usagefür die 5h-/7d-Limits. Speist Pill, Gauges,UsageGateundUsageThrottle.TranscriptUsageReader(161 Zeilen) — parst~/.claude/projects/**/*.jsonlund aggregiert nach Datum/Modell/Scope für die Analytics-Ansicht im Usage-Monitor.
Pfad 2 ist der teure und undankbare Teil: ein Voll-Scan über die Transkript-Historie (gemessen
2026-08-06: 501 Dateien / 230 MB, ein 7-Tage-Range berührt ~190 Dateien / ~106 MB), eigener
mtime/Length-Cache, requestId-Dedupe, <synthetic>-Filter — und trotzdem ohne Kosten in USD,
weil wir keine Preistabelle pflegen.
TokenTracker (MIT, Node 20+) macht genau diese Auswertung als Kernprodukt, inklusive gepflegter Preistabelle. Ziel: die Auswertung abgeben, die Darstellung in ClaudeDo behalten.
Nicht-Ziel: Limits, Gate, Throttle
Die 5h-/7d-Limits bleiben bei unserem eigenen OAuth-Poll. Begründung (gegen den installierten TokenTracker verifiziert, 2026-08-24):
~/.tokentracker/tracker/claude-usage-limits-cache.jsonhat exakt die Shape unserer OAuth-Antwort (five_hour/seven_day/seven_day_opus/weekly_scoped/extra_usage) — dieselbe Quelle, kein inhaltlicher Gewinn.- Der Cache war zum Prüfzeitpunkt 13 Tage alt (
cached_at: 2026-08-11), während die Token-Queue desselben Tools von heute war. Ein Gate, das über das Starten von Tasks entscheidet, darf nicht an einem Cache hängen, der still veraltet.
UsageState, UsageMonitorService, UsageSnapshotBuilder, UsageGate, UsageThrottle,
UsagePillViewModel und die Gauges bleiben unangetastet.
Datenquelle
tokentracker sessions --from YYYY-MM-DD --to YYYY-MM-DD --no-git --format json
Dokumentiert in tokentracker --help. Liefert pro Session:
{
"session_hash": "51d4de2afadecd97f9a600ad",
"source": "claude",
"project_key": "ClaudeDo",
"model": "claude-opus-5",
"started_at": "2026-08-24T07:38:03.653Z",
"ended_at": "2026-08-24T07:48:04.728Z",
"turns": 1, "edit_turns": 0, "retry_turns": 0,
"tokens": { "input_tokens": 32, "cached_input_tokens": 1063433,
"cache_creation_input_tokens": 67524, "output_tokens": 14048,
"reasoning_output_tokens": 0, "total_tokens": 1145037 },
"total_tokens": 1145037,
"cost_usd": 1.3051015,
"productive": false, "first_pass": false, "one_shot": false,
"provenance": { "source": "local-session-log", "content_retained": false },
"version": 11
}
Dazu fertige Aggregate totals und by_model (die wir nicht verwenden, siehe Caching).
Verifizierte Eigenschaften
| Behauptung | Befund |
|---|---|
| Rein lokal | buildSessionAnalytics liest ~/.claude/projects/** direkt (lib/session-analytics.js:976-979). Kein Account, kein Netzwerk, keine Hooks nötig. |
Kein init erforderlich |
Der Export funktioniert mit dem bloßen npm-Paket. |
| Join auf unsere Runs möglich | session_hash = sha256("claude\0" + sessionId).hex[0..24] (lib/session-analytics.js:88), deterministisch in .NET nachrechenbar. |
| Worktree-Sessions enthalten | Drei ClaudeDo-Worktree-Sessions aus ~/.claude/projects/…claudedo-worktrees-… per Hash im Export gefunden (2026-08-24). Der Kommentar auf :149 über „throwaway per-agent checkouts" betrifft nur die Projekt-Attribution, nicht das Verwerfen von Sessions. |
project_key ist für uns unbrauchbar |
Bei Worktrees fällt er teils auf die nackte Worktree-GUID zurück (11da6ebb-… u. a. im Export). Deshalb joinen wir über session_hash, nie über project_key. |
| Laufzeit | 11,5 s für einen 1-Tages-Range (gecacht). buildSessionAnalytics läuft immer über die gesamte Historie; --from/--to filtern erst in summarizeSessions. Also kein Performance-Gewinn gegenüber unserem Reader (1,7 s kalt) — der Gewinn liegt bei Kosten, Effizienzmetriken und entfallender Pflege. |
Warum kein tokentracker init
init schreibt Stop- und SessionEnd-Hooks in die globale ~/.claude/settings.json und
aktiviert Cloud-Sync (cloud-sync-pref.enabled: true) zu srctyff5.us-east.insforge.app. Der
Upload enthält u. a. project_ref — auf dieser Maschine https://git.kuns.dev/releases/ClaudeDo,
also einen internen Hostnamen. Das ist eine zu weitgehende Nebenwirkung, um sie hinter einen
Button in ClaudeDo zu legen. ClaudeDo ruft ausschließlich sessions auf und nie init.
Wer Dashboard und Cloud-Sync will, richtet das selbst ein.
Architektur
Neuer Ordner src/ClaudeDo.Worker/Usage/TokenTracker/:
| Typ | Rolle |
|---|---|
ITokenTrackerClient (in Interfaces/) |
Task<TokenTrackerExport?> ExportAsync(DateOnly from, DateOnly to, CancellationToken) + Task<TokenTrackerProbe> ProbeAsync(CancellationToken) |
TokenTrackerClient |
Startet den Prozess, liest stdout, parst JSON. Timeout 120 s, danach Prozessbaum killen. Auflösung der Binary über ExecutableResolver (ClaudeDo.Data) wie beim claude-CLI, inklusive .cmd-Shim-Start über cmd.exe /c. Nie npx — das würde still nachladen. |
TokenTrackerProbe |
(bool Installed, string? Version, bool NodeOk, string? NodeVersion, string? Error). tokentracker -v und node --version; Ergebnis gecacht bis zu einem expliziten Re-Probe. |
TokenTrackerState |
Threadsafe Singleton nach Vorbild von UsageState: letztes gutes TokenTrackerExport, FetchedAtUtc, LastError. Ein Fehler überschreibt nie ein gutes Snapshot. |
TokenTrackerModels |
TokenTrackerExport, TokenTrackerSession, TokenTrackerProbe. |
TokenTrackerAggregator |
Pure statisch: filtert sessions[] auf einen Range und aggregiert nach Modell bzw. nach Task. Keine I/O, voll testbar. |
TokenTrackerInstaller |
Führt npm i -g tokentracker-cli aus und streamt stdout/stderr zeilenweise an den Aufrufer; danach Re-Probe. |
Caching und Refresh
- Ein Fetch holt ein 90-Tage-Fenster und wird gecacht. Alle Ansichten (7d/30d/custom) werden
lokal aus
sessions[]gefiltert und aggregiert — daher verwenden wirtotals/by_modelaus dem Export nicht, sondern rechnen selbst aus den Session-Zeilen. Range-Wechsel im Modal sind damit instant, die 11,5 s fallen nur beim Refresh an. - Refresh-Auslöser: Modal-Open, wenn das Snapshot älter als 15 min ist (im Hintergrund, nie UI-blockierend), plus ein manueller Refresh-Button. Die UI zeigt „Stand: hh:mm".
- Ist gar kein Snapshot da, zeigt der Analytics-Block einen Ladezustand, nie leere Nullwerte.
Versionsschutz
sessions ist kein zugesagter API-Vertrag. Wir prüfen das version-Feld der Session-Zeilen
(aktuell 11). Bei unbekannter Version zeigt der Analytics-Block einen Klartext-Hinweis
(„Auswertung nicht verfügbar: unerwartetes TokenTracker-Format vX") statt Zahlen zu erfinden.
Fehlende Felder werden defensiv als null/0 behandelt, ein Parse-Fehler landet in
LastError und lässt das letzte gute Snapshot stehen.
Hub-Oberfläche
GetModelUsage(from, to)behält Namen und Aufrufer, speist sich aber ausTokenTrackerState.ModelUsageRowDtobekommt ein zusätzlichesdouble? CostUsd. Der Scope-Splitclaudedo/otherkommt jetzt aus dersession_hash-Mitgliedschaft intask_runsstatt aus der Pfad-Heuristik des alten Readers — das ist genauer, weil Worktree-Pfade und die GUID-Fallbacks vonproject_keykeine Rolle mehr spielen.GetTaskUsage(from, to)bleibttask_runs-basiert (sofort verfügbar, funktioniert ohne TokenTracker).TaskUsageRowDtowird umdouble? CostUsd,int? Retries,bool? Productive,bool? OneShoterweitert. Gefüllt werden diese Felder über dieSessionIdder Runs des Tasks → Hash → passende TokenTracker-Session; ohne Snapshot bleiben sienull.- Neu
GetTokenTrackerStatus()→TokenTrackerStatusDto(bool Installed, string? Version, bool NodeOk, string? NodeVersion, DateTime? LastFetchedUtc, string? LastError, int? FormatVersion). - Neu
RefreshTokenTrackerAsync()→ erzwingt einen Fetch (für den Refresh-Button). - Neu
InstallTokenTrackerAsync()→ startetnpm i -g tokentracker-cli; Fortschritt läuft über den bestehendenOperationProgress-Kanal, Ausgabezeilen überWorkerLog.
IWorkerClient und WorkerClient in ClaudeDo.Ui spiegeln alles.
UI
Nur der Analytics-Teil des Usage-Monitors ändert sich; Gauges, Legenden, Drag-Marker und die Pill bleiben wie sie sind.
Mit TokenTracker:
- Kopfzeile des Analytics-Blocks: Total Tokens und Total Cost (USD) für den Range, plus „Stand: hh:mm" und Refresh-Button.
- Breakdown pro Modell: Tokens (In/Out/Cache) + USD, weiterhin mit ClaudeDo-/Other-Split.
- Breakdown pro Task: bestehende Spalten + USD + Retries + Effizienz-Marker
(
productive/one_shot).
Ohne TokenTracker steht an derselben Stelle eine Hinweis-Karte:
Die Token-Auswertung übernimmt TokenTracker (MIT, läuft rein lokal, liest keine Prompts). ClaudeDo ruft nur den lokalen Export auf und aktiviert kein
init, keine Hooks und keinen Cloud-Sync.
mit Button TokenTracker installieren → npm i -g tokentracker-cli. Die Ausgabezeilen laufen
über WorkerLog in die Footer-Leiste, die Karte selbst zeigt die jeweils letzte Zeile als
Statuszeile; danach automatischer Re-Probe. Fehlt Node 20+, zeigt die Karte statt des Buttons den
Node-Hinweis samt Versionsangabe. Die Task-Tabelle bleibt in diesem Zustand funktionsfähig, nur
ohne die USD-/Effizienz-Spalten. Fehler beim Installieren gehen über FlashFooterError, nicht in
ein catch {}.
Was entfällt
TranscriptUsageReader.ReadAsyncsamt Datums-/Modell-/Scope-Aggregation, mtime/Length-Cache,requestId-Dedupe und dem 106-MB-Scan (~120 der 161 Zeilen).ITranscriptUsageReaderschrumpft aufReadSessionTotalsAsync.UsageAggregateRow/UsageScope, falls nach dem Umbau ohne Konsumenten.
ReadSessionTotalsAsync bleibt: das Per-Run-Accounting in TaskRunner.ApplyUsageAsync
schreibt task_runs.tokens_* direkt am Ende eines Runs und darf nicht auf einen 11 s
dauernden Batch-Export warten.
Tests
- Ein anonymisiertes, eingechecktes Export-Fixture (JSON) für Parser und
TokenTrackerAggregator: Range-Filter, Modell-Aggregation, Task-Aggregation, Kostensummen. session_hash-Test: bekannte Session-ID → erwarteter Hash (Gegenprobe zusha256("claude\0" + id)[0..24]).- Join-Test:
task_runs-Zeilen mit Session-IDs + Fixture → erwartete USD/Effizienz pro Task. - Fail-open-Tests: Binary fehlt, Exit-Code ≠ 0, Timeout, kaputtes JSON, unbekannte
version— jeweils letztes gutes Snapshot bleibt stehen,LastErrorgesetzt. - Kein Test ruft das echte CLI auf (analog zur Regel für den
claude-CLI).ITokenTrackerClientwird gefakt. - Fakes in
Worker.TestsundUi.Testsnachziehen:IWorkerClient,WorkerHubund die erweiterten DTOs brechen die handgeschriebenen Fakes in beiden Projekten.
Offene Verifikationspunkte
- Visueller Pass für den umgebauten Analytics-Block und die Hinweis-Karte (beide Zustände: installiert / nicht installiert) — muss der Nutzer prüfen.
- Einmal echt gegen ein System ohne TokenTracker: Probe, Hinweis-Karte, Install-Button, Re-Probe, erste Auswertung.
- Nach dem Umbau
docs/explore-notes/usage-monitoring.mdfortschreiben (Abschnitte Per-run token accounting, UI surfaces, Hub surface) und den „verified against"-Commit anheben.