Skip to content

Commit 777c767

Browse files
ofershapcursoragent
andcommitted
feat: add insights page, analytics API, enhanced dashboard with spend trends
- Add /insights page with DAU, model adoption, efficiency rankings, MCP usage, file extensions, client versions - Add /api/analytics, /api/team-spend, /api/model-costs, /api/groups endpoints - Enhance dashboard with time range picker, billing cycle progress, daily spend chart, spend breakdown, model cost table - Enhance user detail page with activity profile and improved charts - Add Analytics API support to collector (DAU, models, agent edits, tabs, MCP, file extensions, client versions) - Add billing groups collection from /teams/groups endpoint with cycle dates - Add model name shortening (format-utils) and date formatting (date-utils) - Improve anomaly detection with refined thresholds and trend analysis - Update cursor-client with Analytics API endpoints and groups endpoint - Update README with complete setup guide including detect step - Update cursor rules with current architecture and conventions - Add VS Code tasks for common workflows Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 4fce880 commit 777c767

40 files changed

Lines changed: 4350 additions & 956 deletions

.cursor/rules/coding-conventions.mdc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,8 @@ alwaysApply: false
1313
- Use `import type` for type-only imports
1414

1515
```typescript
16-
// CORRECT
1716
import { getDb } from "@/lib/db";
1817
import type { Anomaly } from "@/lib/types";
19-
20-
// WRONG — will break Next.js build
21-
import { getDb } from "./db.js";
2218
```
2319

2420
## Database
@@ -27,25 +23,29 @@ import { getDb } from "./db.js";
2723
- Use `getDb()` singleton — it initializes schema on first call
2824
- Use transactions for batch inserts: `db.transaction(() => { ... })()`
2925
- SQLite column names use snake_case; TypeScript types use camelCase
26+
- Key-value metadata stored in `metadata` table via `setMetadata()`/`getMetadata()`
3027

3128
## API Routes
3229

3330
- All API routes use `export const dynamic = "force-dynamic"` (data is always fresh from SQLite)
3431
- Cron endpoint requires `x-cron-secret` header or `?secret=` query param
3532
- Use Next.js App Router async params pattern: `{ params }: { params: Promise<{ id: string }> }`
33+
- Most GET endpoints support `?days=N` query param for time range filtering
3634

3735
## Components
3836

3937
- Dashboard pages: server component (page.tsx) fetches data, passes to client component (*-client.tsx)
4038
- Charts are always client components ("use client")
4139
- Use Tailwind CSS with zinc color palette (dark theme)
4240
- No shadcn/ui installed — components are hand-rolled
41+
- Model names displayed via `shortModel()` from `src/lib/format-utils.ts` with full name in tooltip
4342

4443
## Anomaly Detection
4544

4645
- Three layers: thresholds → zscore → trends (all in `src/lib/anomaly/`)
4746
- `detector.ts` orchestrates all three, deduplicates by `userEmail:type:metric` key
4847
- New anomalies get inserted; existing ones that no longer fire get auto-resolved
48+
- Run via `npm run detect` CLI or as part of `POST /api/cron`
4949

5050
## Testing
5151

.cursor/rules/cursor-api.mdc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ Poll at most once per hour (data aggregated hourly). Max 30 days per request.
4242
### POST /teams/user-spend-limit
4343
Body: `{ email, hardLimitDollars }`
4444

45+
### POST /teams/groups
46+
Returns `{ groups: [{ id, name, memberCount, spendCents, members: [{ email, joinedAt }] }], subscriptionCycleStart, subscriptionCycleEnd }`
47+
Also provides billing cycle dates (cycle_start, cycle_end) stored in the metadata table.
48+
4549
## Analytics API Endpoints (separate key: CURSOR_ANALYTICS_API_KEY)
4650

4751
All use `startDate`/`endDate` params (formats: `YYYY-MM-DD`, `7d`, `30d`, `today`, `yesterday`). Max 30 days. Default: last 7 days.

.cursor/rules/project-context.mdc

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Open-source Cursor IDE usage monitoring, anomaly detection, and alerting for ent
1111

1212
- Next.js (App Router, Turbopack) + TypeScript strict
1313
- SQLite via better-sqlite3 (zero-config, file at `data/tracker.db`)
14-
- Recharts for charts, Tailwind CSS for styling
14+
- Recharts for charts, Tailwind CSS for styling (zinc dark theme)
1515
- Vitest for tests, ESLint 9 flat config, Prettier
1616
- Docker (multi-stage Dockerfile + docker-compose.yml)
1717

@@ -27,37 +27,60 @@ Cursor Enterprise APIs → Collector (src/lib/collector.ts) → SQLite (src/lib/
2727
Dashboard (src/app/ pages)
2828
```
2929

30-
Single cron endpoint `POST /api/cron` does: collect → detect → alert in one call.
30+
Two CLI entry points:
31+
- `npm run collect` — fetch data from Cursor APIs into SQLite
32+
- `npm run detect` — run anomaly detection + alerting on stored data
33+
34+
Single cron endpoint `POST /api/cron` does both: collect → detect → alert in one call.
3135

3236
## Key Files
3337

3438
| File | Purpose |
3539
|------|---------|
3640
| `src/lib/types.ts` | All shared types + DetectionConfig + DEFAULT_CONFIG |
3741
| `src/lib/cursor-client.ts` | Cursor API client (Admin + Analytics), pagination, rate-limit retry |
38-
| `src/lib/db.ts` | SQLite schema, all queries, dashboard stats, user stats |
39-
| `src/lib/collector.ts` | Data collection pipeline (members, daily usage, spending, events) |
42+
| `src/lib/db.ts` | SQLite schema, all queries, dashboard stats, analytics, user stats |
43+
| `src/lib/collector.ts` | Data collection pipeline (members, daily usage, spending, events, analytics, groups) |
4044
| `src/lib/anomaly/detector.ts` | Orchestrator — runs all 3 detection layers, deduplicates |
4145
| `src/lib/anomaly/thresholds.ts` | Layer 1: static limits (spend, requests, tokens) |
4246
| `src/lib/anomaly/zscore.ts` | Layer 2: statistical z-score vs team mean |
4347
| `src/lib/anomaly/trends.ts` | Layer 3: personal spikes, drift above P75, model shift |
4448
| `src/lib/incidents.ts` | Incident lifecycle: create, alert, acknowledge, resolve + MTTD/MTTI/MTTR |
4549
| `src/lib/alerts/slack.ts` | Slack webhook with block-kit messages |
4650
| `src/lib/alerts/email.ts` | SMTP email with HTML templates |
51+
| `src/lib/date-utils.ts` | Date formatting helpers (formatDateShort, formatDateTick, formatDateLabel) |
52+
| `src/lib/format-utils.ts` | Model name shortening (MODEL_MAP regex → short labels) |
4753
| `src/app/api/cron/route.ts` | Main cron endpoint (collect + detect + alert) |
4854

4955
## Dashboard Pages
5056

51-
- `/` — Team overview: stat cards, spend bar chart, usage line chart, members table
52-
- `/users/[email]` — Per-user: token timeline, model pie chart, feature breakdown, anomaly history
57+
- `/` — Team overview: stat cards, spend bar chart, daily spend trend, spend breakdown by user, members table with search/sort, time range picker (24h/3d/7d/14d/30d), billing cycle progress
58+
- `/insights` — Analytics: DAU chart, model adoption, model efficiency rankings, MCP tool usage, file extensions, client versions
59+
- `/users/[email]` — Per-user: token timeline, model pie chart, feature breakdown, activity profile, anomaly history
5360
- `/anomalies` — MTTD/MTTI/MTTR metrics, open incidents (acknowledge/resolve), anomaly table
5461
- `/settings` — Configurable detection thresholds
5562

63+
## API Endpoints
64+
65+
| Endpoint | Method | Purpose |
66+
|----------|--------|---------|
67+
| `/api/cron` | POST | Collect + detect + alert (requires x-cron-secret header) |
68+
| `/api/stats` | GET | Full dashboard data (supports `?days=N`) |
69+
| `/api/analytics` | GET | Analytics data: DAU, models, agent edits, tabs, MCP, etc. |
70+
| `/api/team-spend` | GET | Daily team spend breakdown |
71+
| `/api/model-costs` | GET | Model cost breakdown (users, avg/total spend, requests) |
72+
| `/api/groups` | GET | Billing groups with member counts and spend |
73+
| `/api/anomalies` | GET | Anomaly timeline (supports `?days=N`) |
74+
| `/api/users/[email]` | GET | Per-user statistics (supports `?days=N`) |
75+
| `/api/incidents/[id]` | PATCH | Acknowledge or resolve incident |
76+
| `/api/settings` | GET/PUT | Detection configuration |
77+
5678
## Database Tables
5779

58-
members, daily_usage, spending, usage_events, anomalies, incidents, config, collection_log
80+
members, daily_usage, spending, usage_events, anomalies, incidents, config, collection_log, metadata, daily_spend, billing_groups, billing_group_members, analytics_dau, analytics_model_usage, analytics_agent_edits, analytics_tabs, analytics_mcp, analytics_file_extensions, analytics_client_versions
5981

6082
## Important Caveats
6183

6284
- API response shapes may not exactly match the live Cursor API. If real responses differ, update `src/lib/types.ts` and `src/lib/cursor-client.ts`.
6385
- Trend detection can produce duplicate dedup keys (spike + drift both emit `trend:tokens` for same user) — first one wins, second is silently dropped.
86+
- CLI scripts (`npm run collect`, `npm run detect`) do NOT auto-load `.env` — use `source .env && export CURSOR_ADMIN_API_KEY` or run via the Next.js dev server cron endpoint instead.

.env.example

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
CURSOR_ADMIN_API_KEY=key_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
2-
CURSOR_ANALYTICS_API_KEY=
32

43
SLACK_WEBHOOK_URL=
54

.vscode/tasks.json

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "Dev Server",
6+
"type": "shell",
7+
"command": "npm run dev -- --turbopack -p 3456",
8+
"problemMatcher": [],
9+
"isBackground": true,
10+
"presentation": {
11+
"group": "cursor-tracker",
12+
"panel": "dedicated",
13+
"showReuseMessage": false,
14+
"clear": true,
15+
"reveal": "always",
16+
"focus": false
17+
},
18+
"icon": {
19+
"id": "globe",
20+
"color": "terminal.ansiCyan"
21+
}
22+
},
23+
{
24+
"label": "Collect Data",
25+
"type": "shell",
26+
"command": "npm run collect",
27+
"problemMatcher": [],
28+
"presentation": {
29+
"group": "cursor-tracker",
30+
"panel": "dedicated",
31+
"showReuseMessage": false,
32+
"clear": true,
33+
"reveal": "always"
34+
},
35+
"icon": {
36+
"id": "cloud-download",
37+
"color": "terminal.ansiGreen"
38+
}
39+
},
40+
{
41+
"label": "Run Anomaly Detection",
42+
"type": "shell",
43+
"command": "npm run detect",
44+
"problemMatcher": [],
45+
"presentation": {
46+
"group": "cursor-tracker",
47+
"panel": "dedicated",
48+
"showReuseMessage": false,
49+
"clear": true,
50+
"reveal": "always"
51+
},
52+
"icon": {
53+
"id": "warning",
54+
"color": "terminal.ansiYellow"
55+
}
56+
},
57+
{
58+
"label": "Collect + Detect",
59+
"dependsOrder": "sequence",
60+
"dependsOn": ["Collect Data", "Run Anomaly Detection"],
61+
"problemMatcher": [],
62+
"icon": {
63+
"id": "zap",
64+
"color": "terminal.ansiMagenta"
65+
}
66+
},
67+
{
68+
"label": "Type Check",
69+
"type": "shell",
70+
"command": "npm run typecheck",
71+
"problemMatcher": ["$tsc"],
72+
"presentation": {
73+
"reveal": "always",
74+
"panel": "shared"
75+
},
76+
"group": "build",
77+
"icon": {
78+
"id": "check",
79+
"color": "terminal.ansiBlue"
80+
}
81+
},
82+
{
83+
"label": "Run Tests",
84+
"type": "shell",
85+
"command": "npm test",
86+
"problemMatcher": [],
87+
"presentation": {
88+
"reveal": "always",
89+
"panel": "shared"
90+
},
91+
"group": "test",
92+
"icon": {
93+
"id": "beaker",
94+
"color": "terminal.ansiGreen"
95+
}
96+
}
97+
]
98+
}

README.md

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,13 @@ Anomaly Detected ──→ Alert Sent ──→ Acknowledged ──→ Resolved
8080

8181
### Web Dashboard
8282

83-
| Page | What you see |
84-
| ------------------ | ------------------------------------------------------------------------ |
85-
| **Team Overview** | Total spend, requests, tokens, top consumers, daily trends |
86-
| **User Drilldown** | Per-user token timeline, model breakdown, feature usage, anomaly history |
87-
| **Anomalies** | Open incidents, MTTD/MTTI/MTTR metrics, full anomaly timeline |
88-
| **Settings** | Configurable detection thresholds — no code changes needed |
83+
| Page | What you see |
84+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
85+
| **Team Overview** | Stat cards, spend by user, daily spend trend, spend breakdown, members table with search/sort, billing cycle progress, time range picker |
86+
| **Insights** | DAU chart, model adoption trends, model efficiency rankings (cost/precision), MCP tool usage, file extensions, client versions |
87+
| **User Drilldown** | Per-user token timeline, model breakdown, feature usage, activity profile, anomaly history |
88+
| **Anomalies** | Open incidents, MTTD/MTTI/MTTR metrics, full anomaly timeline |
89+
| **Settings** | Configurable detection thresholds — no code changes needed |
8990

9091
---
9192

@@ -123,16 +124,27 @@ CURSOR_ADMIN_API_KEY=your_admin_api_key
123124
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../xxx
124125

125126
# Optional
126-
CURSOR_ANALYTICS_API_KEY=your_analytics_key # for DAU and model breakdowns
127+
CURSOR_ANALYTICS_API_KEY=your_analytics_key # for Insights page (DAU, model breakdowns, MCP)
127128
CRON_SECRET=your_secret_here # protects the cron endpoint
128-
SMTP_HOST=smtp.gmail.com # for email alerts
129+
DASHBOARD_PASSWORD=your_password # optional basic auth for the dashboard
130+
131+
# Email alerts (optional)
132+
SMTP_HOST=smtp.gmail.com
129133
SMTP_PORT=587
130134
SMTP_USER=you@gmail.com
131135
SMTP_PASS=app_password
136+
SMTP_FROM=cursor-tracker@yourcompany.com
132137
ALERT_EMAIL_TO=team-lead@company.com
133138
```
134139

135-
### 3. Collect your first data
140+
### 3. Start the dashboard
141+
142+
```bash
143+
npm run dev
144+
# Open http://localhost:3000
145+
```
146+
147+
### 4. Collect your first data
136148

137149
```bash
138150
npm run collect
@@ -148,14 +160,19 @@ You should see:
148160
Usage events: 12,847
149161
```
150162

151-
### 4. Start the dashboard
163+
### 5. Run anomaly detection
164+
165+
After collecting data, run detection separately:
152166

153167
```bash
154-
npm run dev
155-
# Open http://localhost:3000
168+
npm run detect
156169
```
157170

158-
### 5. Set up recurring collection
171+
This analyzes the stored data against all three detection layers and sends alerts for any anomalies found.
172+
173+
> **Note:** `npm run collect` only fetches data. `npm run detect` only runs detection. The cron endpoint (`POST /api/cron`) does both in one call.
174+
175+
### 6. Set up recurring collection
159176

160177
Trigger the cron endpoint hourly (via crontab, GitHub Actions, or any scheduler):
161178

@@ -206,7 +223,9 @@ Cursor Enterprise APIs
206223
├── /teams/members
207224
├── /teams/spend
208225
├── /teams/daily-usage-data
209-
└── /teams/filtered-usage-events
226+
├── /teams/filtered-usage-events
227+
├── /teams/groups
228+
└── /analytics/team/*
210229
211230
212231
┌─────────────┐ ┌──────────┐ ┌───────────────────┐
@@ -232,26 +251,30 @@ All detection thresholds are configurable via the Settings page or the API:
232251

233252
| Setting | Default | What it does |
234253
| -------------------- | ------- | ------------------------------------------------- |
235-
| Max spend per cycle | $50.00 | Alert when a user exceeds this in a billing cycle |
236-
| Max requests per day | 500 | Alert on excessive daily request count |
254+
| Max spend per cycle | $200 | Alert when a user exceeds this in a billing cycle |
255+
| Max requests per day | 200 | Alert on excessive daily request count |
237256
| Max tokens per day | 5M | Alert on excessive daily token consumption |
238-
| Z-score multiplier | 2.0 | How many standard deviations above mean to flag |
239-
| Z-score window | 14 days | Historical window for statistical comparison |
257+
| Z-score multiplier | 2.5 | How many standard deviations above mean to flag |
258+
| Z-score window | 7 days | Historical window for statistical comparison |
240259
| Spike multiplier | 3.0x | Alert when today > N× user's personal average |
241260
| Drift days above P75 | 3 | Consecutive days above team P75 to flag |
242261

243262
---
244263

245264
## API Endpoints
246265

247-
| Endpoint | Method | Description |
248-
| --------------------- | ------- | --------------------------------------------- |
249-
| `/api/cron` | POST | Collect + detect + alert (use with scheduler) |
250-
| `/api/stats` | GET | Dashboard statistics |
251-
| `/api/anomalies` | GET | Anomaly timeline |
252-
| `/api/users/[email]` | GET | Per-user statistics |
253-
| `/api/incidents/[id]` | PATCH | Acknowledge or resolve incident |
254-
| `/api/settings` | GET/PUT | Detection configuration |
266+
| Endpoint | Method | Description |
267+
| --------------------- | ------- | --------------------------------------------------- |
268+
| `/api/cron` | POST | Collect + detect + alert (use with scheduler) |
269+
| `/api/stats` | GET | Dashboard statistics (`?days=7`) |
270+
| `/api/analytics` | GET | Analytics data: DAU, models, MCP, etc. (`?days=30`) |
271+
| `/api/team-spend` | GET | Daily team spend breakdown |
272+
| `/api/model-costs` | GET | Model cost breakdown by users and spend |
273+
| `/api/groups` | GET | Billing groups with member counts |
274+
| `/api/anomalies` | GET | Anomaly timeline (`?days=30`) |
275+
| `/api/users/[email]` | GET | Per-user statistics (`?days=30`) |
276+
| `/api/incidents/[id]` | PATCH | Acknowledge or resolve incident |
277+
| `/api/settings` | GET/PUT | Detection configuration |
255278

256279
---
257280

@@ -286,15 +309,16 @@ npm run lint # Lint + format check
286309

287310
Requires a **Cursor Enterprise** plan. The tool uses these endpoints:
288311

289-
| Endpoint | Auth | What it provides |
290-
| ----------------------------------- | ----------------- | ------------------------------------------- |
291-
| `GET /teams/members` | Admin API key | Team member list |
292-
| `POST /teams/spend` | Admin API key | Per-user spending data |
293-
| `POST /teams/daily-usage-data` | Admin API key | Daily usage metrics |
294-
| `POST /teams/filtered-usage-events` | Admin API key | Detailed usage events with model/token info |
295-
| `GET /analytics/team/*` | Analytics API key | DAU, model usage breakdowns (optional) |
312+
| Endpoint | Auth | What it provides |
313+
| ----------------------------------- | ----------------- | -------------------------------------------- |
314+
| `GET /teams/members` | Admin API key | Team member list |
315+
| `POST /teams/spend` | Admin API key | Per-user spending data |
316+
| `POST /teams/daily-usage-data` | Admin API key | Daily usage metrics |
317+
| `POST /teams/filtered-usage-events` | Admin API key | Detailed usage events with model/token info |
318+
| `POST /teams/groups` | Admin API key | Billing groups + cycle dates |
319+
| `GET /analytics/team/*` | Analytics API key | DAU, model usage, MCP, tabs, etc. (optional) |
296320

297-
Rate limit: 250 requests/minute. The collector handles rate limiting with automatic retry.
321+
Rate limit: 20 requests/minute (Admin API), 100 requests/minute (Analytics API). The collector handles rate limiting with automatic retry.
298322

299323
---
300324

0 commit comments

Comments
 (0)