adom-vscode

Install?

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

adom-wiki pkg install adom/adom-vscode

Latest: v1.1.25, published

Download for your machine · v1.1.20? ?

Release 1.1.20

Dependencies 1 skill

Contents

README

markdown

adom-vscode

Adom VS Code Extension for Adom's Docker containers running Coder's code-server.

A single Rust binary that ships a VS Code extension + CLI for controlling code-server from any process in the Docker container. AI agents (Claude Code, Codex, Kimi) and scripts use this to open files, manage extensions, control the layout, change settings live, run container commands, and pass events to the Hydrogen Desktop / Hydrogen Web frontend. The binary embeds everything: the VSIX, bash completions, and the paste-screenshot interceptor, so adom-vscode install sets up the entire thing.

Since v1.1.0 the extension is also the native replacement for host-side wsl.exe plumbing: settings apply live through the VS Code API (no window reload, no dropped Claude sessions), and shell commands run inside the container via /exec instead of an external wsl.exe spawn.

What it does

  • Open files in VS Code tabs (images, text, anything)
  • Reveal files/folders in the Explorer sidebar
  • Preview Markdown files in rendered view
  • Settings, live: get/set any VS Code setting, theme, fonts, font sizes. Changes apply immediately via ConfigurationTarget.Global (no reload, active AI sessions survive)
  • Exec: run shell commands in the container as the workspace user, streamed (SSE) or buffered
  • Event queue: any container process pushes events; the HD/HW frontend pulls them when it wants (login-refresh needed, long-job progress, anything)
  • Container identity: query whether this is an hd (local Hydrogen Desktop) or hw (Adom cloud) container, OS, kernel, key env vars
  • API key: check the health of /var/run/adom/api-key and inject a fresh key after a frontend login refresh
  • Extensions: list installed extensions (with Claude/Codex/Kimi summary), query one extension's version + running state + pending update, update one or all, search, install
  • Start new Claude Code conversations
  • Modes: switch VS Code into different full-screen experiences (claudecode, claudetty, tty)
  • Control layout: sidebar, panel, terminal, activity bar (show/hide/toggle)
  • Run any VS Code command (generic escape hatch)
  • Activity bar icon: Adom icon with accordion sidebar showing every verb, AI prompts, and modes
  • Paste image into terminal: Ctrl+V an image while focused in a code-server terminal uploads to /tmp/shots/ and pastes the file path (so the CLI Claude Code can ingest it)

Install

# Download the binary (includes embedded VSIX + completions + paste-screenshot interceptor)
gh release download --repo adom-inc/adom-vscode --pattern "adom-vscode" --output /usr/local/bin/adom-vscode --clobber
sudo chmod +x /usr/local/bin/adom-vscode

# Self-install everything
adom-vscode install

Or via gallia: node install.mjs downloads the binary and runs adom-vscode install.

Port discovery

The extension serves its HTTP API on 127.0.0.1:8821. If 8821 is already taken it walks 8822..8831, then takes an ephemeral port, and writes the truth to:

~/.local/share/adom-vscode/port.json    # { "port": N, "preferredPort": 8821, "pid": ..., "version": ..., "startedAt": ... }

External services that expect the well-known port should read that file when 8821 does not answer. The CLI already does (resolution order: ADOM_VSCODE_PORT env var, then port.json, then 8821). The extension also drops an adom-ports claim breadcrumb when that CLI is present, and GET /health reports both port (actual) and preferredPort.

HTTP API (the verb surface)

Everything the CLI does is a plain HTTP call, so HD, HW, and any script can hit the API directly. GET /health returns {version, port, preferredPort, portFile, verbs: [...]} so callers can feature-detect instead of gating on versions.

Verb Method + route What it does
health GET /health Liveness, version, actual port, full verb roster
open POST /open {path} Open a file in a tab
reveal POST /reveal {path} Reveal in Explorer sidebar
preview-markdown POST /preview-markdown {path} Rendered Markdown preview
config.get GET /config?key=<dotted.key> Effective value + default/global/workspace layers
config.set POST /config {key, value} Set in User (Global) settings, applies live, no reload
theme.get GET /theme Active theme, preferred dark/light, all installed themes
theme.set POST /theme {theme} Switch color theme live
fonts.get GET /fonts All font settings (editor, terminal, chat prose chat.fontFamily, chat code chat.editor.fontFamily) plus the RESOLVED families the renderer actually shows (via document.fonts.check)
fonts.set POST /fonts {editorFamily?, proseFamily?, proseCodeFamily?, editorSize?, terminalSize?, proseSize?, proseCodeSize?} Set any subset live
exec POST /exec {command, cwd?, timeoutSec?} Buffered: run bash -lc as the workspace user, return {exitCode, stdout, stderr, timedOut}. HD-local only; cloud (hw) refuses with exec_disabled_on_cloud
exec.stream POST /exec/stream {command, cwd?, timeoutSec?} Streaming: SSE events start, stdout, stderr, exit {exitCode, timedOut}. HD-local only, same policy
queue.push POST /queue/push {topic?, type?, payload?, ttlSec?} Push an event for the frontend; returns {id, depth}
queue.pull GET/POST /queue/pull {topic?, max?, peek?} Pull (consume) events; peek reads without consuming
queue.status GET /queue/status Depth + per-topic counts
queue.clear POST /queue/clear {topic?} Drop all events, or one topic
container.info GET /container kind: hd|hw|unknown, cloud flag, OS, kernel, hostname, carbon URL, VS Code proxy URI, detection markers
apikey.status GET /apikey /var/run/adom/api-key: exists, readable, size, age, sha256 fingerprint
apikey.set POST /apikey {value} Write a fresh key (root-owned path, goes through passwordless sudo)
extensions.list GET /extensions (?all=1 incl. builtins, ?id=x&latest=1 for one + gallery check) Installed extensions + ai summary for claude/codex/kimi
extensions.update POST /extensions/update {id?} Update one extension, or all when id omitted
extensions.search POST /extensions/search {query} Open Extensions sidebar with a query
extensions.install POST /extensions/install {id} Install by marketplace id
terminal.tui POST /terminal/tui {name, command?, shellPath?, shellArgs?, env?, cwd?, viewColumn?, icon?, color?} (icon: a codicon id, or adom:claude / adom:codex / adom:kimi for the vendor marks the AI launch bar uses; vendor marks carry their own colour) Open a NAMED terminal as an EDITOR TAB running a command (AI CLIs for the Hydrogen AI Threads panel); returns a handle {id}. icon is a codicon name, color a theme color id (terminal.ansi*), so per-AI tabs are distinguishable. Never touches terminal.integrated.defaultLocation. Capability marker terminal.tui.icon in the /health roster
terminal.tui.list GET /terminal/tui/list Handles: id, name, command, running state
terminal.tui.dispose POST /terminal/tui/dispose {id?, name?} Close a named TUI terminal by handle
claude.new POST /claude/new New Claude Code conversation
ai.providers GET /ai/providers Which AIs are installed here (Claude Code, Codex, Kimi, Antigravity), the command a new tab runs, and which icons the user shows
ai.new POST /ai/new {type, tui?, group?, surface?} Open a new thread for that AI in an editor group (default: the active one) and keep it there; tui:true puts the AI's CLI in a terminal tab instead. Antigravity: its extension is a side-bar view that can also host the conversation in an editor panel (its unlisted antigravity.moveBetweenEditorAndSidebar), so the default is a TAB with a new conversation in it while the side bar keeps its own conversation (two at most); surface:"sidebar" sends it back
ai.icons POST /ai/icons {icons?:[...], hideVendorButtons?:[...]} Which AI icons the title bar shows (setting adom-vscode.aiIcons) and which of the AI extensions' own tab-strip buttons stay hidden (setting adom-vscode.hideVendorTabButtons, default both: Claude Code: Open, Open Codex Sidebar); the menu's checklists write both
tabs.close POST /tabs/close {label?, kind?, group?} Close every tab matching an exact label and/or kind (claude, codex, kimi, terminal, ...), optionally in one group; the cleanup half of a ralph test
tabs.list GET /tabs Event-driven tab census from the extension host (window.tabGroups): every tab with kind (claude | codex | kimi | terminal | other), label, group/tab index, active flags; Codex rows carry sessionId from the editor URI. No DOM, no CDP
tabs.events GET /tabs/events (SSE) The same census pushed on every tab open/close/move/focus change (event: census), plus event: claude-session when the SessionStart hook reports a new Claude session. Hydrogen holds one stream per workspace instead of polling the iframes
claude.session-start POST /claude/session-start {session_id, transcript_path?, cwd?, source?} Ingest for a Claude SessionStart hook: records the session (last 100) and broadcasts it on /tabs/events
claude.sessions GET /claude/sessions The recorded Claude sessions (id, transcript path, cwd, source, time)
claude.open-session POST /claude/open-session {sessionId, prompt?, retagEntrypoint?} Open an EXISTING Claude conversation as an editor tab and CONFIRM it bound (bound:true once a CLI resumes that id), instead of the fire-and-forget claude-vscode.editor.open. Rewrites a programmatic transcript entrypoint (sdk-cli/sdk-ts/sdk-py -> claude-vscode) first, because the Claude extension silently declines to restore those and opens a fresh tab
claude.context GET /claude/context Live AI session stats (context %, cache, tools, compactions)
command POST /command {command, args?} Any VS Code command (escape hatch)
notify POST /notify {message, level?} Toast notification
mode.* POST /mode/claudecode|claudetty|tty|reset Full-screen mode switching
paste-screenshot POST /paste-screenshot (raw image body) Save clipboard image to /tmp/shots/

The event queue survives window reloads (persisted to ~/.local/share/adom-vscode/queue.json, capped at 1000 items, optional per-item TTL).

Why the settings verbs matter

POST /config uses vscode.workspace.getConfiguration().update(key, value, ConfigurationTarget.Global), which applies immediately with no window reload. That is the fix for two long-standing HD bugs at once: font-size changes that silently never landed when the wsl.exe settings.json merge hung, and theme changes that needed a full workbench reload (which dropped the user's Claude sessions).

For fonts, GET /fonts reports the resolved family the renderer actually shows (checked with document.fonts.check in the workbench), not just the configured stack. The configured chain often names a font that is not installed on the rendering side; HD needs the true value for its readout.

CLI quick start

adom-vscode health                        # Liveness + version + verb roster
adom-vscode open /path/to/file.png        # Open file in VS Code tab
adom-vscode reveal /path/to/folder/       # Reveal in Explorer sidebar
adom-vscode preview /path/to/README.md    # Markdown preview

# Settings (all apply live, no reload)
adom-vscode config get workbench.colorTheme
adom-vscode config set editor.fontSize 14
adom-vscode theme get                     # active + available themes
adom-vscode theme set "Adom Dark"
adom-vscode font get                      # all font settings + resolved families
adom-vscode font set --editor-size 14 --prose-family Inter
adom-vscode font-size get
adom-vscode font-size set --editor 14 --terminal 13 --prose 15

# Exec (streams live by default; --json for buffered)
adom-vscode exec "ls -la" --cwd /home/adom/project
adom-vscode exec "npm test" --timeout 600
adom-vscode exec "uname -a" --json

# Event queue (container process -> HD/HW frontend)
adom-vscode queue push login --type refresh-needed --payload '{"expiresInHours":24}'
adom-vscode queue pull --topic login      # consume
adom-vscode queue pull --peek             # read without consuming
adom-vscode queue status
adom-vscode queue clear --topic progress

# Container identity + API key
adom-vscode container                     # hd or hw, OS, env, markers
adom-vscode apikey status
adom-vscode apikey set --stdin < new-key.txt

# Extensions
adom-vscode extensions list               # + claude/codex/kimi AI summary
adom-vscode extensions status anthropic.claude-code --latest
adom-vscode extensions update anthropic.claude-code
adom-vscode extensions update             # update all
adom-vscode extensions search "python"
adom-vscode extensions install ms-python.python

# Layout, modes, misc
adom-vscode claude new                    # New Claude Code chat
adom-vscode sidebar toggle                # Toggle sidebar
adom-vscode panel toggle                  # Toggle bottom panel
adom-vscode terminal new                  # New terminal
adom-vscode activity-bar toggle           # Toggle activity bar
adom-vscode notify "Build complete"       # Toast notification
adom-vscode reload                        # Reload VS Code window (drops AI sessions!)
adom-vscode command <any-vscode-command>  # Generic escape hatch
adom-vscode --help                        # All commands

Modes

Modes take over VS Code's full screen for a focused experience. Use mode reset to return to normal.

adom-vscode mode claudecode               # Claude Code panel UI
adom-vscode mode claudetty                # Full-screen terminal + claude CLI
adom-vscode mode tty                      # Blank full-screen terminal
adom-vscode mode tty -c "echo hello" -n "Demo"  # TTY with auto-command
adom-vscode mode reset                    # Exit any mode

URL auto-trigger

Add ?mode=X to any VS Code URL to auto-enter a mode on page load. Perfect for sharing onboarding links with new users.

URL param Mode
?mode=claudecode Claude Code panel UI
?mode=claudetty Full-screen terminal + claude CLI
?mode=tty Blank full-screen terminal
?mode=tty&cmd=echo%20hello TTY with auto-command

Example: https://<slug>.adom.cloud/?mode=claudecode&folder=/home/adom/project

Paste-screenshot interceptor

When you press Ctrl+V with an image on the clipboard while focused in a code-server terminal (e.g. running CLI Claude Code), the interceptor uploads the image to the extension at POST /paste-screenshot, which saves it to /tmp/shots/ (deduplicated via SHA-256) and writes the file path back to the terminal so the CLI can ingest it. Without this, code-server returns an empty string for clipboard.readText() when the clipboard holds only an image, so the paste does nothing.

Activity bar

The extension adds an Adom icon to the VS Code activity bar. Click it to open a sidebar with accordion sections covering the full verb surface:

  • Overview: what the extension does
  • Modes: Claude Code, TTY modes with "open in new window" and "copy URL" buttons
  • Commands: file, layout, and utility commands with hover tooltips
  • AI Prompts: sample prompts to paste into Claude Code chat
  • Settings & Theme: live config/theme/font control
  • Exec & Queue: container exec and the frontend event queue
  • Container & Keys: hd/hw identity and api-key health
  • Extensions: list/status/update, incl. the AI assistants

The skill pack

The page ships a parent skill plus six sub-skills so any AI in the Adom ecosystem can discover and drive every verb. adom-vscode install (and adom-wiki pkg install) deploys all of them to ~/.claude/skills/:

Skill Covers
adom-vscode (parent) Mental model, file ops, workspace scoping, port discovery, management
adom-vscode-settings Live config/theme/font get+set, resolved font families
adom-vscode-exec Streamed + buffered container exec, timeouts, the no-wsl.exe rationale
adom-vscode-queue The container-to-frontend event queue, topics/TTL/peek, patterns
adom-vscode-container hd/hw identity, api-key status/inject, port discovery, /health feature detection
adom-vscode-extensions Extension list/status/update, the Claude/Codex/Kimi AI summary
adom-vscode-modes Full-screen modes, layout verbs, URL auto-triggers, paste-screenshot

Architecture

Claude Code / scripts / HD frontend
    ↓  runs CLI or hits HTTP directly
adom-vscode open /foo/bar.png
    ↓  HTTP POST to localhost:8821 (see Port discovery)
Adom VS Code Extension (inside code-server)
    ↓  vscode.* API / child_process
VS Code UI + container shell

See ARCHITECTURE.md for the full design document. See COMMANDS.md for all VS Code commands usable with adom-vscode command <id>.

Development

# Build everything locally
bash build.sh

# Release (single binary ships everything)
VERSION=$(cat VERSION | tr -d '[:space:]')
gh release create v${VERSION} \
  cli/target/release/adom-vscode \
  --repo adom-inc/adom-vscode \
  --title "v${VERSION}"

Version

The version lives in VERSION (single source of truth) and flows to:

  • extension/package.json
  • cli/Cargo.toml
  • VSIX manifest (via build.sh)
  • Activity bar sidebar footer
  • Health endpoint response
  • CLI --version flag

v1.1.10 added: live config/theme/font verbs, streamed exec, the frontend event queue, container identity, api-key status/inject, extension query/update, port fallback + port.json discovery, and verb-roster feature detection on /health.

v1.1.11 added: the skill pack (parent + 6 sub-skills) installed by both adom-vscode install and the wiki package.

v1.1.12: exec verbs are HD-local only (disabled on cloud hw containers, errorCode exec_disabled_on_cloud); /health reports execAllowed and omits exec verbs where disabled.

v1.1.13: extension-dir GC on every install (prunes stale version dirs VS Code's startup GC never reaps on long-lived windows; keeps current, live-process, and running-server versions), and install now pushes a system/update-pending queue event so the frontend can reload at a safe moment. \nv1.1.15: version realignment and the issue #642 fix (platform-scoped extensions --latest with allPlatforms matrix, --platform flag, honest 'already current' update). \nv1.1.16: extensions install --vsix <path|url> installs a VSIX directly via code-server's CLI with the extension-host env stripped (absorbs the CODE_SERVER_PARENT_PID / not-spawned-with-IPC trap from issue #642).

v1.1.17: named TUI terminals as editor tabs with real handles (POST /terminal/tui, /terminal/tui/dispose, GET /terminal/tui/list; CLI terminal tui / tui-list / tui-dispose), built for the Hydrogen AI Threads panel; /command now passes a non-array args value as a single argument instead of silently dropping it, and reports argsShape.

v1.1.18: terminal.tui accepts optional icon (codicon name -> ThemeIcon) and color (theme color id -> ThemeColor) so AI TUI tabs match the AI Threads panel identities; tui-list echoes them; /health roster gains the terminal.tui.icon capability marker.

v1.1.19: terminal.tui accepts shellPath/shellArgs/env (createTerminal semantics): the CLI runs AS the terminal process, no shell underneath, no echoed launch line pinned atop the TUI, no quoting constraints, and the tab closes when the process exits. shellPath wins over command; a missing or non-executable shellPath fails explicitly (errorCode shell_path_not_executable) instead of falling back to a bare prompt. Capability marker terminal.tui.shell in the /health roster.

v1.1.20: /command returns errorCode command_not_found (HTTP 400) with did-you-mean suggestions when a command id is unknown (args-taking commands often use the bare id, e.g. moveActiveEditor); terminal.tui injects ADOM_VSCODE_TUI_ID into the terminal env and tui-list reports processId, giving callers a DOM-free identity chain (pid -> /proc environ -> registry id).

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.26: AI thread icons in the window title bar (John 2026-09-06), the layout-controls row at the top right. VS Code has no contribution point there, so browser/ai-titlebar.js rides workbench.html (written by adom-vscode install, back on every reload) and calls this extension over /proxy/<port>/ai/*: one icon per INSTALLED AI in the vendor's own colour (Claude Code, Codex, and Kimi or Antigravity when their extension is present), a sparkle menu listing every installed AI plus its TUI (the CLI in a terminal tab), and a VS Code-style checklist of which icons to show (saved as adom-vscode.aiIcons; TUIs off by default). A click opens the thread in THIS window's active group (each Hydrogen VS Code tab is its own window and extension host; the script matches its visible tabs against every candidate port's census, so the thread never lands in another window) and moves it back if the AI extension split the editor. GET /ai/providers, POST /ai/new {type, tui?, group?} (a TUI goes through the /terminal/tui registry with the AI Threads panel's icon and colour), POST /ai/icons {icons}, POST /tabs/close {label|kind}. The icons are drawn as a real monaco-toolbar (VS Code's own action-item markup, hover and active styling) with monaco-hover tooltips; the menu glyph is a plus with a dropdown chevron. Also: extensions install --vsix and adom-vscode install used the remote-cli wrapper, which only works inside a VS Code terminal and reported success while installing nothing; both now use the code-server binary and require its own "successfully installed" line.