namespace ClaudeDo.Ui.Services; // Two named types because DI resolves them by type, not by key. public sealed class InstallerLocator() : InstallArtifactLocator("uninstaller", "ClaudeDo.Installer.exe"); public sealed class WorkerLocator() : InstallArtifactLocator("worker", "ClaudeDo.Worker.exe"); /// /// Locates an executable inside a ClaudeDo install: walk up from the running /// directory to the folder containing install.json, otherwise read the /// uninstall registry key (which is what makes this work from a dev build). /// public abstract class InstallArtifactLocator(string subdir, string exeName) { private const string InstallJson = "install.json"; public string? Find() => FindByWalkingUp(AppContext.BaseDirectory) ?? (OperatingSystem.IsWindows() ? FindByRegistry() : null); public string? FindByWalkingUp(string startDir) { var dir = new DirectoryInfo(startDir); while (dir is not null) { if (File.Exists(Path.Combine(dir.FullName, InstallJson))) return Candidate(dir.FullName); dir = dir.Parent; } return null; } [System.Runtime.Versioning.SupportedOSPlatform("windows")] public string? FindByRegistry() { if (!OperatingSystem.IsWindows()) return null; try { using var key = Microsoft.Win32.Registry.LocalMachine .OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\ClaudeDo"); var location = key?.GetValue("InstallLocation") as string; return string.IsNullOrEmpty(location) ? null : Candidate(location); } catch { return null; } } private string? Candidate(string installDir) { var path = Path.Combine(installDir, subdir, exeName); return File.Exists(path) ? path : null; } }