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












