Letzte Verifikation: 2026-06-22 Geprüfte Dateien: 14 Projektstand: v0.1.0 (committed) Cursor Rule:
.cursor/rules/cursor-usage-dashboard.mdc
Master-Referenz für AI-Chats. Abdeckung: Hub, Analytics (Thin-Shell), Shared-Module, Backend.
| Aufgabe | Primäre Datei(en) |
|---|---|
| CSV/API-Parsing, Event-Modell, Dedupe | static/cursor-analytics/parser.js |
| KPIs, Filter, Aggregationen, Granularität | static/cursor-analytics/metrics.js |
| Chart-Rendering, Legend-Persistenz, Zoom, Marker-Annotationen | static/cursor-analytics/charts.js |
| Projekt-Marker (CRUD, Statistik, Sync, Popover) | static/cursor-analytics/markers.js |
| Analytics-UI, Live-Fetch, Toolbar, Budget | cursor-usage-analytics.html (inline <script>) |
| Live-API, Static-Serving, Event-Cache | serve.py |
| Multi-User-Konfiguration | users-config.js, serve.py (USER_TOKENS), .env |
| Navigation Hub | index.html |
| Server-Start / Live-Setup | start.ps1, setup-live.ps1 |
Muster: Analytics Thin-Shell + optionaler Hub — ein Parser in parser.js, kein Fork.
flowchart LR
subgraph entry [Einstiegspunkte]
hub[index.html]
analytics[cursor-usage-analytics.html]
end
subgraph shared [Shared Frontend]
parser[parser.js]
metrics[metrics.js]
charts[charts.js]
markers[markers.js]
end
subgraph backend [Backend]
serve[serve.py]
cursorAPI[cursor.com API]
end
hub --> analytics
analytics --> parser
analytics --> metrics
analytics --> charts
analytics --> markers
analytics -->|Live CSV| serve
analytics -->|CSV| data[data/]
serve --> cursorAPI
| Einstiegspunkt | Muster | Datenquellen | Shared-Module |
|---|---|---|---|
| Analytics | Thin-Shell | CSV, Live (Proxy), Beides (Merge) | Ja — window.CursorAnalytics |
| Hub | Static HTML | — | Nein |
| Backend | Python http.server |
Proxy zu cursor.com | — |
Kein Build-Step: Vanilla HTML/CSS/JS, Chart.js 4.4.7 + chartjs-plugin-zoom 2.2.0 + chartjs-plugin-annotation 3.1.0 + Hammer.js 2.0.8 (CDN, defer).
serve.py # Static + API-Proxy
index.html # Hub
cursor-usage-analytics.html # Analytics Thin-Shell
static/cursor-analytics/
parser.js # Event-Modell
metrics.js # Aggregationen
markers.js # Projekt-Marker
charts.js # Chart.js-Rendering
data/ # CSV-Exports + project-markers.json (gitignored)
.env # Session-Tokens (gitignored)
- Head (defer): Hammer.js → Chart.js → chartjs-plugin-zoom → chartjs-plugin-annotation
- DOMContentLoaded →
initWhenReady(): wartet aufChart(Polling 50 ms) **ensureModules():** sequentiellparser.js?v=15→metrics.js?v=15→markers.js?v=15→charts.js?v=15**syncFromServer()** (Marker) →**initMarkerUi()**→**initToolbar()**+**loadDefaultCsvs()**
Cache-Busting: Query ?v=16 auf Modul-URLs.
| Route | Methode | Zweck |
|---|---|---|
/ |
GET | Static → index.html |
/health |
GET | Token-Status, Port |
/api/summary?user= |
GET | Proxy → cursor.com/api/usage-summary |
/api/events?user=&startDate=&endDate= |
GET | Proxy → Events (paginiert, gecacht) |
/api/events |
POST | JSON-Body: { user, startDate?, endDate? } |
/api/markers |
GET | Projekt-Marker (data/project-markers.json), optional ?user= |
/api/markers |
PUT | Marker-Store speichern (JSON-Body { version, markers }) |
/* (Datei existiert) |
GET | Static aus PROJECT_DIR |
Event-Cache: In-Memory, TTL CURSOR_EVENTS_CACHE_TTL (Default 120 s), Key user:startDate:endDate.
- Ein
<main>mit Link zu Analytics - Inline-CSS, keine JS-Abhängigkeiten
| Section | ID / Selektor | Inhalt |
|---|---|---|
| Toolbar | #main-toolbar |
Datenquelle, User, CSV, Live, Export, Marker Export/Import |
| Projekt-Marker | #marker-card, #marker-table-body, #marker-charts-section |
Intervall-Statistik, Breakdown-Charts (Projekt/Kategorie), optionaler Hover-Popover ([data-marker-table-popover]) |
| Projekt-Filter | #project-filter |
Filter für Einzelanfragen-Tabelle |
| Marker-Dialog | #marker-modal, #marker-form |
CRUD (Von/Bis/Projekt/Aufgabe/Notiz) |
| Zeitraum / Anfragen | #date-range-panel |
Modus-Umschalter (data-selection-mode), Zeitraum-Presets (#time-range-group), Anfragen-Presets (#count-range-group, data-count), Custom datetime |
| Granularität | #granularity-select (nur Zeitraum-Modus) |
event / quarter / hour / day / week / month |
| Drop-Zone | #drop-zone |
Drag-and-Drop CSV (wird nach Load versteckt) |
| KPIs | #kpi-grid |
Dynamisch gerendert |
| Übersicht-Chart | #overview-section, #chart-overview-daily |
Zeitraum: Buckets nach Granularität; Anfragen: Buckets pro Event (wie Granularität „Pro Anfrage“) |
| Detail-Charts | #chart-top-cost, #chart-top-tokens, … |
8 Canvas-Elemente |
| Tabellen | #daily-table-body, #expensive-table-body, #events-table-body |
Tages-, Teuerste-, Einzelanfragen (Spalte Projekt); markierte Zeilen: data-marker-id → Marker-Popover beim Hover (abschaltbar) |
| Pagination | #events-pagination |
50 Events/Seite |
| Budget | #budget-input, #budget-panel |
Monatsbudget USD |
| Hook | Typ | Verwendung |
|---|---|---|
| `[data-source="csv | live | merge"]` |
| `[data-user="all | info | slope"]` |
| `[data-selection-mode="time | count"]` | Button |
[data-hours="N"] |
Button | Zeitraum-Preset (Stunden), nur im Modus time |
[data-all="true"] |
Button | Modus „Alle Events“ (Zeitraum) |
[data-count="N"] |
Button | Letzte N Anfragen (10–1000), nur im Modus count |
#count-from, #count-to, #count-custom-apply |
Number / Button | Bereich nach Rang (1 = neueste Anfrage), Modus countRange |
[data-count-all="true"] |
Button | Alle Anfragen (Count-Modus) |
#granularity-select |
<select> |
Aggregation für Overview + Cumulative (nur selectionMode=time) |
[data-chart-key] |
Button | Chart-Höhe 90 %, Zoom-Reset |
#status-line |
<p> |
Haupt-Status |
#load-hint |
<p> |
Lade-Details |
#marker-add-overview / [data-marker-add] |
Button | Marker-Dialog (aktuelle Datum/Uhrzeit als Start) |
#marker-export-btn, #marker-import-input |
Button / File | Marker JSON Export/Import |
[data-marker-table-popover] |
Checkbox | Tabellen-Hover-Popover ein/aus (3× synchron: Teuerste Events, Einzelanfragen, Projekt-Marker) |
[data-marker-display-host] |
Container | Chart-Marker-Steuerung (Anzeigen, Beschriftungen, Projekt-Filter) |
[data-marker-chart-visible] |
Button | Marker-Linien/Boxen in Charts ein/aus |
[data-marker-labels-visible] |
Button | Marker-Beschriftungen in Charts ein/aus |
[data-marker-project-filter] |
<select> |
Marker in Charts nach Projekt filtern |
#marker-chart-popover |
<div> |
Gemeinsamer Marker-Info-Popover (Charts + Tabellen) |
#project-filter |
<select> |
Events-Tabelle nach Projekt filtern |
Mapping in CHART_CANVAS_IDS (inline JS):
| Key | Canvas-ID | charts.js-Key |
|---|---|---|
| overview | chart-overview-daily |
renderOverviewBuckets / renderOverviewTimeline |
| topCost | chart-top-cost |
topCost |
| topTokens | chart-top-tokens |
topTokens |
| tokenTypes | chart-token-types |
tokenTypes |
| modelFamily | chart-model-family |
modelFamily |
| byHour | chart-by-hour |
byHour |
| cumulative | chart-cumulative |
renderCumulativeBuckets / renderCumulativeTimeline |
| inputOutput | chart-input-output |
inputOutput |
| cacheEfficiency | chart-cache |
cacheEfficiency |
| byWeekday | chart-weekday |
byWeekday |
| markerByProject | chart-marker-by-project |
markerByProject |
| markerByCategory | chart-marker-by-category |
markerByCategory |
| maxMode | chart-max-mode |
maxMode |
Analytics-Event (parser.js): { timestamp, dayKey, userLabel, model, kind, … costCents, source }.
Keine Monkey-Patches. CSV-Parsing ausschließlich in parser.js (parseUsageEventsCsv, normalizeApiEvent).
- Kein gemeinsames Stylesheet — Design-Tokens in
:rootincursor-usage-analytics.html - Tokens:
--bg,--surface,--surface-2,--text,--muted,--accent,--warn,--danger,--border,--radius - Analytics-Klassen:
.dashboard-grid,.kpi-grid,.drop-zone,.live-loading,.events-pagination,.marker-chart-popover - Dynamischer Zustand:
.btn--active,.btn--loading,.drop-zone--hidden,.status-error,[aria-pressed="true"]auf Chart-Höhe-Buttons
DOMContentLoaded
→ initWhenReady (poll Chart.js)
→ ensureModules (parser → metrics → markers → charts)
→ syncFromServer (Marker)
→ initMarkerUi + initToolbar
→ loadDefaultCsvs (fetch ./data/*.csv)
→ renderAll
→ filteredEvents → KPIs, Tabellen, charts.renderAll
Live-Pfad bei dataSource === 'live'|'merge':
applyRangeAndRender / live-refresh
→ fetchLiveEvents
→ GET /api/events?user=…&startDate=&endDate=
→ normalizeApiEvent (parser.js)
→ mergeEvents (bei incremental/beides)
→ renderAll
Client-Cache: liveFetchState (5 Min TTL), incremental overlap 5 Min.
python serve.py
→ load_dotenv(.env)
→ ThreadingHTTPServer(CURSOR_WEB_HOST:CURSOR_WEB_PORT)
→ CursorUsageHandler (GET/POST/PUT/OPTIONS)
Marker-Sync: GET/PUT /api/markers → data/project-markers.json (atomisches Schreiben). Client: localStorage + Server-Merge bei Start; Server gewinnt bei gleicher id und neuerem updatedAt.
Marker sind keine CSV-Daten — manuell gesetzte Metadaten zu Zeitintervallen.
{
"version": 1,
"markers": [
{
"id": "m-uuid",
"user": "info",
"start": "2026-06-20T14:30:00.000Z",
"end": null,
"project": "Cursor-Usage-Dashboard",
"task": "REFERENCE.md",
"note": "",
"createdAt": "...",
"updatedAt": "..."
}
]
}**end: null:** Intervall[start, nächster Marker)oder bis Filter-Ende (in UI mit*gekennzeichnet).**user:**info|slope|all- Statistik (
computeStats): Events im Intervall — nicht persistiert.
Chart-Annotationen: Overview + Cumulative nutzen Kategorie-Achse → Bucket-Index-Mapping via sortKey. Hover auf Annotationen öffnet #marker-chart-popover (showChartPopover). Timeline-Charts ergänzen den Chart.js-Tooltip um Projekt, Aufgabe und Notiz (markerTooltipLines in charts.js).
Gemeinsame UI-Komponente in markers.js (buildPopoverHtml, #marker-chart-popover):
| Kontext | Auslöser | API |
|---|---|---|
| Charts | Hover/Klick auf Marker-Annotation (Overview, Cumulative, Timeline) | showChartPopover() |
| Tabellen | Hover auf tr[data-marker-id] in #expensive-table-body, #events-table-body, #marker-table-body |
showTableMarkerPopover() via mountMarkerTableHover() (Analytics-HTML) |
Popover-Inhalt: Projekt, Aufgabe, Benutzer, Von/Bis, Notiz (falls gesetzt), Intervall-Statistik (Events, Tokens, Kosten), Button „Bearbeiten“ → #marker-modal.
Tabellen abschalten: Checkbox [data-marker-table-popover] (i18n: showTableMarkerPopover) — drei synchronisierte Instanzen in den Toolbars von Teuerste Events, Einzelne Anfragen und Projekt-Marker. Persistenz: cursor-marker-chart-display → Feld showTablePopover (Default true). Bei Deaktivierung wird ein sichtbarer Popover sofort geschlossen.
Chart-Marker-Anzeige: Buttons [data-marker-chart-visible], [data-marker-labels-visible], Select [data-marker-project-filter] — ebenfalls in cursor-marker-chart-display (showMarkers, showLabels, projectFilter).
Gotcha Granularität: Bei Wechsel der Granularität verschieben sich Bucket-Grenzen — Marker-Positionen in Overview/Cumulative (Zeitraum-Modus mit grober Granularität) sind Näherungen. Marker-Boxen nutzen bucketIndexRangeForInterval (Überlappung von Intervall und sichtbaren Buckets; bei Pro-Anfrage-Buckets optional User-Filter).
Anfragen-Modus: filterEventsByCount liefert die neuesten N Events oder einen Von–Bis-Bereich (countRange, 1 = neueste). Live-Fetch nutzt heuristische Zeitfenster nach Anzahl (nicht mehr pauschal „gesamter Verlauf“), nur User mit Token (/health). Beim Wechsel zurück zu Zeitraum wird der Vollcache invalidiert.
Marker sind manuell — ohne einheitliche Benennung lassen sich Projekt- und Kategorie-Charts schwer vergleichen. Empfohlenes Schema:
| Feld | Bedeutung | Beispiele |
|---|---|---|
project |
Repo, Modul, Cursor-Modus oder Projektphase | Cursor-Usage-Dashboard, Grow-Tagebuch/API, Agent, Editor |
task |
Kategorie + Kurzbeschreibung (Kategorie: Aufgabe) |
Feature: Marker-Charts, Bugfix: Login-Timeout, Analyse: fullEntryService.js |
note |
Freitext, Scope-Hinweise, Effizienz | nur 2 Dateien, gesamtes Projekt |
start / end |
Arbeitsintervall | Bei Task-Start setzen, bei Task-Ende end setzen oder nächsten Marker starten |
Kategorie-Prefix in task: Parser parseTaskCategory() erkennt Präfixe vor :, -, – oder —. Empfohlene Werte: Bugfix, Feature, Refactoring, Analyse, Dokumentation, Suche. Ohne Prefix → Gruppe „Ohne Kategorie“ in den Charts.
Modus (Agent vs. Editor): Cursor exportiert kein Agent/Editor-Feld in Events. Modus als project markieren (z. B. Agent, Editor) oder in note festhalten — dann über Marker-Statistik und Projekt-Charts auswertbar.
Workflow: Vor größeren Aufgaben Marker setzen (Marker setzen / Overview-Chart), nach Abschluss end setzen. 2–3 Wochen konsequent → belastbare Vergleiche (Modus, Kategorie, Projektphase).
Charts: aggregateEventsByMarkerDimension() gruppiert gefilterte Events per getMarkerForEvent (keine Doppelzählung bei überlappenden Intervallen). UI: Doughnut/Bar „Tokens & Kosten nach Projekt/Kategorie“ in #marker-charts-section.
Siehe parser.js → normalizeEvent(). Wichtige Felder: timestamp, userLabel, model, kind, maxMode, totalTokens, costCents, isIncluded, source (csv|api).
maxMode: CSV-Spalte Max Mode (Yes/No); Live-API → Boolean. UI: Filter in Einzelanfragen, Chart „Max Mode“ in Detail-Charts (aggregateByMaxMode in metrics.js).
Grundprinzip: Das Dashboard berechnet keine Kosten aus Token-Mengen und Modell-Preisen. Jede Anfrage erhält ein costCents-Feld aus der Cursor-Quelle (CSV-Spalte Cost oder Live-API). KPIs, Charts, Budget und Marker-Statistik summieren diese Werte — sie leiten sie nicht neu ab.
Ohne Gewähr: Alle angezeigten Kosten, Summen, Budget-Vergleiche und Prognosen sind rein informativ und ohne Gewähr. Sie sind keine offizielle Abrechnung von Cursor, können von der tatsächlichen Rechnung abweichen und dienen nicht steuerlichen oder vertraglichen Zwecken. Abweichungen sind u. a. möglich durch: veraltete oder unvollständige CSV-Exports, Parse-Fehler, API-Änderungen, Merge/Dedupe, Included-/Chargeable-Logik, Rundung, unvollständige Live-Daten oder die Monats-Prognose-Heuristik (Ø/Tag × 30). Maßgeblich ist ausschließlich die Abrechnung im Cursor-Dashboard bzw. bei Cursor.
Implementierung: parseCostCents() (CSV + API-Fallback) und normalizeApiEvent().
| Quelle | Eingabe | Regel |
|---|---|---|
| CSV | Spalte Cost (optional), Kind |
Siehe Tabelle unten |
| Live-API | 1. chargedCents → 2. tokenUsage.totalCents → 3. usageBasedCosts (String wie CSV) |
Gerundet auf ganze Cent; costDisplay aus API-String oder $X.XX |
**parseCostCents(costRaw, kindRaw) — Text → Cent:**
Cost / Kind |
costCents |
isIncluded |
Anzeige |
|---|---|---|---|
leer, Included, Kind included |
0 | ja | Original oder „Included“ |
No charge, Errored |
0 | nein | Originaltext |
Dollar-String (z. B. $0.04, 0,04) |
Math.round(USD × 100) |
nein | Originaltext |
| nicht parsebar | 0 | nein | Originaltext |
API-Zusatz: isIncluded auch wenn costCents === 0 und Kind INCLUDED enthält oder !isChargeable. isChargeable = API-Flag oder costCents > 0.
Anzeige in „Einzelne Anfragen“: Bei Included → costDisplay (nicht $0.00); sonst USD-Format aus costCents / 100 (formatEventCost in Analytics-HTML).
Alle folgenden Werte nutzen dieselbe Event-Liste nach Toolbar-Filter (Datenquelle, User, Zeitraum/Anfragen-Modus), sofern nicht anders vermerkt:
| UI / Metrik | Berechnung | Besonderheit |
|---|---|---|
| KPI Gesamtkosten | sum(costCents) |
Included = 0 $ |
| KPI Ø/Tag, Prognose/Monat | Kosten ÷ Tage im Filter × 30 | Heuristik, kein Abrechnungswert von Cursor |
| Charts (Overview, Top-Kosten, kumuliert, …) | Bucket-/Modell-Summen aus costCents |
in metrics.js / charts.js |
| Budget (aktueller Monat) | sum(costCents) aller Events ab Monatsanfang |
Ignoriert Zeitraum-Toolbar; nutzt geladene Events (CSV + ggf. Live/Merge) |
| Projekt-Marker | sum(costCents) im Intervall [start, end) |
in markers.js → computeStats |
| Marker-Charts (Projekt/Kategorie) | Summe Tokens/Kosten pro Event-Marker-Zuordnung | markers.js → aggregateEventsByMarkerDimension |
| Max-Mode-Chart | Summe nach maxMode Yes/No |
metrics.js → aggregateByMaxMode |
| Filter Min. Kosten ($) | Events mit costCents ≥ eingegebener USD × 100 |
Nur Tabellenfilter, ändert keine Berechnung |
| Einstellung | Speicher | Wirkung |
|---|---|---|
| Monatsbudget ($) | localStorage cursor-analytics-monthly-budget-usd (Default 70) |
Vergleich „Ausgaben Monat vs. Budget“ — kein Faktor für costCents pro Event |
| Min. Kosten ($) | Session (Input #events-min-cost) |
Filter in Einzelanfragen-Tabelle |
Nicht unterstützt (Stand v0.1): Eigene $/Token-Raten, manuelle Kosten pro Modell, Schätzung fehlender CSV-Cost-Spalten aus Token-Zahlen, Umrechnung Währung. Fehlt Cost in CSV → costCents = 0 (Tokens werden trotzdem gezählt).
- CSV: Kosten wie im Export von Cursor.
- Live: Kosten wie von der inoffiziellen API geliefert (
chargedCents/usageBasedCosts). - Beides (Merge):
parser.mergeEventsdedupliziert pereventDedupeKey; bei gleichem Key gewinnt der spätere Eintrag in der Liste — im Modus Beides typischerweise Live vor CSV (mergeEvents([allCsvEvents(), allLiveEvents()])). Keine Mittelung oder Neuberechnung.
Pflicht: Date, Input (w/o Cache Write), Cache Read, Output Tokens, Total Tokens. Optional: Kind, Model, Max Mode, Input (w/ Cache Write), Cost.
Upstream: https://cursor.com/api/usage-summary, https://cursor.com/api/dashboard/get-filtered-usage-events. Auth: Cookie WorkosCursorSessionToken.
| Key | Default | Zweck |
|---|---|---|
cursor-analytics-monthly-budget-usd |
70 | Monatsbudget (Analytics) |
cursor-analytics-granularity |
hour |
Overview-Aggregation (Zeitraum-Modus) |
cursor-analytics-selection-mode |
time |
time = Zeitraum-Filter, count = letzte N Anfragen |
cursor-analytics-count |
50 |
Default-Anzahl im Anfragen-Modus |
cursor-analytics-time-range |
hours / 24 h |
Aktiver Zeitraum-Filter (mode, hours, optional customFrom/customTo) |
cursor-analytics-custom-range |
heute−2d / heute | Von/Bis-Zeitraum (Analytics, JSON { customFrom, customTo }) |
cursor-analytics-chart-visibility |
{} |
Legend-Sichtbarkeit pro Chart-Key |
cursor-marker-chart-display |
siehe unten | Chart-Marker-Sichtbarkeit + Tabellen-Hover-Popover |
cursor-usage-markers-v1 |
{ version: 1, markers: [] } |
Projekt-Marker (Primary Client-Cache) |
cursor-event-chart-markers-v1 |
— | Legacy-Key (Migration → cursor-usage-markers-v1) |
cursor-marker-chart-display (JSON): { showMarkers: true, showLabels: true, projectFilter: 'all', showTablePopover: true } — geladen via loadMarkerChartDisplay() / saveMarkerChartDisplay() in markers.js.
Server-Datei: data/project-markers.json (liegt unter gitignored data/).
| Änderung an … | Prüfen auch … |
|---|---|
| CSV-Spalten / Parser-Logik | parser.js |
| User-IDs / CSV-Pfade | users-config.js, serve.py USER_TOKENS, .env, config/users.example.json |
| Demo-Daten / Samples | scripts/generate_demo_data.py, samples/, Marker-Seed in serve.py |
| Design-Tokens / Theme | cursor-usage-analytics.html (:root) |
| Chart.js-Version (CDN) | cursor-usage-analytics.html Head |
| API-Response-Format | parser.js normalizeApiEvent, serve.py Proxy |
serve.py-Routen |
Analytics fetch() (PROXY_BASE = ''), Marker /api/markers |
| Marker-Schema / Sync | markers.js, serve.py, cursor-usage-analytics.html |
| Marker-Popover / Tabellen-Hover | markers.js, charts.js (markerTooltipLines), i18n.js, cursor-usage-analytics.html |
- Kosten in Analytics: übernommen/summiert aus CSV/API — ohne Gewähr, keine offizielle Abrechnung (siehe §9 Kostenberechnung)
- Kein Prompt-Text in CSV/API — „Top teuerste Prompts“ nur als Event-Liste, nicht semantisch
- Keine Git-/Commit-Metriken (Tokens pro Commit, Tokens pro geänderter Datei) — würde Cursor-Hooks, Git-Integration oder manuelle Effizienz-Felder am Marker erfordern; bewusst nicht im Scope v0.1
- Agent/Editor/Auto als Modus nur über Marker-Konvention, nicht automatisch aus Events
- Enterprise Admin API nicht implementiert
- Anfragen-Modus: Pro-Anfrage-Bucket-Charts wie Zeitraum + Granularität
event - Sehr große Event-Mengen → Browser-Performance
- Session-Tokens laufen ab (401 → Hinweis in
serve.py) file://-Öffnung: Modul-Laden und CSV-Fetch schlagen fehl →python serve.pynötig
- Setup:
[README.md](../README.md) - Entwickler-Referenz:
[docs/REFERENCE.md](REFERENCE.md) - Cursor Rule (Scope):
[.cursor/rules/cursor-usage-dashboard.mdc](../.cursor/rules/cursor-usage-dashboard.mdc)