This repo is connected to Vercel for automatic deployments:
- Production: https://vibetracking.dev (deploys from
mainbranch) - Preview: Automatic preview deployments for all branches/PRs
- Vercel Project:
lfglabs/vibetracking.dev - Dashboard: https://vercel.com/lfglabs/vibetracking.dev
Environment variables are configured in Vercel dashboard for both Production and Preview environments:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYVIBETRACKING_URLGITHUB_PAT(optional) - GitHub Personal Access Token for higher API rate limits when fetching org details
Vibetracking.dev is a web application that tracks and visualizes AI coding tool usage statistics. It aggregates usage data from multiple AI coding assistants:
- OpenCode - Open-source AI coding CLI
- Claude Code - Anthropic's CLI coding assistant
- Codex - OpenAI's coding CLI tool
- Cursor - AI-powered code editor (via API sync)
- Gemini - Google's AI coding assistant
- Amp - Sourcegraph's AI coding assistant
- Droid - Android Studio AI assistant
The app presents user statistics in an engaging, gamified interface with:
- Global leaderboards ranked by estimated API spending
- Personal dashboards with activity heatmaps
- Model usage breakdowns and charts
- Fun comparisons (tokens = novels, marathons, etc.)
- Streak tracking and achievement badges
Target Users: Developers who want to understand and share their AI coding tool usage patterns - tokens consumed, models used, session activity, and estimated API spending.
IMPORTANT: The CLI is based on tokscale and we aim to keep it that way.
The native CLI (packages/cli/ and packages/core/) is derived from the tokscale project by @junhoyeo. Our strategy is to:
- Minimize CLI changes - Make as few modifications to the core CLI as possible
- Focus on the frontend - Our value-add is the web application, UI/UX, and viral features
- Stay upstream-compatible - Keep the CLI architecture aligned with tokscale for easier merging of improvements
- DO NOT refactor or restructure the CLI/core packages unless absolutely necessary
- DO NOT change the data formats or parsing logic without strong justification
- PREFER fixing bugs upstream in tokscale when possible
- KEEP changes to
packages/cli/andpackages/core/minimal and well-documented - FOCUS development effort on
src/(the Next.js web app)
- Bug fixes that can't wait for upstream
- Adding support for new AI tools (following existing patterns)
- Integration changes needed for vibetracking.dev specifically (auth, submission)
krakow/
├── src/ # Next.js web application
│ ├── app/ # App Router pages & API routes
│ ├── components/ # React components
│ ├── lib/ # Utilities & Supabase client
│ └── middleware.ts # URL rewriting (/@user → /user/user)
├── packages/
│ ├── cli/ # Bun-based CLI tool (TypeScript)
│ │ └── src/
│ │ ├── cli.ts # Main CLI entry point
│ │ ├── auth.ts # Device flow authentication
│ │ ├── submit.ts # Data submission to API
│ │ ├── native.ts # Native module bindings
│ │ └── cursor.ts # Cursor API sync
│ └── core/ # Native Rust module (NAPI-RS)
│ └── src/
│ ├── lib.rs # Main exports & NAPI bindings
│ ├── sessions/ # Tool-specific parsers
│ ├── pricing/ # LiteLLM & OpenRouter pricing
│ ├── scanner.rs # File system scanner
│ └── aggregator.rs # Data aggregation
├── supabase/migrations/ # Database schema
├── public/ # Static assets
└── scripts/ # Manual test scripts
| Component | Technology |
|---|---|
| Web Framework | Next.js 16 + React 19 |
| Database | Supabase (PostgreSQL) |
| Authentication | GitHub OAuth via Supabase Auth |
| Styling | Tailwind CSS v4 |
| Charts | Recharts v3 |
| CLI Runtime | Bun |
| CLI Framework | Commander.js |
| Native Core | Rust + NAPI-RS v3 |
| JSON Parsing | simd-json (SIMD-accelerated) |
| Parallelism | Rayon (Rust) |
| Deployment | Vercel |
┌─────────────────────────────────────────────────────────────┐
│ Local Machine │
├─────────────────────────────────────────────────────────────┤
│ OpenCode: ~/.local/share/opencode/storage/message/**/*.json │
│ Claude: ~/.claude/projects/**/conversation.jsonl │
│ Codex: ~/.codex/tasks/**/*.json │
│ Gemini: ~/.gemini/gemini-cli/conversations/**/*.json │
│ Amp: ~/.ampcode/sessions/**/*.json │
│ Droid: ~/Library/.../googleAiStudio/history/*.json │
│ Cursor: ~/.vibetracking/cursor-cache/usage.csv (synced) │
└───────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ CLI (vibetracking submit) │
├─────────────────────────────────────────────────────────────┤
│ Phase 1 (Parallel): │
│ - Parse local sources (Rust native, parallel) │
│ - Sync Cursor usage via API │
│ - Fetch model pricing (LiteLLM + OpenRouter) │
│ │
│ Phase 2 (Finalize): │
│ - Apply pricing to all messages │
│ - Combine local + Cursor data │
│ - Generate TokenContributionData graph │
└───────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Browser: /import#encoded_data │
├─────────────────────────────────────────────────────────────┤
│ User logs in with GitHub OAuth in browser │
│ Data decoded from URL hash, submitted to /api/import │
└───────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Supabase (PostgreSQL) │
├─────────────────────────────────────────────────────────────┤
│ users, daily_activity, token_usage, user_stats │
└─────────────────────────────────────────────────────────────┘
-
users - User profiles
id,github_id,username,display_name,avatar_url,companyis_anonymous,anonymous_id(for non-GitHub users)
-
daily_activity - Heatmap data
user_id,date,tool,message_count,session_count,total_tokens,cost- Tools:
claude_code,codex,cursor,opencode,claude,gemini,amp,droid
-
token_usage - Model breakdown
user_id,date,tool,modelinput_tokens,output_tokens,cache_read_tokens,cache_creation_tokens,reasoning_tokens,cost
-
user_stats - Aggregated statistics
total_tokens,total_cost,total_sessions,favorite_model,favorite_toollongest_streak_days,current_streak_days, activity dates
- Row Level Security (RLS) enabled on all tables
- Public read access for profiles and stats
- Write access restricted to authenticated users (own data)
The project follows an atomic design pattern for chart components:
components/ui/- Pure UI primitives (no data fetching, receive data via props)components/[domain]/- Data-connected components that use UI primitives
Reusable chart primitives are located in src/components/ui/charts/:
| Component | Description | Wraps |
|---|---|---|
LineChart |
Multi-line time series charts | Recharts LineChart |
BarChart |
Horizontal/vertical bar charts | Recharts BarChart |
AreaChart |
Single area chart with gradient | Recharts AreaChart |
PieChart |
Donut/pie charts | Recharts PieChart |
StackedAreaChart |
Stacked percentage area charts | Recharts AreaChart |
ChartTooltip |
Consistent tooltip styling | - |
ChartCard |
Card wrapper with title slot | - |
src/components/ui/charts/constants.ts exports:
MODEL_COLORS- Color palette for model chartsTOOL_COLORS- Colors for each AI toolTOOL_LABELS- Display names for toolsAXIS_STYLE- Consistent axis stylingGRID_STYLE- Grid line stylingTOOLTIP_STYLE- Tooltip container stylinggetColorFromString()- Generate consistent color from string
// Import from UI primitives
import { LineChart, ChartCard, TOOL_COLORS } from "@/components/ui/charts";
// Data component handles fetching/transformation
export function UsageByToolChart({ dailyActivity, unit }) {
// Transform data...
const lines = tools.map(tool => ({
dataKey: tool,
color: TOOL_COLORS[tool],
label: TOOL_LABELS[tool],
}));
return (
<ChartCard title="Usage by IDE" rightSlot={<TimeframeSelector />}>
<LineChart data={chartData} lines={lines} xAxisKey="date" />
</ChartCard>
);
}DO NOT create new chart components directly in dashboard/. Instead:
- Check if an existing UI primitive fits your need
- If not, create a new primitive in
components/ui/charts/ - Then create the data-connected component in
components/dashboard/
| Path | Purpose |
|---|---|
app/page.tsx |
Homepage with leaderboard |
app/user/[username]/page.tsx |
User profile page |
app/import/page.tsx |
Data import flow |
app/api/submit/route.ts |
CLI data submission endpoint |
app/api/sync/route.ts |
Legacy CLI sync endpoint |
app/api/leaderboard/route.ts |
Leaderboard data |
components/dashboard/ |
Charts (heatmap, model usage, etc.) |
lib/pricing.ts |
Model pricing calculations |
lib/supabase/ |
Supabase client & middleware |
| File | Purpose |
|---|---|
cli.ts |
Main CLI entry, Commander setup |
native.ts |
Native Rust module bindings |
cursor.ts |
Cursor API sync (fetch usage CSV) |
table.ts |
CLI table formatting |
spinner.ts |
Loading spinner UI |
| File | Purpose |
|---|---|
lib.rs |
NAPI exports, main entry point |
scanner.rs |
Parallel file system scanning |
aggregator.rs |
Aggregate messages by date |
sessions/opencode.rs |
OpenCode JSON parser |
sessions/claudecode.rs |
Claude Code JSONL parser |
sessions/codex.rs |
Codex JSON parser |
sessions/cursor.rs |
Cursor CSV parser |
sessions/gemini.rs |
Gemini JSON parser |
sessions/amp.rs |
Amp JSON parser |
sessions/droid.rs |
Droid JSON parser |
pricing/litellm.rs |
LiteLLM pricing API |
pricing/openrouter.rs |
OpenRouter pricing API |
pricing/lookup.rs |
Model pricing lookup with aliases |
pricing/cache.rs |
Disk cache for pricing data |
- Node.js 22+ (for native module compatibility)
- Bun (CLI runtime)
- Rust 1.88+ (for native core compilation)
- pnpm (workspace manager)
- Supabase project (for database)
- GitHub OAuth app (for authentication)
Create .env.vibetracking with:
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=xxx
SUPABASE_SERVICE_ROLE_KEY=xxx # For server-side operations# Ensure Node.js 22+ is active (if using nvm)
nvm use 22
# Install dependencies
pnpm install
# Build native core (required first time)
cd packages/core && pnpm build
# Run the web app
pnpm dev # Uses dotenvx to load .env.vibetracking
# CLI development (in packages/cli/)
cd packages/cli
bun run src/cli.ts models # Show model usage report
bun run src/cli.ts submit # Submit data to APIcd packages/core
# Build for current platform
pnpm build
# Run tests
pnpm test| Command | Purpose | Time |
|---|---|---|
/e2e/test-quick |
Fast smoke test | ~2 min |
/e2e/test-homepage |
Homepage (auth + unauth) | ~5 min |
/e2e/test-import |
Import page flow | ~5 min |
/e2e/test-profile |
Profile page tests | ~8 min |
/e2e/test-team |
Team creation and profile | ~10 min |
/e2e/test-full-suite |
Complete regression | ~20 min |
/test-cli |
CLI manual testing | Manual |
/test-onboarding |
Quick onboarding flow | ~3 min |
cd packages/core
pnpm test # Run AVA tests for native modulecd packages/cli
# Run CLI (opens browser with encoded data)
bun run src/cli.ts
# Cursor integration
bun run src/cli.ts cursor login
bun run src/cli.ts cursor logout
bun run src/cli.ts cursor statusE2E tests use Playwright MCP tools. Credentials from Bitwarden when needed.
-
Start the dev server:
pnpm dev # Server runs at http://localhost:3000 -
Access test credentials via Bitwarden (when needed):
# List available projects dotenvx run -f .env.local -- ~/bin/bws project list # Get credentials for testing dotenvx run -f .env.local -- ~/bin/bws secret get <SECRET_ID> -o json | jq -r '.value'
| Command | What it Tests |
|---|---|
/e2e/test-quick |
Smoke test: homepage → import → registration → profile |
/e2e/test-homepage |
Homepage layout, copy button, leaderboard, auth states |
/e2e/test-import |
Error states, data preview, registration flow |
/e2e/test-profile |
Profile sections, responsive design, share button |
/e2e/test-full-suite |
All of the above in dependency order (creates 2 test users) |
vibetracking # Scan data and open browser to import- Run
vibetrackingin your terminal - If Cursor is installed, opens browser to download usage CSV from cursor.com
- CLI scans local AI tool data (Claude Code, Codex, Gemini, Amp, Droid)
- Opens browser to
/import#encoded_data - User logs in with GitHub (if needed) and confirms import
Cursor usage data is imported via browser download:
- CLI detects Cursor installation
- Opens browser to
cursor.com/api/dashboard/export-usage-events-csv - CSV downloads automatically (user must be logged into Cursor in browser)
- CLI detects the download and imports it
- If auto-detection fails, user can drag-drop the CSV file
# Cache location for Cursor data
~/.vibetracking/cursor-cache/usage.csv| Endpoint | Method | Purpose | Auth |
|---|---|---|---|
/api/import |
POST | Browser-based data import | GitHub OAuth |
/api/leaderboard |
GET | Get leaderboard data | None |
/og/user/[username] |
GET | Open Graph image | None |
/auth/callback |
GET | GitHub OAuth callback | - |
The middleware (src/middleware.ts) handles URL patterns:
/@username→/user/username(Twitter-style URLs)/username→/user/username(clean URLs, except reserved paths)
Reserved paths that are NOT rewritten:
/api/*,/import,/auth/*,/_next/*,/og/*
- Create/modify components in
src/components/ - Update API routes in
src/app/api/ - Add database migrations in
supabase/migrations/ - Test locally with
pnpm dev - Run E2E tests with Playwright MCP
- Modify code in
packages/cli/src/ - Test manually:
bun run src/cli.ts <command>
- Modify Rust code in
packages/core/src/ - Rebuild:
cd packages/core && pnpm build - Run tests:
pnpm test - Test via CLI:
cd packages/cli && bun run src/cli.ts models
- Create new parser in
packages/core/src/sessions/<tool>.rs - Add to
sessions/mod.rsexports - Update
scanner.rsto scan new tool's file locations - Update
lib.rsto include in parsing functions - Add tool to database constraints (migration)
| File | What it does |
|---|---|
src/lib/pricing.ts |
Web app model pricing |
src/lib/utils.ts |
Number formatting, date utilities |
packages/core/src/pricing/lookup.rs |
Native pricing lookup with aliases |
packages/core/src/pricing/litellm.rs |
LiteLLM API pricing fetch |
packages/cli/src/native.ts |
Native module bindings |
packages/cli/src/credentials.ts |
Token storage |
-
"Native module required" error
- Build the native core:
cd packages/core && pnpm build - Ensure Rust 1.88+ is installed:
rustup update stable
- Build the native core:
-
"No data found" on submit
- Check if AI tools have created session files
- Verify file paths in scanner.rs match your system
-
Cursor sync failing
- Check ~/.vibetracking/cursor-credentials.json exists
- Verify access token is still valid
-
Database connection issues
- Verify
.env.vibetrackinghas correct Supabase credentials - Check Supabase project is active
- Verify
-
E2E tests failing
- Ensure dev server is running on port 3000
- Check Playwright MCP is available
- Verify Bitwarden access token is configured
The native core fetches pricing from two sources:
-
LiteLLM (
/model_prices/current_prices)- Primary source for most models
- Cached to
~/.cache/vibetracking/pricing-litellm.json
-
OpenRouter (
/api/v1/models/{id}/endpoints)- Fallback for OpenRouter-specific models
- Fetches author pricing (direct from model provider)
- Cached to
~/.cache/vibetracking/pricing-openrouter.json
Cache TTL: 24 hours
The CLI uses the npm optional dependencies pattern (like esbuild/swc):
- npm:
vibetracking(CLI JS package) - npm:
@starknetid/vibetracking-core(main package with optional deps) - npm:
@starknetid/vibetracking-core-*(7 platform-specific binary packages)
When users install vibetracking, npm pulls only the matching platform package.
- Bump versions:
packages/core/package.json(and itsoptionalDependencies)packages/core/npm/*/package.jsonpackages/cli/package.json(and dependency on@starknetid/vibetracking-core)
- Commit + push the version change to
main. - Tag:
git tag cli-vX.Y.Z && git push origin cli-vX.Y.Z - Wait for CI to build artifacts (
.github/workflows/release.yml). - Download artifacts:
gh run download <RUN_ID> -D ./artifacts - Authenticate npm:
npm login(opens browser for verification)- If using a token:
npm config set //registry.npmjs.org/:_authToken=...
- Publish all packages (bash 5 on macOS):
/opt/homebrew/bin/bash ./scripts/publish-all.sh- It will open your browser for verification; approve it.
- Verify that the right packages are published:
npm view vibetracking@X.Y.Z versionnpm view @starknetid/vibetracking-core@X.Y.Z versionnpm view @starknetid/vibetracking-core-darwin-arm64@X.Y.Z versionnpm view @starknetid/vibetracking-core-darwin-x64@X.Y.Z versionnpm view @starknetid/vibetracking-core-darwin-universal@X.Y.Z versionnpm view @starknetid/vibetracking-core-linux-x64-gnu@X.Y.Z versionnpm view @starknetid/vibetracking-core-linux-arm64-gnu@X.Y.Z versionnpm view @starknetid/vibetracking-core-win32-x64-msvc@X.Y.Z versionnpm view @starknetid/vibetracking-core-win32-arm64-msvc@X.Y.Z versionbunx vibetracking@X.Y.Z --version
See the publish-cli skill for detailed steps and troubleshooting. Trigger terms: publish, release, npm, cli, version, tag.