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.119
13 files changed
+111−15
VERSION+1−1@@ -1 +1 @@-1.1.118+1.1.119
bin/adom-vscode⋯ 1 unchanged line ⋯
bin/hydrogen-widget-claude-rc+101−6@@ -64,6 +64,70 @@ def hd_threads(): 'working': bool((t.get('working') or {}).get('state') in ('working', 'running', 'answering'))}) return rows, None +# ── The other machines, over the Adom Bridge mesh (John 2026-10-20: "combine adom bridge mesh to+# understand what account i'm logged into on other boxes like arav-rog or crr cuz i sometimes log+# into those on other accounts ... so i know when i'm on my phone which account to log into"). Each+# machine's own Hydrogen answers which Claude account its workspace uses (/claude/auth-status) and+# its Claude threads with their Remote Control state (/ai-threads). Asked in parallel, at most every+# 5 minutes: these are real calls into every PC, the cloud list above is the live part.+MESH_TTL = 300+_mesh = {'t': 0, 'v': None}++def _ab(args, timeout):+ import subprocess, shutil+ exe = shutil.which('adom-bridge') or shutil.which('adom-bridge-cli')+ if not exe: return None+ try:+ r = subprocess.run([exe, '--ai-thread', 'Claude RC widget'] + args, capture_output=True, text=True, timeout=timeout)+ raw = r.stdout+ return json.loads(raw[raw.index('{'):]) if '{' in raw else None+ except Exception: return None++def _hd(target, path, timeout=40):+ d = _ab(['--target', target, 'hd_api', json.dumps({'method': 'GET', 'path': path, 'timeoutSeconds': timeout})], timeout + 15)+ if not d: return None+ b = d.get('body') if isinstance(d, dict) else None+ if isinstance(b, str):+ try: b = json.loads(b)+ except Exception: b = None+ return b if isinstance(b, dict) else None++def _one(name):+ out = {'name': name}+ a = _hd(name, '/claude/auth-status', 30)+ if a is None: out['unreachable'] = True; return out+ out['account'] = a.get('email'); out['logged_in'] = a.get('logged_in'); out['plan'] = a.get('subscription_type')+ t = _hd(name, '/ai-threads', 45)+ if t and t.get('activated') is False:+ # AI Threads is parked there (deliberately free until its panel opens). The extension's+ # own tab mirror still lists every Claude tab at no cost; RC state then comes from the+ # cloud list (a conversation with RC on is a connected session under its title).+ tb = _hd(name, '/ai-threads/tabs', 30) or {}+ tabs = [x for x in ((tb.get('mirror') or {}).get('tabs') or []) if x.get('kind') == 'claude']+ out['threads'] = len(tabs)+ out['thread_rows'] = [{'name': x.get('label') or 'Claude Code', 'rc': None} for x in tabs][:40]+ out['threads_from'] = 'tab list'+ return out+ if t and t.get('groups') is not None:+ rows = [th for g in t.get('groups') or [] for th in g.get('threads') or [] if th.get('type') == 'claude' and not th.get('tui')]+ out['threads'] = len(rows)+ out['threads_rc'] = sum(1 for th in rows if th.get('remote_control') is True)+ out['threads_unknown'] = sum(1 for th in rows if th.get('remote_control') is None)+ out['thread_rows'] = [{'name': th.get('name') or th.get('ai_title') or th.get('label') or 'Claude Code', 'rc': th.get('remote_control')} for th in rows][:30]+ elif t and t.get('error'): out['threads_note'] = str(t['error'])[:90]+ return out++def mesh():+ if _mesh['v'] is not None and time.time() - _mesh['t'] < MESH_TTL: return _mesh['v']+ tg = _ab(['targets'], 30)+ if not tg: return _mesh['v'] # no bridge CLI here, or it did not answer: keep the last answer+ names = [t.get('name') for t in tg.get('targets') or [] if t.get('name') and t.get('os', 'Windows') in ('Windows', 'macOS', 'Darwin')]+ from concurrent.futures import ThreadPoolExecutor+ with ThreadPoolExecutor(max_workers=8) as ex:+ v = list(ex.map(_one, names))+ _mesh.update(t=time.time(), v=v)+ return v+ def data(): tok, err = token() if err: return {'error': err}@@ -123,6 +187,27 @@ def data(): acc = (lib.read_json_if_changed('~/.claude.json').get('oauthAccount') or {}) if acc.get('emailAddress'): out['account'] = acc['emailAddress'] except Exception: pass+ # Every machine on the mesh: its Claude account, and its threads with their RC state. A machine+ # the cloud list does not know (its sessions live under ANOTHER account) is added here, so the+ # hover can say which account to open on the phone to see it.+ me_acc = (out.get('account') or '').lower()+ for mm in mesh() or []:+ key = mm['name'].lower()+ m = next((x for x in out['machines'] if x['name'].lower() == key), None)+ if m is None:+ m = {'name': mm['name'], 'this_pc': key == me.lower(), 'host': None, 'sessions': []}+ out['machines'].append(m)+ for k in ('account', 'plan', 'logged_in', 'unreachable', 'threads', 'threads_rc', 'threads_unknown', 'thread_rows', 'threads_note'):+ if k in mm: m[k] = mm[k]+ m['other_account'] = bool(mm.get('account')) and mm['account'].lower() != me_acc+ if not m['other_account'] and m.get('thread_rows'):+ live = [x['title'] for x in m['sessions'] if x['connected']]+ def on_rc(label):+ lab = label.replace('\u2026', '').strip().lower()+ return any(t.lower().startswith(lab) or lab.startswith(t.lower()) for t in live if lab and t)+ for r in m['thread_rows']:+ if r['rc'] is None: r['rc'] = on_rc(r['name'])+ out['machines'].sort(key=lambda m: (not m['this_pc'], m['name'].lower())) # Something to fix: this PC's RC host is not connected, or it has Claude threads RC cannot see. mine = next((m for m in out['machines'] if m['this_pc']), None) off = [r for r in (rows or []) if r['rc'] is False]@@ -148,6 +233,9 @@ X_CSS = r""" .rr .a{font-size:10.5px;color:rgba(230,235,239,.5);white-space:nowrap} .rn{font-size:11px;color:rgba(230,235,239,.6);padding:2px 0 0 16px} .rn.bad{color:#f0a060}+.ra{font-size:11px;color:rgba(230,235,239,.62);padding:0 0 4px}+.ra.other{color:#f0a060}+.ra b{font-weight:600} """ X_JS = r"""@@ -162,14 +250,21 @@ function xCustom(d0){const d=d0&&d0.stale?d0.stale:d0; h+=`<div class="rq">Your phone can reach <b>${d.live}</b> session${d.live===1?'':'s'} right now.</div>`; for(const m of d.machines){ h+=`<div class="rm"><h4>${esc(m.name)}${m.this_pc?'<span class="me">this PC</span>':''}</h4>`;- if(m.host)h+=rcRow(m.host.connected?'on':'off','Remote Control host',m.host.connected?'On your phone: start a new session on this machine':'Not connected: this machine is not on your phone',m.host.connected?'live':rcAgo(m.host.last)+' ago',m.host.title);+ if(m.account)h+=`<div class="ra${m.other_account?' other':''}">${m.other_account?'Signed in as <b>'+esc(m.account)+'</b>: a different account. Open the Claude app as '+esc(m.account)+' to see this machine.':'Claude: '+esc(m.account)+(m.plan?' · '+esc(m.plan):'')}</div>`;+ else if(m.unreachable)h+=`<div class="ra">Not reachable over Adom Bridge right now</div>`;+ else if(m.logged_in===false)h+=`<div class="ra other">Claude Code is not signed in on this machine</div>`;+ if(m.other_account){ /* its sessions live under the other account: this account's cloud list cannot speak for it */ }+ else if(m.host)h+=rcRow(m.host.connected?'on':'off','Remote Control host',m.host.connected?'On your phone: start a new session on this machine':'Not connected: this machine is not on your phone',m.host.connected?'live':rcAgo(m.host.last)+' ago',m.host.title); else h+=`<div class="rn bad">No Remote Control host on this machine</div>`; for(const s of m.sessions)h+=rcRow(s.connected?'on':'off',s.title,(s.needs?'Needs you: '+s.needs:s.detail),s.connected?(s.worker==='working'?'working':'live'):rcAgo(s.last)+' ago',s.connected?'On your phone now':'Listed, but its machine is not connected');- if(m.this_pc){- if(d.threads){const on=d.threads.filter(t=>t.rc===true).length,off=d.threads.filter(t=>t.rc===false).length,unk=d.threads.length-on-off;- h+=`<div class="rn${off?' bad':''}">${d.threads.length} Claude thread${d.threads.length===1?'':'s'} open here: ${on} on Remote Control${off?', '+off+' not (not on your phone)':''}${unk?', '+unk+' not loaded yet':''}</div>`;- for(const t of d.threads)h+=rcRow(t.rc===true?'on':(t.rc===false?'off':'unk'),t.name,'',t.rc===true?'on phone':(t.rc===false?'not on RC':'unknown'),t.rc===true?'Remote Control is on for this conversation':(t.rc===false?'Remote Control is off: run /remote-control in this conversation to see it on your phone':'Its tab has not loaded since Hydrogen started, so its state is unknown'));}- else if(d.threads_note)h+=`<div class="rn">${esc(d.threads_note)}</div>`;}+ /* Threads: this PC from its own Hydrogen (d.threads), the others over the mesh (m.thread_rows). */+ const rows=m.this_pc&&d.threads?d.threads:(m.thread_rows||null);+ if(rows){const on=rows.filter(t=>t.rc===true).length,off=rows.filter(t=>t.rc===false).length,unk=rows.length-on-off;+ h+=`<div class="rn${off?' bad':''}">${rows.length} Claude thread${rows.length===1?'':'s'} open: ${on} on Remote Control${off?', '+off+' not (not on your phone)':''}${unk?', '+unk+' not loaded yet':''}</div>`;+ const SHOW=6;for(const t of rows.slice(0,SHOW))h+=rcRow(t.rc===true?'on':(t.rc===false?'off':'unk'),t.name,'',t.rc===true?'on phone':(t.rc===false?'not on RC':'unknown'),t.rc===true?'Remote Control is on for this conversation':(t.rc===false?'Remote Control is off: run /remote-control in this conversation to see it on your phone':'Its tab has not loaded since Hydrogen started, so its state is unknown'));+ if(rows.length>SHOW)h+=`<div class="rn">and ${rows.length-SHOW} more</div>`;}+ else if(m.this_pc&&d.threads_note)h+=`<div class="rn">${esc(d.threads_note)}</div>`;+ else if(m.threads_note)h+=`<div class="rn">${esc(m.threads_note)}</div>`; h+=`</div>`;} const foot=[]; if(d.old_listed)foot.push(`<span>${d.old_listed} older session${d.old_listed===1?'':'s'} still listed in the cloud but not heard from in 6 h+ (hidden: they are not on your phone).</span>`);
bin/hydrogen-widget-claude-usagebin/hydrogen-widget-codex-usagebin/hydrogen-widget-lib.pybin/hydrogen-widget-marks.pyhydrogen-widget.json+3−3@@ -45,7 +45,7 @@ "id": "claude-rc", "name": "Claude RC", "icon": "vendor:claude",- "brief": "How many Claude sessions your phone can reach right now through Remote Control. Hover it for every machine (is it on your phone, when was it last seen) and which of this PC's Claude threads have Remote Control on.",+ "brief": "How many Claude sessions your phone can reach right now through Remote Control. Hover it for every machine on your Adom Bridge mesh: which Claude account it is signed into, whether it is on your phone, and which of its Claude conversations have Remote Control on.", "width": 90, "install": "adom/adom-vscode", "launch": {@@ -58,8 +58,8 @@ }, "docs": "widgets/README.md", "width_tall": 90,- "about": "Reads the Claude Code sign-in in this workspace and asks the session list the Claude app and claude.ai/code read: every Remote Control session on your account, which machine it runs on, and whether it is connected right now (what your phone shows). Each machine's Remote Control host (the <machine>-rc session Hydrogen keeps) is listed even when it dropped off, with when it was last seen. For this PC it also asks Hydrogen which Claude threads are open and whether each has Remote Control on, because a conversation only reaches the phone once Remote Control is on for it. Sessions the cloud still lists but has not heard from in 6 hours are counted, not shown. No model runs.",- "refresh": "once a minute; pauses while the window is hidden; backs off on errors; re-reads the sign-in file only when it changes"+ "about": "Reads the Claude Code sign-in in this workspace and asks the session list the Claude app and claude.ai/code read: every Remote Control session on your account, which machine it runs on, and whether it is connected right now (what your phone shows). Each machine's Remote Control host (the <machine>-rc session Hydrogen keeps) is listed even when it dropped off, with when it was last seen. Over the Adom Bridge mesh it also asks each machine's Hydrogen which Claude account it is signed into (a machine on another account is flagged with the account to open on the phone) and which Claude conversations are open there, with whether each has Remote Control on, because a conversation only reaches the phone once Remote Control is on for it. Sessions the cloud still lists but has not heard from in 6 hours are counted, not shown. No model runs.",+ "refresh": "the cloud list once a minute, the machines over the mesh at most every 5 minutes; pauses while the window is hidden; backs off on errors" } ] }
install.shpackage.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-vscode",- "version": "1.1.118",+ "version": "1.1.119", "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.118",+ "version": "1.1.119", "license": "PROPRIETARY", "visibility": "public", "hero": {@@ -141,4 +141,4 @@ "adom-vscode-extensions", "adom-vscode-modes" ]-}+}
widgets/README.md+3−2@@ -30,11 +30,12 @@ Hover it for the full picture, one block per machine: - **The machine's Remote Control host** (the `<machine>-rc` session Hydrogen keeps): green when it is connected, so the machine is on your phone; amber with "last seen" when it dropped off. - **Every connected session** on that machine, with Claude's own one-line status.-- **On this PC, every open Claude thread** and whether Remote Control is on for it. A conversation reaches your phone only once Remote Control is on for it (`/remote-control` in that conversation). Threads whose tab has not loaded since Hydrogen started show as unknown.+- **Which Claude account each machine is signed into**, asked over the Adom Bridge mesh. A machine on a different account is flagged in amber with the account to open in the Claude app to see it.+- **Every machine's open Claude conversations** (over the mesh) and whether Remote Control is on for each. A conversation reaches your phone only once Remote Control is on for it (`/remote-control` in that conversation). Threads whose tab has not loaded since Hydrogen started show as unknown. Sessions the cloud still lists but has not heard from in 6 hours are counted at the bottom, not shown: they are not on your phone either. -It reads the same session list the Claude app and claude.ai/code read, with the Claude Code sign-in in this workspace. No model runs.+It reads the same session list the Claude app and claude.ai/code read, with the Claude Code sign-in in this workspace, and asks each machine's Hydrogen over the Adom Bridge mesh at most every 5 minutes. No model runs. <img src="docs/claude-rc-hover.png" width="380" alt="Claude RC hover: AdomLapper and ConfRoomROG live, arav-rog and winvm not on the phone">
widgets/docs/claude-rc-hover.png⋯ 1 unchanged line ⋯