ai-lens
v0.8.5
Published
Centralized session analytics for AI coding tools
Readme
AI Lens
Hook-based analytics for AI coding sessions. Captures events from Claude Code and Cursor, normalizes them to a unified format, queues locally, and ships to a centralized server with a web dashboard.
Hook fires → capture.js → normalize → queue.jsonl → sender.js → POST /api/events → server → dashboardDeveloper Setup (client)
Run the init command on each developer machine:
npx -y ai-lens initThis will:
- Detect installed AI tools (Claude Code, Cursor)
- Copy client files to
~/.ai-lens/client/ - Configure hooks in
~/.claude/settings.jsonand/or~/.cursor/hooks.json
Re-running is safe — it updates outdated hooks and skips current ones.
Configure the server URL and optionally filter projects:
# In your shell profile (~/.zshrc, ~/.bashrc)
export AI_LENS_SERVER_URL=http://your-server:13300
export AI_LENS_PROJECTS="~/work/, ~/projects/" # optional, default: allClaude Code — ~/.claude/settings.json (hooks section):
{
"hooks": {
"SessionStart": [{ "matcher": "", "hooks": [{ "type": "command", "command": "node ~/.ai-lens/client/capture.js" }] }],
"UserPromptSubmit": [{ "matcher": "", "hooks": [{ "type": "command", "command": "node ~/.ai-lens/client/capture.js" }] }],
"PreToolUse": [{ "matcher": "", "hooks": [{ "type": "command", "command": "node ~/.ai-lens/client/capture.js" }] }],
"PostToolUse": [{ "matcher": "", "hooks": [{ "type": "command", "command": "node ~/.ai-lens/client/capture.js" }] }],
"Stop": [{ "matcher": "", "hooks": [{ "type": "command", "command": "node ~/.ai-lens/client/capture.js" }] }]
}
}Cursor — ~/.cursor/hooks.json:
{
"version": 1,
"hooks": {
"sessionStart": [{ "command": "node ~/.ai-lens/client/capture.js" }],
"beforeSubmitPrompt": [{ "command": "node ~/.ai-lens/client/capture.js" }],
"postToolUse": [{ "command": "node ~/.ai-lens/client/capture.js" }],
"afterFileEdit": [{ "command": "node ~/.ai-lens/client/capture.js" }],
"afterShellExecution": [{ "command": "node ~/.ai-lens/client/capture.js" }],
"afterMCPExecution": [{ "command": "node ~/.ai-lens/client/capture.js" }],
"stop": [{ "command": "node ~/.ai-lens/client/capture.js" }],
"sessionEnd": [{ "command": "node ~/.ai-lens/client/capture.js" }]
}
}Server Setup
Docker (production)
docker compose up -dStarts three containers:
- nginx — reverse proxy with basic auth, port
13300 - app — Node.js Express server with dashboard
- postgres — PostgreSQL 16 database
Dashboard: http://your-server:13300
Default credentials:
| User | Password | Purpose |
|------|----------|---------|
| collector | secret-collector-token-2026-ai-lens | Client sender (automatic via AI_LENS_AUTH_TOKEN) |
| meta | meta | Browser / dashboard access |
Local development
npm install --prefix server # Install server deps (auto-runs via prestart)
npm start # Express on port 3000, SQLite at ~/.ai-lens-server/data.dbSQLite is used when DATABASE_URL is not set. PostgreSQL is used in Docker via DATABASE_URL=postgresql://....
Dashboard
React + TypeScript SPA with session timelines, tool breakdowns, adoption trends, and per-developer analytics.
cd dashboard
npm install
npm run dev # Vite dev server with HMR (proxies API to localhost:3000)Production build (served by Express as static files):
npm run build:dashboardTech: Vite, Tailwind CSS, Nivo charts, TanStack Query, react-router-dom.
API
POST /api/events
Batch insert events. Deduplicates by event_id (ON CONFLICT DO NOTHING) — safe to re-send.
Headers: X-Developer-Git-Email, X-Developer-Name, Authorization: Basic <base64>
Body: [{ source, session_id, type, project_path, timestamp, data, raw, event_id }]
Response: { received, skipped, deduplicated }GET /api/sessions
List sessions. Query params: developer_id, source, days (default 30).
GET /api/sessions/:id
Session detail with all events, plan segments, and metadata.
GET /api/developers
List all developers.
GET /api/dashboard/*
Aggregate endpoints for dashboard charts (stats, trends, tool usage, etc.).
Event Types
| Type | Source | Description |
|------|--------|-------------|
| SessionStart | Both | Session opened |
| SessionEnd | Both | Session closed |
| UserPromptSubmit | Both | User sent a prompt |
| PostToolUse | Both | Tool execution completed |
| PostToolUseFailure | Both | Tool execution failed |
| Stop | Both | Agent stopped |
| PreCompact | Both | Context compaction triggered |
| PlanModeStart | Claude Code | Entered plan mode |
| PlanModeEnd | Claude Code | Exited plan mode (plan content in raw payload) |
| SubagentStart | Both | Subagent spawned |
| SubagentStop | Both | Subagent finished |
| FileEdit | Cursor | File edited |
| ShellExecution | Cursor | Shell command executed |
| MCPExecution | Cursor | MCP tool executed |
| AgentResponse | Cursor | Agent response |
| AgentThought | Cursor | Agent reasoning |
Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| PORT | 3000 (local), 13300 (Docker) | Server port |
| DATABASE_URL | (unset = SQLite) | PostgreSQL connection string |
| AI_LENS_SERVER_URL | http://localhost:3000 | Client → server endpoint |
| AI_LENS_AUTH_TOKEN | collector:secret-collector-token-2026-ai-lens | Client auth (user:password) |
| AI_LENS_PROJECTS | (all) | Comma-separated project paths to monitor (~ supported) |
Client Data
Stored in ~/.ai-lens/:
| File | Purpose |
|------|---------|
| client/ | Installed client files (capture.js, sender.js, config.js) |
| queue.jsonl | Pending events |
| queue.sending.jsonl | Events being sent (atomic rename as mutex) |
| sender.log | Sender activity log |
| session-paths.json | Session-to-project path cache |
Development
npm test # Run all tests (vitest, 204 tests)
npm run test:watch # Watch mode
npm run dev:dashboard # Dashboard dev serverTests use in-memory SQLite via initTestDb().
Deployment
GitLab CI (.gitlab-ci.yml) on push to main:
rsyncto deploy hostdocker compose down && docker compose up -d --build- Health check
Data Migration
Sync local SQLite data to a remote PostgreSQL server:
node scripts/sync-to-remote.js # Default remote
node scripts/sync-to-remote.js http://custom:13300 # Custom URLSafe to re-run — deduplicates by event_id.
Requirements
- Node.js 20+
- Docker + Docker Compose (for production deployment)
