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