← Commit history
VERSION+1−1
@@ -1 +1 @@-1.1.116+1.1.117
bin/adom-vscode
⋯ 1 unchanged line ⋯
bin/hydrogen-widget-claude-usage+8−1
@@ -60,6 +60,13 @@ def data():                 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):@@ -68,4 +75,4 @@ def data():     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='nums')+lib.serve('Claude usage', data, RENDER, 180000, mark=marks.MARKS['claude'], default_view='big:week')
bin/hydrogen-widget-codex-usage+10−2
@@ -31,7 +31,15 @@ def data():                 'name': name(w), 'resets_at': at, 'window_s': w.get('limit_window_seconds') or 0}     items = [item(p)]     if s: items.append(item(s))-    return {'plan': u.get('plan_type'), 'items': items}+    return {'plan': u.get('plan_type'), 'account': u.get('email') or id_email(t.get('id_token')), 'items': items}++def id_email(jwt):+    # The sign-in's id_token names the account (its email claim); only read, never verified or sent.+    try:+        import base64+        p = jwt.split('.')[1]; p += '=' * (-len(p) % 4)+        return json.loads(base64.urlsafe_b64decode(p)).get('email')+    except Exception: return None  RENDER = r"""(d.error?`<span class="err">${d.error}</span>`:(d.items||[]).map(b=>bar(b.label,b.pct,b.severity)).join(''))"""-lib.serve('Codex usage', data, RENDER, 180000, mark=marks.MARKS['codex'], default_view='nums')+lib.serve('Codex usage', data, RENDER, 180000, mark=marks.MARKS['codex'], default_view='big:week')
bin/hydrogen-widget-lib.py+19−6
@@ -121,7 +121,12 @@ function renderItems(items,id){const [kind,arg]=String(id).split(':');const at=( function render(d0){const d=d0&&d0.stale?d0.stale:d0;const el=document.getElementById('w');const wrap=document.getElementById('wrap');   el.classList.toggle('stale',!!(d0&&d0.stale));let tip=(d0&&d0.stale)?String(d0.error||''):'';   if(d0&&d0.view&&d0.view!==curView){curView=d0.view;try{localStorage.setItem('hw.view',curView);}catch(e){}}-  if(d&&Array.isArray(d.items)&&d.items.length){const vs=viewsFor(d.items);let k=vs.findIndex(v=>v.id===curView);if(k<0){k=vs.findIndex(v=>v.id===DEF_VIEW);if(k<0&&DEF_VIEW==='nums')k=vs.findIndex(v=>v.id==='big:0');if(k<0)k=0;curView=vs[k].id;}+  if(d&&Array.isArray(d.items)&&d.items.length){const vs=viewsFor(d.items);+    /* A default may name a limit by its label ('big:week'): the week sits at a different place in+       each widget (Claude 5h first, Codex and Antigravity week first). John 2026-10-18: every usage+       widget opens on its week as one big number. */+    const byLabel=(id)=>{const m=/^(big|ring):([^0-9].*)$/.exec(String(id));if(!m)return id;const i=d.items.findIndex(it=>it.label===m[2]);return m[1]+':'+(i<0?0:i);};+    curView=byLabel(curView);let k=vs.findIndex(v=>v.id===curView);if(k<0){k=vs.findIndex(v=>v.id===byLabel(DEF_VIEW));if(k<0&&DEF_VIEW==='nums')k=vs.findIndex(v=>v.id==='big:0');if(k<0)k=0;curView=vs[k].id;}     el.classList.toggle('hz',curView!=='rows');el.innerHTML=renderItems(d.items,curView);     tip=(tip?tip+'. ':'')+'%(title)s: '+vs[k].name+'. Click for the next view ('+(k+1)+' of '+vs.length+').';}   else{el.classList.remove('hz');el.innerHTML=%(render_js)s;tip=(d0&&d0.error?String(d0.error)+'. ':'')+'Click to check again now.';}@@ -194,9 +199,11 @@ X_CSS = r""" @media (min-height:100px){.w{display:none!important}.x{display:block}} .xhd{display:flex;align-items:center;justify-content:space-between;gap:9px;font-size:11px;color:rgba(230,235,239,.6)} .xhd .pl{padding:1px 7px;border-radius:9px;border:1px solid rgba(255,255,255,.14);color:rgba(230,235,239,.8)}+.xac{padding-top:3px;font-size:11px;color:rgba(230,235,239,.6);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}+.xac b{font-weight:500;color:rgba(230,235,239,.85)} .xl{display:grid;grid-template-columns:54px minmax(0,1fr);gap:12px;align-items:center;padding:10px 0;border-top:1px solid rgba(255,255,255,.07)} .xl.nr{grid-template-columns:minmax(0,1fr)}-.xhd+.xl,.xe+.xl{border-top:0}+.xhd+.xl,.xac+.xl,.xe+.xl{border-top:0} .cd{position:relative;width:54px;height:54px} .cd svg{width:100%;height:100%;transform:rotate(-90deg);display:block} .cd circle{fill:none;stroke-width:4.5}@@ -269,9 +276,12 @@ function renderX(d0){const x=document.getElementById('x');if(!x)return;      the card keeps its plain brief. */   if(!items.length&&!(d0&&d0.error)){x.innerHTML='';withdrawExpand();return;}   /* No title here: Hydrogen's card already shows the widget's name and mark right above this view.-     One quiet line instead: how fresh the numbers are, and the plan when the provider says. */+     One quiet line instead: how fresh the numbers are, and the plan when the provider says. Under+     it, which account the numbers belong to (d.account), so a second sign-in is never a mystery+     (John 2026-10-18: Antigravity showed 0% for one account while the team account was at 14%). */   const plan=d&&d.plan?esc(String(d.plan).charAt(0).toUpperCase()+String(d.plan).slice(1))+' plan':'';   let h=`<div class="xhd"><span class="ag"></span>${plan?`<span class="pl">${plan}</span>`:''}</div>`;+  if(d&&d.account)h+=`<div class="xac" title="${esc(d.account)}">Signed in as <b>${esc(d.account)}</b></div>`;   if(d0&&d0.error){const msg=d0.error==='starting'?'Checking your limits…':d0.error;     h+=`<div class="xe"><span>${esc(msg.charAt(0).toUpperCase()+msg.slice(1))}${items.length&&d0.at?'. Showing the numbers from '+new Date(d0.at*1000).toLocaleTimeString([],{hour:'numeric',minute:'2-digit'})+'.':''}</span>${d0.error==='starting'?'':'<button class="xr">Check now</button>'}</div>`;}   h+=items.map(limRow).join('');@@ -329,14 +339,17 @@ def _save_state(title, st):     except Exception: pass  def serve(title, data_fn, render_js, refresh_ms=60000, mark=None, align_ms=0, default_view='rows'):+    # mark = {'svg': '<path .../>', 'color': '#hex', 'viewBox': '0 0 W H'}: viewBox is the grid the+    # path is drawn on (default 0 0 16 16). A 24-unit path in the 16-unit box is cut off at the right+    # and bottom (the Antigravity arch, John 2026-10-17 and again 2026-10-18).     mark = mark or {'svg': '', 'color': 'currentColor'}     port = int(os.environ.get('HYDROGEN_WIDGET_PORT') or (sys.argv[sys.argv.index('--port')+1] if '--port' in sys.argv else 8930))-    provider = 'Anthropic' if 'Claude' in title else ('OpenAI' if 'Codex' in title else 'the provider')+    provider = 'Anthropic' if 'Claude' in title else ('OpenAI' if 'Codex' in title else ('Antigravity' if 'Antigravity' in title else 'the provider'))     # One upstream call per interval at most, however often the page asks. On an upstream error     # the next tries come at 5, 15, 45, 90 s, then minutes, capped at 15; back to normal on the     # next success. The last good numbers live on disk so a restart shows them at once, dimmed.     st = _load_state(title)-    # The view a fresh install opens on: the widget's choice (Claude and Codex: every limit as a+    # The view a fresh install opens on: the widget's choice (every usage widget: the week as one big number, John 2026-10-18; was every limit as a     # big number in a row, John 2026-10-01; Codex was "the week only" from 2026-09-19), until the     # user clicks to another one, which is remembered on disk and wins from then on.     cache = {'t': 0, 'v': {'error': 'starting'}, 'wait': refresh_ms/1000, 'good': None, 'fails': 0, 'view': str(st.get('view') or default_view)}@@ -440,7 +453,7 @@ def serve(title, data_fn, render_js, refresh_ms=60000, mark=None, align_ms=0, de                             title, '40px' if expand else '150px 0 40px 40px', w, h, ground, 'true' if fixed else 'false', 'true' if expand else 'false')                 self._send(200, 'text/html; charset=utf-8', page.encode())             elif self.path.startswith('/health'): self._send(200, 'application/json', b'{"ok":true}')-            else: self._send(200, 'text/html; charset=utf-8', (PAGE % {'title': title, 'default_view': default_view, 'refresh_ms': refresh_ms, 'align_ms': int(align_ms), 'render_js': render_js, 'x_css': X_CSS, 'x_js': X_JS, 'mark_html': ('<span class="mark" style="color:%s"><svg viewBox="0 0 16 16">%s</svg></span>' % (mark.get('color', 'currentColor'), mark.get('svg', ''))) if mark.get('svg') else ''}).encode())+            else: self._send(200, 'text/html; charset=utf-8', (PAGE % {'title': title, 'default_view': default_view, 'refresh_ms': refresh_ms, 'align_ms': int(align_ms), 'render_js': render_js, 'x_css': X_CSS, 'x_js': X_JS, 'mark_html': ('<span class="mark" style="color:%s"><svg viewBox="%s">%s</svg></span>' % (mark.get('color', 'currentColor'), mark.get('viewBox', '0 0 16 16'), mark.get('svg', ''))) if mark.get('svg') else ''}).encode())     # Bind with retries: a previous instance Hydrogen just stopped may still be releasing the     # port for a moment. Ten tries over five seconds, then give up loudly (the log says why).     ThreadingHTTPServer.allow_reuse_address = True
bin/hydrogen-widget-marks.py

  
install.sh

  
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-vscode",-  "version": "1.1.116",+  "version": "1.1.117",   "type": "app",   "description": "CLI + VS Code extension that lets AI assistants and the Hydrogen Desktop frontend drive VS Code from inside an Adom container: open files, live get/set of any setting/theme/font (no reload), native streamed exec, a frontend event queue, container identity (hd/hw), api-key status/inject, extension query/update, modes, paste screenshots, and any VS Code command via a local HTTP API on port 8821 (with port-file fallback discovery).",   "license": "PROPRIETARY",
page.json+2−2
@@ -4,7 +4,7 @@   "slug": "adom-vscode",   "title": "adom-vscode",   "brief": "Let Claude, Codex and Kimi drive VS Code inside your Adom container",-  "version": "1.1.116",+  "version": "1.1.117",   "license": "PROPRIETARY",   "visibility": "public",   "hero": {@@ -141,4 +141,4 @@     "adom-vscode-extensions",     "adom-vscode-modes"   ]-}\ No newline at end of file+}
widgets/README.md+4
@@ -20,6 +20,8 @@ Where the numbers come from: the Claude Code sign-in in your workspace (`~/.clau  Your plan's rate-limit window as percent used, plus the secondary window when your plan has one. From the Codex sign-in in your workspace (`~/.codex/auth.json`), asked at `chatgpt.com/backend-api/wham/usage`, what Codex's `/status` reads. With no Codex sign-in the slot says so. +Each widget opens on its weekly limit as one big number. Click it to change the look (all meters, one limit, a ring, a pair, all as numbers or rings); your choice is remembered.+ ## Hover for when your limits reset  Rest the pointer on either widget and its hover card opens a bigger, live view of the same numbers (Hydrogen 1.0.429 and later; older builds keep the plain card):@@ -28,6 +30,8 @@ Rest the pointer on either widget and its hover card opens a bigger, live view o  ![Codex usage hover view: the weekly window with its countdown ring, plan, reset date and a pace warning](widgets/docs/codex-usage-hover.png) +At the top: how fresh the numbers are, your plan, and **Signed in as** with the account the numbers belong to (Claude: the email in `~/.claude.json`; Codex: the email on the sign-in), so a second account is never a mystery.+ For each limit:  - **The ring** is the time left until that limit resets. It drains as the window runs out and counts down on your own clock, to the second in the last hour, without asking the provider again.