feat(installer): add WPF installer/configurator project

Standalone WPF app (ClaudeDo.Installer) that handles full installation
and ongoing configuration of ClaudeDo. Two modes: wizard for first run,
tabbed settings panel for subsequent launches. Page-based extensibility
via IInstallerPage interface — adding new config sections requires only
one new class.

Install pipeline: dotnet publish, deploy binaries, write configs, init
DB (via SchemaInitializer from ClaudeDo.Data), register Windows Service,
create shortcuts. Dark theme matching the Avalonia app (forest teal accent).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
CubeGameLP
2026-04-14 21:01:03 +02:00
parent 2479bb6ea1
commit 78831b2263
47 changed files with 2351 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
using System.Windows;
using ClaudeDo.Installer.Core;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace ClaudeDo.Installer.Views;
public partial class WizardViewModel : ObservableObject
{
private readonly InstallContext _context;
public IReadOnlyList<IInstallerPage> Pages { get; }
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanGoBack))]
[NotifyPropertyChangedFor(nameof(IsLastPage))]
[NotifyPropertyChangedFor(nameof(NextButtonText))]
[NotifyPropertyChangedFor(nameof(CurrentPage))]
private int _currentPageIndex;
public IInstallerPage CurrentPage => Pages[CurrentPageIndex];
public bool CanGoBack => CurrentPageIndex > 0;
public bool IsLastPage => CurrentPageIndex == Pages.Count - 1;
public string NextButtonText => IsLastPage ? "Install" : "Next \u2192";
[ObservableProperty]
private string? _validationError;
public WizardViewModel(PageResolver resolver, InstallContext context)
{
Pages = resolver.WizardPages;
_context = context;
if (Pages.Count > 0)
_ = InitAsync();
}
private async Task InitAsync()
{
try { await Pages[0].LoadAsync(); }
catch { /* first page loads with defaults on error */ }
}
[RelayCommand]
private async Task GoBack()
{
if (!CanGoBack) return;
CurrentPageIndex--;
await CurrentPage.LoadAsync();
ValidationError = null;
}
[RelayCommand]
private async Task GoNext()
{
if (!CurrentPage.Validate())
{
ValidationError = "Please fix the highlighted errors before continuing.";
return;
}
ValidationError = null;
await CurrentPage.ApplyAsync();
if (CurrentPageIndex < Pages.Count - 1)
{
CurrentPageIndex++;
await CurrentPage.LoadAsync();
}
}
[RelayCommand]
private void Close()
{
Application.Current.Shutdown();
}
}