Adom Desktop
Public Made by Adomby adom
Installs the adom-desktop CLI and its Claude skills INTO A CLOUD CONTAINER so an AI assistant there can understand and drive Adom Desktop over the relay. This is the container side, NOT the app itself. The Adom Desktop app is the signed Windows installer under Download below: run THAT on the PC you want the AI to control. Rule of thumb: pkg install here (in your Linux container), Download there (on your Windows PC). Once installed, the AI can drive file transfer, screenshots, notifications, KiCa
name: adom-desktop-installer description: How to invoke the Adom Desktop NSIS installer from a parent installer (especially Hydrogen Desktop's setup steps) — silent install, /NOLAUNCH switch for HD-managed bundling, live progress via the structured log file at %TEMP%\adom-desktop-install.log. Use when bundling AD inside another Windows installer, writing an install supervisor / setup-step UI, detecting AD's installed version, or upgrading an existing AD install programmatically.
Adom Desktop installer integration
Adom Desktop (AD) ships as a single NSIS installer:
Adom Desktop_<version>_x64-setup.exe (~4.8 MB on v1.8.45)
It installs to %LOCALAPPDATA%\Adom Desktop\ — no UAC prompt, no admin needed. Tauri-2 bundled NSIS with our customisations in src-tauri/installer-hooks.nsh.
Command-line surface
| Flag | Meaning |
|---|---|
| (none) | Installs silently, auto-launches adom-desktop.exe after install |
/S |
Standard NSIS silent flag — same behavior since the top-level SilentInstall silent directive already suppresses UI |
/NOLAUNCH |
v1.8.45+ — install silently, do NOT auto-launch after install. Caller is responsible for spawning AD with the desired flags |
/D=<path> |
Install destination override. Default is $LOCALAPPDATA\Adom Desktop. Rarely needed |
<uninstall.exe> /S |
Silent uninstall. The uninstaller lives at %LOCALAPPDATA%\Adom Desktop\uninstall.exe |
UI behavior: zero UI in all cases. SilentInstall silent is set at the top of the NSIS script, so even setup.exe invoked from Explorer runs without a window. No Welcome page, no progress bar, no Finish page.
Auto-launch: by default the installer calls ExecShell "" "$INSTDIR\adom-desktop.exe" at the end of POSTINSTALL. Pass /NOLAUNCH to suppress this. Used by HD when bundling AD because HD wants to spawn AD with --embedded --start-hidden --relay-url ... --session-token ... itself, not let AD launch standalone first and get respawned via single-instance.
Live progress log
The installer writes structured progress to %TEMP%\adom-desktop-install.log (Windows expands %TEMP% to the user's temp dir, typically C:\Users\<name>\AppData\Local\Temp). Each line is [STAGE] message\r\n:
[START] adom-desktop installer 1.8.45 starting
[PREINSTALL] Cleaning legacy desktop shortcuts
[PREINSTALL] Done. Tauri will now copy bundle files.
[POSTINSTALL] Bundle files copied. Cleaning Tauri's auto-created desktop shortcut.
[POSTINSTALL] /NOLAUNCH passed — skipping auto-launch (caller will spawn)
[DONE] adom-desktop installer complete (no-launch mode)
[START] opens with a truncated write (fresh log per install). [DONE] is always the final line — readers tailing the file can use that as the completion sentinel without needing to wait for the process exit.
Stage tags currently emitted:
[START]— once, at the top[PREINSTALL]— Tauri's beforeBuildCommand hook ran, files about to be copied[POSTINSTALL]— files copied; Tauri's standard shortcuts created and our hook is cleaning the unwanted ones[DONE]— final line, installer about to exit
The uninstaller writes the same log with [PREUNINSTALL] / [POSTUNINSTALL] stages.
Recipe: HD's setup-step UI tails the log
Spawn the installer with /NOLAUNCH, poll the log file on a 200 ms timer, surface each new line to HD's UI. When [DONE] shows up OR the process exits, the install is complete.
Rust (HD's installer driver)
use std::{env, fs, path::PathBuf, process::Command, time::Duration};
use std::os::windows::process::CommandExt;
use tokio::time::sleep;
const CREATE_NO_WINDOW: u32 = 0x08000000;
async fn install_adom_desktop<F: FnMut(String)>(
installer_path: &str,
mut on_line: F,
) -> Result<(), String> {
let log_path: PathBuf = [&env::var("TEMP").map_err(|e| e.to_string())?, "adom-desktop-install.log"]
.iter()
.collect();
// Pre-clear so we don't read stale lines from a previous install.
let _ = fs::remove_file(&log_path);
let mut child = Command::new(installer_path)
.args(["/S", "/NOLAUNCH"])
.creation_flags(CREATE_NO_WINDOW)
.spawn()
.map_err(|e| format!("spawn installer: {e}"))?;
let mut last_pos = 0usize;
loop {
// Tail any new content
if let Ok(content) = fs::read_to_string(&log_path) {
if content.len() > last_pos {
let slice = &content[last_pos..];
if let Some(last_nl) = slice.rfind('\n') {
let consumed = &slice[..=last_nl];
for line in consumed.lines() {
on_line(line.to_string());
if line.starts_with("[DONE]") {
// Don't return yet — wait for process exit
// so we surface the actual exit code, but
// we know progress streaming is finished.
}
}
last_pos += consumed.len();
}
}
}
// Check if installer has exited
if let Some(status) = child.try_wait().map_err(|e| e.to_string())? {
return if status.success() {
Ok(())
} else {
Err(format!("installer exited with {}", status))
};
}
sleep(Duration::from_millis(200)).await;
}
}
// Caller:
// install_adom_desktop(r"C:\path\to\Adom Desktop_1.8.45_x64-setup.exe", |line| {
// hd_setup_step.append_live_output(&line);
// }).await?;
Bash (debugging / scripting)
# Pre-clear stale log
rm -f "$TEMP/adom-desktop-install.log" 2>/dev/null
# Spawn installer in background, tail the log
"./Adom Desktop_1.8.45_x64-setup.exe" /S /NOLAUNCH &
INSTALLER_PID=$!
# Tail until process exits OR [DONE] appears
while kill -0 $INSTALLER_PID 2>/dev/null; do
[ -f "$TEMP/adom-desktop-install.log" ] && tail -F "$TEMP/adom-desktop-install.log" &
sleep 0.2
done
wait
echo "Installer exited"
Recipe: detect if AD is already installed (and at what version)
The signed NSIS installer (installMode: "currentUser") writes AD's uninstall info to HKCU — no admin needed. ⚠ The key name is the product name Adom Desktop (WITH a space), NOT AdomDesktop (verified live 2026-07-03). Two history notes so you don't chase the wrong key:
- The NSIS uninstall key = the product name →
…\Uninstall\Adom Desktop(space). - AD's OWN launch-time code used to ALSO write an
AdomDesktop(no-space) key to HKLM — that write was DELETED in v1.9.68 (the no-UAC fix). SoAdomDesktopno longer exists anywhere; readAdom Desktop.
HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\Adom Desktop
DisplayName = "Adom Desktop"
DisplayVersion = "1.9.74"
InstallLocation = "C:\Users\<name>\AppData\Local\Adom Desktop"
UninstallString = "C:\Users\<name>\AppData\Local\Adom Desktop\uninstall.exe"
Publisher = "Adom Industries, Inc."
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;
fn adom_desktop_installed_version() -> Option<String> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
// NOTE the SPACE — the NSIS uninstall key is the product name "Adom Desktop".
let key = hkcu
.open_subkey(r"Software\Microsoft\Windows\CurrentVersion\Uninstall\Adom Desktop")
.ok()?;
key.get_value("DisplayVersion").ok()
}
Compare with semver::Version::parse to decide whether to skip / upgrade / leave-alone.
Recipe: HD's setup-step ladder for embedding AD
The full pattern HD's setup-step UI runs when embedding AD:
1. Check installed version → registry read above
2. Decide:
no install → install bundled AD
older than bundled → install bundled AD (upgrade)
same or newer → skip install entirely
3. Run installer (silent + /NOLAUNCH) → tail log, surface to UI
4. Write embedded marker file → %LOCALAPPDATA%\Adom Desktop\embedded.json
5. Delete AD's user-facing shortcuts → Start Menu, Desktop, Startup folder
(so user can't double-launch standalone)
6. Spawn AD with embedded flags → adom-desktop.exe --embedded
--start-hidden
--relay-url ws://127.0.0.1:8765
--relay-name hydrogen-desktop
--session-token <hd's token>
7. Poll AD's direct API for liveness → GET http://127.0.0.1:47200/health
Steps 4 and 5 are also covered by AD's installer if you pass --embedded at AD's first launch (the embedded-mode detection writes the marker and AD's autostart-upgrade routine handles its own shortcuts). Doing them in HD's installer is belt-and-suspenders.
Bundle-resource pattern
HD's tauri.conf.json should include the AD installer as a bundle resource so it ships inside HD's NSIS installer:
"bundle": {
"resources": {
"resources/Adom Desktop_1.8.45_x64-setup.exe": "resources/"
}
}
At runtime HD finds it under its install dir at $INSTDIR\resources\Adom Desktop_<ver>_x64-setup.exe — pass that path to the install function above.
Versioning + upgrade strategy
| Scenario | What HD's installer should do |
|---|---|
| No AD installed | Install bundled version |
| AD older than bundled | Install bundled version (upgrade) |
| AD same as bundled | Skip install step; still spawn with embedded flags |
| AD newer than bundled | Skip install step; spawn with embedded flags. Don't downgrade — embedded mode works for any AD ≥ v1.8.42 |
The bundled AD installer is the minimum supported version for the HD release. HD can spawn newer ADs without modification.
Exit codes
NSIS returns:
0— install succeeded2— user cancelled (won't happen withSilentInstall silentsince there's no UI to cancel)- Other — install error (rare with our hooks; typical cause is disk full or
%LOCALAPPDATA%not writable)
Treat anything non-zero as a hard failure and surface the log file's last 20 lines to the user.
Where the installer lives
v1.8.115+ — the end-user binary home is the V2 wiki blob store (the GitHub repo is private; GH release URLs 404 for anonymous downloads). Two discovery surfaces, both anonymous:
Preferred for parent installers (HD setup): the packages API. Resolve
the latest registered version and its assets in one or two GETs — note
?org=adom is REQUIRED (the slug exists in multiple orgs; bare URLs 400):
https://wiki.adom.inc/api/v1/packages/adom-desktop/dist-tags?org=adom
→ {"dist_tags":{"latest":"X.Y.Z"}}
https://wiki.adom.inc/api/v1/packages/adom-desktop/manifest?org=adom
→ { version, ..., assets: { "windows-x64": { filename, url, sha256, size } } }
The assets map is server-derived from the page's blobs (sha256 computed
from the actual bytes — integrity-checkable). assets[...].url is
server-relative (/blob/app/adom-desktop/<file>); prefix the git-wiki host.
Fallback / updater path: version.json, V2-first with V1 fallback:
https://wiki.adom.inc/api/v1/pages/adom-desktop/files/version.json
https://wiki.adom.inc/api/v1/pages/adom-desktop/files/version.json
The V2 manifest's windows.installer_url / cli.<plat>.binary_url point at
the V2 files endpoint; V1's point at V1 static copies. Follow the
manifest's URLs — do NOT construct binary URLs by hand.
Network quirk: HEAD on V2 blob/file URLs may return 404 while GET works
(git-wiki #40 — fix in flight). Probe with a Range GET (Range: bytes=0-0,
expect 206), never bare HEAD. Asset filenames are dot-form everywhere
(Adom.Desktop_<ver>_windows_x64-setup.exe); V1 dot-normalized since v1.8.72.
The wiki page's metadata.install.windows_installer field points at the
canonical filename for the current published version. HD's release build
should fetch the latest via the V2-first pattern:
# Try V2 first, fall back to V1.
fetch_manifest() {
curl -fL --max-time 15 \
"https://wiki.adom.inc/api/v1/pages/adom-desktop/files/version.json" \
2>/dev/null \
|| curl -fL --max-time 15 \
"https://wiki.adom.inc/api/v1/pages/adom-desktop/files/version.json"
}
MANIFEST=$(fetch_manifest)
INSTALLER_URL=$(echo "$MANIFEST" | jq -r .windows.installer_url)
INSTALLER_SHA=$(echo "$MANIFEST" | jq -r .windows.installer_sha256)
# Each manifest carries a self-consistent installer_url that points at the
# SAME backend the manifest was served from — V2 manifest → V2 installer URL,
# V1 manifest → V1 installer URL. No URL mixing across backends.
curl -fL "$INSTALLER_URL" -o resources/ad-setup.exe
# Verify SHA256 before bundling — defends against partial fetches + tampering
DOWNLOAD_SHA=$(sha256sum resources/ad-setup.exe | awk '{print $1}')
[ "$DOWNLOAD_SHA" = "$INSTALLER_SHA" ] || { echo "SHA mismatch"; exit 1; }
The desktop AD's built-in auto-updater (src-tauri/src/updater.rs) walks an
ordered MANIFEST_URLS list — V2 first, V1 fallback — and uses whichever
responds first. Once V2 has been the source of truth for a release cycle
without incident, V1 will be retired from the pipeline.
version.json also carries the SHA256 hash of the installer so HD's build
pipeline can verify the download before bundling.
Asset naming convention (v1.8.113+)
The Adom Desktop wiki page hosts TWO distinct types of artifacts per release:
- Full AD GUI installer — the actual desktop app (Tauri window, bundled bridges, relauncher). One per OS / architecture.
- CLI companion binary — a separate executable that talks to AD over the relay. Used by HD's WSL2 container, gallia builds, sibling-app installers, cron jobs. Does NOT contain AD itself.
Both ship together at the same version (single python scripts/bump-version.py patch bumps both, single cargo tauri build + cargo build --release in
cli/ produces both, single scripts/release-publish.sh uploads both).
Filename pattern
| Artifact | Wiki asset name pattern | Example (v1.8.113) |
|---|---|---|
| Full AD GUI — Windows | Adom.Desktop_<ver>_windows_x64-setup.exe |
Adom.Desktop_1.8.113_windows_x64-setup.exe |
| Full AD GUI — macOS | Adom.Desktop_<ver>_macos_<arch>.dmg |
Adom.Desktop_1.8.113_macos_aarch64.dmg (future) |
| Full AD GUI — Linux | Adom.Desktop_<ver>_linux_<arch>.AppImage |
Adom.Desktop_1.8.113_linux_x86_64.AppImage (future) |
| CLI companion — Linux | adom-desktop-cli_<ver>_linux_<arch> |
adom-desktop-cli_1.8.113_linux_x86_64 |
| CLI companion — macOS | adom-desktop-cli_<ver>_macos_<arch> |
adom-desktop-cli_1.8.113_macos_aarch64 (future) |
| CLI companion — Windows | adom-desktop-cli_<ver>_windows_<arch>.exe |
adom-desktop-cli_1.8.113_windows_x64.exe (future) |
The _cli_ token in the middle is the only signal you need to know "this is
the companion, not the app." linux_x86_64 always means Linux x86_64,
regardless of whether the file is a GUI installer or a CLI binary.
After install, the binary name is adom-desktop
The wiki asset name disambiguates at FETCH time. At INSTALL time the binary
is renamed to adom-desktop so the command users type doesn't change:
# Linux example — install the CLI companion
curl -fL "https://wiki.adom.inc/api/v1/pages/adom-desktop/files/adom-desktop-cli_1.8.113_linux_x86_64" \
-o /tmp/ad
sudo install -m 755 /tmp/ad /usr/local/bin/adom-desktop
adom-desktop --version
# → adom-desktop 1.8.113 (<sha>, built <ts>)
Version.json .cli slot
The auto-update manifest (v1.8.113+) carries a typed cli sibling section
for the CLI binaries:
{
"manifest_version": 1,
"version": "1.8.113",
"windows": { "installer_url": ".../Adom.Desktop_1.8.113_windows_x64-setup.exe", ... },
"cli": {
"linux_x86_64": {
"binary_url": ".../adom-desktop-cli_1.8.113_linux_x86_64",
"binary_filename": "adom-desktop-cli_1.8.113_linux_x86_64",
"binary_sha256": "...",
"binary_size": 8012408
}
},
"changelog_url": "...",
"minimum_required": "1.3.30"
}
Pre-v1.8.113 readers ignore .cli entirely (additive schema; no
manifest_version bump required). The desktop AD's own auto-updater only
reads .windows / .macos / .linux for its GUI installer URL — it doesn't
touch .cli.
For HD / gallia / Docker-side fetchers, prefer the typed lookup:
# Best: read the URL out of the manifest (so the filename can evolve)
MANIFEST=$(fetch_manifest)
LINUX_CLI_URL=$(echo "$MANIFEST" | jq -r '.cli.linux_x86_64.binary_url')
LINUX_CLI_SHA=$(echo "$MANIFEST" | jq -r '.cli.linux_x86_64.binary_sha256')
curl -fL "$LINUX_CLI_URL" -o /tmp/ad
[ "$(sha256sum /tmp/ad | awk '{print $1}')" = "$LINUX_CLI_SHA" ] || exit 1
sudo install -m 755 /tmp/ad /usr/local/bin/adom-desktop
Windows CLI is shipped with the NSIS (v1.8.114+)
Installing AD on Windows now drops the CLI companion next to the GUI exe and adds the install dir to the user's PATH so PowerShell / Claude Desktop / cmd find it without configuration:
%LOCALAPPDATA%\Adom Desktop\adom-desktop.exe ← GUI (Windows subsystem)
%LOCALAPPDATA%\Adom Desktop\adom-desktop-cli.exe ← CLI companion (Console subsystem)
%LOCALAPPDATA%\Adom Desktop\ad-relauncher.exe ← visible self-restart helper
NSIS's POSTINSTALL hook prepends %LOCALAPPDATA%\Adom Desktop to
HKCU\Environment\Path (no UAC — user-level only) and broadcasts
WM_SETTINGCHANGE so freshly-opened shells pick up the change. Already-
open shells need a re-launch (OS limitation).
After install, in a fresh PowerShell:
adom-desktop-cli --version
# adom-desktop 1.8.114 (<sha>, built <ts>)
adom-desktop-cli status
adom-desktop-cli screenshot_window '{"title-contains":"VS Code"}'
The CLI auto-discovers the local AD via direct-API probe at
127.0.0.1:47200 — no relay needed when AD is on the same box. ~5ms RTT
for compatible verbs; verbs in AD's cliRequired list still flow through
a relay if one's running (HD's WSL2 distro typically owns the relay).
Relay self-detect (v1.8.114+): adom-desktop-cli serve checks for
another adom-desktop relay on 127.0.0.1:8766/health before binding. If
one already answers, it prints a friendly note and exits cleanly without
starting a second relay (which would just sit idle — AD's WS client only
attaches to the first one it finds). Pass --force-bind to override.
PREUNINSTALL strips the PATH entry back out and rebroadcasts so an uninstall of AD also gets PATH cleanup.
Legacy alias migration window
Releases prior to v1.8.113 published the Linux CLI as adom-desktop-linux
(no version, no architecture). HD's WSL2 setup and a few cron-based
installers have that literal filename baked in.
To keep them working through the rename, v1.8.113 publishes BOTH names:
| Name | Status | Wiki URL (V2 / V1) |
|---|---|---|
adom-desktop-cli_<ver>_linux_x86_64 |
canonical, v1.8.113+ | V2 / V1 |
adom-desktop-linux |
deprecated alias — byte-identical copy | V2 / V1 |
Both files are byte-identical with the same SHA256. The legacy alias stays through v1.8.116, then disappears. Migrate by then. Recommended pattern:
# Migration period (v1.8.113 → v1.8.115): try new name, fall back to alias
LINUX_CLI_URL_NEW=$(echo "$MANIFEST" | jq -r '.cli.linux_x86_64.binary_url // empty')
if [ -n "$LINUX_CLI_URL_NEW" ]; then
curl -fL "$LINUX_CLI_URL_NEW" -o /tmp/ad
else
# Falling back — likely talking to a pre-v1.8.113 manifest
curl -fL "https://wiki.adom.inc/api/v1/pages/adom-desktop/files/adom-desktop-linux" \
-o /tmp/ad
fi
Post-v1.8.116, the fallback branch dies; only the canonical name will exist.
---
name: adom-desktop-installer
description: How to invoke the Adom Desktop NSIS installer from a parent installer (especially Hydrogen Desktop's setup steps) — silent install, /NOLAUNCH switch for HD-managed bundling, live progress via the structured log file at %TEMP%\adom-desktop-install.log. Use when bundling AD inside another Windows installer, writing an install supervisor / setup-step UI, detecting AD's installed version, or upgrading an existing AD install programmatically.
---
# Adom Desktop installer integration
Adom Desktop (AD) ships as a single NSIS installer:
```
Adom Desktop_<version>_x64-setup.exe (~4.8 MB on v1.8.45)
```
It installs to `%LOCALAPPDATA%\Adom Desktop\` — no UAC prompt, no admin needed. Tauri-2 bundled NSIS with our customisations in `src-tauri/installer-hooks.nsh`.
## Command-line surface
| Flag | Meaning |
|---|---|
| (none) | Installs silently, auto-launches `adom-desktop.exe` after install |
| `/S` | Standard NSIS silent flag — same behavior since the top-level `SilentInstall silent` directive already suppresses UI |
| `/NOLAUNCH` | **v1.8.45+** — install silently, do NOT auto-launch after install. Caller is responsible for spawning AD with the desired flags |
| `/D=<path>` | Install destination override. Default is `$LOCALAPPDATA\Adom Desktop`. Rarely needed |
| `<uninstall.exe> /S` | Silent uninstall. The uninstaller lives at `%LOCALAPPDATA%\Adom Desktop\uninstall.exe` |
**UI behavior**: zero UI in all cases. `SilentInstall silent` is set at the top of the NSIS script, so even `setup.exe` invoked from Explorer runs without a window. No Welcome page, no progress bar, no Finish page.
**Auto-launch**: by default the installer calls `ExecShell "" "$INSTDIR\adom-desktop.exe"` at the end of POSTINSTALL. Pass `/NOLAUNCH` to suppress this. Used by HD when bundling AD because HD wants to spawn AD with `--embedded --start-hidden --relay-url ... --session-token ...` itself, not let AD launch standalone first and get respawned via single-instance.
## Live progress log
The installer writes structured progress to `%TEMP%\adom-desktop-install.log` (Windows expands `%TEMP%` to the user's temp dir, typically `C:\Users\<name>\AppData\Local\Temp`). Each line is `[STAGE] message\r\n`:
```
[START] adom-desktop installer 1.8.45 starting
[PREINSTALL] Cleaning legacy desktop shortcuts
[PREINSTALL] Done. Tauri will now copy bundle files.
[POSTINSTALL] Bundle files copied. Cleaning Tauri's auto-created desktop shortcut.
[POSTINSTALL] /NOLAUNCH passed — skipping auto-launch (caller will spawn)
[DONE] adom-desktop installer complete (no-launch mode)
```
**`[START]`** opens with a truncated write (fresh log per install). **`[DONE]`** is always the final line — readers tailing the file can use that as the completion sentinel without needing to wait for the process exit.
Stage tags currently emitted:
- `[START]` — once, at the top
- `[PREINSTALL]` — Tauri's beforeBuildCommand hook ran, files about to be copied
- `[POSTINSTALL]` — files copied; Tauri's standard shortcuts created and our hook is cleaning the unwanted ones
- `[DONE]` — final line, installer about to exit
The uninstaller writes the same log with `[PREUNINSTALL]` / `[POSTUNINSTALL]` stages.
## Recipe: HD's setup-step UI tails the log
Spawn the installer with `/NOLAUNCH`, poll the log file on a 200 ms timer, surface each new line to HD's UI. When `[DONE]` shows up OR the process exits, the install is complete.
### Rust (HD's installer driver)
```rust
use std::{env, fs, path::PathBuf, process::Command, time::Duration};
use std::os::windows::process::CommandExt;
use tokio::time::sleep;
const CREATE_NO_WINDOW: u32 = 0x08000000;
async fn install_adom_desktop<F: FnMut(String)>(
installer_path: &str,
mut on_line: F,
) -> Result<(), String> {
let log_path: PathBuf = [&env::var("TEMP").map_err(|e| e.to_string())?, "adom-desktop-install.log"]
.iter()
.collect();
// Pre-clear so we don't read stale lines from a previous install.
let _ = fs::remove_file(&log_path);
let mut child = Command::new(installer_path)
.args(["/S", "/NOLAUNCH"])
.creation_flags(CREATE_NO_WINDOW)
.spawn()
.map_err(|e| format!("spawn installer: {e}"))?;
let mut last_pos = 0usize;
loop {
// Tail any new content
if let Ok(content) = fs::read_to_string(&log_path) {
if content.len() > last_pos {
let slice = &content[last_pos..];
if let Some(last_nl) = slice.rfind('\n') {
let consumed = &slice[..=last_nl];
for line in consumed.lines() {
on_line(line.to_string());
if line.starts_with("[DONE]") {
// Don't return yet — wait for process exit
// so we surface the actual exit code, but
// we know progress streaming is finished.
}
}
last_pos += consumed.len();
}
}
}
// Check if installer has exited
if let Some(status) = child.try_wait().map_err(|e| e.to_string())? {
return if status.success() {
Ok(())
} else {
Err(format!("installer exited with {}", status))
};
}
sleep(Duration::from_millis(200)).await;
}
}
// Caller:
// install_adom_desktop(r"C:\path\to\Adom Desktop_1.8.45_x64-setup.exe", |line| {
// hd_setup_step.append_live_output(&line);
// }).await?;
```
### Bash (debugging / scripting)
```bash
# Pre-clear stale log
rm -f "$TEMP/adom-desktop-install.log" 2>/dev/null
# Spawn installer in background, tail the log
"./Adom Desktop_1.8.45_x64-setup.exe" /S /NOLAUNCH &
INSTALLER_PID=$!
# Tail until process exits OR [DONE] appears
while kill -0 $INSTALLER_PID 2>/dev/null; do
[ -f "$TEMP/adom-desktop-install.log" ] && tail -F "$TEMP/adom-desktop-install.log" &
sleep 0.2
done
wait
echo "Installer exited"
```
## Recipe: detect if AD is already installed (and at what version)
The signed NSIS installer (`installMode: "currentUser"`) writes AD's uninstall info to `HKCU` — no admin needed. **⚠ The key name is the product name `Adom Desktop` (WITH a space), NOT `AdomDesktop`** (verified live 2026-07-03). Two history notes so you don't chase the wrong key:
- The NSIS uninstall key = the product name → `…\Uninstall\Adom Desktop` (space).
- AD's OWN launch-time code used to ALSO write an `AdomDesktop` (no-space) key to **HKLM** — that write was **DELETED in v1.9.68** (the no-UAC fix). So `AdomDesktop` no longer exists anywhere; read `Adom Desktop`.
```
HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\Adom Desktop
DisplayName = "Adom Desktop"
DisplayVersion = "1.9.74"
InstallLocation = "C:\Users\<name>\AppData\Local\Adom Desktop"
UninstallString = "C:\Users\<name>\AppData\Local\Adom Desktop\uninstall.exe"
Publisher = "Adom Industries, Inc."
```
```rust
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;
fn adom_desktop_installed_version() -> Option<String> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
// NOTE the SPACE — the NSIS uninstall key is the product name "Adom Desktop".
let key = hkcu
.open_subkey(r"Software\Microsoft\Windows\CurrentVersion\Uninstall\Adom Desktop")
.ok()?;
key.get_value("DisplayVersion").ok()
}
```
Compare with `semver::Version::parse` to decide whether to skip / upgrade / leave-alone.
## Recipe: HD's setup-step ladder for embedding AD
The full pattern HD's setup-step UI runs when embedding AD:
```
1. Check installed version → registry read above
2. Decide:
no install → install bundled AD
older than bundled → install bundled AD (upgrade)
same or newer → skip install entirely
3. Run installer (silent + /NOLAUNCH) → tail log, surface to UI
4. Write embedded marker file → %LOCALAPPDATA%\Adom Desktop\embedded.json
5. Delete AD's user-facing shortcuts → Start Menu, Desktop, Startup folder
(so user can't double-launch standalone)
6. Spawn AD with embedded flags → adom-desktop.exe --embedded
--start-hidden
--relay-url ws://127.0.0.1:8765
--relay-name hydrogen-desktop
--session-token <hd's token>
7. Poll AD's direct API for liveness → GET http://127.0.0.1:47200/health
```
Steps 4 and 5 are also covered by AD's installer if you pass `--embedded` at AD's first launch (the embedded-mode detection writes the marker and AD's autostart-upgrade routine handles its own shortcuts). Doing them in HD's installer is belt-and-suspenders.
## Bundle-resource pattern
HD's `tauri.conf.json` should include the AD installer as a bundle resource so it ships inside HD's NSIS installer:
```json
"bundle": {
"resources": {
"resources/Adom Desktop_1.8.45_x64-setup.exe": "resources/"
}
}
```
At runtime HD finds it under its install dir at `$INSTDIR\resources\Adom Desktop_<ver>_x64-setup.exe` — pass that path to the install function above.
## Versioning + upgrade strategy
| Scenario | What HD's installer should do |
|---|---|
| No AD installed | Install bundled version |
| AD older than bundled | Install bundled version (upgrade) |
| AD same as bundled | Skip install step; still spawn with embedded flags |
| AD newer than bundled | Skip install step; spawn with embedded flags. Don't downgrade — embedded mode works for any AD ≥ v1.8.42 |
The bundled AD installer is the minimum supported version for the HD release. HD can spawn newer ADs without modification.
## Exit codes
NSIS returns:
- `0` — install succeeded
- `2` — user cancelled (won't happen with `SilentInstall silent` since there's no UI to cancel)
- Other — install error (rare with our hooks; typical cause is disk full or `%LOCALAPPDATA%` not writable)
Treat anything non-zero as a hard failure and surface the log file's last 20 lines to the user.
## Where the installer lives
**v1.8.115+** — the end-user binary home is the **V2 wiki blob store** (the
GitHub repo is private; GH release URLs 404 for anonymous downloads). Two
discovery surfaces, both anonymous:
**Preferred for parent installers (HD setup): the packages API.** Resolve
the latest registered version and its assets in one or two GETs — note
`?org=adom` is REQUIRED (the slug exists in multiple orgs; bare URLs 400):
```
https://wiki.adom.inc/api/v1/packages/adom-desktop/dist-tags?org=adom
→ {"dist_tags":{"latest":"X.Y.Z"}}
https://wiki.adom.inc/api/v1/packages/adom-desktop/manifest?org=adom
→ { version, ..., assets: { "windows-x64": { filename, url, sha256, size } } }
```
The `assets` map is server-derived from the page's blobs (sha256 computed
from the actual bytes — integrity-checkable). `assets[...].url` is
server-relative (`/blob/app/adom-desktop/<file>`); prefix the git-wiki host.
**Fallback / updater path: version.json**, V2-first with V1 fallback:
```
https://wiki.adom.inc/api/v1/pages/adom-desktop/files/version.json
https://wiki.adom.inc/api/v1/pages/adom-desktop/files/version.json
```
The V2 manifest's `windows.installer_url` / `cli.<plat>.binary_url` point at
the V2 files endpoint; V1's point at V1 static copies. **Follow the
manifest's URLs — do NOT construct binary URLs by hand.**
Network quirk: HEAD on V2 blob/file URLs may return 404 while GET works
(git-wiki #40 — fix in flight). Probe with a Range GET (`Range: bytes=0-0`,
expect 206), never bare HEAD. Asset filenames are dot-form everywhere
(`Adom.Desktop_<ver>_windows_x64-setup.exe`); V1 dot-normalized since v1.8.72.
The wiki page's `metadata.install.windows_installer` field points at the
canonical filename for the current published version. HD's release build
should fetch the latest via the V2-first pattern:
```bash
# Try V2 first, fall back to V1.
fetch_manifest() {
curl -fL --max-time 15 \
"https://wiki.adom.inc/api/v1/pages/adom-desktop/files/version.json" \
2>/dev/null \
|| curl -fL --max-time 15 \
"https://wiki.adom.inc/api/v1/pages/adom-desktop/files/version.json"
}
MANIFEST=$(fetch_manifest)
INSTALLER_URL=$(echo "$MANIFEST" | jq -r .windows.installer_url)
INSTALLER_SHA=$(echo "$MANIFEST" | jq -r .windows.installer_sha256)
# Each manifest carries a self-consistent installer_url that points at the
# SAME backend the manifest was served from — V2 manifest → V2 installer URL,
# V1 manifest → V1 installer URL. No URL mixing across backends.
curl -fL "$INSTALLER_URL" -o resources/ad-setup.exe
# Verify SHA256 before bundling — defends against partial fetches + tampering
DOWNLOAD_SHA=$(sha256sum resources/ad-setup.exe | awk '{print $1}')
[ "$DOWNLOAD_SHA" = "$INSTALLER_SHA" ] || { echo "SHA mismatch"; exit 1; }
```
The desktop AD's built-in auto-updater (`src-tauri/src/updater.rs`) walks an
ordered `MANIFEST_URLS` list — V2 first, V1 fallback — and uses whichever
responds first. Once V2 has been the source of truth for a release cycle
without incident, V1 will be retired from the pipeline.
`version.json` also carries the SHA256 hash of the installer so HD's build
pipeline can verify the download before bundling.
## Asset naming convention (v1.8.113+)
The Adom Desktop wiki page hosts TWO distinct types of artifacts per release:
1. **Full AD GUI installer** — the actual desktop app (Tauri window, bundled
bridges, relauncher). One per OS / architecture.
2. **CLI companion binary** — a separate executable that talks to AD over
the relay. Used by HD's WSL2 container, gallia builds, sibling-app
installers, cron jobs. Does NOT contain AD itself.
Both ship together at the same version (single `python scripts/bump-version.py
patch` bumps both, single `cargo tauri build` + `cargo build --release` in
`cli/` produces both, single `scripts/release-publish.sh` uploads both).
### Filename pattern
| Artifact | Wiki asset name pattern | Example (v1.8.113) |
|---|---|---|
| Full AD GUI — Windows | `Adom.Desktop_<ver>_windows_x64-setup.exe` | `Adom.Desktop_1.8.113_windows_x64-setup.exe` |
| Full AD GUI — macOS | `Adom.Desktop_<ver>_macos_<arch>.dmg` | `Adom.Desktop_1.8.113_macos_aarch64.dmg` (future) |
| Full AD GUI — Linux | `Adom.Desktop_<ver>_linux_<arch>.AppImage` | `Adom.Desktop_1.8.113_linux_x86_64.AppImage` (future) |
| CLI companion — Linux | `adom-desktop-cli_<ver>_linux_<arch>` | `adom-desktop-cli_1.8.113_linux_x86_64` |
| CLI companion — macOS | `adom-desktop-cli_<ver>_macos_<arch>` | `adom-desktop-cli_1.8.113_macos_aarch64` (future) |
| CLI companion — Windows | `adom-desktop-cli_<ver>_windows_<arch>.exe` | `adom-desktop-cli_1.8.113_windows_x64.exe` (future) |
The `_cli_` token in the middle is the only signal you need to know "this is
the companion, not the app." `linux_x86_64` always means Linux x86_64,
regardless of whether the file is a GUI installer or a CLI binary.
### After install, the binary name is `adom-desktop`
The wiki asset name disambiguates at FETCH time. At INSTALL time the binary
is renamed to `adom-desktop` so the command users type doesn't change:
```bash
# Linux example — install the CLI companion
curl -fL "https://wiki.adom.inc/api/v1/pages/adom-desktop/files/adom-desktop-cli_1.8.113_linux_x86_64" \
-o /tmp/ad
sudo install -m 755 /tmp/ad /usr/local/bin/adom-desktop
adom-desktop --version
# → adom-desktop 1.8.113 (<sha>, built <ts>)
```
### Version.json `.cli` slot
The auto-update manifest (v1.8.113+) carries a typed `cli` sibling section
for the CLI binaries:
```json
{
"manifest_version": 1,
"version": "1.8.113",
"windows": { "installer_url": ".../Adom.Desktop_1.8.113_windows_x64-setup.exe", ... },
"cli": {
"linux_x86_64": {
"binary_url": ".../adom-desktop-cli_1.8.113_linux_x86_64",
"binary_filename": "adom-desktop-cli_1.8.113_linux_x86_64",
"binary_sha256": "...",
"binary_size": 8012408
}
},
"changelog_url": "...",
"minimum_required": "1.3.30"
}
```
Pre-v1.8.113 readers ignore `.cli` entirely (additive schema; no
`manifest_version` bump required). The desktop AD's own auto-updater only
reads `.windows / .macos / .linux` for its GUI installer URL — it doesn't
touch `.cli`.
For HD / gallia / Docker-side fetchers, prefer the typed lookup:
```bash
# Best: read the URL out of the manifest (so the filename can evolve)
MANIFEST=$(fetch_manifest)
LINUX_CLI_URL=$(echo "$MANIFEST" | jq -r '.cli.linux_x86_64.binary_url')
LINUX_CLI_SHA=$(echo "$MANIFEST" | jq -r '.cli.linux_x86_64.binary_sha256')
curl -fL "$LINUX_CLI_URL" -o /tmp/ad
[ "$(sha256sum /tmp/ad | awk '{print $1}')" = "$LINUX_CLI_SHA" ] || exit 1
sudo install -m 755 /tmp/ad /usr/local/bin/adom-desktop
```
### Windows CLI is shipped with the NSIS (v1.8.114+)
Installing AD on Windows now drops the CLI companion next to the GUI exe
and adds the install dir to the user's PATH so PowerShell / Claude Desktop /
cmd find it without configuration:
```
%LOCALAPPDATA%\Adom Desktop\adom-desktop.exe ← GUI (Windows subsystem)
%LOCALAPPDATA%\Adom Desktop\adom-desktop-cli.exe ← CLI companion (Console subsystem)
%LOCALAPPDATA%\Adom Desktop\ad-relauncher.exe ← visible self-restart helper
```
NSIS's POSTINSTALL hook prepends `%LOCALAPPDATA%\Adom Desktop` to
`HKCU\Environment\Path` (no UAC — user-level only) and broadcasts
`WM_SETTINGCHANGE` so freshly-opened shells pick up the change. Already-
open shells need a re-launch (OS limitation).
After install, in a fresh PowerShell:
```powershell
adom-desktop-cli --version
# adom-desktop 1.8.114 (<sha>, built <ts>)
adom-desktop-cli status
adom-desktop-cli screenshot_window '{"title-contains":"VS Code"}'
```
The CLI auto-discovers the local AD via direct-API probe at
`127.0.0.1:47200` — no relay needed when AD is on the same box. ~5ms RTT
for compatible verbs; verbs in AD's `cliRequired` list still flow through
a relay if one's running (HD's WSL2 distro typically owns the relay).
**Relay self-detect (v1.8.114+):** `adom-desktop-cli serve` checks for
another adom-desktop relay on `127.0.0.1:8766/health` before binding. If
one already answers, it prints a friendly note and exits cleanly without
starting a second relay (which would just sit idle — AD's WS client only
attaches to the first one it finds). Pass `--force-bind` to override.
PREUNINSTALL strips the PATH entry back out and rebroadcasts so an
uninstall of AD also gets PATH cleanup.
### Legacy alias migration window
Releases prior to v1.8.113 published the Linux CLI as `adom-desktop-linux`
(no version, no architecture). HD's WSL2 setup and a few cron-based
installers have that literal filename baked in.
To keep them working through the rename, v1.8.113 publishes BOTH names:
| Name | Status | Wiki URL (V2 / V1) |
|---|---|---|
| `adom-desktop-cli_<ver>_linux_x86_64` | **canonical, v1.8.113+** | V2 / V1 |
| `adom-desktop-linux` | **deprecated alias** — byte-identical copy | V2 / V1 |
Both files are byte-identical with the same SHA256. The legacy alias stays
through **v1.8.116**, then disappears. Migrate by then. Recommended pattern:
```bash
# Migration period (v1.8.113 → v1.8.115): try new name, fall back to alias
LINUX_CLI_URL_NEW=$(echo "$MANIFEST" | jq -r '.cli.linux_x86_64.binary_url // empty')
if [ -n "$LINUX_CLI_URL_NEW" ]; then
curl -fL "$LINUX_CLI_URL_NEW" -o /tmp/ad
else
# Falling back — likely talking to a pre-v1.8.113 manifest
curl -fL "https://wiki.adom.inc/api/v1/pages/adom-desktop/files/adom-desktop-linux" \
-o /tmp/ad
fi
```
Post-v1.8.116, the fallback branch dies; only the canonical name will exist.