Monitoriza tu consumo de Claude Code en GNOME con Python y AppIndicator
Si trabajas a diario con Claude Code, seguramente te haya pasado: estás a mitad de una tarea larga y de pronto te quedas sin cuota. Existe el comando /usage dentro de Claude Code, pero hay que pararse a escribirlo. Lo cómodo sería tener el dato siempre delante, en la barra superior de Ubuntu.
En este artículo montamos justo eso: un indicador que muestra de forma permanente el porcentaje consumido de la sesión de 5 horas y el de la cuota semanal, con el tiempo que falta para cada reset. Se refresca solo cada minuto y arranca automáticamente al iniciar sesión.
De dónde salen los datos
Claude Code guarda sus credenciales OAuth en ~/.claude/.credentials.json. Dentro hay un accessToken que sirve para consultar el mismo endpoint que usa internamente el comando /usage:
TOKEN=$(python3 -c "import json;print(json.load(open('$HOME/.claude/.credentials.json'))['claudeAiOauth']['accessToken'])")
curl -s https://api.anthropic.com/api/oauth/usage \
-H "Authorization: Bearer $TOKEN" \
-H "anthropic-beta: oauth-2025-04-20"La respuesta trae bastantes campos, pero los dos que nos interesan son estos:
{
"five_hour": {
"utilization": 56.0,
"resets_at": "2026-08-08T11:30:00+00:00"
},
"seven_day": {
"utilization": 14.0,
"resets_at": "2026-08-13T20:00:00+00:00"
}
}utilization es el porcentaje consumido y resets_at la marca temporal en UTC en la que ese contador vuelve a cero. Con eso ya tenemos todo lo necesario.
Un aviso antes de seguir: ese token da acceso a tu cuenta. Úsalo solo en local y no lo pegues en ningún sitio público, ni en capturas de pantalla ni en pastebins.
Por qué una app de bandeja y no una extensión de GNOME
El primer impulso es escribir una extensión de GNOME Shell, que es lo que pinta texto nativo en la barra. Y funciona, pero tiene un inconveniente serio en Ubuntu moderno: bajo Wayland, una extensión recién instalada no se carga hasta que reinicias GNOME Shell, y eso significa cerrar sesión. El clásico Alt+F2 seguido de r solo funciona en X11.
La alternativa que evita el cierre de sesión es una aplicación de bandeja con AyatanaAppIndicator3. Se apoya en la extensión ubuntu-appindicators, que Ubuntu trae activada de serie, y se registra en caliente: la lanzas y aparece al instante.
El script del indicador
Guarda esto en ~/.local/bin/claude-usage-indicator.py:
#!/usr/bin/env python3
import fcntl
import json
import sys
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("AyatanaAppIndicator3", "0.1")
from gi.repository import AyatanaAppIndicator3 as AppIndicator
from gi.repository import GLib, Gtk
CRED = Path.home() / ".claude" / ".credentials.json"
URL = "https://api.anthropic.com/api/oauth/usage"
REFRESH_SECONDS = 60
def eta(iso):
if not iso:
return ""
try:
t = datetime.fromisoformat(iso)
except ValueError:
return ""
secs = (t - datetime.now(timezone.utc)).total_seconds()
if secs <= 0:
return "0m"
d, rem = divmod(int(secs), 86400)
h, m = divmod(rem // 60, 60)
if d:
return f"{d}d{h}h"
if h:
return f"{h}h{m:02d}m"
return f"{m}m"
def reset_clock(iso):
if not iso:
return ""
try:
t = datetime.fromisoformat(iso).astimezone()
except ValueError:
return ""
return t.strftime("%a %d %H:%M")
def fetch():
token = json.loads(CRED.read_text())["claudeAiOauth"]["accessToken"]
req = urllib.request.Request(
URL,
headers={
"Authorization": f"Bearer {token}",
"anthropic-beta": "oauth-2025-04-20",
},
)
with urllib.request.urlopen(req, timeout=8) as r:
return json.load(r)
class Indicator:
def __init__(self):
self.ind = AppIndicator.Indicator.new(
"claude-usage",
"utilities-system-monitor-symbolic",
AppIndicator.IndicatorCategory.SYSTEM_SERVICES,
)
self.ind.set_status(AppIndicator.IndicatorStatus.ACTIVE)
self.menu = Gtk.Menu()
self.detail = Gtk.MenuItem(label="Cargando...")
self.detail.set_sensitive(False)
self.menu.append(self.detail)
self.menu.append(Gtk.SeparatorMenuItem())
refresh = Gtk.MenuItem(label="Actualizar ahora")
refresh.connect("activate", lambda _w: self.refresh())
self.menu.append(refresh)
quit_item = Gtk.MenuItem(label="Salir")
quit_item.connect("activate", lambda _w: Gtk.main_quit())
self.menu.append(quit_item)
self.menu.show_all()
self.ind.set_menu(self.menu)
self.refresh()
GLib.timeout_add_seconds(REFRESH_SECONDS, self.refresh)
def refresh(self):
try:
d = fetch()
s, w = d["five_hour"], d["seven_day"]
sp, wp = int(round(s["utilization"])), int(round(w["utilization"]))
label = f"S {sp}% ({eta(s['resets_at'])}) · W {wp}% ({eta(w['resets_at'])})"
detail = (
f"Sesion 5h: {sp}% -> resetea {reset_clock(s['resets_at'])}\n"
f"Semanal: {wp}% -> resetea {reset_clock(w['resets_at'])}"
)
except Exception as e:
label = "Claude: --"
detail = f"Sin datos: {e}"
self.ind.set_label(label, "S 100% (0h00m) · W 100% (0d0h)")
self.detail.set_label(detail)
return GLib.SOURCE_CONTINUE
if __name__ == "__main__":
_lock = open("/tmp/claude-usage-indicator.lock", "w")
try:
fcntl.flock(_lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
print("Ya hay una instancia en ejecucion.", file=sys.stderr)
sys.exit(0)
Indicator()
Gtk.main()Hay dos detalles que merece la pena señalar. El primero es el bloqueo de instancia única con fcntl.flock al final: sin él es facilísimo acabar con tres o cuatro copias apiladas en la barra si lanzas el script a mano mientras el autoarranque ya lo tenía corriendo.
El segundo es el segundo argumento de set_label, la llamada guide. Sirve para que el panel reserve el ancho del texto más largo posible y el icono no baile cada vez que el número cambia de una a tres cifras.
Dale permisos de ejecución y pruébalo:
chmod +x ~/.local/bin/claude-usage-indicator.py
python3 ~/.local/bin/claude-usage-indicator.pyDebería aparecer al momento en la barra superior algo así:
S 57% (3h27m) · W 14% (5d11h)
Y al pinchar sobre el icono, un menú con la hora exacta de cada reset, un botón para actualizar al instante y otro para salir.
Que arranque solo al iniciar sesión
GNOME lanza automáticamente todo lo que encuentre en ~/.config/autostart/. Creamos ahí claude-usage-indicator.desktop:
[Desktop Entry]
Type=Application
Name=Claude Usage Indicator
Comment=Muestra la cuota usada de Claude en la barra superior
Exec=/usr/bin/python3 /home/TU_USUARIO/.local/bin/claude-usage-indicator.py
Icon=utilities-system-monitor-symbolic
Terminal=false
X-GNOME-Autostart-enabled=trueOjo con la línea Exec, porque aquí es donde es fácil pegarse. La tentación es escribir %h para referirse al home, pero %h no existe en la especificación de Desktop Entry. Los únicos códigos válidos son %f, %u, %F, %U, %i, %c y %k. Si pones %h, GNOME descarta la entrada en silencio y te vuelves loco buscando por qué no arranca. Usa la ruta absoluta.
Puedes verificar que el fichero es correcto antes de reiniciar:
desktop-file-validate ~/.config/autostart/claude-usage-indicator.desktopSi no devuelve nada, está bien. También aparecerá listado en Aplicaciones al Inicio por si algún día lo quieres desactivar desde la interfaz gráfica sin borrar el fichero.
Resumen de ficheros
| Fichero | Para qué sirve |
|---|---|
~/.local/bin/claude-usage-indicator.py | El indicador de la barra |
~/.config/autostart/claude-usage-indicator.desktop | Arranque automático al iniciar sesión |
~/.claude/.credentials.json | Token OAuth, lo genera Claude Code |
La limitación que conviene conocer
El indicador depende del token de acceso que guarda Claude Code, y ese token caduca cada pocas horas. Claude Code lo renueva por su cuenta, pero solo cuando lo usas. Si pasas un día entero sin abrirlo, el indicador mostrará Claude: -- hasta que vuelvas a lanzar claude en una terminal.
Se podría hacer que el script renovase el token él mismo con el refreshToken, pero es mala idea: ese proceso rota el refresh token, y hacerlo desde fuera puede desloguearte de Claude Code. Mejor asumir el hueco y que sea la propia herramienta la que gestione sus credenciales.
Si ves el icono pero no el texto
Puede ocurrir según la versión de ubuntu-appindicators. La extensión sabe pintar etiquetas, pero en algunas builds las propiedades XAyatanaLabel vienen comentadas en su definición de interfaz D-Bus, con este razonamiento en el propio código fuente:
These are commented out because GDBusProxy would otherwise require them, but they are not available for KDE indicators
Puedes comprobar si tu etiqueta se está publicando correctamente por D-Bus con:
gdbus call --session --dest org.kde.StatusNotifierWatcher \
--object-path /StatusNotifierWatcher \
--method org.freedesktop.DBus.Properties.Get \
org.kde.StatusNotifierWatcher RegisteredStatusNotifierItemsSi tu indicador aparece en esa lista, el problema está en el lado de la extensión y no en tu script. En ese caso la salida es escribir una extensión de GNOME Shell propia, asumiendo el cierre de sesión que comentábamos al principio.
Conclusión
Poco más de cien líneas de Python y un fichero de arranque para no volver a quedarte a medias sin saber por qué. Lo interesante del montaje es que no depende de ninguna extensión de terceros ni de reiniciar nada: se apoya en piezas que Ubuntu ya trae puestas.
El mismo endpoint sirve para cualquier otra cosa que se te ocurra: un aviso cuando pases del 80%, un registro histórico de consumo, o integrarlo en Conky o en Polybar si no usas GNOME.



