app
adom-vscode
Public Made by Adomby adom
Let Claude, Codex and Kimi drive VS Code inside your Adom container
master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
#!/usr/bin/env python3
"""Claude usage in the title bar: 5-hour session, weekly, and the weekly model buckets that
apply to your plan, as percent used. Reads the Claude Code sign-in in ~/.claude/.credentials.json
and asks the same usage endpoint the Claude Code CLI shows in /usage. Asks the provider at most once every three minutes, pauses while the window is hidden, retries at 5, 15, 45 s on errors. Click the slot to cycle the views (rows, one number, a ring, a pair, all numbers, all rings)."""
import json, os, sys, time
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
from importlib.machinery import SourceFileLoader
marks = SourceFileLoader('hwmarks', os.path.join(os.path.dirname(os.path.realpath(__file__)), 'hydrogen-widget-marks.py')).load_module()
lib = SourceFileLoader('hwlib', os.path.join(os.path.dirname(os.path.realpath(__file__)), 'hydrogen-widget-lib.py')).load_module()
SESSION_S, WEEK_S = 5 * 3600, 7 * 86400
def epoch(iso):
"""The provider's ISO reset time as Unix seconds (None when absent or unreadable)."""
if not iso: return None
try:
from datetime import datetime
return round(datetime.fromisoformat(str(iso).replace('Z', '+00:00')).timestamp())
except Exception: return None
def data():
if not os.path.exists(os.path.expanduser('~/.claude/.credentials.json')):
return {'error': 'sign in to Claude Code in this workspace'}
c = lib.read_json_if_changed('~/.claude/.credentials.json')
oa = c.get('claudeAiOauth') or c
tok = oa.get('accessToken')
if not tok: return {'error': 'not signed in to Claude Code'}
exp = oa.get('expiresAt') or 0
if exp and exp < time.time() * 1000:
# Do not spend a call on a token the file already says is expired; Claude Code refreshes
# it on its next run and read_json_if_changed picks the new file up.
e = Exception('token expired'); e.code = 401; raise e
u = lib.http_json('https://api.anthropic.com/api/oauth/usage', {'Authorization': 'Bearer ' + tok, 'anthropic-beta': 'oauth-2025-04-20', 'User-Agent': 'hydrogen-widget-claude-usage'})
bars = []
# The structured list is what Claude Code's own Usage panel draws: the session window, the
# weekly window, and every weekly limit scoped to a model (Fable, Opus, ...), each with the
# provider's severity. Fall back to the flat fields on an older response.
# Every limit also carries when it resets and how long its window is, so the hover view can
# draw a countdown and say whether the current pace runs out before the reset (Drew
# 2026-10-12: "a meter to show time remaining until my usage limits reset").
for lim in u.get('limits') or []:
kind = lim.get('kind'); pct = lim.get('percent')
if pct is None: continue
if kind == 'session': label, name, win = '5h', 'Current session', SESSION_S
elif kind == 'weekly_all': label, name, win = 'week', 'This week, all models', WEEK_S
elif kind == 'weekly_scoped':
scope = lim.get('scope') or {}; model = scope.get('model') or {}
label = model.get('display_name') or 'model'
name, win = label + ' this week', WEEK_S
else: label, name, win = kind or 'limit', (kind or 'limit').replace('_', ' ').capitalize(), 0
bars.append({'label': label, 'pct': pct, 'severity': lim.get('severity') or 'normal',
'name': name, 'resets_at': epoch(lim.get('resets_at')), 'window_s': win})
if not bars:
for label, key, name, win in (('5h', 'five_hour', 'Current session', SESSION_S), ('week', 'seven_day', 'This week, all models', WEEK_S)):
v = u.get(key) or {}
if v.get('utilization') is not None:
bars.append({'label': label, 'pct': v['utilization'], 'severity': 'normal', 'name': name, 'resets_at': epoch(v.get('resets_at')), 'window_s': win})
for k, v in u.items():
if k.startswith('seven_day_') and isinstance(v, dict) and v.get('utilization') is not None:
label = k[len('seven_day_'):].replace('_', ' ')
bars.append({'label': label, 'pct': v['utilization'], 'severity': 'normal', 'name': label.capitalize() + ' this week', 'resets_at': epoch(v.get('resets_at')), 'window_s': WEEK_S})
out = {'items': bars}
# Which account and plan: the plan is in the sign-in file, the email in Claude Code's own
# settings (~/.claude.json oauthAccount), so a second sign-in is never a mystery.
if oa.get('subscriptionType'): out['plan'] = oa['subscriptionType']
try:
acc = (lib.read_json_if_changed('~/.claude.json').get('oauthAccount') or {})
if acc.get('emailAddress'): out['account'] = acc['emailAddress']
except Exception: pass
# Usage credits (the paid overflow past the plan limits), when the account has them turned on.
sp = u.get('spend') or {}
if sp.get('enabled') and isinstance(sp.get('limit'), dict):
def money(m): return (m.get('amount_minor') or 0) / (10 ** (m.get('exponent') or 0))
out['credits'] = {'used': money(sp.get('used') or {}), 'limit': money(sp['limit']), 'currency': (sp['limit'].get('currency') or 'USD')}
return out
RENDER = r"""(d.error?`<span class="err">${d.error}</span>`:(d.items||[]).map(b=>bar(b.label,b.pct,b.severity)).join(''))"""
lib.serve('Claude usage', data, RENDER, 180000, mark=marks.MARKS['claude'], default_view='big:week')