using System.Windows; using ClaudeDo.Installer.Core; using ClaudeDo.Releases; using ClaudeDo.Installer.Steps; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; namespace ClaudeDo.Installer.Views; public partial class SettingsViewModel : ObservableObject { private readonly InstallContext _context; private readonly IReleaseClient _releases; private readonly StopServiceStep _stopService; private readonly StartServiceStep _startService; private readonly DownloadAndExtractStep _downloadStep; private readonly UninstallRunner _uninstallRunner; public IReadOnlyList Pages { get; } [ObservableProperty] private IInstallerPage? _selectedPage; [ObservableProperty] private string? _statusMessage; [ObservableProperty] private bool _isStatusError; [ObservableProperty] private string _versionLabel = ""; [ObservableProperty] private bool _removeAppData; public SettingsViewModel( PageResolver resolver, InstallContext context, IReleaseClient releases, StopServiceStep stopService, StartServiceStep startService, DownloadAndExtractStep downloadStep, UninstallRunner uninstallRunner) { Pages = resolver.SettingsPages; _context = context; _releases = releases; _stopService = stopService; _startService = startService; _downloadStep = downloadStep; _uninstallRunner = uninstallRunner; _selectedPage = Pages.FirstOrDefault(); var label = $"Installed: {context.InstalledVersion ?? "?"} Installer: {context.InstallerVersion ?? "?"}"; if (!string.IsNullOrEmpty(context.LatestVersion)) label += $" Latest: {context.LatestVersion}"; if (context.LatestTagUnparseable) label += " (pre-release tag — auto-update disabled)"; VersionLabel = label; _ = LoadAllAsync(); } private async Task LoadAllAsync() { foreach (var page in Pages) await page.LoadAsync(); } [RelayCommand] private async Task Save() { foreach (var page in Pages) { if (!page.Validate()) { SelectedPage = page; StatusMessage = $"Validation failed on {page.Title}. Please fix the errors."; IsStatusError = true; return; } } foreach (var page in Pages) await page.ApplyAsync(); var workerCfg = new InstallerWorkerConfig { DbPath = _context.DbPath, SandboxRoot = _context.SandboxRoot, LogRoot = _context.LogRoot, WorktreeRootStrategy = _context.WorktreeRootStrategy, CentralWorktreeRoot = _context.CentralWorktreeRoot, QueueBackstopIntervalMs = _context.QueueBackstopIntervalMs, SignalRPort = _context.SignalRPort, ClaudeBin = _context.ClaudeBin, }; workerCfg.Save(); var uiCfg = new InstallerAppSettings { DbPath = _context.UiDbPath, SignalRUrl = _context.SignalRUrl, }; uiCfg.Save(); IsStatusError = false; StatusMessage = "Settings saved."; // Worker reads its config at process start, so changes only take effect after a restart. var restart = MessageBox.Show( "Restart the worker service now so the new settings take effect?", "Restart Worker", MessageBoxButton.YesNo, MessageBoxImage.Question); if (restart != MessageBoxResult.Yes) { StatusMessage = "Settings saved. Restart the worker service manually to apply."; return; } var progress = new Progress(msg => StatusMessage = msg); var stop = await _stopService.ExecuteAsync(_context, progress, CancellationToken.None); if (!stop.Success) { StatusMessage = $"Saved, but worker stop failed: {stop.ErrorMessage}"; IsStatusError = true; return; } var start = await _startService.ExecuteAsync(_context, progress, CancellationToken.None); if (!start.Success) { StatusMessage = $"Saved, but worker start failed: {start.ErrorMessage}"; IsStatusError = true; return; } StatusMessage = "Settings saved. Worker restarted."; } [RelayCommand] private async Task Repair() { var confirm = MessageBox.Show( "Re-download and reinstall the ClaudeDo binaries? Your config and database are NOT affected.", "Repair ClaudeDo", MessageBoxButton.OKCancel, MessageBoxImage.Question); if (confirm != MessageBoxResult.OK) return; StatusMessage = "Repairing..."; IsStatusError = false; var progress = new Progress(msg => StatusMessage = msg); var steps = new IInstallStep[] { _stopService, _downloadStep, _startService }; foreach (var step in steps) { var r = await step.ExecuteAsync(_context, progress, CancellationToken.None); if (!r.Success) { StatusMessage = $"{step.Name} failed: {r.ErrorMessage}"; IsStatusError = true; return; } } StatusMessage = "Repair complete."; } [RelayCommand] private async Task Uninstall() { var dataNote = RemoveAppData ? "This will remove ClaudeDo AND delete all of your tasks, configuration, and database.\n\nContinue?" : "This will remove ClaudeDo. Your tasks, configuration, and database in ~/.todo-app will be kept.\n\nContinue?"; var confirm = MessageBox.Show( dataNote, "Uninstall ClaudeDo", MessageBoxButton.YesNo, MessageBoxImage.Warning); if (confirm != MessageBoxResult.Yes) return; var progress = new Progress(msg => StatusMessage = msg); var r = await _uninstallRunner.RunAsync(RemoveAppData, progress, CancellationToken.None); if (!r.Success) { StatusMessage = $"Uninstall failed: {r.ErrorMessage}"; IsStatusError = true; return; } MessageBox.Show("ClaudeDo has been removed.", "Uninstall complete", MessageBoxButton.OK, MessageBoxImage.Information); Application.Current.Shutdown(); } [RelayCommand] private void Close() => Application.Current.Shutdown(); }