app
Adom Bridge (macOS)
Public Made by Adomby adom
Adom Bridge is a small operating system for AI on your Mac: it gives an AI real hands on your native desktop, including your signed-in browser, KiCad, Fusion 360, the shell, files, windows and screenshots. It provisions its own Node and Python so bridges run without touching your setup, and it talks straight to your machine. It ships with Adom Hydrogen and installs standalone too, so if you drive your Mac with Claude Desktop or another AI, this is what gives that AI hands.
← Commit history
Delete skills/adom-bridge-windows-host/SKILL.md
1 file changed
−163
skills/adom-bridge-windows-host/SKILL.mddeleted−163@@ -1,163 +0,0 @@-name: adom-bridge-windows-host-user-invokable: false-description: "Read this BEFORE doing any work on adom-bridge from a Claude Code session running directly on the user's Windows machine. Explains how to route everything through the adom-bridge CLI so paths, file operations, and process spawns reach the user's real filesystem instead of getting silently virtualized into Claude Code's Windows AppContainer sandbox. Skipping this causes hour-long debugging spirals where 'open install folder' fails, bridges install to the wrong place, screenshots come back blank, and synthesized clicks don't reach the WebView2."--# Working on adom-bridge from Claude Code on Windows--This skill applies when Claude Code is running **on the user's Windows machine** — i.e. you have a `bash`/`PowerShell` tool that hits the local filesystem directly, instead of running inside the Adom Docker container.--## The single most important fact--Claude Code on Windows runs inside a **Windows AppContainer sandbox**. Every filesystem write from your `bash` or `PowerShell` tool gets transparently redirected:--| What you write | Where it actually lands |-|---|---|-| `C:\Users\<you>\AppData\Local\Adom Bridge\...` | `C:\Users\<you>\AppData\Local\Packages\Claude_<pkgid>\LocalCache\Local\Adom Bridge\...` |-| `C:\Users\<you>\Documents\...` (any AppData/user-roaming write) | The Packages\Claude_<pkgid>\LocalCache equivalent |--**From your perspective both paths show identical contents.** The redirect is invisible inside the sandbox. From the user's real filesystem (their explorer.exe, their PowerShell from Start menu), only the `Packages\Claude_<pkgid>\...` location exists.--**Consequence:** If you launch the GUI from your `bash` (`start "" "C:\...\adom-bridge.exe"`), the GUI inherits your AppContainer. Bridges it installs land in the virtualized cache. The user clicks "Open install folder" and Explorer says **"Location is not available"** because the path the GUI wrote into the registry doesn't exist in the user's real session.--This is the entire reason "Open install folder" took an hour to ship in v1.8.11. Don't repeat it.--## The fix: drive the GUI through `adom-bridge` CLI like Docker does--Instead of using your own `bash`/`PowerShell` for anything user-visible, route every operation through the `adom-bridge` CLI. The CLI talks to a relay (HTTP) which dispatches to the running GUI (WS). The GUI executes commands in **its own process context** — which, when launched correctly, is the user's normal Windows session, unsandboxed.--### Setup (do this once per session)--1. **Pick alt ports if 8765/8766 are taken.** Docker Desktop typically holds `8765` and `8766` for its internal use. Check with `netstat -ano | grep -E ":876[56] "`. Use `8865`/`8866` (or any free pair).--2. **Start the local relay on the alt ports:**- ```bash- nohup C:/Github/adom-bridge/cli/target/release/adom-bridge.exe serve \- --http-port 8866 --ws-port 8865 > /tmp/relay.log 2>&1 &- ```- Verify: `cat /tmp/relay.log` should show `Adom relay server started`.--3. **Add the alt-port localhost server to `~/.adom/config.json`** so the GUI auto-connects on launch:- ```json- {- "auth_token": "<your-token>",- "auto_connect": true,- "enabled": true,- "id": "local-dev-server",- "name": "localhost",- "url": "ws://localhost:8865"- }- ```- (Edit the existing `localhost` entry if it's already there — just enable it and bump the port.)--4. **Launch the GUI via `cmd /c start ""`** — this routes through the Windows Shell broker, which spawns the new process in the user's normal session, breaking your AppContainer inheritance:- ```bash- cmd //c start "" "C:\Users\<you>\AppData\Local\Adom Bridge\adom-bridge.exe"- ```- **DO NOT** use bare `start "" "..."` from your bash — that inherits your sandbox.--5. **Set `ADOM_API` env var** so the CLI hits your alt port:- ```bash- export ADOM_API="http://127.0.0.1:8866"- ```- Pin this near the top of every command sequence; it doesn't persist across shells.--6. **Verify the GUI is unsandboxed.** Run via shell_execute and check the token:- ```bash- adom-bridge shell_execute '{"command":"powershell -NoProfile -ExecutionPolicy Bypass -Command \"Add-Type -TypeDefinition @\\\"using System; using System.Runtime.InteropServices; public class T { [DllImport(\\\\\\\"advapi32.dll\\\\\\\")] public static extern bool OpenProcessToken(IntPtr p, int a, out IntPtr t); [DllImport(\\\\\\\"advapi32.dll\\\\\\\")] public static extern bool GetTokenInformation(IntPtr h, int c, IntPtr b, int l, out int r); [DllImport(\\\\\\\"kernel32.dll\\\\\\\")] public static extern IntPtr GetCurrentProcess(); }\\\"@; $t=[IntPtr]::Zero; [void][T]::OpenProcessToken([T]::GetCurrentProcess(), 8, [ref]$t); $b=[Runtime.InteropServices.Marshal]::AllocHGlobal(8); $l=0; [void][T]::GetTokenInformation($t, 29, $b, 8, [ref]$l); Write-Host TokenIsAppContainer=$([Runtime.InteropServices.Marshal]::ReadInt32($b))\"","timeout_secs":5}'- ```- Expect `TokenIsAppContainer=0`. If it returns `1`, you launched the GUI from a sandboxed parent — kill it and relaunch via `cmd //c start ""`.--### Everything you do from here goes through the CLI--| Need to... | Use this | NOT this |-|---|---|---|-| Run PowerShell on user's machine | `adom-bridge shell_execute '{"command":"powershell ...","timeout_secs":N}'` | Your own `PowerShell` tool |-| Screenshot a window | `adom-bridge desktop_screenshot_window '{"hwnd":N}'` | Win32 `PrintWindow` from your PowerShell |-| Screenshot the screen | `adom-bridge desktop_screenshot_screen` | Your own screen capture |-| Find windows | `adom-bridge desktop_list_windows` | UIA enumeration from your PowerShell |-| Install/uninstall a bridge | `adom-bridge bridge_install`, `bridge_uninstall` | Manually copying zip contents to the cache dir |-| Inspect the bridge registry | `adom-bridge bridge_list` | Reading `bridge.json` from disk paths |-| Open a folder in Explorer | (test by invoking the same Rust code path; see below) | `Start-Process explorer.exe` from your bash |--**Quoting trick for `shell_execute`:** the JSON arg gets parsed once, then PowerShell parses it again. Backslashes need quadrupling (`\\\\` for a literal `\`). When the command is non-trivial, **write it to a `.ps1` file and `shell_execute` the file** instead of inlining:-```bash-cat > /c/tmp/probe.ps1 << 'EOF'-# ... powershell here, single-quoted heredoc so no shell interpolation-EOF-adom-bridge shell_execute '{"command":"powershell -NoProfile -ExecutionPolicy Bypass -File C:\\\\tmp\\\\probe.ps1","timeout_secs":10}'-```--## Specifically: opening folders in Explorer--This is the one that took TWO hours across multiple iterations. Lessons crystallized:--1. **`explorer.exe /select,<path>`** — the only reliable form. Opens a NEW Explorer window navigated to the path's parent, with the path itself pre-selected. Use this in `open_path_in_explorer`-style Tauri commands.-2. **`explorer.exe <path>` (no flag)** — silently no-ops when an Explorer process is already up. Don't use.-3. **`ShellExecuteW("open", <path>)`** — returns success (HINSTANCE > 32) but often doesn't visibly open a window. Don't use for folders.-4. **`cmd /c start "" "<path>"`** — same fate as #2 for folders. Fine for URLs (used by `open_external_url`).--### The `raw_arg` trap--`std::process::Command::new("explorer").arg("/select,C:\\path with space\\hello").spawn()` does **NOT** work. Rust auto-wraps any arg containing spaces in double quotes, emitting:-- explorer.exe "/select,C:\path with space\hello"--Explorer parses the whole quoted string as one token, fails to match `/select,`, and falls back to opening the user's Documents folder. The user reports "Open install folder takes me to Documents."--What Explorer actually wants:-- explorer.exe /select,"C:\path with space\hello"--— quotes around the **path portion only**, not the whole arg. The fix is `std::os::windows::process::CommandExt::raw_arg`, which emits the literal command-line bytes without Rust's escaping rules:--```rust-use std::os::windows::process::CommandExt;-let raw = format!(r#"/select,"{}""#, win_path);-std::process::Command::new("explorer").raw_arg(&raw).spawn()?;-```--**General rule**: when invoking a Windows exe with a quirky / legacy command-line parser (`explorer`, `msbuild`, `cl`, some `cmd` builtins), assume `Command::arg` will mis-quote. Use `raw_arg` and construct the command line by hand. You can verify the emitted command line via `shell_execute` running a PowerShell simulation:--```powershell-$psi = New-Object System.Diagnostics.ProcessStartInfo-$psi.FileName = "explorer.exe"-$psi.Arguments = '/select,"' + $path + '"' # ← matches raw_arg output-$psi.UseShellExecute = $false # ← matches Rust's CreateProcessW path-[void][System.Diagnostics.Process]::Start($psi)-```--Then enumerate `(New-Object -ComObject Shell.Application).Windows()` to see what location Explorer actually navigated to. If it's the path you wanted: ship. If it's `Documents` / `Home`: your quoting is wrong.--Before passing the path to explorer, normalize:-- Replace `/` with `\` — `BridgeRegistry` stores cache paths with forward slashes via `dirs::data_local_dir()`.-- Strip `\\?\` prefix — Tauri's `resource_dir()` returns paths in this verbatim form for bundled bridges; Explorer rejects it.--**Always check the path exists BEFORE shelling out**, and return a structured error if it doesn't. The Rust side:-```rust-if !p.exists() {- return Err(format!("Path does not exist: {}", path));-}-```-catches the sandbox-write-to-wrong-place class of bugs early. If you see this error, you almost certainly installed the bridge while your GUI was sandboxed; reinstall via the CLI-driven user-session GUI.--## Debugging checklist when something "doesn't work"--If a user-visible action (open folder, screenshot, bridge install) seems broken, before iterating on the Rust code:--1. **Is the GUI unsandboxed?** Run the TokenIsAppContainer probe above. If `1`, the entire path is poisoned — relaunch via `cmd //c start ""` and reinstall any cache state.-2. **Are you using `ADOM_API=http://127.0.0.1:8866`?** If your CLI hits the default 8766 and Docker has it, you get `Connection Failed`.-3. **Does the path EXIST on the user's real filesystem?** Use `shell_execute` to run `Get-ChildItem <path>` — that's the source of truth. Don't trust `ls` from your own bash.-4. **Did `taskkill /F /IM adom-bridge.exe` collateral-kill the relay?** The CLI binary and the GUI binary share the same exe name. Kill by PID, not by image name, so the relay survives.-5. **Are you reading explorer's view via COM?** Explorer.exe caches folder listings aggressively and can show stale state. Trust `Get-ChildItem` (kernel API) over `Shell.Application.Windows().Document.Folder.Items()` (cached UI view) — the FS API is the truth.--## Why this gets re-learned every session--Every Claude on the Windows host starts in the sandbox without knowing it. Path operations *appear* to work — your `Test-Path` returns True, your `ls` shows files. The redirect is invisible until something user-visible (Explorer, another non-sandboxed process) tries to use the same path and finds it missing.--Read this skill at session start when you're working on adom-bridge locally. It saves an hour.--## Related memory--- `~/.claude/projects/C--Github-adom-bridge/memory/feedback_dont_kill_gui_during_iter.md` — don't kill the GUI repeatedly during iteration. The CLI-driven approach above means you almost never *need* to kill the GUI: changes that don't touch Rust ship without a rebuild via `release-bridge.sh`, and changes that do touch Rust use FAST tauri (~1-2 min) with a single GUI restart.