← Commit history

Delete skills/adom-bridge-windows-host/SKILL.md

Kyle Bergstedt ·eade88ba98 ·21d ago ·parent d28a9f7
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.