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() read more

Leer más » Comentar

No soy yo

no-soy-yo

Leer más » Comentar

Sin batería

sin-bateria

Leer más » Comentar

432 CVEs

432cve

Leer más » Comentar

Anti-IA

anti-ia

Leer más » Comentar

Memoria

memoria

Leer más » Comentar

Mal uso

mal-uso

Leer más » Comentar

Precio de la RAM

precio-ram

Leer más » Comentar

AUR

aur

Leer más » Comentar

Huevo o gallina

huevo-gallina

Leer más » Comentar