Files
ClaudeDo/docs/superpowers/specs/2026-08-24-tokentracker-usage-analytics-design.md
T

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:

  1. ClaudeOAuthUsageClient (170 Zeilen) — pollt https://api.anthropic.com/api/oauth/usage für die 5h-/7d-Limits. Speist Pill, Gauges, UsageGate und UsageThrottle.
  2. TranscriptUsageReader (161 Zeilen) — parst ~/.claude/projects/**/*.jsonl und 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.json hat 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 wir totals/by_model aus 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 aus TokenTrackerState. ModelUsageRowDto bekommt ein zusätzliches double? CostUsd. Der Scope-Split claudedo / other kommt jetzt aus der session_hash-Mitgliedschaft in task_runs statt aus der Pfad-Heuristik des alten Readers — das ist genauer, weil Worktree-Pfade und die GUID-Fallbacks von project_key keine Rolle mehr spielen.
  • GetTaskUsage(from, to) bleibt task_runs-basiert (sofort verfügbar, funktioniert ohne TokenTracker). TaskUsageRowDto wird um double? CostUsd, int? Retries, bool? Productive, bool? OneShot erweitert. Gefüllt werden diese Felder über die SessionId der Runs des Tasks → Hash → passende TokenTracker-Session; ohne Snapshot bleiben sie null.
  • 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() → startet npm i -g tokentracker-cli; Fortschritt läuft über den bestehenden OperationProgress-Kanal, Ausgabezeilen über WorkerLog.

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 installierennpm 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.ReadAsync samt Datums-/Modell-/Scope-Aggregation, mtime/Length-Cache, requestId-Dedupe und dem 106-MB-Scan (~120 der 161 Zeilen).
  • ITranscriptUsageReader schrumpft auf ReadSessionTotalsAsync.
  • 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 zu sha256("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, LastError gesetzt.
  • Kein Test ruft das echte CLI auf (analog zur Regel für den claude-CLI). ITokenTrackerClient wird gefakt.
  • Fakes in Worker.Tests und Ui.Tests nachziehen: IWorkerClient, WorkerHub und 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.md fortschreiben (Abschnitte Per-run token accounting, UI surfaces, Hub surface) und den „verified against"-Commit anheben.