Track and analyze AI coding tool usage across your team. Supports multiple providers with a modular architecture.
- Dashboard: Token consumption, costs, model breakdown, top users
- User Analytics: Per-user usage history, model preferences, trends
- Pivot Table: Sortable/filterable view of all users with detailed metrics
- Multi-Provider: Mix and match Claude Code, Cursor, or add your own
- Automated Sync: Cron jobs for continuous data fetching
- CSV Import: Manual import when APIs are unavailable or for backfills
| Provider | Data Source | Features |
|---|---|---|
| Claude Code | Anthropic Admin API | Token usage, costs, model breakdown, API key mapping |
| Cursor | Cursor Admin API or CSV | Token usage, costs, model breakdown |
| GitHub Commits | GitHub App webhook + API | AI-attributed commit tracking (Co-Authored-By detection) |
Each provider is optional—configure only the ones you use.
vercel storage create postgresThis automatically sets POSTGRES_URL in your environment.
- Go to Google Cloud Console → APIs & Services → Credentials
- Create an OAuth 2.0 Client ID (Web application)
- Add redirect URI:
https://your-app.vercel.app/api/auth/callback/google - Copy the Client ID and Client Secret
Set these in Vercel project settings (Settings → Environment Variables):
| Variable | Description |
|---|---|
GOOGLE_CLIENT_ID | From step 3 |
GOOGLE_CLIENT_SECRET | From step 3 |
NEXT_PUBLIC_DOMAIN | Email domain to restrict access (e.g., sentry.io) |
BETTER_AUTH_SECRET | Run: openssl rand -base64 32 |
CRON_SECRET | Run: openssl rand -hex 32 |
Add credentials for the providers you want to use:
| Variable | Provider |
|---|---|
ANTHROPIC_ADMIN_KEY | Claude Code (how to get) |
CURSOR_ADMIN_KEY | Cursor (how to get) |
GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, GITHUB_APP_INSTALLATION_ID, GITHUB_WEBHOOK_SECRET | GitHub Commits (how to get) |
Redeploy to apply environment variables. Migrations run automatically on build.
npm install # Create .env.local with your credentials cat > .env.local << 'EOF' POSTGRES_URL=postgres://... BETTER_AUTH_SECRET=your-secret-here GOOGLE_CLIENT_ID=your-client-id GOOGLE_CLIENT_SECRET=your-client-secret NEXT_PUBLIC_DOMAIN=yourcompany.com CRON_SECRET=your-cron-secret # Providers (optional) ANTHROPIC_ADMIN_KEY=sk-admin-... CURSOR_ADMIN_KEY=... EOF npm run cli db:migrate npm run devOpen http://localhost:3000.
Note: Add http://localhost:3000/api/auth/callback/google to your Google OAuth redirect URIs for local development.
Claude Code usage is tracked via the Anthropic Admin API.
- Go to Anthropic Console → Settings → Admin API keys
- Click Create Key (requires org admin access)
- Copy the key (starts with
sk-admin-)
- Schedule: Daily at 6 AM UTC
- Data: Token counts, model, cost, API key ID
- User Mapping: API keys are mapped to emails via the Anthropic API. Unmapped keys appear in the UI for manual assignment.
npm run cli sync anthropic --days 7 npm run cli backfill anthropic --from 2025-01-01 --to 2025-06-01Cursor usage can be imported via API or CSV.
- Go to Cursor Team Settings
- Request admin API access from Cursor support
- Set
CURSOR_ADMIN_KEYin your environment
Sync Behavior:
- Schedule: Hourly
- Data: Token counts, model, user email, cost
npm run cli sync cursor --days 7Faster for large historical imports.
- Export from Cursor team dashboard → Usage/Analytics → Export CSV
- Import:
npm run cli import:cursor-csv /path/to/export.csvTrack AI-attributed commits across your organization by detecting Co-Authored-By headers from Claude, Cursor, Copilot, and other AI tools.
-
Go to GitHub → Your Organization → Settings → Developer settings → GitHub Apps → New GitHub App
-
Configure basic info:
- GitHub App name:
Abacus Commit Tracker(must be unique across GitHub) - Homepage URL: Your Abacus deployment URL (e.g.,
https://abacus.yourcompany.com)
- GitHub App name:
-
Configure webhook:
- Webhook URL:
https://your-app.vercel.app/api/webhooks/github(replace with your actual domain) - Webhook secret: Generate with
openssl rand -hex 32and save it for later
- Webhook URL:
-
Set permissions (under "Permissions & events"):
- Repository permissions:
- Contents: Read-only (required to fetch commit history)
- Metadata: Read-only (automatically granted)
- Subscribe to events:
- Check Push (this is what triggers the webhook)
- Repository permissions:
-
Configure installation access:
- Where can this GitHub App be installed?: Select "Only on this account"
- This restricts the app to your organization only (recommended for internal tools)
-
Click Create GitHub App
-
After creation, on the app settings page:
- Note the App ID (shown near the top)
- Scroll down and click Generate a private key (downloads a
.pemfile)
-
Install the app on your organization:
- Go to Install App in the left sidebar
- Click Install next to your organization
- Choose All repositories or select specific repos
- After installing, note the Installation ID from the URL:
github.com/organizations/ORG/settings/installations/INSTALLATION_ID
# GitHub App credentials (required for production) GITHUB_APP_ID=123456 GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----" GITHUB_APP_INSTALLATION_ID=12345678 GITHUB_WEBHOOK_SECRET=your-webhook-secret # Or for local development/testing (fine-grained token) GITHUB_TOKEN=github_pat_...Note: The private key must be the full PEM content. In Vercel, you can paste the entire key including newlines, or escape newlines as \n.
For local development, use a fine-grained personal access token (not classic tokens):
- Go to GitHub → Settings → Developer settings → Fine-grained tokens
- Generate new token with:
- Repository access: Select specific repos or "All repositories"
- Permissions: Contents (read-only)
- Set
GITHUB_TOKENin.env.local
Fine-grained tokens are scoped to exactly what's needed and don't require org-level access.
- Real-time: Webhook receives push events instantly
- Backfill: Cron job runs every 6 hours to fill gaps (90-day target)
- Detection: Identifies commits with AI attribution patterns
Detected patterns:
| Pattern | Tool |
|---|---|
Co-Authored-By: Claude <*@anthropic.com> | Claude Code |
🤖 Generated with [Claude Code] | Claude Code |
Co-Authored-By: Codex <*> | Codex |
Co-Authored-By: GitHub Copilot <*> | GitHub Copilot |
Co-Authored-By: Copilot <*> | GitHub Copilot |
Author: copilot-swe-agent[bot] | GitHub Copilot |
Co-Authored-By: Cursor <*> | Cursor |
Co-Authored-By: Windsurf <*> | Windsurf |
Co-Authored-By: Codeium <*> | Windsurf |
npm run cli github:sync getsentry/sentry --dry-run # Test detection (no database needed) npm run cli github:status # Check sync state npm run cli github:sync getsentry/sentry --days 30 # Sync specific repo npm run cli backfill github --from 2024-10-01 # Backfill all repos# Database npm run cli db:migrate # Run pending migrations # Sync (API-based) npm run cli sync # Sync all providers (last 7 days) npm run cli sync anthropic --days 30 npm run cli sync cursor --days 7 # Backfill (historical data) npm run cli backfill anthropic --from 2025-01-01 --to 2025-06-01 npm run cli backfill cursor --from 2025-01-01 --to 2025-06-01 npm run cli backfill github --from 2024-10-01 # GitHub only needs --from npm run cli backfill:complete anthropic # Mark backfill as complete npm run cli backfill:reset cursor # Reset backfill status # CSV Import npm run cli import:cursor-csv <file> # API Key Mappings (Claude Code) npm run cli mappings # List current mappings npm run cli mappings:sync # Sync from Anthropic API npm run cli mappings:fix # Interactive unmapped key assignment # Data Analysis npm run cli gaps # Check for gaps in usage data npm run cli gaps anthropic npm run cli gaps cursor # Status npm run cli anthropic:status # Show Claude Code sync state npm run cli cursor:status # Show Cursor sync state npm run cli github:status # Show GitHub commits sync state npm run cli stats # Database statistics # GitHub-specific npm run cli github:sync <repo> [--days N] # Sync a specific repoVercel cron jobs keep data current:
| Endpoint | Schedule | Purpose |
|---|---|---|
/api/cron/sync-anthropic | Daily 6 AM UTC | Sync recent Claude Code usage |
/api/cron/sync-cursor | Hourly | Sync recent Cursor usage |
/api/cron/backfill-anthropic | Every 6 hours | Backfill historical data |
/api/cron/backfill-cursor | Every 6 hours | Backfill historical data |
/api/cron/backfill-github | Every 6 hours | Backfill GitHub commits (90 days) |
Note: GitHub commits are primarily synced via webhook (real-time). The backfill cron catches any missed events and fills historical data.
Requires CRON_SECRET to be set.
src/ ├── app/ │ ├── page.tsx # Main dashboard │ ├── users/ # Users list and profiles │ ├── status/ # Sync status page │ └── api/ │ ├── auth/ # Authentication (better-auth) │ └── cron/ # Cron job endpoints ├── lib/ │ ├── queries.ts # Database queries │ ├── sync/ # Provider sync modules │ │ ├── anthropic.ts │ │ ├── cursor.ts │ │ ├── github.ts # GitHub commits tracking │ │ └── index.ts │ └── utils.ts └── scripts/ └── cli.ts # CLI tool - Create
src/lib/sync/your-provider.tsimplementing the sync interface - Add cron routes in
src/app/api/cron/ - Add CLI commands in
scripts/cli.ts - Update the status page
Each provider should:
- Store data in
usage_recordswith a uniquetoolidentifier - Aggregate by date/email/model before inserting
- Handle deduplication via the existing upsert logic
| Variable | Description |
|---|---|
NEXT_PUBLIC_SENTRY_DSN | Sentry DSN for error tracking |
Apache 2.0 - see LICENSE
