# ClaudeDo.Installer

WPF GUI installer, updater, and configuration tool for ClaudeDo. Not WiX/NSIS — the app is its own installer.

Note: this is the one project where `System.Windows` is correct (WPF, not Avalonia).

## Project Facts

- `<UseWPF>true</UseWPF>`, `WinExe`, `net8.0-windows`
- `<EnableWindowsTargeting>true</EnableWindowsTargeting>` — allows Linux CI to cross-compile
- Single-file framework-dependent publish: `dotnet publish -r win-x64 -p:PublishSingleFile=true` (needs .NET 8 Desktop Runtime)
- Entry point: `App.xaml` / `App.xaml.cs` (no `Program.cs`)
- References: `ClaudeDo.Data`, `ClaudeDo.Releases`, `ClaudeDo.Localization`
- Manifests: `app.manifest` (requireAdministrator, Release) / `app.debug.manifest` (asInvoker, Debug)
- No CLI args — mode is detected from `install.json` + the Gitea API

## Startup Sequence (`App.OnStartup`)

1. Load locale
2. Detect mode — `InstallModeDetector` reads `install.json` + Gitea API
3. Open `WizardWindow` (FreshInstall / Update) or `SettingsWindow` (Config)

The installer does **not** self-update. Each release ships a stable-named
`ClaudeDo.Installer.exe` asset (permanent URL `…/releases/latest/download/ClaudeDo.Installer.exe`);
the binary only changes when the user downloads a fresh copy. The in-app "Update" button relaunches
the on-disk installer to run the *app* update. App-update detection is unaffected:
`WriteInstallManifestStep` records `ctx.InstalledVersion` (the release tag from
`DownloadAndExtractStep`), which `InstallModeDetector` compares against the latest tag.

## Modes (`Core/InstallerMode.cs`)

| Mode | Condition | Window |
|---|---|---|
| `FreshInstall` | No `install.json` | Full wizard (all pages) |
| `Update` | `install.json` present + newer release available | Wizard — Welcome + Install pages only |
| `Config` | Current version, or Gitea API unreachable | `SettingsWindow` (settings / repair / uninstall) |

## Install Pipelines

Each step implements `IInstallStep`; `InstallerService` runs them sequentially, stops on failure.

**FreshInstall:**
`DownloadAndExtractStep` → `WriteConfigStep` → `InitDatabaseStep` → `RegisterMcpStep` (optional) → `RegisterAutostartStep` → `CreateShortcutsStep` → `WriteUninstallRegistryStep` → `WriteInstallManifestStep` → `StartWorkerStep`

**Update:**
`StopWorkerStep` → `DownloadAndExtractStep` → `RegisterAutostartStep` → `RegisterMcpStep` → `StartWorkerStep` → `WriteInstallManifestStep` → `WriteUninstallRegistryStep`

**Repair** (via `SettingsViewModel`):
`StopWorkerStep` → `DownloadAndExtractStep` → `RegisterAutostartStep` → `StartWorkerStep`

**Uninstall** (`UninstallRunner`):
Stop worker → remove legacy task/service → delete HKLM uninstall key + shortcuts → delete install dir (cmd.exe trampoline if uninstaller exe is inside it) → optionally delete `~/.todo-app`

## Folder Layout

```
Installer/
  Steps/    — one class per action (see pipeline lists above)
  Core/     — InstallContext, InstallerMode, InstallModeDetector, InstallManifest(+Store),
               ConfigModels, InstallerService, UninstallRunner, PageResolver,
               AutostartShortcut, ShortcutFactory, ProcessRunner, DarkTitleBar
              Interfaces/ — IInstallStep + StepResult/StepStatus/StepProgress, IInstallerPage
  Pages/    — WelcomePage, PathsPage, ServicePage, UiSettingsPage, InstallPage
               (each: ViewModel + View.xaml)
  Views/    — WizardWindow(+WizardViewModel), SettingsWindow(+SettingsViewModel)
```

## Key Step Behaviors

**`RegisterMcpStep`** — registers the external MCP endpoint with the Claude CLI:
```
claude mcp remove --scope user claudedo
claude mcp add --transport http --scope user claudedo http://127.0.0.1:{ExternalMcpPort}/mcp
```
Non-fatal if `claude` CLI is missing or too old (prints the manual command). Server name: `claudedo`.

**`RegisterAutostartStep`** — creates a per-user Startup-folder shortcut `ClaudeDo Worker.lnk` (`Environment.SpecialFolder.Startup`). Also migrates away from legacy mechanisms:
- Deletes legacy Windows service: `sc.exe stop/delete ClaudeDoWorker`
- Deletes legacy scheduled task: `schtasks /Delete /TN ClaudeDoWorker`

No new service or scheduled task is created. Rationale: the worker must run in the user's interactive session so Claude CLI auth works.

**`DownloadAndExtractStep`** — fetches `checksums.txt` first and only touches the install dir after
the zip verifies. The zip is cached in `%TEMP%\ClaudeDo-download-cache` (ctor takes an override for
tests) and reused on a retry when its SHA-256 still matches, so a failed attempt doesn't cost
another full download. Cache is dropped after a successful install, a bad download is deleted
immediately, other versions are pruned. `app\`/`worker\` are stashed to `*.bak` before extraction
and restored if it fails.

### Gotcha: the installer must never run from inside the install dir

`App.OnStartup` sets `Environment.CurrentDirectory` to `%TEMP%`, and the UI passes an explicit `WorkingDirectory` when it relaunches us. A process's current directory is locked by Windows: inheriting the app's CWD (`<InstallDir>\app`, from the Start Menu shortcut's "start in") made the installer block its own `app` → `app.bak` rename, so every update failed with "Could not replace the existing files" — unaffected by retries or a reboot. Keep both guards.

## `InstallContext` Defaults

| Property | Default |
|---|---|
| `InstallDirectory` | `C:\Program Files\ClaudeDo` |
| `DbPath` | `~/.todo-app/todo.db` |
| `LogRoot` | `~/.todo-app/logs` |
| `SandboxRoot` | `~/.todo-app/sandbox` |
| `WorktreeRootStrategy` | `sibling` |
| `SignalRPort` | `47821` |
| `ExternalMcpPort` | `47822` |
| `QueueBackstopIntervalMs` | `30000` |
| `ClaudeBin` | `claude` |
| `AutoStart` | `true` |
| `SignalRUrl` | `http://127.0.0.1:47821/hub` |

## Files Written by Install

| Path | Content |
|---|---|
| `~/.todo-app/worker.config.json` | Worker config |
| `~/.todo-app/ui.config.json` | UI config |
| `~/.todo-app/todo.db` | SQLite DB (EF migrations) |
| `<InstallDir>\install.json` | Install manifest |
| `<InstallDir>\app\` | UI binaries |
| `<InstallDir>\worker\` | Worker binaries |
| `<InstallDir>\uninstaller\ClaudeDo.Installer.exe` | Uninstaller copy |
| `HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall\ClaudeDo` | Uninstall registry key |
| Start Menu shortcut | `ClaudeDo.lnk` |
| Desktop shortcut (optional) | `ClaudeDo.lnk` |
| `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\ClaudeDo Worker.lnk` | Worker autostart |

The Apps & Features uninstall string and "Rerun Installer" both point at `<InstallDir>\uninstaller\ClaudeDo.Installer.exe` with no `/uninstall` flag — Config mode is detected from `install.json`.
