Calcula cuánto habría costado generar un repositorio completo usando IA.
TokenMeter analiza repositorios públicos de GitHub, cuenta tokens con un encoder real (jtokkit / o200k_base) y calcula el coste estimado de generación con varios modelos de IA bajo tres modos de uso (raw, assisted, agentic).
Important
El modo raw solo cuenta los tokens del código final (sin prompts, sin reintentos, sin razonamiento extra). Los modos assisted y agentic aplican multiplicadores fijos para aproximar overhead de input/razonamiento, pero siguen siendo una estimación con suelo — no contabilidad exacta.
Home |
Análisis |
Resultado |
| Característica | Descripción |
|---|---|
| 🌍 Repos públicos GitHub | Analiza cualquier repositorio público simplemente con su URL |
| 📄 Conteo de tokens | Estimación aproximada de tokens por archivo y repositorio |
| 💸 Coste estimado | Combina tokens × precios input/output reales por modelo |
| 📊 Tres modos | raw, assisted y agentic con multiplicadores fijos |
| 📈 Desglose detallado | Por lenguaje, extensión, carpeta y archivos |
| 🔗 Reportes públicos | Comparte resultados mediante URL |
| 🛠 Open Source | Proyecto transparente y extensible |
| 🗄 Histórico | Guarda y compara análisis |
| ⚙️ Precios configurables | Soporte para distintos modelos |
Definidos en CostEstimationMode:
| Modo | Output × base | Input × base | Interpretación |
|---|---|---|---|
🟢 raw |
1 | 0 | Solo tokens del código final. Suelo absoluto. |
🔵 assisted |
5 | 1 | IA + iteraciones humanas + razonamiento moderado. |
🟣 agentic |
20 | 4 | Agente autónomo: iteraciones, herramientas, razonamiento. |
- Recibe URL del repositorio y encola un job asíncrono (
POST /api/analyze→202 Accepted) - Clona el repositorio temporalmente
- Filtra archivos relevantes
- Cuenta tokens por archivo
- Calcula costes según modelo
- Persiste el análisis y expone el progreso vía
GET /api/analyze/jobs/{jobId} - Genera reporte público a partir del
analysisIdresultante
React SPA (Vite :3000)
↕ HTTP /api/*
Spring Boot REST API (:8080)
↕
┌────┴────┬───────────────┐
↓ ↓ ↓
PostgreSQL Filesystem pricing.yaml
(:5432) (clones tmp) (classpath)
+ Flyway + git CLI + jtokkit
Detalle completo en docs/ARCHITECTURE.md.
- Java 21
- Spring Boot 3
- Gradle Kotlin DSL
- PostgreSQL
- Flyway
- Docker
- React
- Vite
- TailwindCSS
- Docker Compose
- Nginx
- Let's Encrypt
- Cloudflare DNS
backend/ ├── domain/ ├── application/ └── infrastructure/
Lógica de negocio principal.
Casos de uso y orquestación.
Persistencia, GitHub, filesystem y REST APIs.
tokenmeter/
├── backend/ Spring Boot (Java 21, Gradle KTS)
│ ├── src/main/java/dev/diegobarrioh/tokenmeter/
│ │ ├── domain/ núcleo de negocio (records, enums, VOs)
│ │ ├── application/ casos de uso + ports
│ │ └── infrastructure/ adapters: web, persistence, git, pricing
│ └── src/main/resources/ application.yml, pricing.yaml, db/migration/
├── frontend/ React 19 + Vite 8 + Tailwind 4
├── docs/
│ ├── ARCHITECTURE.md
│ ├── API.md
│ └── assets/
├── .github/workflows/ci.yml
├── docker-compose.yml
├── CLAUDE.md
├── CONTRIBUTING.md
└── README.md
TokenMeter analiza archivos de texto relevantes: .java .kt .js .ts .tsx .jsx .py .go .rs .md .yml .yaml .json .xml .sql .html .css .scss Dockerfile .properties .gradle .kts .toml
.git/ node_modules/ target/ build/ dist/ .gradle/ .idea/ .vscode/ coverage/
*.png *.jpg *.jpeg *.gif *.webp *.jar *.zip *.tar *.gz *.pdf *.min.js *.map
Lockfiles podrán excluirse opcionalmente.
Para cada combinación (modelo, modo):
inputCost = baseTokens × inputMultiplier × inputTokenPricePerMillion / 1_000_000
outputCost = baseTokens × outputMultiplier × outputTokenPricePerMillion / 1_000_000
totalCost = inputCost + outputCost (HALF_UP, 6 decimales)
baseTokens son los tokens del repositorio escaneado.
Ejemplo (raw, GPT-4o, $10 / 1M output):
850 000 tokens × 1 × $10 / 1 000 000 = $8.50
TokenMeter usa el encoder real o200k_base (compatible con gpt-4o/o1) vía com.knuddels:jtokkit. El nombre del encoder se persiste en analysis.token_encoding para trazabilidad.
Limitación conocida: hoy se aplica el encoder OpenAI también a modelos Anthropic, Google y DeepSeek. Tokenizers nativos por proveedor están en el roadmap.
POST /api/analyze
Content-Type: application/json
{
"repositoryUrl": "https://github.com/user/repo"
}Devuelve 202 Accepted con el jobId y la URL para pollear el progreso:
{
"jobId": "0d4b8c8e-9a32-4d2a-9b58-6e9c1d6f7a01",
"status": "QUEUED",
"statusUrl": "/api/analyze/jobs/0d4b8c8e-9a32-4d2a-9b58-6e9c1d6f7a01",
"analysisId": null
}Estados posibles del job: QUEUED → RUNNING → SUCCESS (happy path) o FAILED desde cualquier fase no terminal. La saturación de slots ya no devuelve 429: el job se admite y queda en QUEUED con queueState.queuePosition. La cola interna del executor admite hasta tokenmeter.analyze-throttle.queue-capacity jobs (default 256); sólo al sobrepasar ese techo se devuelve 429 RATE_LIMITED.
GET /api/analyze/jobs/{jobId}Polling recomendado cada 1.5–2 s. Este endpoint no está sujeto al rate limiter. Cuando status=SUCCESS el body trae analysisId y progressPercent=100; en FAILED trae error.code/error.message. Más detalle en docs/API.md.
GET /api/analyze/{id}GET /api/analyze/{id}/cost-breakdownRespuesta agrupada por provider/model:
{
"analysisId": "uuid",
"repositoryUrl": "https://github.com/user/repo",
"summary": {
"totalTokens": 850000,
"totalModels": 4,
"totalModes": 12
},
"models": [
{
"provider": "openai",
"model": "gpt-4o",
"pricing": {
"inputTokenPricePerMillion": 2.5,
"outputTokenPricePerMillion": 10.0
},
"modes": [
{
"mode": "raw",
"baseTokens": 850000,
"estimatedInputTokens": 0,
"estimatedOutputTokens": 850000,
"inputCost": 0.0,
"outputCost": 8.5,
"totalCost": 8.5,
"formula": "inputCost=(baseTokens*0*inputPricePerMillion)/1_000_000; outputCost=(baseTokens*1*outputPricePerMillion)/1_000_000; totalCost=inputCost+outputCost"
}
]
}
]
}Errores estándar:
404 ANALYSIS_NOT_FOUNDsi no existe el análisis.400 INVALID_REQUESTsi el id está mal formado.
GET /api/pricing- Java 21+
- Node.js 22+
- Docker & Docker Compose
- Git
cd backend
./gradlew bootRuncd frontend
npm install
npm run devcp .env.example .env
# ajusta .env si necesitas otros puertos/credenciales
docker compose up --build -dProcedimiento dev por defecto: cuando se solicite desplegar TokenMeter en desarrollo, usar
docker compose up --build -ddesde la raíz del repo.
Los contenedores publican frontend/backend en la IP configurada por TOKENMETER_BIND_ADDRESS. PostgreSQL queda interno en la red Docker, sin puerto host.
| Servicio | Default | Variable |
|---|---|---|
| Frontend | http://localhost:3001 | TOKENMETER_FRONTEND_PORT |
| Backend API | http://localhost:8081 | TOKENMETER_BACKEND_PORT |
| Backend Prometheus | http://localhost:8081/actuator/prometheus | TOKENMETER_BACKEND_PORT |
| PostgreSQL | interno db:5432 |
— |
Los puertos internos siguen siendo frontend:80, backend:8080 y db:5432.
| Variable | Default | Descripción |
|---|---|---|
SPRING_PROFILES_ACTIVE |
local |
local / docker / prod |
TOKENMETER_BIND_ADDRESS |
127.0.0.1 |
IP host donde publicar frontend/backend. Si Nginx, Prometheus o Grafana están en otra máquina, usar IP privada del host Docker o 0.0.0.0 con firewall |
TOKENMETER_FRONTEND_PORT |
3001 |
Puerto host del frontend Docker |
TOKENMETER_BACKEND_PORT |
8081 |
Puerto host del backend Docker |
TOKENMETER_DB_NAME |
tokenmeter |
Nombre de la BBDD PostgreSQL (Docker) |
TOKENMETER_DB_USER |
obligatorio | Usuario PostgreSQL (Docker). docker compose up aborta si no está definido. |
TOKENMETER_DB_PASSWORD |
obligatorio | Contraseña PostgreSQL (Docker). Generar con openssl rand -base64 32. docker compose up aborta si no está definida. |
TOKENMETER_WORKDIR |
${java.io.tmpdir}/tokenmeter-repositories |
Directorio temporal para clones |
TOKENMETER_MAX_REPOSITORY_BYTES |
314572800 (300 MiB) |
Tamaño máximo permitido al clonar |
TOKENMETER_CLONE_TIMEOUT |
120s |
Timeout de clonado |
TOKENMETER_PRICING_REFRESH_ENABLED (tokenmeter.pricing.refresh.enabled) |
false (local/test), true (docker/prod) |
Activa el cron de refresh remoto desde LiteLLM |
TOKENMETER_PRICING_REFRESH_CRON (tokenmeter.pricing.refresh.cron) |
0 0 3 * * MON |
Expresión cron Spring; lunes 03:00 UTC por defecto |
TOKENMETER_PRICING_LITELLM_URL (tokenmeter.pricing.litellm.url) |
https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json |
Origen del catálogo LiteLLM |
TOKENMETER_PRICING_ADMIN_ENABLED (tokenmeter.pricing.admin.enabled) |
true (local/docker), false (prod) |
Habilita POST /api/admin/pricing/refresh |
DATABASE_URL / DATABASE_USERNAME / DATABASE_PASSWORD |
— | Sobrescritura explícita para datasource |
Recursos relacionados con pricing dinámico:
| Archivo | Uso |
|---|---|
backend/src/main/resources/pricing.yaml |
Catálogo semilla (FALLBACK) cargado por YamlPricingProvider cuando model_pricing está vacío |
backend/src/main/resources/pricing-mapping.yaml |
Mapeo (provider, model) → litellm-key consumido por LiteLlmPricingMapper |
backend/src/main/resources/pricing-overrides.yaml |
Opcional. Tarifas negociadas / parches puntuales (capa OVERRIDE, in-memory). No commitear con tarifas reales; usar .gitignore o ruta externa vía tokenmeter.pricing.overrides-location |
El docker-compose.yml arranca PostgreSQL, backend y frontend con healthchecks y puertos configurables vía .env.
docker compose ps
docker compose logs -f backend frontendTokenMeter no arranca Prometheus ni Grafana en este compose: se asume que viven en un servidor separado.
El backend expone métricas Spring/Micrometer en:
GET /actuator/prometheus
Si Prometheus corre fuera del host Docker, configura TOKENMETER_BIND_ADDRESS con una IP alcanzable desde el servidor de monitorización —por ejemplo la IP privada del host— y limita el acceso con firewall/security groups.
Archivos incluidos para el servidor externo:
| Archivo | Uso |
|---|---|
deploy/prometheus/tokenmeter-scrape.yml |
scrape_configs para Prometheus externo |
deploy/grafana/tokenmeter-backend-dashboard.json |
Dashboard importable en Grafana |
Healthchecks disponibles: /actuator/health, /actuator/health/liveness y /actuator/health/readiness.
Los perfiles docker y prod emiten logs estructurados JSON por stdout. El perfil local mantiene logs legibles en consola.
Hay una plantilla en deploy/nginx/tokenmeter.conf.template para tokenmeter.backendtothefuture.com.
Si Nginx corre en otra máquina, copia la plantilla a /etc/nginx/sites-available/tokenmeter.conf, sustituye ${TOKENMETER_UPSTREAM_HOST} por la IP/DNS privado del host Docker y activa el site desde sites-enabled. El frontend ya enruta /api/* al backend dentro de la red Docker.
Variables a sustituir en la plantilla:
| Placeholder | Ejemplo |
|---|---|
${TOKENMETER_UPSTREAM_HOST} |
10.0.0.25 |
${TOKENMETER_SSL_CERTIFICATE} |
/etc/letsencrypt/live/tokenmeter.backendtothefuture.com/fullchain.pem |
${TOKENMETER_SSL_CERTIFICATE_KEY} |
/etc/letsencrypt/live/tokenmeter.backendtothefuture.com/privkey.pem |
- Backend Spring Boot
- Frontend React
- PostgreSQL + Flyway
- Clonado de repos públicos
- Conteo de tokens
- Estimación de costes
- Reportes públicos
- Docker Compose
- Tokenizers reales
- GitHub Action
- Badge README
- Comparación entre ramas
- Histórico de repositorios
- Leaderboards
- GitHub App para repos privados
- Exportación CSV/JSON
- API pública
Lee CONTRIBUTING.md para setup, convenciones de código y formato de commits (gitmoji + conventional commits).
Si vas a usar un asistente IA para contribuir, CLAUDE.md tiene los comandos, convenciones y zonas no-go.
TokenMeter es gratuito y open source. Si te resulta útil y quieres apoyar el desarrollo:
- GitHub Sponsors — recurrente, 0% comisión.
- Buy Me a Coffee — donación one-off.
Cualquier apoyo ayuda a cubrir hosting, dominio y a dedicarle más tiempo a roadmap.
TokenMeter intenta responder una pregunta simple:
“¿Cuál es el coste mínimo de este repositorio como salida de IA?”
No pretende ser contabilidad exacta.
Pretende dar:
- perspectiva
- curiosidad
- transparencia
- conversación
Porque algunos repositorios cuestan más emocionalmente que económicamente.
MIT
MVP en desarrollo activo.
- Refresh dinámico de precios desde LiteLLM con capas
OVERRIDE > REMOTE > FALLBACKestá implementado (cambiodynamic-pricing-fetch). Detalle endocs/ARCHITECTURE.mdydocs/RUNBOOK.md. - Jobs de análisis observables y asíncronos (
POST /api/analyzedevuelve202conjobId; progreso víaGET /api/analyze/jobs/{jobId}) está implementado (cambioobservable-analysis-jobs). Detalle endocs/ARCHITECTURE.mdydocs/API.md. - Progreso vivo del análisis — barra honesta y transparente (epic TKM-60) está completo: emisión granular por archivo en
COUNTING_TOKENS, tween de progreso, timeline de fases, detalles live (X/Y archivos, tokens acumulados, mensaje del backend) y ETA prudente en análisis largos. La barra nunca llega a 100% antes deSUCCESSconanalysisId. Las extensiones descartadas por scope (progreso ponderado por bytes, log de actividad, streaming SSE) quedan reabribles si surge necesidad.
Contribuciones, ideas y experimentos son bienvenidos.
