app
adom-vscode
Public Made by Adomby adom
Let Claude, Codex and Kimi drive VS Code inside your Adom container
← Commit history
release 1.1.78
10 files changed
+30−11
README.md+2@@ -276,6 +276,8 @@ v1.1.20: /command returns errorCode command_not_found (HTTP 400) with did-you-me v1.1.21: event-driven tab census + Claude session identity (John 2026-09-03: never poll the iframes again). GET /tabs and the SSE stream GET /tabs/events publish the extension host's own tabGroups census on every change; POST /claude/session-start ingests the Claude SessionStart hook (id + transcript path, before the jsonl is flushed) and GET /claude/sessions lists it; POST /claude/open-session opens an existing conversation by id and confirms the bind (retagging sdk-cli transcripts, which the Claude extension otherwise declines). Capability markers in /health: tabs.list, tabs.events, claude.open-session, claude.session-start, claude.sessions. +v1.1.78: an expired provider token is never "sign in again": the usage widgets say the token is waiting for Claude Code or Codex to refresh it, keep the last good numbers on screen dimmed, and retry every minute; the Claude widget skips the call when the file already says the token is expired.+ v1.1.77: a new AI thread unlocks the active editor group first (VS Code keeps a group locked after its terminal tab closes and then refuses the new tab). v1.1.76: an expired provider sign-in (HTTP 401 or 403) reads "sign in to Codex again" in the slot instead of the raw HTTP error.
VERSION+1−1@@ -1 +1 @@-1.1.77+1.1.78
bin/adom-vscode⋯ 1 unchanged line ⋯
bin/hydrogen-widget-claude-usage+6−1@@ -2,7 +2,7 @@ """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, backs off on errors."""-import json, os, sys+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()@@ -15,6 +15,11 @@ def data(): 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
bin/hydrogen-widget-codex-usagebin/hydrogen-widget-lib.py+18−6@@ -23,6 +23,7 @@ html,body{margin:0;height:100%%;background:transparent;color:#e6ebef;font-family .v{font-variant-numeric:tabular-nums;text-align:right} .big{font-weight:600;font-variant-numeric:tabular-nums} .err{color:#f0a060}+#w.stale{opacity:.45} @media (max-height:33px){ .w{font-size:clamp(7px,37vh,12px);line-height:1.2} .lbl{font-size:clamp(6px,30vh,9px)}.bar{width:clamp(24px,130vh,40px);height:clamp(4px,20vh,6px)}.v{min-width:2.4em}@@ -47,7 +48,9 @@ html,body{margin:0;height:100%%;background:transparent;color:#e6ebef;font-family <script> const R=%(refresh_ms)d; function bar(l,p,sev){const c=(sev==='critical'||p>=90)?'hot':(sev==='warning'||p>=70)?'warn':'';return `<span class="row"><span class="lbl">${l}</span><span class="bar ${c}"><i style="width:${Math.max(0,Math.min(100,p))}%%"></i></span><span class="v">${Math.round(p)}%%</span></span>`;}-function render(d){document.getElementById('w').innerHTML=%(render_js)s;postWidth();}+/* d.stale = the last good data while the provider is unreachable or the token is between refreshes:+ drawn dimmed, the reason in the tooltip, never an error where numbers were. */+function render(d0){const d=d0&&d0.stale?d0.stale:d0;const el=document.getElementById('w');el.classList.toggle('stale',!!(d0&&d0.stale));el.title=(d0&&d0.stale)?String(d0.error||''):'';el.innerHTML=%(render_js)s;postWidth();} /* THE WIDGET DECIDES ITS WIDTH: ask the host for the intrinsic width of what is shown in the current layout (tall rows are narrow, the compact row is wide). Measured on a hidden clone laid out at max-content, so the slot's current width never feeds back into the answer (rows stretch to@@ -71,19 +74,28 @@ def serve(title, data_fn, render_js, refresh_ms=60000, mark=None): port = int(os.environ.get('HYDROGEN_WIDGET_PORT') or (sys.argv[sys.argv.index('--port')+1] if '--port' in sys.argv else 8930)) # One upstream call per interval at most, however often the page asks; on an upstream # error the interval doubles up to 15 minutes and returns to normal on the next success.- cache = {'t': 0, 'v': {'error': 'starting'}, 'wait': refresh_ms/1000}+ cache = {'t': 0, 'v': {'error': 'starting'}, 'wait': refresh_ms/1000, 'good': None} lock = threading.Lock() def data(): with lock: if time.time() - cache['t'] >= cache['wait'] - 1: try: cache['v'] = data_fn(); cache['wait'] = refresh_ms/1000+ if not cache['v'].get('error'): cache['good'] = cache['v'] except Exception as e:- # A 401/403 from the provider is an expired sign-in, so say that instead of- # printing the HTTP error (ConfRoomROG 2026-09-14: "HTTP Error 401: Unauthorized").+ # A 401/403 is almost always an EXPIRED access token, not a missing sign-in:+ # Claude Code and Codex refresh their own tokens the next time they run, and+ # the widget re-reads the file. Never tell a signed-in user to sign in again+ # (AdomLapper 2026-09-14: "sign in to Claude again" while John was chatting).+ # Say what is true, keep the last good numbers on screen (dimmed), and retry+ # every minute instead of backing off; other errors still back off. code = getattr(e, 'code', None)- msg = ('sign in to %s again' % title.replace(' usage', '')) if code in (401, 403) else str(e)[:120]- cache['v'] = {'error': msg}; cache['wait'] = min(900, max(refresh_ms/1000, cache['wait']) * 2)+ if code in (401, 403):+ msg = '%s token expired, waiting for %s to refresh it' % (title.replace(' usage', ''), 'Claude Code' if 'Claude' in title else 'Codex')+ cache['wait'] = 60+ else:+ msg = str(e)[:120]; cache['wait'] = min(900, max(refresh_ms/1000, cache['wait']) * 2)+ cache['v'] = {'error': msg, 'stale': cache['good']} if cache['good'] else {'error': msg} cache['t'] = time.time() return cache['v'] class H(BaseHTTPRequestHandler):
bin/hydrogen-widget-marks.pyinstall.shpackage.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-vscode",- "version": "1.1.77",+ "version": "1.1.78", "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.77",+ "version": "1.1.78", "license": "PROPRIETARY", "visibility": "public", "hero": {@@ -141,4 +141,4 @@ "adom-vscode-extensions", "adom-vscode-modes" ]-}+}