This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
CLIProxy Dashboard là hệ thống monitoring gồm hai phần chính:
Theo dõi API usage từ CLIProxy (AI API proxy) theo thời gian thực:
- Collector (Python/Flask): Polls CLIProxy Management API mỗi 5 phút, tính cost, lưu vào PostgreSQL
- Frontend (React/Nginx): Hiển thị analytics, cost breakdown, credential tracking
- PostgreSQL: Self-hosted DB, auto-initialized từ
init-db/schema.sql - PostgREST: REST API layer cho frontend đọc (anonymous, SELECT-only via
web_anonrole)
Data Flow:
CLIProxy API → Collector (Python/Flask) → PostgreSQL:5432
Browser → Nginx:8417 → /rest/v1/* → PostgREST:3000 → PostgreSQL (reads)
→ /api/collector/* → collector:5001 (writes + triggers)
Các plugin mở rộng khả năng của Claude Code, gửi telemetry về dashboard:
plugin/claude-skills-tracker(git submodule): HookPostToolUsetrênSkilltool, thu thập metrics (tokens, duration, model, project) và gửi về/api/collector/skill-events. Plugin được phân phối qua marketplace của dashboard.
Plugin Data Flow:
Claude Code → Skill tool called
→ PostToolUse hook fires (scripts/on-skill-use.mjs)
→ POST /api/collector/skill-events → collector:5001
→ INSERT INTO skill_runs (PostgreSQL)
→ Hiển thị trên tab Skills của dashboard
⚠️ Lưu ý về plugin metrics: HookPostToolUsefires ngay khi Skill tool trả về prompt — TRƯỚC khi Claude thực thi skill. Vì vậytokens_usedthường phản ánh turn gọi skill (nhỏ), không phải toàn bộ execution. Xem chi tiết:plugin/claude-skills-tracker/README.md#known-limitations
Docker dev convention:
docker-compose.ymlis the base runtime config.docker-compose.override.ymlis the local dev override and is loaded automatically bydocker compose.When editing source code only: prefer bind mounts +
docker compose restart <service>(or local process likenpm run dev/python main.py). Do not rebuild Docker images unless Dockerfile, base image, or dependencies changed.
Frontend (requires postgres + postgrest running in Docker):
docker compose up -d postgres postgrest # Starts with docker-compose.override.yml automatically
cd frontend
npm install
POSTGREST_HOST_PORT=8418 npm run dev # Start Vite dev server on localhost:5173Collector (local testing):
cd collector
python -m venv venv
source venv/bin/activate # or `venv\Scripts\activate` on Windows
pip install -r requirements.txt
python main.py # Requires DATABASE_URL env varStart all services:
docker compose up -dView logs:
docker compose logs -f # All services
docker compose logs -f collector # Collector only
docker compose logs -f frontend # Frontend only
docker compose logs -f postgres # PostgreSQL onlyCheck health:
docker compose psRestart services:
docker compose restart collector
docker compose restart frontendAccess dashboard:
http://localhost:8417
Schema auto-applied from init-db/schema.sql on first postgres container boot.
⚠️ Schema Migration Rule — QUAN TRỌNG:init-db/schema.sqlchỉ chạy một lần duy nhất khi postgres volume được tạo lần đầu. Nếu thêm cột mới vào schema, BẮT BUỘC phải thêm migration SQL tương ứng vàocollector/migrations/để collector tự chạy khi khởi động.Khi thay đổi schema (thêm/sửa cột, bảng):
- Cập nhật
init-db/schema.sql(cho fresh install)- Tạo file
collector/migrations/NNNN_description.sqlvớiALTER TABLE ... ADD COLUMN IF NOT EXISTS ...- Collector sẽ tự apply migrations chưa chạy khi startup (xem
collector/db.py→run_migrations())Không làm điều này = production DB thiếu cột, collector crash khi INSERT.
Core Tables:
usage_snapshots: Raw snapshots collected every 5 minutesmodel_usage: Per-model breakdown of each snapshot (FK → usage_snapshots.id CASCADE DELETE)daily_stats: Daily aggregated statistics (upserted daily)model_pricing: Pricing config (USD per 1M tokens), supports wildcard pattern matchingcredential_usage_summary: OAuth credential status (singleton row, id=1)credential_daily_stats: Daily credential usage breakdown
PostgREST Access:
web_anonrole has SELECT-only on all tables- PostgREST uses
web_anonwhen no Authorization header present - nginx and Vite proxy both strip
Authorizationandapikeyheaders → PostgREST always uses anonymous role
Core Components:
-
Flask API Server (port 5001, Waitress WSGI):
/api/collector/health— Health check/api/collector/trigger— Manual sync trigger
-
Background Scheduler (APScheduler):
- Polls CLIProxy API every
COLLECTOR_INTERVAL_SECONDS(default: 300s) - Calculates usage deltas, stores snapshots + daily_stats
- Polls CLIProxy API every
-
PostgreSQL Client (
collector/db.py):PostgreSQLClientwrapspsycopg2.pool.ThreadedConnectionPool- Mimics supabase-js Python SDK interface (
.table().select().eq().execute()) - Handles JSONB column auto-wrapping via
psycopg2.extras.Json - INSERT uses
RETURNING *to get auto-generated IDs
Critical Implementation Details:
- Delta Calculation: Cumulative snapshots from CLIProxy → daily deltas by subtracting previous snapshot
- Restart Detection: If new value < old value, treat new value as delta (handles CLIProxy restarts)
- Timezone Handling:
TIMEZONE_OFFSET_HOURSenv var (default: 7 for UTC+7); all date boundaries calculated in local time, stored as UTC
Main Components:
App.jsx: Date range selection, data fetching via supabase-js → PostgRESTDashboard.jsx: All visualization cardslib/supabase.js: PostgREST client — useswindow.location.originas URL,'anon'as key (JWT never sent)
Date Range Logic (App.jsx):
- Today/Yesterday → query
daily_statsexact date, show delta - Multi-day ranges → aggregate multiple
daily_statsrows, show total
Key Libraries:
@supabase/supabase-js— PostgREST queries (FK embedding viamodel_usage(...))recharts— Charts- React 18 + Vite
Required:
DB_PASSWORD: PostgreSQL passwordCLIPROXY_URL: CLIProxy Management API URL (host.docker.internal:PORTfrom Docker)CLIPROXY_MANAGEMENT_KEY: CLIProxy management secret
Optional:
COLLECTOR_INTERVAL_SECONDS: Polling interval (default: 300)TIMEZONE_OFFSET_HOURS: UTC offset (default: 7)
Services:
postgres: PostgreSQL 16, auto-initialized frominit-db/schema.sql, volumepostgres_datapostgrest: PostgREST v12.2.3, reads from postgres, anonymous roleweb_anoncollector: Python Flask, writes to postgres via psycopg2, port 5001 (internal only)frontend: Nginx on port 8417, proxies/rest/v1/→ postgrest,/api/collector/→ collector
Images are published to GHCR (ghcr.io/leolionart/cliproxy-*) via GitHub Actions. Use docker compose pull to update.
- Model name matched against
model_patterninmodel_pricing(wildcard) - Cost = (input_tokens / 1M) × input_price + (output_tokens / 1M) × output_price
MODEL_PRICING_DEFAULTSincollector/main.pyseeded on first run
To update pricing: Edit model_pricing table directly via psql or update MODEL_PRICING_DEFAULTS and restart collector.
Collector Can't Connect to CLIProxy:
- Verify CLIProxy has
remote-management.allow-remote: true - Check
CLIPROXY_MANAGEMENT_KEYmatches CLIProxy'ssecret - Use
host.docker.internal(mapped to host gateway in docker-compose)
Dashboard Shows No Data:
- Wait 5 minutes for first collection cycle
- Check:
docker compose logs -f collector - Ensure all services are
healthy:docker compose ps
PostgREST JWT Errors:
- Both nginx and Vite proxy strip
Authorization/apikeyheaders - If seeing JWT errors, check those proxy configs are correct
Date Range Showing Wrong Data:
- Verify
TIMEZONE_OFFSET_HOURSmatches your timezone daily_statsmust have entries for the date range
Skill Tracker: tokens = 0 / model = NULL:
tokens_used = 0là bình thường với hầu hết skills (xem Known Limitations trong plugin README)model = NULLở data cũ: bug đã fix trong v1.0.1, data mới sẽ có model- Nếu
ON CONFLICTerror trong postgres logs: bảngskill_runsthiếu UNIQUE constraint trênevent_uid— chạy:ALTER TABLE skill_runs ADD CONSTRAINT skill_runs_event_uid_key UNIQUE (event_uid);
Thư mục plugin/ chứa các git submodule — mỗi submodule là một plugin Claude Code độc lập:
plugin/
└── claude-skills-tracker/ # git submodule: leolionart/claude-skills-tracker
├── hooks/hooks.json # PostToolUse hook registration
├── scripts/on-skill-use.mjs # hook script (Node.js, zero deps)
└── README.md
Collector nhận events từ plugin qua:
POST /api/collector/skill-events
Body: { events: [{ skill_name, session_id, tokens_used, ... }] }
Ghi vào bảng skill_runs (PostgreSQL). Schema của bảng này được quản lý bởi migration trong collector/migrations/.
Để thêm plugin mới:
- Tạo repo riêng với cấu trúc tương tự
claude-skills-tracker - Thêm làm submodule:
git submodule add <url> plugin/<name> - Nếu cần endpoint collector mới: thêm route vào
collector/main.pyvà migration tương ứng