# adom-vscode — VS Code Extension + Rust CLI

## Context

Docker processes (shotlog, gallia, Claude Code) need to control VS Code (code-server) programmatically — open files, reveal folders, search/install extensions, start new Claude Code chats. The Hydrogen workspace API only controls the outer shell; VS Code is an iframe running code-server with its own API. A VS Code extension inside code-server exposes a local HTTP server, and a Rust CLI wraps those calls for the AI.

Source lives in its own repo `adom-inc/adom-vscode`. Pre-built binary + VSIX attached to GitHub Releases. `gallia/install.mjs` downloads them — no Rust toolchain needed on user containers.

## Architecture

```
Claude Code / scripts / shotlog
    ↓  runs CLI
adom-vscode open /foo/bar.png
    ↓  HTTP POST to localhost:8821
Adom VS Code Extension (inside code-server)
    ↓  vscode.* API
VS Code UI
```

## File Structure

**Repo**: `adom-inc/adom-vscode`
```
/home/adom/project/adom-vscode/
├── extension/
│   ├── package.json                   — Extension manifest
│   ├── src/extension.ts               — HTTP server + route handlers
│   ├── tsconfig.json                  — TS config (IDE only, esbuild compiles)
│   └── .vscodeignore                  — Exclude src/ from VSIX
├── cli/
│   ├── Cargo.toml                     — Rust CLI
│   └── src/main.rs                    — CLI entry (clap, raw HTTP client)
├── SKILL.md                           — Claude Code skill (CLI usage, endpoints, modes)
├── COMMANDS.md                        — Full VS Code + extension command reference
├── ARCHITECTURE.md                    — This plan file, saved permanently
├── README.md                          — Project overview, links to ARCHITECTURE.md
├── build.sh                           — Local build + install (dev only)
└── .gitignore
```

**Distribution** (GitHub Releases on adom-inc/adom-vscode):
- `adom-vscode` — single pre-built Linux x86_64 binary (embeds VSIX + SKILL.md via `include_bytes!`/`include_str!`)

**Gallia integration** — `install.mjs` downloads the one binary, then self-installs:
- `gh release download` → `/usr/local/bin/adom-vscode`
- `adom-vscode install-extension` (extracts embedded VSIX, installs into code-server)
- `adom-vscode skills install` (writes embedded SKILL.md to `~/.claude/skills/`)

## Extension (TypeScript)

### Port: 8821 (127.0.0.1 only)

### Endpoints

| Method | Path | Body | VS Code API |
|--------|------|------|-------------|
| GET | `/health` | — | — |
| POST | `/open` | `{"path":"..."}` | `showTextDocument(uri)` |
| POST | `/reveal` | `{"path":"..."}` | `executeCommand('revealInExplorer')` |
| POST | `/preview-markdown` | `{"path":"..."}` | `executeCommand('markdown.showPreview')` |
| POST | `/extensions/search` | `{"query":"..."}` | `executeCommand('workbench.extensions.search')` |
| POST | `/extensions/install` | `{"id":"..."}` | `executeCommand('workbench.extensions.installExtension')` |
| POST | `/claude/new` | `{}` | `executeCommand('claude-vscode.newConversation')` |
| POST | `/command` | `{"command":"...","args":[]}` | `executeCommand(cmd, ...args)` |
| POST | `/notify` | `{"message":"...","level":"info"}` | `showInformationMessage()` |
| POST | `/mode/claudecode` | `{}` | See below — enters Claude Code-only mode |
| POST | `/mode/reset` | `{}` | Restores default VS Code layout |

### Claude Code-only mode (`POST /mode/claudecode`)

Strips VS Code down to just a Claude Code chat. For onboarding new users.

The handler runs these VS Code commands in sequence:
1. `workbench.action.closeAllEditors` — clear all tabs
2. `workbench.action.closeSidebar` — hide file explorer
3. `workbench.action.closePanel` — hide terminal/bottom panel
4. `workbench.action.toggleActivityBarVisibility` — hide left icon strip (check if visible first via config)
5. `workbench.action.toggleStatusBarVisibility` — hide bottom status bar
6. `claude-vscode.editor.open` — open Claude Code in full-width editor tab

Result: clean full-screen AI chat, nothing else.

`POST /mode/reset` reverses it — shows activity bar, status bar, sidebar.

### URL-based auto-trigger (`?mode=claudecode`)

Code-server persists all URL query params to `~/.local/share/code-server/coder.json` under the `query` key. When a user visits `https://slug.adom.cloud/?mode=claudecode`, that file becomes:
```json
{ "query": { "folder": "...", "mode": "claudecode" } }
```

On extension activation, read that file and check `query.mode`:
```typescript
const coderSettings = JSON.parse(fs.readFileSync(
  path.join(os.homedir(), '.local/share/code-server/coder.json'), 'utf-8'
));
if (coderSettings.query?.mode === 'claudecode') {
  await enterClaudeCodeMode();
}
```

No temp files, no middleware, no proxy changes. Just the URL param and the extension.

### Key details

- `activationEvents: ["*"]` — starts with code-server
- Node.js `http` module (built into extension host), zero runtime deps
- CORS headers, EADDRINUSE retry, `context.subscriptions` cleanup
- On activate: read `~/.local/share/code-server/coder.json` for `query.mode` auto-trigger
- Build: `esbuild` bundles to single `out/extension.js` (~3KB)
- Package: `npx @vscode/vsce package --no-dependencies`

## Rust CLI

### Binary: `adom-vscode`

```
# File operations
adom-vscode open /path/to/file.png
adom-vscode reveal /path/to/folder/
adom-vscode preview /path/to/README.md

# Extensions
adom-vscode extensions search "python"
adom-vscode extensions install ms-python.python

# Claude Code
adom-vscode claude new

# UI modes
adom-vscode mode claudecode
adom-vscode mode reset

# Generic VS Code command (escape hatch)
adom-vscode command workbench.action.toggleSidebarVisibility
adom-vscode command workbench.action.reloadWindow

# Notifications
adom-vscode notify "Build complete!" --level info

# Skill management
adom-vscode skills install
adom-vscode skills list

# Extension management
adom-vscode install-extension          # writes embedded VSIX to /tmp, installs into code-server

# Health
adom-vscode health
```

### AI-oriented output

```
OK: Opened /home/adom/project/README.md in VS Code tab.
OK: Revealed /home/adom/project/src/ in VS Code Explorer sidebar.
OK: Opened new Claude Code conversation in VS Code.

ERROR: Cannot connect to adom-vscode extension on port 8821.
Hint: The extension may need a window reload. Install with: bash /home/adom/project/adom-vscode/build.sh
```

### Deps: `clap`, `serde_json` only. Raw TCP HTTP client (no reqwest).

## Gallia Integration

### install.mjs addition (~line 136, after adom-cli install)

```javascript
// ── Install adom-vscode (binary + extension from GitHub Releases) ─────
console.log('\n1e. Installing adom-vscode...');
{
  try {
    execSync(
      'gh release download --repo adom-inc/adom-vscode --pattern "adom-vscode" --output /usr/local/bin/adom-vscode --clobber',
      { stdio: 'ignore' }
    );
    execSync('sudo chmod +x /usr/local/bin/adom-vscode', { stdio: 'ignore' });
    console.log('   ✓ adom-vscode CLI installed');
  } catch { console.log('   ⚠ Could not download adom-vscode CLI'); }

  // Self-install extension + skill (both embedded in binary)
  try {
    execSync('adom-vscode install-extension', { stdio: 'ignore' });
    console.log('   ✓ adom-vscode extension installed');
  } catch { console.log('   ⚠ Could not install adom-vscode extension'); }
  try {
    execSync('adom-vscode skills install', { stdio: 'ignore' });
    console.log('   ✓ adom-vscode skills installed');
  } catch { console.log('   ⚠ Could not install adom-vscode skills'); }
}
```

### build.sh (dev only — for building locally before cutting a release)

1. `cd extension && npm install && npx esbuild ... && npx @vscode/vsce package`
2. Install VSIX into local code-server
3. `cd ../cli && cargo build --release && sudo cp target/release/adom-vscode /usr/local/bin/`

### Releasing

```bash
cd /home/adom/project/adom-vscode
bash build.sh
gh release create v0.1.0 \
  cli/target/release/adom-vscode \
  --repo adom-inc/adom-vscode \
  --title "v0.1.0" --notes "Initial release — single binary ships CLI + extension + skill"
```

## Implementation Steps

1. Create `extension/` — package.json, tsconfig.json, .vscodeignore, src/extension.ts
2. Build + package VSIX, install into code-server
3. Reload VS Code window, verify `curl http://127.0.0.1:8821/health`
4. Test each endpoint with curl
5. Create `cli/` — Cargo.toml, src/main.rs
6. Build CLI, install to /usr/local/bin/
7. Test: `adom-vscode health`, `adom-vscode open /path/to/file`
8. Write SKILL.md + build.sh
9. Add install.mjs hook
10. Add row to main adom SKILL.md capabilities table
11. Update shotlog filepath links to use `adom-vscode open` instead of code-server CLI directly

## Verification

```bash
curl http://127.0.0.1:8821/health
adom-vscode open /home/adom/project/shotlog/data/test/shot-2026-03-28T13-43-30.png
adom-vscode reveal /home/adom/project/shotlog/data/test/
adom-vscode claude new
adom-vscode extensions search "python"
adom-vscode notify "Hello from adom-vscode!"
```

## Critical Files

| File | Purpose |
|------|---------|
| `/home/adom/project/adom-vscode/extension/src/extension.ts` | HTTP server + VS Code API handlers |
| `/home/adom/project/adom-vscode/extension/package.json` | Extension manifest |
| `/home/adom/project/adom-vscode/cli/src/main.rs` | Rust CLI |
| `/home/adom/project/adom-vscode/SKILL.md` | Claude Code skill (embedded in binary) |
| `/home/adom/project/adom-vscode/ARCHITECTURE.md` | This plan file |
| `/home/adom/project/adom-vscode/README.md` | Project overview |
| `/home/adom/project/adom-vscode/build.sh` | Build + install script |
| `/home/adom/gallia/install.mjs` | Add download hook (~line 136) |
