Ctrl K
ComunidadGuías…

native/src/cli_main.rs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
mod autostart_control;
mod bootstrap;
mod cli;
mod cli_executor;
mod legacy_cleanup;
mod legacy_migration;
#[cfg(any(target_os = "macos", test))]
mod macos_bundle;
mod management;
mod path_control;
mod process_control;
mod runtime_compute_status;
mod system_capabilities;
mod update_manager;

use autostart_control::AutoStartStatus;
use cli::{
    CliError, Command, CommandResult, ConfigAction, HotkeyAction, MicAction, ModelAction,
    OutputFormat, ToggleAction, UninstallOptions, UpdateAction,
};
use cli_executor::CoreCliExecutor;
use management::DataLayout;
use origin_speak_lib::{AppState, TranscriptionRuntimePhase};
use origin_speak_lib::{
    State, app_data_root, get_audio_devices, get_config, get_transcription_settings,
    get_trigger_hotkey, list_local_models, normalize_hotkey_string,
};
use process_control::RuntimeState;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::ffi::OsString;
use std::fs;
use std::future::Future;
use std::io::{self, IsTerminal, Write};
use std::path::{Path, PathBuf};
use std::process::{Command as ProcessCommand, Stdio};
use std::sync::Arc;
use std::time::{Duration, Instant};
use system_capabilities::SystemCapabilities;

const COMMAND_TIMEOUT: Duration = Duration::from_secs(8);
const HELPER_RETRY_TIMEOUT: Duration = Duration::from_secs(20);
const UPDATE_RESULT_FILE: &str = "update-result.jsonl";
const UPDATE_RESULT_MAX_BYTES: u64 = 256 * 1024;
#[cfg(target_os = "windows")]
const UNINSTALL_COMMIT_TIMEOUT: Duration = Duration::from_secs(120);
#[cfg(target_os = "windows")]
const STALE_HELPER_MIN_AGE: Duration = Duration::from_secs(60);

#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
struct UpdateResultRecord {
    version: String,
    status: String,
    error: Option<String>,
}

struct TerminalProgress {
    enabled: bool,
    label: String,
    started: Instant,
    last_sample: Instant,
    last_bytes: u64,
    bytes_per_second: f64,
    last_line_width: usize,
    seen: bool,
    downloaded: u64,
    total: Option<u64>,
}

impl TerminalProgress {
    fn new(enabled: bool) -> Self {
        let now = Instant::now();
        Self {
            enabled,
            label: String::new(),
            started: now,
            last_sample: now,
            last_bytes: 0,
            bytes_per_second: 0.0,
            last_line_width: 0,
            seen: false,
            downloaded: 0,
            total: None,
        }
    }

    fn update(&mut self, label: &str, downloaded: u64, total: Option<u64>) {
        if !self.enabled {
            return;
        }
        if self.seen && self.label != label {
            self.finish_current();
        }
        if self.label != label {
            let now = Instant::now();
            self.label.clear();
            self.label.push_str(label);
            self.started = now;
            self.last_sample = now;
            self.last_bytes = downloaded;
            self.bytes_per_second = 0.0;
        }

        let now = Instant::now();
        if downloaded < self.last_bytes {
            self.last_sample = now;
            self.last_bytes = downloaded;
            self.bytes_per_second = 0.0;
        }
        let elapsed = now.duration_since(self.last_sample).as_secs_f64();
        if elapsed >= 0.2 && downloaded >= self.last_bytes {
            let instant = (downloaded - self.last_bytes) as f64 / elapsed;
            self.bytes_per_second = if self.bytes_per_second == 0.0 {
                instant
            } else {
                self.bytes_per_second * 0.7 + instant * 0.3
            };
            self.last_sample = now;
            self.last_bytes = downloaded;
        }
        self.downloaded = downloaded;
        self.total = total;
        self.seen = true;

        let line = progress_line(
            &self.label,
            downloaded,
            total,
            self.bytes_per_second,
            self.started.elapsed(),
        );
        let padding = self.last_line_width.saturating_sub(line.chars().count());
        eprint!("\r{line}{}", " ".repeat(padding));
        let _ = io::stderr().flush();
        self.last_line_width = line.chars().count();
    }

    fn finish_current(&mut self) {
        if !self.enabled || !self.seen {
            return;
        }
        let suffix = self
            .total
            .map(|total| format!("  {}", format_bytes(total)))
            .unwrap_or_else(|| format!("  {}", format_bytes(self.downloaded)));
        let line = format!("  ✓ {}{}", self.label, suffix);
        let padding = self.last_line_width.saturating_sub(line.chars().count());
        eprintln!("\r{line}{}", " ".repeat(padding));
        let _ = io::stderr().flush();
        self.last_line_width = 0;
        self.seen = false;
    }

    fn abort(&mut self) {
        if self.enabled && self.seen {
            eprintln!();
            let _ = io::stderr().flush();
        }
        self.last_line_width = 0;
        self.seen = false;
    }
}

fn progress_enabled(format: OutputFormat) -> bool {
    matches!(format, OutputFormat::Human) && io::stderr().is_terminal()
}

fn progress_line(
    label: &str,
    downloaded: u64,
    total: Option<u64>,
    bytes_per_second: f64,
    _elapsed: Duration,
) -> String {
    let speed = if bytes_per_second > 0.0 {
        format!("{}/s", format_bytes(bytes_per_second as u64))
    } else {
        "--/s".to_string()
    };
    match total.filter(|total| *total > 0) {
        Some(total) => {
            const WIDTH: usize = 22;
            let ratio = (downloaded as f64 / total as f64).clamp(0.0, 1.0);
            let filled = (ratio * WIDTH as f64).round() as usize;
            let bar = format!("{}{}", "█".repeat(filled), "░".repeat(WIDTH - filled));
            let eta = if bytes_per_second > 0.0 && downloaded < total {
                format!(
                    "  ETA {}",
                    format_eta(((total - downloaded) as f64 / bytes_per_second).ceil() as u64)
                )
            } else {
                String::new()
            };
            format!(
                "  ↓ {label}  [{bar}] {:>3}%  {} / {}  {speed}{eta}",
                (ratio * 100.0).floor() as u64,
                format_bytes(downloaded),
                format_bytes(total)
            )
        }
        None => format!("  ↓ {label}  {}  {speed}", format_bytes(downloaded)),
    }
}

fn format_eta(seconds: u64) -> String {
    let minutes = seconds / 60;
    let seconds = seconds % 60;
    if minutes > 0 {
        format!("{minutes}m {seconds:02}s")
    } else {
        format!("{seconds}s")
    }
}

fn format_bytes(bytes: u64) -> String {
    const KIB: f64 = 1024.0;
    const MIB: f64 = 1024.0 * 1024.0;
    const GIB: f64 = 1024.0 * 1024.0 * 1024.0;
    let bytes = bytes as f64;
    if bytes >= GIB {
        format!("{:.2} GiB", bytes / GIB)
    } else if bytes >= MIB {
        format!("{:.1} MiB", bytes / MIB)
    } else if bytes >= KIB {
        format!("{:.1} KiB", bytes / KIB)
    } else {
        format!("{} B", bytes as u64)
    }
}

async fn with_model_download_progress<T, F>(
    state: Arc<AppState>,
    future: F,
    format: OutputFormat,
) -> Result<T, String>
where
    F: Future<Output = Result<T, String>>,
{
    if !progress_enabled(format) {
        return future.await;
    }

    tokio::pin!(future);
    let mut progress = TerminalProgress::new(true);
    let mut verifying_model: Option<String> = None;
    let mut ticker = tokio::time::interval(Duration::from_millis(120));
    ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
    loop {
        tokio::select! {
            result = &mut future => {
                if let Some(model) = verifying_model.take() {
                    match &result {
                        Ok(_) => eprintln!("\r  ✓ Verified model {model}"),
                        Err(_) => eprintln!(),
                    }
                }
                match &result {
                    Ok(_) => progress.finish_current(),
                    Err(_) => progress.abort(),
                }
                return result;
            }
            _ = ticker.tick() => {
                let status = state.transcription.runtime_status();
                if matches!(status.phase, TranscriptionRuntimePhase::Downloading) {
                    progress.update(
                        &format!("Model {}", status.model),
                        status.download_bytes.unwrap_or(0),
                        status.download_total_bytes,
                    );
                    verifying_model = None;
                } else if matches!(status.phase, TranscriptionRuntimePhase::Verifying)
                    && verifying_model.as_deref() != Some(status.model.as_str())
                {
                    progress.finish_current();
                    eprint!("  • Verifying model {} (SHA-256)...", status.model);
                    let _ = io::stderr().flush();
                    verifying_model = Some(status.model);
                }
            }
        }
    }
}

fn update_payload_label(path: &str) -> &'static str {
    if path.contains("runtime") {
        "Resident runtime"
    } else {
        "CLI manager"
    }
}

fn append_update_result(layout: &DataLayout, record: &UpdateResultRecord) -> Result<(), String> {
    append_update_result_at(&layout.data_root.join(UPDATE_RESULT_FILE), record)
}

fn append_update_result_at(path: &Path, record: &UpdateResultRecord) -> Result<(), String> {
    let parent = path
        .parent()
        .ok_or_else(|| "update result path has no parent directory".to_string())?;
    fs::create_dir_all(parent).map_err(|error| {
        format!(
            "create update result directory {}: {error}",
            parent.display()
        )
    })?;
    if let Ok(metadata) = fs::symlink_metadata(path) {
        if !metadata.is_file() || metadata.file_type().is_symlink() {
            return Err(format!(
                "refusing update result path that is not a regular file: {}",
                path.display()
            ));
        }
        if metadata.len() > UPDATE_RESULT_MAX_BYTES {
            return Err(format!(
                "refusing oversized update result log: {}",
                path.display()
            ));
        }
    }
    let mut options = fs::OpenOptions::new();
    options.create(true).append(true);
    let mut file = options
        .open(path)
        .map_err(|error| format!("open update result log {}: {error}", path.display()))?;
    let mut line =
        serde_json::to_vec(record).map_err(|error| format!("serialize update result: {error}"))?;
    line.push(b'\n');
    file.write_all(&line)
        .map_err(|error| format!("write update result log: {error}"))?;
    file.sync_all()
        .map_err(|error| format!("sync update result log: {error}"))
}

fn take_update_result(layout: &DataLayout) -> Result<Option<UpdateResultRecord>, String> {
    take_update_result_at(&layout.data_root.join(UPDATE_RESULT_FILE))
}

fn take_update_result_at(path: &Path) -> Result<Option<UpdateResultRecord>, String> {
    let metadata = match fs::symlink_metadata(path) {
        Ok(metadata) => metadata,
        Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
        Err(error) => {
            return Err(format!(
                "inspect update result log {}: {error}",
                path.display()
            ));
        }
    };
    if !metadata.is_file() || metadata.file_type().is_symlink() {
        return Err(format!(
            "refusing update result path that is not a regular file: {}",
            path.display()
        ));
    }
    if metadata.len() > UPDATE_RESULT_MAX_BYTES {
        return Err(format!(
            "refusing oversized update result log: {}",
            path.display()
        ));
    }
    let contents = fs::read_to_string(path)
        .map_err(|error| format!("read update result log {}: {error}", path.display()))?;
    let record = contents
        .lines()
        .rev()
        .find_map(|line| serde_json::from_str::<UpdateResultRecord>(line).ok())
        .ok_or_else(|| "update result log contained no valid record".to_string())?;
    fs::remove_file(path)
        .map_err(|error| format!("consume update result log {}: {error}", path.display()))?;
    Ok(Some(record))
}

fn with_previous_update_result(
    mut result: CommandResult,
    previous: Option<UpdateResultRecord>,
) -> CommandResult {
    let Some(previous) = previous else {
        return result;
    };
    result = result
        .field("previous_update_version", previous.version)
        .field("previous_update_status", previous.status);
    if let Some(error) = previous.error {
        result = result.field("previous_update_error", error);
    }
    result
}

#[cfg(target_os = "windows")]
struct ArmedUninstallHelper {
    helper_path: PathBuf,
    commit_path: PathBuf,
}

#[derive(Debug)]
struct PersistedFileSnapshot {
    path: PathBuf,
    contents: Option<Vec<u8>>,
}

#[derive(Debug)]
struct SetupPersistenceSnapshot {
    files: Vec<PersistedFileSnapshot>,
}

impl SetupPersistenceSnapshot {
    fn capture() -> Result<Self, String> {
        let root = app_data_root()?;
        Self::capture_from_root(&root)
    }

    fn capture_from_root(root: &Path) -> Result<Self, String> {
        let mut files = Vec::new();
        for name in [
            "config.json",
            "config.json.bak",
            "config.json.tmp",
            "transcription_settings.json",
        ] {
            let path = root.join(name);
            let contents = match fs::symlink_metadata(&path) {
                Ok(metadata) => {
                    if metadata.file_type().is_symlink() || !metadata.is_file() {
                        return Err(format!(
                            "refusing setup persistence snapshot for non-regular app-owned file {}",
                            path.display()
                        ));
                    }
                    Some(fs::read(&path).map_err(|error| {
                        format!(
                            "read setup persistence snapshot {}: {error}",
                            path.display()
                        )
                    })?)
                }
                Err(error) if error.kind() == io::ErrorKind::NotFound => None,
                Err(error) => {
                    return Err(format!(
                        "inspect setup persistence file {}: {error}",
                        path.display()
                    ));
                }
            };
            files.push(PersistedFileSnapshot { path, contents });
        }
        Ok(Self { files })
    }

    fn restore(&self) -> Result<(), String> {
        for snapshot in &self.files {
            match snapshot.contents.as_ref() {
                Some(contents) => {
                    if let Some(parent) = snapshot.path.parent() {
                        fs::create_dir_all(parent).map_err(|error| {
                            format!(
                                "recreate setup persistence directory {}: {error}",
                                parent.display()
                            )
                        })?;
                    }
                    if let Ok(metadata) = fs::symlink_metadata(&snapshot.path)
                        && (metadata.file_type().is_symlink() || !metadata.is_file())
                    {
                        return Err(format!(
                            "refusing to restore setup persistence through non-regular path {}",
                            snapshot.path.display()
                        ));
                    }
                    fs::write(&snapshot.path, contents).map_err(|error| {
                        format!(
                            "restore setup persistence file {}: {error}",
                            snapshot.path.display()
                        )
                    })?;
                }
                None => match fs::symlink_metadata(&snapshot.path) {
                    Ok(metadata) if metadata.is_file() && !metadata.file_type().is_symlink() => {
                        fs::remove_file(&snapshot.path).map_err(|error| {
                            format!(
                                "remove setup-created persistence file {}: {error}",
                                snapshot.path.display()
                            )
                        })?;
                    }
                    Ok(_) => {
                        return Err(format!(
                            "refusing to remove non-regular setup persistence path {}",
                            snapshot.path.display()
                        ));
                    }
                    Err(error) if error.kind() == io::ErrorKind::NotFound => {}
                    Err(error) => {
                        return Err(format!(
                            "inspect setup persistence path {} during rollback: {error}",
                            snapshot.path.display()
                        ));
                    }
                },
            }
        }
        Ok(())
    }
}

fn main() {
    let args = std::env::args_os().collect::<Vec<_>>();
    if let Some(code) = run_internal_helper(&args) {
        std::process::exit(code);
    }
    #[cfg(target_os = "windows")]
    remove_stale_post_exit_helpers(
        &std::env::temp_dir(),
        std::env::current_exe().ok().as_deref(),
        STALE_HELPER_MIN_AGE,
    );
    origin_speak_lib::disable_cli_transcription_logging();

    let requested_format = if args.iter().any(|arg| arg == "--json") {
        OutputFormat::Json
    } else {
        OutputFormat::Human
    };
    let request = match cli::parse(args.clone()) {
        Ok(request) => request,
        Err(error) => {
            eprintln!("{}", error.render(requested_format));
            std::process::exit(2);
        }
    };

    let runtime = match tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
    {
        Ok(runtime) => runtime,
        Err(error) => {
            let error = CliError {
                code: "runtime_init_failed",
                message: format!("could not initialize CLI runtime: {error}"),
            };
            eprintln!("{}", error.render(request.output));
            std::process::exit(1);
        }
    };

    match runtime.block_on(execute(request.command, request.output)) {
        Ok(result) => println!("{}", result.render(request.output)),
        Err(message) => {
            let error = CliError {
                code: "command_failed",
                message,
            };
            eprintln!("{}", error.render(request.output));
            std::process::exit(1);
        }
    }
}

async fn execute(command: Command, format: OutputFormat) -> Result<CommandResult, String> {
    match command {
        Command::Help => Ok(CommandResult::success("help", cli::usage())),
        Command::Version => Ok(CommandResult::success(
            "version",
            format!("Origin Speak {}", env!("CARGO_PKG_VERSION")),
        )
        .field("version", env!("CARGO_PKG_VERSION"))),
        Command::Setup(options) => {
            if setup_needs_guidance(&options, format) {
                match guided_setup(options).await? {
                    Some(options) => setup(options, format).await,
                    None => Ok(CommandResult::success(
                        "setup_cancelled",
                        "Origin Speak setup cancelled; no changes were made",
                    )),
                }
            } else {
                setup(options, format).await
            }
        }
        Command::Status => status().await,
        Command::Doctor => doctor().await,
        Command::Config(action) => config_command(action).await,
        Command::Dictionary(action) => CoreCliExecutor::new().dictionary(&action).await,
        Command::Model(action) => model_command(action, format).await,
        Command::Mic(action) => mic_command(action).await,
        Command::Hotkey(action) => hotkey_command(action).await,
        Command::Autostart(action) => autostart(action).await,
        Command::Start => start(),
        Command::Stop => stop(),
        Command::Restart => restart(),
        Command::Update(action) => update(action, format).await,
        Command::Uninstall(options) => uninstall(options, format),
    }
}

async fn setup(options: cli::SetupOptions, format: OutputFormat) -> Result<CommandResult, String> {
    let layout = DataLayout::discover()?;
    #[cfg(target_os = "macos")]
    {
        // Do this before stopping/replacing anything. Historical ListenOS macOS
        // builds used SMAppService for the main app but exposed no bounded remote
        // unregister protocol, so their bundle must be explicitly retired first.
        legacy_migration::preflight_legacy_macos_bundle()?;
        macos_bundle::recover_stale_transactions(&layout.install_root)?;
    }
    let current_exe = std::env::current_exe()
        .map_err(|error| format!("resolve current Origin Speak manager: {error}"))?;
    let runtime_state = process_control::runtime_status().ok();
    let runtime_was_running = setup_preinstall_stop_required(runtime_state);

    // The legacy NSIS executable and the new manager alias the same Windows
    // path case-insensitively. Stop the resident before replacing either binary.
    if runtime_was_running {
        process_control::stop_runtime(COMMAND_TIMEOUT)?;
    }
    setup_after_runtime_stop(&options, &layout, &current_exe, runtime_was_running, format).await
}

async fn setup_after_runtime_stop(
    options: &cli::SetupOptions,
    layout: &DataLayout,
    current_exe: &Path,
    runtime_was_running: bool,
    format: OutputFormat,
) -> Result<CommandResult, String> {
    if options.interactive {
        eprintln!("Installing Origin Speak manager and resident runtime...");
    }
    let mut legacy_data_migration = management::LegacyDataMigration::begin()?;
    let persistence_snapshot = match SetupPersistenceSnapshot::capture() {
        Ok(snapshot) => snapshot,
        Err(error) => {
            let _ = legacy_data_migration.rollback();
            return Err(error);
        }
    };
    let mut manager_swap: Option<bootstrap::TransactionalSwap> = None;
    #[cfg(target_os = "macos")]
    let mut runtime_swap: Option<macos_bundle::BundleSwap> = None;
    #[cfg(not(target_os = "macos"))]
    let mut runtime_swap: Option<bootstrap::TransactionalSwap> = None;
    let mut release_staging: Option<PathBuf> = None;
    let mut manager_path_added_by_setup = false;
    let mut desired_autostart = None;

    let setup_result = async {
        #[cfg(target_os = "macos")]
        {
            let bundle_parent = layout
                .install_root
                .parent()
                .ok_or_else(|| "macOS runtime bundle has no parent directory".to_string())?;
            fs::create_dir_all(bundle_parent).map_err(|error| {
                format!(
                    "create macOS Applications directory {}: {error}",
                    bundle_parent.display()
                )
            })?;
        }
        #[cfg(not(target_os = "macos"))]
        fs::create_dir_all(&layout.install_root).map_err(|error| {
            format!(
                "create install directory {}: {error}",
                layout.install_root.display()
            )
        })?;
        fs::create_dir_all(&layout.manager_root).map_err(|error| {
            format!(
                "create manager directory {}: {error}",
                layout.manager_root.display()
            )
        })?;

        let manager_installed = if same_path(current_exe, &layout.manager_path) {
            false
        } else {
            manager_swap = Some(bootstrap::transactional_replace(
                current_exe,
                &layout.manager_path,
            )?);
            true
        };
        let manager_path_registration = path_control::ensure(&layout.manager_root)?;
        manager_path_added_by_setup = manager_path_registration.changed;

        let mut runtime_installed = false;
        #[cfg(target_os = "macos")]
        {
            if let Some(sibling) =
                bootstrap::find_sibling_runtime(current_exe, env!("CARGO_PKG_VERSION"))?
            {
                runtime_swap = Some(bootstrap::transactional_install_macos_runtime_bundle(
                    &sibling,
                    &layout.install_root,
                )?);
                runtime_installed = true;
            } else if !installed_macos_bundle_matches_manager(layout) {
                let staged = stage_bootstrap_release_runtime(layout, format).await?;
                release_staging = Some(staged.staging);
                runtime_swap = Some(bootstrap::transactional_install_macos_runtime_bundle(
                    &staged.payload,
                    &layout.install_root,
                )?);
                runtime_installed = true;
            }
        }
        #[cfg(not(target_os = "macos"))]
        {
            if let Some(sibling) =
                bootstrap::find_sibling_runtime(current_exe, env!("CARGO_PKG_VERSION"))?
            {
                runtime_swap = Some(bootstrap::transactional_replace(
                    &sibling,
                    &layout.runtime_path,
                )?);
                runtime_installed = true;
            } else if cfg!(target_os = "windows") || !layout.runtime_path.is_file() {
                let staged = stage_bootstrap_release_runtime(layout, format).await?;
                release_staging = Some(staged.staging);
                runtime_swap = Some(bootstrap::transactional_replace(
                    &staged.payload,
                    &layout.runtime_path,
                )?);
                runtime_installed = true;
            }
        }

        if !layout.runtime_path.is_file() {
            return Err(format!(
                "resident runtime is not installed at {}; provide a sibling runtime payload or run a matching release manager",
                layout.runtime_path.display()
            ));
        }

        if options.interactive {
            eprintln!("Configuring local voice-to-text...");
        }
        let executor = CoreCliExecutor::new();
        let state = executor.state();
        let mut result =
            with_model_download_progress(state.clone(), executor.setup(options), format).await?;
        let persisted = get_config(State::new(state.as_ref())).await?;
        desired_autostart = Some(persisted.auto_start);

        if options.interactive {
            eprintln!("Starting Origin Speak resident runtime...");
        }
        let started = process_control::start_runtime(&layout.runtime_path)?;

        result = result
            .field("manager_path", layout.manager_path.display().to_string())
            .field("runtime_path", layout.runtime_path.display().to_string())
            .field("manager_installed_now", manager_installed.to_string())
            .field("runtime_installed_now", runtime_installed.to_string())
            .field(
                "legacy_data_roots_migrated",
                legacy_data_migration.migrated_count().to_string(),
            )
            .field(
                "manager_on_path",
                manager_path_registration.on_path.to_string(),
            )
            .field(
                "manager_path_changed",
                manager_path_registration.changed.to_string(),
            )
            .field("runtime_started_now", started.to_string())
            .field("runtime_reloaded", runtime_was_running.to_string());
        Ok(result)
    }
    .await;

    match setup_result {
        Ok(mut result) => {
            finalize_setup_binary_swaps(&mut manager_swap, &mut runtime_swap)?;
            if let Some(staging) = release_staging {
                let _ = fs::remove_dir_all(staging);
            }
            let desired_autostart = desired_autostart.ok_or_else(|| {
                "setup completed without resolving the persisted autostart preference".to_string()
            })?;
            let native_autostart = autostart_control::set(&layout.runtime_path, desired_autostart)
                .map_err(|error| {
                    format!(
                        "Origin Speak is installed and the resident runtime is ready, but native autostart configuration failed: {error}. Run `origin autostart {}` to retry without reinstalling",
                        if desired_autostart { "enable" } else { "disable" }
                    )
                })?;
            let legacy_cleanup = legacy_migration::cleanup_after_replacement(current_exe)?;
            result = result
                .field("autostart_native", autostart_label(native_autostart))
                .field(
                    "legacy_artifacts_removed",
                    legacy_cleanup.removed.len().to_string(),
                )
                .field(
                    "legacy_cleanup_warnings",
                    legacy_cleanup.warnings.len().to_string(),
                );
            Ok(result)
        }
        Err(error) => {
            let had_binary_changes = manager_swap.is_some() || runtime_swap.is_some();
            let stop_error = match process_control::runtime_status() {
                Ok(RuntimeState::Running) => process_control::stop_runtime(COMMAND_TIMEOUT).err(),
                Ok(RuntimeState::Stopped) => None,
                Err(status_error) => Some(format!(
                    "inspect replacement runtime before rollback: {status_error}"
                )),
            };
            let rollback_error =
                rollback_setup_binary_swaps(&mut manager_swap, &mut runtime_swap).err();
            let persistence_restore_error = persistence_snapshot.restore().err();
            let data_migration_restore_error = legacy_data_migration.rollback().err();
            let restart_error = if runtime_was_running
                && stop_error.is_none()
                && rollback_error.is_none()
                && persistence_restore_error.is_none()
                && layout.runtime_path.is_file()
            {
                process_control::start_runtime(&layout.runtime_path).err()
            } else {
                None
            };
            if let Some(staging) = release_staging {
                let _ = fs::remove_dir_all(staging);
            }

            let path_restore_error = if manager_path_added_by_setup {
                path_control::remove(&layout.manager_root).err()
            } else {
                None
            };

            let mut recovery = Vec::new();
            if let Some(stop_error) = stop_error {
                recovery.push(format!(
                    "stopping the replacement runtime failed: {stop_error}"
                ));
            }
            if let Some(rollback_error) = rollback_error {
                recovery.push(format!("binary rollback failed: {rollback_error}"));
            }
            if let Some(restart_error) = restart_error {
                recovery.push(format!(
                    "restarting the previous runtime failed: {restart_error}"
                ));
            }
            if let Some(path_restore_error) = path_restore_error {
                recovery.push(format!(
                    "restoring the pre-setup PATH registration failed: {path_restore_error}"
                ));
            }
            if let Some(persistence_restore_error) = persistence_restore_error {
                recovery.push(format!(
                    "restoring the pre-setup persisted voice settings failed: {persistence_restore_error}"
                ));
            } else if runtime_was_running && recovery.is_empty() {
                recovery.push(if had_binary_changes {
                    "previous binaries were restored and the runtime was restarted".to_string()
                } else {
                    "the previous runtime was restarted".to_string()
                });
            } else if !runtime_was_running && recovery.is_empty() {
                recovery.push(if had_binary_changes {
                    "installed binary changes were rolled back".to_string()
                } else {
                    "no binary replacement was committed".to_string()
                });
            }
            if let Some(data_migration_restore_error) = data_migration_restore_error {
                recovery.push(format!(
                    "restoring migrated legacy data failed: {data_migration_restore_error}"
                ));
            }
            Err(format!("{error}; {}", recovery.join("; ")))
        }
    }
}

#[cfg(not(target_os = "macos"))]
fn rollback_setup_binary_swaps(
    manager: &mut Option<bootstrap::TransactionalSwap>,
    runtime: &mut Option<bootstrap::TransactionalSwap>,
) -> Result<(), String> {
    let manager_result = manager.as_mut().map_or(Ok(()), |swap| swap.rollback());
    let runtime_result = runtime.as_mut().map_or(Ok(()), |swap| swap.rollback());
    combine_setup_swap_results("rollback", manager_result, runtime_result)
}

#[cfg(target_os = "macos")]
fn rollback_setup_binary_swaps(
    manager: &mut Option<bootstrap::TransactionalSwap>,
    runtime: &mut Option<macos_bundle::BundleSwap>,
) -> Result<(), String> {
    let manager_result = manager.as_mut().map_or(Ok(()), |swap| swap.rollback());
    let runtime_result = runtime.as_mut().map_or(Ok(()), |swap| swap.rollback());
    combine_setup_swap_results("rollback", manager_result, runtime_result)
}

#[cfg(not(target_os = "macos"))]
fn finalize_setup_binary_swaps(
    manager: &mut Option<bootstrap::TransactionalSwap>,
    runtime: &mut Option<bootstrap::TransactionalSwap>,
) -> Result<(), String> {
    if let Some(runtime) = runtime.as_mut() {
        runtime.finalize()?;
    }
    if let Some(manager) = manager.as_mut() {
        manager.finalize()?;
    }
    Ok(())
}

#[cfg(target_os = "macos")]
fn finalize_setup_binary_swaps(
    manager: &mut Option<bootstrap::TransactionalSwap>,
    runtime: &mut Option<macos_bundle::BundleSwap>,
) -> Result<(), String> {
    if let Some(runtime) = runtime.as_mut() {
        runtime.finalize()?;
    }
    if let Some(manager) = manager.as_mut() {
        manager.finalize()?;
    }
    Ok(())
}

fn combine_setup_swap_results(
    action: &str,
    manager: Result<(), String>,
    runtime: Result<(), String>,
) -> Result<(), String> {
    match (manager, runtime) {
        (Ok(()), Ok(())) => Ok(()),
        (Err(error), Ok(())) => Err(format!("manager {action} failed: {error}")),
        (Ok(()), Err(error)) => Err(format!("runtime {action} failed: {error}")),
        (Err(manager_error), Err(runtime_error)) => Err(format!(
            "manager {action} failed ({manager_error}); runtime {action} failed ({runtime_error})"
        )),
    }
}

fn setup_preinstall_stop_required(state: Option<RuntimeState>) -> bool {
    matches!(state, Some(RuntimeState::Running))
}

fn setup_needs_guidance(options: &cli::SetupOptions, format: OutputFormat) -> bool {
    setup_needs_guidance_with_terminal(
        options,
        format,
        io::stdin().is_terminal(),
        io::stdout().is_terminal(),
    )
}

fn setup_needs_guidance_with_terminal(
    options: &cli::SetupOptions,
    format: OutputFormat,
    stdin_tty: bool,
    stdout_tty: bool,
) -> bool {
    matches!(format, OutputFormat::Human)
        && stdin_tty
        && stdout_tty
        && !options.has_explicit_choices()
        && !options.interactive
}

async fn guided_setup(mut options: cli::SetupOptions) -> Result<Option<cli::SetupOptions>, String> {
    let capabilities = SystemCapabilities::detect();
    let recommendation = capabilities.recommend_model();
    let executor = CoreCliExecutor::new();
    let state = executor.state();
    let models = list_local_models(State::new(state.as_ref())).await?;
    let config = get_config(State::new(state.as_ref())).await?;

    eprintln!("Origin Speak setup");
    eprintln!("Detected system:");
    eprintln!(
        "  CPU: {} · {} logical cores",
        capabilities.arch, capabilities.logical_cpus
    );
    eprintln!(
        "  RAM: {}",
        capabilities
            .memory_gib()
            .map(|value| format!("{value} GiB"))
            .unwrap_or_else(|| "unknown".to_string())
    );
    eprintln!("  Accelerator: {}", capabilities.accelerator);
    eprintln!("  Recommended model: {}", recommendation.model);
    eprintln!();

    let default_model_index = models
        .iter()
        .position(|model| model.id == recommendation.model)
        .unwrap_or(0);
    eprintln!("Transcription model:");
    for (index, model) in models.iter().enumerate() {
        let marker = if index == default_model_index {
            " (recommended)"
        } else {
            ""
        };
        let installed = if model.downloaded {
            " · installed"
        } else {
            ""
        };
        eprintln!(
            "  {}. {} [{}]{}{}",
            index + 1,
            model.label,
            model.id,
            marker,
            installed
        );
    }
    let model_index = prompt_index("Choose model", models.len(), default_model_index + 1)? - 1;
    options.model = Some(models[model_index].id.clone());

    let devices = get_audio_devices().await?;
    eprintln!();
    eprintln!("Microphone:");
    eprintln!("  1. System default");
    for (index, device) in devices.iter().enumerate() {
        let marker = if device.is_default {
            " (OS default)"
        } else {
            ""
        };
        eprintln!("  {}. {}{}", index + 2, device.name, marker);
    }
    let default_mic = config
        .selected_audio_device
        .as_ref()
        .and_then(|selected| devices.iter().position(|device| &device.name == selected))
        .map(|index| index + 2)
        .unwrap_or(1);
    let mic_choice = prompt_index("Choose microphone", devices.len() + 1, default_mic)?;
    let selected_mic = if mic_choice == 1 {
        None
    } else {
        Some(devices[mic_choice - 2].name.clone())
    };
    options.microphone = Some(
        selected_mic
            .clone()
            .unwrap_or_else(|| "default".to_string()),
    );

    let current_hotkey = get_trigger_hotkey(State::new(state.as_ref())).await?;
    eprintln!();
    let hotkey = loop {
        let raw = prompt_text("Dictation hotkey", &current_hotkey)?;
        match normalize_hotkey_string(&raw) {
            Ok(normalized) => break normalized,
            Err(error) => eprintln!("  {error}"),
        }
    };
    options.hotkey = Some(hotkey.clone());

    let autostart = prompt_yes_no(
        "Start Origin Speak automatically when you sign in?",
        config.auto_start,
    )?;
    options.autostart = Some(autostart);

    eprintln!();
    eprintln!("Setup summary:");
    eprintln!(
        "  Model: {}",
        options.model.as_deref().unwrap_or(recommendation.model)
    );
    eprintln!(
        "  Microphone: {}",
        selected_mic.as_deref().unwrap_or("system default")
    );
    eprintln!("  Dictation hotkey: {hotkey}");
    eprintln!(
        "  Autostart: {}",
        if autostart { "enabled" } else { "disabled" }
    );
    eprintln!("  Models and application data remain local to this user account.");
    if !prompt_yes_no("Apply these settings?", true)? {
        return Ok(None);
    }

    options.interactive = true;
    Ok(Some(options))
}

fn prompt_index(label: &str, count: usize, default: usize) -> Result<usize, String> {
    loop {
        let raw = prompt_text(label, &default.to_string())?;
        match raw.parse::<usize>() {
            Ok(value) if (1..=count).contains(&value) => return Ok(value),
            _ => eprintln!("  Enter a number from 1 to {count}."),
        }
    }
}

fn prompt_yes_no(label: &str, default: bool) -> Result<bool, String> {
    let hint = if default { "Y/n" } else { "y/N" };
    loop {
        eprint!("{label} [{hint}] ");
        io::stderr()
            .flush()
            .map_err(|error| format!("flush setup prompt: {error}"))?;
        let mut answer = String::new();
        io::stdin()
            .read_line(&mut answer)
            .map_err(|error| format!("read setup prompt: {error}"))?;
        match answer.trim().to_ascii_lowercase().as_str() {
            "" => return Ok(default),
            "y" | "yes" => return Ok(true),
            "n" | "no" => return Ok(false),
            _ => eprintln!("  Enter yes or no."),
        }
    }
}

fn prompt_text(label: &str, default: &str) -> Result<String, String> {
    eprint!("{label} [{default}]: ");
    io::stderr()
        .flush()
        .map_err(|error| format!("flush setup prompt: {error}"))?;
    let mut value = String::new();
    io::stdin()
        .read_line(&mut value)
        .map_err(|error| format!("read setup prompt: {error}"))?;
    let value = value.trim();
    Ok(if value.is_empty() {
        default.to_string()
    } else {
        value.to_string()
    })
}

async fn config_command(action: ConfigAction) -> Result<CommandResult, String> {
    let mutating = matches!(action, ConfigAction::Set { .. } | ConfigAction::Reset(_));
    let result = CoreCliExecutor::new().config(&action).await?;
    let mut result = restart_after_mutation(result, mutating)?;
    if mutating {
        result.human_output = Some(render_config_change(&result));
    }
    Ok(result)
}

async fn model_command(action: ModelAction, format: OutputFormat) -> Result<CommandResult, String> {
    let executor = CoreCliExecutor::new();
    let state = executor.state();
    let status_requested = matches!(action, ModelAction::Status);
    let mut result = with_model_download_progress(state, executor.model(&action), format).await?;
    if status_requested {
        let selected = result
            .fields
            .iter()
            .find(|(key, _)| key == "selected")
            .map(|(_, value)| value.clone())
            .unwrap_or_default();
        result = append_runtime_compute_status(result, &selected)?;
    }
    let restart = matches!(action, ModelAction::Select(_))
        && (result_field(&result, "changed") == Some("true")
            || result_field(&result, "installed_now") == Some("true"));
    let mut result = restart_after_mutation(result, restart)?;
    if !matches!(
        action,
        ModelAction::Help | ModelAction::List | ModelAction::Installed
    ) {
        result.human_output = Some(render_model_command_result(&result));
    }
    Ok(result)
}

async fn mic_command(action: MicAction) -> Result<CommandResult, String> {
    let mutating = matches!(action, MicAction::Select(_));
    let result = CoreCliExecutor::new().mic(&action).await?;
    restart_after_mutation(result, mutating)
}

async fn hotkey_command(action: HotkeyAction) -> Result<CommandResult, String> {
    let mutating = matches!(action, HotkeyAction::Set(_));
    let result = CoreCliExecutor::new().hotkey(&action).await?;
    restart_after_mutation(result, mutating)
}

fn restart_after_mutation(result: CommandResult, mutating: bool) -> Result<CommandResult, String> {
    if !mutating {
        return Ok(result);
    }
    let layout = DataLayout::discover()?;
    if !layout.runtime_path.is_file() {
        return Ok(result.field("runtime_restarted", "false (runtime not installed)"));
    }
    match process_control::runtime_status()? {
        RuntimeState::Stopped => Ok(result.field("runtime_restarted", "false")),
        RuntimeState::Running => {
            if let Err(error) =
                process_control::restart_runtime(&layout.runtime_path, COMMAND_TIMEOUT)
            {
                return Err(format!(
                    "{} was saved, but the resident runtime could not restart.\n\nDetails: {error}\n\nThe saved setting will be used the next time the runtime starts. Retry now with:\n  origin restart",
                    result.message
                ));
            }
            Ok(result.field("runtime_restarted", "true"))
        }
    }
}

fn result_field<'a>(result: &'a CommandResult, key: &str) -> Option<&'a str> {
    result
        .fields
        .iter()
        .chain(result.human_fields.iter())
        .find(|(candidate, _)| candidate == key)
        .map(|(_, value)| value.as_str())
}

fn runtime_outcome(result: &CommandResult) -> &'static str {
    match result_field(result, "runtime_restarted") {
        Some("true") => "Runtime restarted successfully.",
        Some("false") => "Runtime is stopped; the new setting will apply when it starts.",
        Some(value) if value.contains("not installed") => {
            "Runtime is not installed; the new setting was saved."
        }
        _ => "",
    }
}

fn render_config_change(result: &CommandResult) -> String {
    let action = if result.code == "config_reset" {
        "Reset to default"
    } else {
        "Saved"
    };
    let mut output = format!(
        "Origin Speak · Configuration\n\nSetting  {}\nValue    {}\nStatus   {action}",
        result_field(result, "key").unwrap_or("unknown"),
        result_field(result, "value").unwrap_or("unknown")
    );
    let runtime = runtime_outcome(result);
    if !runtime.is_empty() {
        output.push_str(&format!("\n\n{runtime}"));
    }
    output
}

fn render_model_command_result(result: &CommandResult) -> String {
    match result.code {
        "model_status" => {
            let mut output = format!(
                "Origin Speak · Model Status\n\nDefault model    {}\nInstalled        {}\nGPU preference   {}",
                result_field(result, "selected").unwrap_or("unknown"),
                yes_no(result_field(result, "downloaded") == Some("true")),
                result_field(result, "gpu_preference").unwrap_or("unknown")
            );
            if let Some(compute) = result_field(result, "compute") {
                output.push_str(&format!("\nActive compute   {compute}"));
            }
            if let Some(accelerator) = result_field(result, "accelerator") {
                output.push_str(&format!("\nAccelerator      {accelerator}"));
            }
            if let Some(reason) = result_field(result, "compute_fallback") {
                output.push_str(&format!("\nFallback reason  {reason}"));
            }
            output.push_str(
                "\n\nChange model      origin model use <model-id>\nChange GPU mode   origin config set use_gpu <true|false>",
            );
            output
        }
        "model_selected" => {
            let installed_now = result_field(result, "installed_now") == Some("true");
            let changed = result_field(result, "changed") == Some("true");
            let current = result_field(result, "selected").unwrap_or("unknown");
            let mut output = format!(
                "Origin Speak · Model Selection\n\nModel       {}\nIdentifier  {}\nStatus      {}",
                result_field(result, "model_name").unwrap_or(current),
                current,
                if installed_now {
                    "Installed and verified successfully"
                } else {
                    "Already installed"
                }
            );
            if changed {
                output.push_str(&format!(
                    "\n\nDefault model changed\n  Previous  {}\n  Current   {current}",
                    result_field(result, "previous").unwrap_or("unknown")
                ));
            } else {
                output.push_str(
                    "\n\nThis model is already the default; no selection change was needed.",
                );
            }
            let runtime = runtime_outcome(result);
            if !runtime.is_empty() {
                output.push_str(&format!("\n\n{runtime}"));
            }
            output.push_str("\n\nOrigin Speak is ready.");
            output
        }
        "model_downloaded" => format!(
            "Origin Speak · Model Installation\n\nModel       {}\nIdentifier  {}\nStatus      {}\n\nSelect this model\n  origin model use {}",
            result_field(result, "model_name").unwrap_or("unknown"),
            result_field(result, "model").unwrap_or("unknown"),
            if result_field(result, "downloaded_now") == Some("true") {
                "Installed and verified successfully"
            } else {
                "Already installed; no download was needed"
            },
            result_field(result, "model").unwrap_or("<model-id>")
        ),
        "model_removed" => {
            let mut output = format!(
                "Origin Speak · Model Removal\n\nRemoved      {}\nIdentifier   {}",
                result_field(result, "model_name").unwrap_or("unknown"),
                result_field(result, "model").unwrap_or("unknown")
            );
            if let Some(bytes) =
                result_field(result, "freed_bytes").and_then(|value| value.parse::<u64>().ok())
            {
                output.push_str(&format!("\nFreed        {}", format_bytes(bytes)));
            }
            output.push_str(&format!(
                "\n\nInstalled models remaining: {}\nDefault model: {}",
                result_field(result, "remaining_count").unwrap_or("unknown"),
                result_field(result, "selected").unwrap_or("unknown")
            ));
            output
        }
        "model_not_installed" => format!(
            "Origin Speak · Model Removal\n\n{} ({}) is not installed, so nothing was removed.\n\nView installed models\n  origin model installed",
            result_field(result, "model_name").unwrap_or("Model"),
            result_field(result, "model").unwrap_or("unknown")
        ),
        _ => result.message.clone(),
    }
}

fn yes_no(value: bool) -> &'static str {
    if value { "Yes" } else { "No" }
}

struct StagedBootstrapRuntime {
    payload: PathBuf,
    staging: PathBuf,
}

async fn stage_bootstrap_release_runtime(
    layout: &DataLayout,
    format: OutputFormat,
) -> Result<StagedBootstrapRuntime, String> {
    #[cfg(any(target_os = "windows", target_os = "macos"))]
    {
        let manager_version = Version::parse(env!("CARGO_PKG_VERSION"))
            .map_err(|error| format!("invalid manager version: {error}"))?;
        let release = update_manager::fetch_release_for_version(&manager_version).await?;
        let staging = layout.update_staging_dir(env!("CARGO_PKG_VERSION"))?;
        let mut progress = TerminalProgress::new(progress_enabled(format));
        let payload =
            update_manager::stage_runtime_payload(&release, &staging, |path, downloaded, total| {
                progress.update(update_payload_label(path), downloaded, total);
            })
            .await;
        match payload {
            Ok(payload) => {
                progress.finish_current();
                return Ok(StagedBootstrapRuntime { payload, staging });
            }
            Err(error) => {
                progress.abort();
                return Err(error);
            }
        }
    }
    #[cfg(not(any(target_os = "windows", target_os = "macos")))]
    {
        let _ = layout;
        Err(
            "release runtime bootstrap is not integrated for this platform; use a sibling development runtime payload"
                .to_string(),
        )
    }
}

async fn status() -> Result<CommandResult, String> {
    let layout = DataLayout::discover()?;
    let previous_update = take_update_result(&layout)?;
    let executor = CoreCliExecutor::new();
    let state = executor.state();
    let config = get_config(State::new(state.as_ref())).await?;
    let transcription = get_transcription_settings(State::new(state.as_ref())).await?;
    let runtime_state = process_control::runtime_status();
    let runtime = runtime_state
        .as_ref()
        .map(|state| runtime_state_label(*state))
        .unwrap_or_else(|error| format!("unsupported: {error}"));
    let native_autostart = autostart_control::status(&layout.runtime_path)
        .map(autostart_label)
        .unwrap_or_else(|error| format!("unsupported: {error}"));
    let manager_path = path_control::status(&layout.manager_root)
        .map(|status| status.on_path.to_string())
        .unwrap_or_else(|error| format!("unknown: {error}"));

    let result = CommandResult::success("status", "Origin Speak status")
        .field("version", env!("CARGO_PKG_VERSION"))
        .field("runtime", runtime)
        .field(
            "runtime_installed",
            layout.runtime_path.is_file().to_string(),
        )
        .field(
            "manager_installed",
            layout.manager_path.is_file().to_string(),
        )
        .field("model", transcription.model)
        .field(
            "gpu_preference",
            if config.use_gpu {
                "enabled"
            } else {
                "disabled"
            },
        )
        .field(
            "microphone",
            config
                .selected_audio_device
                .unwrap_or_else(|| "system default".to_string()),
        )
        .field("autostart_preference", config.auto_start.to_string())
        .field("autostart_native", native_autostart)
        .field("manager_on_path", manager_path)
        .field("data_root", layout.data_root.display().to_string())
        .field("runtime_path", layout.runtime_path.display().to_string())
        .field("manager_path", layout.manager_path.display().to_string());
    let selected_model = result
        .fields
        .iter()
        .find(|(key, _)| key == "model")
        .map(|(_, value)| value.clone())
        .unwrap_or_default();
    let result = append_runtime_compute_status_with_state(result, &selected_model, runtime_state)?;
    Ok(with_previous_update_result(result, previous_update))
}

fn append_runtime_compute_status(
    result: CommandResult,
    selected_model: &str,
) -> Result<CommandResult, String> {
    append_runtime_compute_status_with_state(
        result,
        selected_model,
        process_control::runtime_status(),
    )
}

fn append_runtime_compute_status_with_state(
    mut result: CommandResult,
    selected_model: &str,
    runtime_state: Result<RuntimeState, String>,
) -> Result<CommandResult, String> {
    match runtime_state {
        Ok(RuntimeState::Stopped) => {
            result = result
                .field("compute", "unavailable (runtime stopped)")
                .field("compute_status", "runtime-stopped");
        }
        Ok(RuntimeState::Running) => match runtime_compute_status::read()? {
            Some(status) if status.model == selected_model => {
                result = result
                    .field(
                        "compute",
                        status
                            .compute
                            .unwrap_or_else(|| "pending (model not loaded yet)".to_string()),
                    )
                    .field("compute_status", status.phase);
                if let Some(accelerator) = status.accelerator {
                    result = result.field("accelerator", accelerator);
                }
                if let Some(reason) = status.fallback_reason {
                    result = result.field("compute_fallback", reason);
                }
            }
            Some(_) | None => {
                result = result
                    .field("compute", "pending (model not loaded yet)")
                    .field("compute_status", "pending");
            }
        },
        Err(error) => {
            result = result.field("compute", "unknown").field(
                "compute_status",
                format!("runtime status unavailable: {error}"),
            );
        }
    }
    Ok(result)
}

async fn doctor() -> Result<CommandResult, String> {
    let layout = DataLayout::discover()?;
    let capabilities = SystemCapabilities::detect();
    let executor = CoreCliExecutor::new();
    let state = executor.state();
    let config_check = get_config(State::new(state.as_ref()))
        .await
        .map(|_| "ok".to_string())
        .unwrap_or_else(|error| format!("error: {error}"));
    let microphone_check = get_audio_devices()
        .await
        .map(|devices| format!("ok ({} input device(s))", devices.len()))
        .unwrap_or_else(|error| format!("error: {error}"));
    let runtime = process_control::runtime_status()
        .map(runtime_state_label)
        .unwrap_or_else(|error| format!("unsupported: {error}"));
    let autostart = autostart_control::status(&layout.runtime_path)
        .map(autostart_label)
        .unwrap_or_else(|error| format!("unsupported: {error}"));
    let manager_path = path_control::status(&layout.manager_root)
        .map(|status| status.on_path.to_string())
        .unwrap_or_else(|error| format!("unknown: {error}"));
    let recommendation = capabilities.recommend_model();

    Ok(
        CommandResult::success("doctor", "Origin Speak diagnostics complete")
            .field("os", capabilities.os)
            .field("arch", capabilities.arch)
            .field("logical_cpus", capabilities.logical_cpus.to_string())
            .field(
                "memory_gib",
                capabilities
                    .memory_gib()
                    .map(|value| value.to_string())
                    .unwrap_or_else(|| "unknown".to_string()),
            )
            .field("accelerator", capabilities.accelerator.to_string())
            .field("recommended_model", recommendation.model)
            .field("config", config_check)
            .field("microphone", microphone_check)
            .field("runtime", runtime)
            .field("autostart", autostart)
            .field("manager_on_path", manager_path)
            .field("manager_root", layout.manager_root.display().to_string())
            .field("runtime_payload", path_check(&layout.runtime_path))
            .field("manager_install", path_check(&layout.manager_path))
            .field("data_root", layout.data_root.display().to_string()),
    )
}

async fn autostart(action: ToggleAction) -> Result<CommandResult, String> {
    let layout = DataLayout::discover()?;
    let executor = CoreCliExecutor::new();
    match action {
        ToggleAction::Status => {
            let mut result = executor.autostart(action).await?;
            result = result.field(
                "native",
                autostart_label(autostart_control::status(&layout.runtime_path)?),
            );
            Ok(result)
        }
        ToggleAction::Enable => {
            if !layout.runtime_path.is_file() {
                return Err(format!(
                    "cannot enable autostart before the resident runtime is installed at {}",
                    layout.runtime_path.display()
                ));
            }
            let native = autostart_control::set(&layout.runtime_path, true)?;
            Ok(executor
                .autostart(action)
                .await?
                .field("native", autostart_label(native)))
        }
        ToggleAction::Disable => {
            let native = autostart_control::set(&layout.runtime_path, false)?;
            Ok(executor
                .autostart(action)
                .await?
                .field("native", autostart_label(native)))
        }
    }
}

fn start() -> Result<CommandResult, String> {
    let layout = DataLayout::discover()?;
    #[cfg(target_os = "macos")]
    {
        // Refuse before current Origin Speak autostart/PATH/runtime state is
        // changed. A legacy main-app login item cannot be safely unregistered by
        // the differently branded Origin Speak main bundle.
        legacy_migration::preflight_legacy_macos_bundle()?;
        macos_bundle::recover_stale_transactions(&layout.install_root)?;
    }
    if !layout.runtime_path.is_file() {
        return Err(format!(
            "resident runtime is not installed at {}; run 'origin setup' first",
            layout.runtime_path.display()
        ));
    }
    let started = process_control::start_runtime(&layout.runtime_path)?;
    Ok(CommandResult::success(
        "runtime_started",
        if started {
            "Origin Speak runtime started"
        } else {
            "Origin Speak runtime is already running"
        },
    )
    .field("started_now", started.to_string()))
}

fn stop() -> Result<CommandResult, String> {
    let stopped = process_control::stop_runtime(COMMAND_TIMEOUT)?;
    Ok(CommandResult::success(
        "runtime_stopped",
        if stopped {
            "Origin Speak runtime stopped"
        } else {
            "Origin Speak runtime is already stopped"
        },
    )
    .field("stopped_now", stopped.to_string()))
}

fn restart() -> Result<CommandResult, String> {
    let layout = DataLayout::discover()?;
    #[cfg(target_os = "macos")]
    {
        // Refuse before disabling current Origin Speak autostart, stopping the
        // resident, or removing PATH registration. The cleanup-time preflight is
        // retained as a race fence in case the historical bundle reappears.
        legacy_migration::preflight_legacy_macos_bundle()?;
        macos_bundle::recover_stale_transactions(&layout.install_root)?;
    }
    if !layout.runtime_path.is_file() {
        return Err(format!(
            "resident runtime is not installed at {}; run 'origin setup' first",
            layout.runtime_path.display()
        ));
    }
    process_control::restart_runtime(&layout.runtime_path, COMMAND_TIMEOUT)?;
    Ok(CommandResult::success(
        "runtime_restarted",
        "Origin Speak runtime restarted",
    ))
}

async fn update(action: UpdateAction, format: OutputFormat) -> Result<CommandResult, String> {
    let layout = DataLayout::discover()?;
    let previous_update = take_update_result(&layout)?;
    let Some(available) = update_manager::check_for_update(env!("CARGO_PKG_VERSION"), None).await?
    else {
        return Ok(with_previous_update_result(
            CommandResult::success("up_to_date", "Origin Speak is up to date")
                .field("version", env!("CARGO_PKG_VERSION")),
            previous_update,
        ));
    };

    if matches!(action, UpdateAction::Check) {
        return Ok(with_previous_update_result(
            CommandResult::success(
                "update_available",
                format!("Origin Speak {} is available", available.version),
            )
            .field("current_version", env!("CARGO_PKG_VERSION"))
            .field("available_version", available.version.to_string()),
            previous_update,
        ));
    }

    let staging = layout.update_staging_dir(&available.version.to_string())?;
    let mut progress = TerminalProgress::new(progress_enabled(format));
    let staged = update_manager::stage_update(&available, &staging, |path, downloaded, total| {
        progress.update(update_payload_label(path), downloaded, total);
    })
    .await;
    let staged = match staged {
        Ok(staged) => {
            progress.finish_current();
            staged
        }
        Err(error) => {
            progress.abort();
            return Err(error);
        }
    };
    let result;

    #[cfg(target_os = "windows")]
    {
        if layout.current_manager_is_installed() {
            let runtime_was_running = process_control::runtime_status()? == RuntimeState::Running;
            if runtime_was_running {
                process_control::stop_runtime(COMMAND_TIMEOUT)?;
            }
            append_update_result(
                &layout,
                &UpdateResultRecord {
                    version: staged.version.to_string(),
                    status: "pending".to_string(),
                    error: None,
                },
            )?;
            let helper = match spawn_post_exit_helper(
                "__post-exit-update",
                &[
                    OsString::from(staged.version.to_string()),
                    staged.manager_path.as_os_str().to_os_string(),
                    staged.runtime_path.as_os_str().to_os_string(),
                    OsString::from(staged.manager_sha256.clone()),
                    OsString::from(staged.runtime_sha256.clone()),
                    OsString::from(update_runtime_state_token(runtime_was_running)),
                ],
            ) {
                Ok(helper) => helper,
                Err(error) => {
                    let _ = append_update_result(
                        &layout,
                        &UpdateResultRecord {
                            version: staged.version.to_string(),
                            status: "failed".to_string(),
                            error: Some(format!(
                                "could not start post-exit update helper: {error}"
                            )),
                        },
                    );
                    if runtime_was_running {
                        return match process_control::start_runtime(&layout.runtime_path) {
                            Ok(_) => Err(format!(
                                "could not start the post-exit update helper; the existing runtime was restarted: {error}"
                            )),
                            Err(restart_error) => Err(format!(
                                "could not start the post-exit update helper ({error}); restarting the existing runtime also failed ({restart_error})"
                            )),
                        };
                    }
                    return Err(error);
                }
            };
            result = CommandResult::success(
                "update_scheduled",
                format!(
                    "Origin Speak {} downloaded and verified; installation will complete after this command exits",
                    staged.version
                ),
            )
                .field("manager_payload", staged.manager_path.display().to_string())
                .field("runtime_payload", staged.runtime_path.display().to_string())
                .field("post_exit_swap", "pending")
                .field("helper", helper.display().to_string())
                .field(
                    "runtime_restart",
                    if runtime_was_running {
                        "pending"
                    } else {
                        "preserve stopped"
                    },
                );
        } else {
            result = CommandResult::success(
                "update_downloaded",
                format!(
                    "Origin Speak {} downloaded and verified; automatic replacement was not scheduled",
                    staged.version
                ),
            )
            .field("manager_payload", staged.manager_path.display().to_string())
            .field("runtime_payload", staged.runtime_path.display().to_string())
            .field("post_exit_swap", "not scheduled")
            .field(
                "reason",
                "this manager is not running from the app-owned install path",
            );
        }
    }
    #[cfg(target_os = "macos")]
    {
        let runtime_was_running = process_control::runtime_status()? == RuntimeState::Running;
        if runtime_was_running {
            process_control::stop_runtime(COMMAND_TIMEOUT)?;
        }

        if let Err(error) = update_manager::verify_staged_payload(
            &staging,
            &staged.version,
            &staged.runtime_path,
            &staged.runtime_sha256,
        ) {
            return Err(macos_update_failure_after_stop(
                &layout,
                runtime_was_running,
                error,
            ));
        }
        let mut runtime_swap = match bootstrap::transactional_install_macos_runtime_bundle(
            &staged.runtime_path,
            &layout.install_root,
        ) {
            Ok(swap) => swap,
            Err(error) => {
                return Err(macos_update_failure_after_stop(
                    &layout,
                    runtime_was_running,
                    error,
                ));
            }
        };

        if let Err(error) = update_manager::verify_staged_payload(
            &staging,
            &staged.version,
            &staged.runtime_path,
            &staged.runtime_sha256,
        ) {
            let rollback = runtime_swap.rollback();
            return Err(macos_update_failure_with_rollback(
                &layout,
                runtime_was_running,
                format!("macOS runtime archive changed during apply: {error}"),
                rollback,
            ));
        }

        if let Err(error) = update_manager::verify_staged_payload(
            &staging,
            &staged.version,
            &staged.manager_path,
            &staged.manager_sha256,
        ) {
            let rollback = runtime_swap.rollback();
            return Err(macos_update_failure_with_rollback(
                &layout,
                runtime_was_running,
                error,
                rollback,
            ));
        }
        let mut manager_swap =
            match bootstrap::transactional_replace(&staged.manager_path, &layout.manager_path) {
                Ok(swap) => swap,
                Err(error) => {
                    let rollback = runtime_swap.rollback();
                    return Err(macos_update_failure_with_rollback(
                        &layout,
                        runtime_was_running,
                        format!("manager update failed: {error}"),
                        rollback,
                    ));
                }
            };

        if let Err(error) = update_manager::verify_regular_file_sha256(
            &layout.manager_path,
            &staged.manager_sha256,
            "installed manager",
        ) {
            let rollback = rollback_macos_update_pair(&mut manager_swap, &mut runtime_swap);
            return Err(macos_update_failure_with_rollback(
                &layout,
                runtime_was_running,
                error,
                rollback,
            ));
        }

        if runtime_was_running {
            if let Err(error) = process_control::start_runtime(&layout.runtime_path) {
                let stop_replacement = ensure_runtime_stopped();
                let rollback = rollback_macos_update_pair(&mut manager_swap, &mut runtime_swap);
                if let Err(stop_error) = stop_replacement {
                    return Err(match rollback {
                        Ok(()) => format!(
                            "updated macOS runtime failed readiness ({error}); stopping the replacement runtime also failed ({stop_error}); previous files were restored"
                        ),
                        Err(rollback_error) => format!(
                            "updated macOS runtime failed readiness ({error}); stopping the replacement runtime failed ({stop_error}); rollback also failed ({rollback_error})"
                        ),
                    });
                }
                return Err(macos_update_failure_with_rollback(
                    &layout,
                    true,
                    format!("updated macOS runtime failed readiness: {error}"),
                    rollback,
                ));
            }
        } else if let Err(error) = ensure_runtime_stopped() {
            let rollback = rollback_macos_update_pair(&mut manager_swap, &mut runtime_swap);
            return Err(macos_update_failure_with_rollback(
                &layout,
                false,
                format!("updated macOS runtime did not preserve stopped state: {error}"),
                rollback,
            ));
        }

        manager_swap.finalize()?;
        runtime_swap.finalize()?;
        fs::remove_dir_all(&staging).map_err(|error| {
            format!(
                "remove completed macOS update staging {}: {error}",
                staging.display()
            )
        })?;
        result = CommandResult::success(
            "update_applied",
            format!("Origin Speak {} update installed", staged.version),
        )
        .field("manager_payload", staged.manager_path.display().to_string())
        .field("runtime_payload", staged.runtime_path.display().to_string())
        .field("post_exit_swap", "not required on macOS")
        .field(
            "runtime_restart",
            if runtime_was_running {
                "complete"
            } else {
                "preserved stopped"
            },
        );
    }
    #[cfg(not(any(target_os = "windows", target_os = "macos")))]
    {
        result = CommandResult::success(
            "update_ready",
            format!(
                "Origin Speak {} downloaded and verified; installing update",
                staged.version
            ),
        )
        .field("manager_payload", staged.manager_path.display().to_string())
        .field("runtime_payload", staged.runtime_path.display().to_string())
        .field(
            "post_exit_swap",
            "unsupported on this platform; verified payloads remain staged",
        );
    }
    Ok(with_previous_update_result(result, previous_update))
}

#[cfg(target_os = "macos")]
fn rollback_macos_update_pair(
    manager: &mut bootstrap::TransactionalSwap,
    runtime: &mut macos_bundle::BundleSwap,
) -> Result<(), String> {
    let manager_result = manager.rollback();
    let runtime_result = runtime.rollback();
    match (manager_result, runtime_result) {
        (Ok(()), Ok(())) => Ok(()),
        (Err(error), Ok(())) => Err(format!("manager rollback failed: {error}")),
        (Ok(()), Err(error)) => Err(format!("runtime bundle rollback failed: {error}")),
        (Err(manager_error), Err(runtime_error)) => Err(format!(
            "manager rollback failed ({manager_error}); runtime bundle rollback failed ({runtime_error})"
        )),
    }
}

#[cfg(target_os = "macos")]
fn macos_update_failure_after_stop(
    layout: &DataLayout,
    runtime_was_running: bool,
    error: String,
) -> String {
    if !runtime_was_running {
        return error;
    }
    match process_control::start_runtime(&layout.runtime_path) {
        Ok(_) => format!("{error}; previous runtime restarted"),
        Err(restart_error) => {
            format!("{error}; restarting the previous runtime also failed ({restart_error})")
        }
    }
}

#[cfg(target_os = "macos")]
fn macos_update_failure_with_rollback(
    layout: &DataLayout,
    runtime_was_running: bool,
    error: String,
    rollback: Result<(), String>,
) -> String {
    match rollback {
        Ok(()) => macos_update_failure_after_stop(layout, runtime_was_running, error),
        Err(rollback_error) => format!("{error}; rollback failed ({rollback_error})"),
    }
}

#[cfg(target_os = "macos")]
fn installed_macos_bundle_matches_manager(layout: &DataLayout) -> bool {
    let expected = env!("CARGO_PKG_VERSION")
        .split(|character| character == '-' || character == '+')
        .next()
        .unwrap_or(env!("CARGO_PKG_VERSION"));
    macos_bundle::verify_bundle(&layout.install_root).is_ok()
        && macos_bundle::bundle_version(&layout.install_root)
            .is_ok_and(|version| version == expected)
}

fn uninstall(options: UninstallOptions, format: OutputFormat) -> Result<CommandResult, String> {
    let layout = DataLayout::discover()?;
    cli::uninstall_requires_confirmation(
        options,
        management::non_interactive_terminal() || matches!(format, OutputFormat::Json),
    )
    .map_err(|error| error.message)?;

    if options.dry_run {
        let plan = layout.uninstall_plan(options.keep_data)?;
        return Ok(CommandResult::success(
            "uninstall_dry_run",
            "Uninstall dry run; no files were changed",
        )
        .field("manager", plan.manager_path.display().to_string())
        .field("runtime", plan.runtime_path.display().to_string())
        .field(
            "data_root",
            plan.data_root
                .map(|path| path.display().to_string())
                .unwrap_or_else(|| "kept".to_string()),
        )
        .field(
            "local_data_root",
            plan.local_data_root
                .map(|path| path.display().to_string())
                .unwrap_or_else(|| {
                    if options.keep_data {
                        "kept".to_string()
                    } else {
                        "same as data_root".to_string()
                    }
                }),
        )
        .field("model_roots", join_paths(&plan.model_roots))
        .field("stale_update_paths", join_paths(&plan.stale_update_paths))
        .field("install_root", plan.install_root.display().to_string())
        .field("manager_root", plan.manager_root.display().to_string()));
    }

    if !options.yes && !confirm_uninstall(options.keep_data)? {
        return Ok(CommandResult::success(
            "uninstall_cancelled",
            "Uninstall cancelled",
        ));
    }

    #[cfg(target_os = "macos")]
    {
        // Refuse before current Origin Speak autostart, runtime, or PATH state
        // is changed. cleanup_after_replacement() repeats the same preflight as
        // a race fence immediately before legacy compatibility cleanup.
        legacy_migration::preflight_legacy_macos_bundle()?;
        macos_bundle::recover_stale_transactions(&layout.install_root)?;
    }

    // The running manager cannot delete itself on Windows. Prepare and start a
    // helper before any destructive work, but gate actual manager deletion on a
    // one-time commit marker written only after every irreversible uninstall
    // step below has succeeded. If cleanup fails, the helper times out and leaves
    // the manager in place so the user can retry.
    #[cfg(target_os = "windows")]
    let armed_uninstall_helper = if layout.current_manager_is_installed() {
        Some(arm_post_exit_uninstall_helper()?)
    } else {
        None
    };

    // Remove the OS login hook first so a partially completed uninstall cannot
    // launch a missing runtime on the next sign-in.
    let autostart_cleanup = autostart_control::disable_for_uninstall(&layout.runtime_path)?;
    process_control::stop_runtime(COMMAND_TIMEOUT)?;
    let path_registration = path_control::remove(&layout.manager_root)?;
    let current_exe = std::env::current_exe()
        .map_err(|error| format!("resolve current Origin Speak manager: {error}"))?;
    let legacy = legacy_migration::cleanup_after_replacement(&current_exe)?;

    if !options.keep_data {
        management::purge_user_data(&layout)?;
    }
    let runtime_removed = management::remove_installed_runtime(&layout)?;

    let mut result =
        CommandResult::success("uninstall_complete", "Origin Speak uninstall completed")
            .field("runtime_removed", runtime_removed.to_string())
            .field("data_removed", (!options.keep_data).to_string())
            .field("legacy_artifacts_removed", legacy.removed.len().to_string())
            .field("legacy_cleanup_warnings", legacy.warnings.len().to_string())
            .field("autostart_cleanup", autostart_cleanup.detail)
            .field(
                "manager_path_registration_removed",
                path_registration.changed.to_string(),
            );

    #[cfg(target_os = "windows")]
    {
        if let Some(armed) = armed_uninstall_helper {
            commit_post_exit_uninstall(&armed.commit_path)?;
            result = result
                .field("manager_removal", "pending until this command exits")
                .field("helper", armed.helper_path.display().to_string());
        } else {
            let removed = management::remove_installed_manager(&layout)?;
            management::remove_empty_install_root(&layout)?;
            result = result.field("manager_removed", removed.to_string());
        }
    }
    #[cfg(not(target_os = "windows"))]
    {
        let removed = management::remove_installed_manager(&layout)?;
        management::remove_empty_install_root(&layout)?;
        result = result.field("manager_removed", removed.to_string());
    }

    Ok(result)
}

fn confirm_uninstall(keep_data: bool) -> Result<bool, String> {
    let action = if keep_data {
        "remove the installed Origin Speak manager/runtime and keep user data"
    } else {
        "remove Origin Speak, downloaded models, configuration, and runtime data"
    };
    eprint!("This will {action}. Continue? [y/N] ");
    io::stderr()
        .flush()
        .map_err(|error| format!("flush confirmation prompt: {error}"))?;
    let mut answer = String::new();
    io::stdin()
        .read_line(&mut answer)
        .map_err(|error| format!("read confirmation: {error}"))?;
    Ok(matches!(
        answer.trim().to_ascii_lowercase().as_str(),
        "y" | "yes"
    ))
}

fn run_internal_helper(args: &[OsString]) -> Option<i32> {
    let mode = args.get(1)?.to_str()?;
    match mode {
        "__post-exit-uninstall" => Some(match post_exit_uninstall(&args[2..]) {
            Ok(()) => 0,
            Err(error) => {
                eprintln!("post-exit uninstall failed: {error}");
                1
            }
        }),
        "__post-exit-update" => Some(match post_exit_update(&args[2..]) {
            Ok(()) => 0,
            Err(error) => {
                eprintln!("post-exit update failed: {error}");
                1
            }
        }),
        _ => None,
    }
}

#[cfg(target_os = "windows")]
fn post_exit_uninstall(args: &[OsString]) -> Result<(), String> {
    let result = post_exit_uninstall_inner(args);
    // No manager remains to sweep this helper later. Delete-on-reboot only
    // succeeds with admin rights; otherwise the copy stays in the temp
    // directory for Windows' own temp cleanup, which is not an uninstall error.
    let _ = schedule_current_helper_delete();
    result
}

#[cfg(target_os = "windows")]
fn post_exit_uninstall_inner(args: &[OsString]) -> Result<(), String> {
    if args.len() != 1 {
        return Err("invalid post-exit uninstall helper arguments".to_string());
    }
    let commit_path = PathBuf::from(&args[0]);
    wait_for_uninstall_commit(&commit_path, UNINSTALL_COMMIT_TIMEOUT)?;
    fs::remove_file(&commit_path).map_err(|error| {
        format!(
            "remove uninstall commit marker {}: {error}",
            commit_path.display()
        )
    })?;
    let layout = DataLayout::discover()?;
    retry_until(HELPER_RETRY_TIMEOUT, || {
        management::remove_installed_manager(&layout).map(|_| ())
    })?;
    management::remove_empty_install_root(&layout)?;
    remove_stale_post_exit_helpers(
        &std::env::temp_dir(),
        std::env::current_exe().ok().as_deref(),
        STALE_HELPER_MIN_AGE,
    );
    Ok(())
}

#[cfg(not(target_os = "windows"))]
fn post_exit_uninstall(_: &[OsString]) -> Result<(), String> {
    Err("post-exit uninstall helper is only required on Windows".to_string())
}

#[cfg(target_os = "windows")]
fn arm_post_exit_uninstall_helper() -> Result<ArmedUninstallHelper, String> {
    use std::os::windows::process::CommandExt;
    const CREATE_NO_WINDOW: u32 = 0x0800_0000;

    let current = std::env::current_exe()
        .map_err(|error| format!("resolve current manager for uninstall helper: {error}"))?;
    let token = uuid::Uuid::new_v4();
    let temp = std::env::temp_dir();
    let helper_path = temp.join(format!("origin-speak-uninstall-helper-{token}.exe"));
    let commit_path = temp.join(format!("origin-speak-uninstall-commit-{token}.marker"));
    if helper_path.exists() || commit_path.exists() {
        return Err("refusing colliding uninstall helper paths".to_string());
    }
    fs::copy(&current, &helper_path).map_err(|error| {
        format!(
            "create post-exit uninstall helper {}: {error}",
            helper_path.display()
        )
    })?;
    let spawn = ProcessCommand::new(&helper_path)
        .arg("__post-exit-uninstall")
        .arg(&commit_path)
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .creation_flags(CREATE_NO_WINDOW)
        .spawn();
    if let Err(error) = spawn {
        let _ = fs::remove_file(&helper_path);
        return Err(format!("start post-exit uninstall helper: {error}"));
    }
    Ok(ArmedUninstallHelper {
        helper_path,
        commit_path,
    })
}

#[cfg(target_os = "windows")]
fn commit_post_exit_uninstall(path: &Path) -> Result<(), String> {
    let mut options = fs::OpenOptions::new();
    options.write(true).create_new(true);
    let mut file = options
        .open(path)
        .map_err(|error| format!("create uninstall commit marker {}: {error}", path.display()))?;
    file.write_all(b"commit\n")
        .map_err(|error| format!("write uninstall commit marker: {error}"))?;
    file.sync_all()
        .map_err(|error| format!("sync uninstall commit marker: {error}"))
}

#[cfg(target_os = "windows")]
fn wait_for_uninstall_commit(path: &Path, timeout: Duration) -> Result<(), String> {
    let started = Instant::now();
    while started.elapsed() < timeout {
        match fs::symlink_metadata(path) {
            Ok(metadata) => {
                if !metadata.is_file() || metadata.file_type().is_symlink() {
                    return Err(format!(
                        "uninstall commit marker is not a regular file: {}",
                        path.display()
                    ));
                }
                let contents = fs::read(path)
                    .map_err(|error| format!("read uninstall commit marker: {error}"))?;
                if contents == b"commit\n" {
                    return Ok(());
                }
                return Err("uninstall commit marker contents are invalid".to_string());
            }
            Err(error) if error.kind() == io::ErrorKind::NotFound => {
                std::thread::sleep(Duration::from_millis(100));
            }
            Err(error) => {
                return Err(format!(
                    "inspect uninstall commit marker {}: {error}",
                    path.display()
                ));
            }
        }
    }
    Err("uninstall helper timed out waiting for commit".to_string())
}

#[cfg(target_os = "windows")]
fn post_exit_update(args: &[OsString]) -> Result<(), String> {
    let version = args
        .first()
        .and_then(|value| value.to_str())
        .unwrap_or("unknown")
        .to_string();
    // This helper's own copy in the temp directory is removed by the next
    // manager run; see remove_stale_post_exit_helpers.
    let result = post_exit_update_inner(args);
    let record = UpdateResultRecord {
        version,
        status: if result.is_ok() {
            "completed"
        } else {
            "failed"
        }
        .to_string(),
        error: result.as_ref().err().cloned(),
    };
    let record_result =
        DataLayout::discover().and_then(|layout| append_update_result(&layout, &record));
    match (result, record_result) {
        (Ok(()), Ok(())) => Ok(()),
        (Err(error), Ok(())) => Err(error),
        (Ok(()), Err(record_error)) => Err(format!(
            "update completed but recording its post-exit result failed: {record_error}"
        )),
        (Err(error), Err(record_error)) => Err(format!(
            "{error}; recording the post-exit update result also failed: {record_error}"
        )),
    }
}

#[cfg(target_os = "windows")]
fn post_exit_update_inner(args: &[OsString]) -> Result<(), String> {
    if args.len() != 6 {
        return Err("invalid post-exit update helper arguments".to_string());
    }
    let version_text = args[0]
        .to_str()
        .ok_or_else(|| "update version is not valid UTF-8".to_string())?;
    let version = Version::parse(version_text)
        .map_err(|error| format!("update version is invalid: {error}"))?;
    let staged_manager = PathBuf::from(&args[1]);
    let staged_runtime = PathBuf::from(&args[2]);
    let manager_sha256 = args[3]
        .to_str()
        .ok_or_else(|| "manager SHA-256 is not valid UTF-8".to_string())?;
    let runtime_sha256 = args[4]
        .to_str()
        .ok_or_else(|| "runtime SHA-256 is not valid UTF-8".to_string())?;
    let runtime_was_running = parse_update_runtime_state(&args[5])?;
    let layout = DataLayout::discover()?;
    let staging = layout.update_staging_dir(version_text)?;

    let mut runtime_swap = match retry_verified_replace(
        &staging,
        &version,
        &staged_runtime,
        runtime_sha256,
        &layout.runtime_path,
    ) {
        Ok(swap) => swap,
        Err(error) => {
            return Err(windows_update_failure_after_stop(
                &layout,
                runtime_was_running,
                error,
            ));
        }
    };
    if let Err(error) = update_manager::verify_regular_file_sha256(
        &layout.runtime_path,
        runtime_sha256,
        "installed runtime",
    ) {
        let rollback = runtime_swap.rollback();
        return Err(windows_update_failure_with_rollback(
            &layout,
            runtime_was_running,
            error,
            rollback,
        ));
    }

    let mut manager_swap = match retry_verified_replace(
        &staging,
        &version,
        &staged_manager,
        manager_sha256,
        &layout.manager_path,
    ) {
        Ok(swap) => swap,
        Err(error) => {
            let rollback = runtime_swap.rollback();
            return Err(windows_update_failure_with_rollback(
                &layout,
                runtime_was_running,
                format!("manager update failed: {error}"),
                rollback,
            ));
        }
    };
    if let Err(error) = update_manager::verify_regular_file_sha256(
        &layout.manager_path,
        manager_sha256,
        "installed manager",
    ) {
        let rollback = rollback_update_pair(&mut manager_swap, &mut runtime_swap);
        return Err(windows_update_failure_with_rollback(
            &layout,
            runtime_was_running,
            error,
            rollback,
        ));
    }

    if runtime_was_running {
        let mut child = match bootstrap::spawn_runtime_silent(&layout.runtime_path, &[]) {
            Ok(child) => child,
            Err(error) => {
                let rollback = rollback_update_pair(&mut manager_swap, &mut runtime_swap);
                return Err(windows_update_failure_with_rollback(
                    &layout,
                    true,
                    format!("updated runtime could not be started: {error}"),
                    rollback,
                ));
            }
        };

        if let Err(error) = wait_for_runtime_ready(&mut child, Duration::from_secs(6)) {
            let _ = child.kill();
            let _ = child.wait();
            let rollback = rollback_update_pair(&mut manager_swap, &mut runtime_swap);
            return Err(windows_update_failure_with_rollback(
                &layout,
                true,
                format!("updated runtime failed its readiness check: {error}"),
                rollback,
            ));
        }
    } else if let Err(error) = ensure_runtime_stopped() {
        let rollback = rollback_update_pair(&mut manager_swap, &mut runtime_swap);
        return Err(windows_update_failure_with_rollback(
            &layout,
            false,
            format!("updated runtime did not preserve stopped state: {error}"),
            rollback,
        ));
    }

    manager_swap.finalize()?;
    runtime_swap.finalize()?;
    fs::remove_dir_all(&staging).map_err(|error| {
        format!(
            "remove completed update staging {}: {error}",
            staging.display()
        )
    })?;
    Ok(())
}

#[cfg(target_os = "windows")]
fn retry_verified_replace(
    staging: &Path,
    version: &Version,
    source: &Path,
    expected_sha256: &str,
    destination: &Path,
) -> Result<bootstrap::TransactionalSwap, String> {
    let started = Instant::now();
    loop {
        update_manager::verify_staged_payload(staging, version, source, expected_sha256)?;
        match bootstrap::transactional_replace(source, destination) {
            Ok(swap) => return Ok(swap),
            Err(error) if started.elapsed() < HELPER_RETRY_TIMEOUT => {
                let _ = error;
                std::thread::sleep(Duration::from_millis(100));
            }
            Err(error) => return Err(error),
        }
    }
}

#[cfg(target_os = "windows")]
fn windows_update_failure_after_stop(
    layout: &DataLayout,
    runtime_was_running: bool,
    error: String,
) -> String {
    if !runtime_was_running {
        return error;
    }
    match process_control::start_runtime(&layout.runtime_path) {
        Ok(_) => format!("{error}; previous runtime restarted"),
        Err(restart_error) => {
            format!("{error}; restarting the previous runtime also failed ({restart_error})")
        }
    }
}

#[cfg(target_os = "windows")]
fn windows_update_failure_with_rollback(
    layout: &DataLayout,
    runtime_was_running: bool,
    error: String,
    rollback: Result<(), String>,
) -> String {
    match rollback {
        Ok(()) => windows_update_failure_after_stop(layout, runtime_was_running, error),
        Err(rollback_error) => format!("{error}; rollback failed ({rollback_error})"),
    }
}

#[cfg(target_os = "windows")]
fn rollback_update_pair(
    manager: &mut bootstrap::TransactionalSwap,
    runtime: &mut bootstrap::TransactionalSwap,
) -> Result<(), String> {
    let manager_result = manager.rollback();
    let runtime_result = runtime.rollback();
    match (manager_result, runtime_result) {
        (Ok(()), Ok(())) => Ok(()),
        (Err(error), Ok(())) => Err(format!("manager rollback failed: {error}")),
        (Ok(()), Err(error)) => Err(format!("runtime rollback failed: {error}")),
        (Err(manager_error), Err(runtime_error)) => Err(format!(
            "manager rollback failed ({manager_error}); runtime rollback failed ({runtime_error})"
        )),
    }
}

#[cfg(target_os = "windows")]
fn wait_for_runtime_ready(
    child: &mut std::process::Child,
    timeout: Duration,
) -> Result<(), String> {
    let started = Instant::now();
    loop {
        if process_control::runtime_ready()? {
            return Ok(());
        }
        if let Some(status) = child
            .try_wait()
            .map_err(|error| format!("inspect updated runtime process: {error}"))?
        {
            return Err(format!(
                "updated runtime exited before becoming ready ({status})"
            ));
        }
        if started.elapsed() >= timeout {
            return Err(format!(
                "updated runtime did not become ready within {} ms",
                timeout.as_millis()
            ));
        }
        std::thread::sleep(Duration::from_millis(50));
    }
}

#[cfg(not(target_os = "windows"))]
fn post_exit_update(_: &[OsString]) -> Result<(), String> {
    Err("post-exit update swap is not integrated on this platform".to_string())
}

#[cfg(target_os = "windows")]
fn spawn_post_exit_helper(mode: &str, args: &[OsString]) -> Result<PathBuf, String> {
    use std::os::windows::process::CommandExt;
    const CREATE_NO_WINDOW: u32 = 0x0800_0000;

    let current = std::env::current_exe()
        .map_err(|error| format!("resolve current manager for helper: {error}"))?;
    let helper = create_unique_post_exit_helper(&current, mode, &std::env::temp_dir())?;
    let mut command = ProcessCommand::new(&helper);
    command
        .arg(mode)
        .args(args)
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .creation_flags(CREATE_NO_WINDOW);
    if let Err(error) = command.spawn() {
        let _ = fs::remove_file(&helper);
        return Err(format!("start post-exit helper: {error}"));
    }
    Ok(helper)
}

#[cfg(target_os = "windows")]
fn create_unique_post_exit_helper(
    current: &Path,
    mode: &str,
    temp_root: &Path,
) -> Result<PathBuf, String> {
    let kind = if mode.contains("uninstall") {
        "uninstall"
    } else {
        "update"
    };
    for _ in 0..8 {
        let helper = temp_root.join(format!(
            "origin-speak-{kind}-helper-{}.exe",
            uuid::Uuid::new_v4()
        ));
        let mut options = fs::OpenOptions::new();
        options.write(true).create_new(true);
        let mut output = match options.open(&helper) {
            Ok(file) => file,
            Err(error) if error.kind() == io::ErrorKind::AlreadyExists => continue,
            Err(error) => {
                return Err(format!(
                    "create exclusive post-exit helper {}: {error}",
                    helper.display()
                ));
            }
        };
        let copy_result = (|| {
            let mut input = fs::File::open(current)
                .map_err(|error| format!("open manager helper source: {error}"))?;
            io::copy(&mut input, &mut output)
                .map_err(|error| format!("copy post-exit helper: {error}"))?;
            output
                .flush()
                .map_err(|error| format!("flush post-exit helper: {error}"))?;
            output
                .sync_all()
                .map_err(|error| format!("sync post-exit helper: {error}"))?;
            Ok::<(), String>(())
        })();
        drop(output);
        if let Err(error) = copy_result {
            let _ = fs::remove_file(&helper);
            return Err(error);
        }
        let permissions = fs::metadata(current)
            .map_err(|error| format!("read manager helper permissions: {error}"))?
            .permissions();
        if let Err(error) = fs::set_permissions(&helper, permissions) {
            let _ = fs::remove_file(&helper);
            return Err(format!("set post-exit helper permissions: {error}"));
        }
        return Ok(helper);
    }
    Err("could not allocate a unique post-exit helper path".to_string())
}

#[cfg(not(target_os = "windows"))]
fn spawn_post_exit_helper(_: &str, _: &[OsString]) -> Result<PathBuf, String> {
    Err("post-exit helpers are not required on this platform".to_string())
}

fn retry_until<T>(
    timeout: Duration,
    mut operation: impl FnMut() -> Result<T, String>,
) -> Result<T, String> {
    let started = Instant::now();
    loop {
        match operation() {
            Ok(value) => return Ok(value),
            Err(error) if started.elapsed() < timeout => {
                let _ = error;
                std::thread::sleep(Duration::from_millis(100));
            }
            Err(error) => return Err(error),
        }
    }
}

#[cfg(target_os = "windows")]
fn schedule_current_helper_delete() -> Result<(), String> {
    use std::os::windows::ffi::OsStrExt;
    use std::ptr;

    const MOVEFILE_DELAY_UNTIL_REBOOT: u32 = 0x0000_0004;
    #[link(name = "kernel32")]
    unsafe extern "system" {
        fn MoveFileExW(existing: *const u16, replacement: *const u16, flags: u32) -> i32;
    }

    let current = std::env::current_exe()
        .map_err(|error| format!("resolve helper path for cleanup: {error}"))?;
    let wide = current
        .as_os_str()
        .encode_wide()
        .chain(std::iter::once(0))
        .collect::<Vec<_>>();
    let ok = unsafe { MoveFileExW(wide.as_ptr(), ptr::null(), MOVEFILE_DELAY_UNTIL_REBOOT) };
    if ok == 0 {
        return Err(format!(
            "schedule helper cleanup: {}",
            std::io::Error::last_os_error()
        ));
    }
    Ok(())
}

#[cfg(target_os = "windows")]
fn is_post_exit_helper_name(name: &str) -> bool {
    [
        "origin-speak-update-helper-",
        "origin-speak-uninstall-helper-",
    ]
    .iter()
    .any(|prefix| {
        name.strip_prefix(prefix)
            .and_then(|rest| rest.strip_suffix(".exe"))
            .is_some_and(|id| uuid::Uuid::parse_str(id).is_ok())
    })
}

/// Removes post-exit helper copies left in `temp_root` by earlier runs. A
/// helper cannot delete its own running image, and delete-on-reboot needs
/// admin rights, so finished helpers are swept here instead. Windows refuses
/// to delete a helper that is still running, and `min_age` skips one whose
/// copy is still being written.
#[cfg(target_os = "windows")]
fn remove_stale_post_exit_helpers(
    temp_root: &Path,
    keep: Option<&Path>,
    min_age: Duration,
) -> usize {
    let Ok(entries) = fs::read_dir(temp_root) else {
        return 0;
    };
    let mut removed = 0;
    for entry in entries.flatten() {
        let path = entry.path();
        if keep.is_some_and(|keep| keep == path) {
            continue;
        }
        if !path
            .file_name()
            .and_then(|name| name.to_str())
            .is_some_and(is_post_exit_helper_name)
        {
            continue;
        }
        let Ok(metadata) = fs::symlink_metadata(&path) else {
            continue;
        };
        if !metadata.is_file() {
            continue;
        }
        let old_enough = metadata
            .modified()
            .ok()
            .and_then(|modified| modified.elapsed().ok())
            .is_some_and(|age| age >= min_age);
        if old_enough && fs::remove_file(&path).is_ok() {
            removed += 1;
        }
    }
    removed
}

#[cfg(not(target_os = "windows"))]
fn schedule_current_helper_delete() -> Result<(), String> {
    let current = std::env::current_exe()
        .map_err(|error| format!("resolve helper path for cleanup: {error}"))?;
    fs::remove_file(current).map_err(|error| format!("remove helper: {error}"))
}

fn ensure_runtime_stopped() -> Result<(), String> {
    if process_control::runtime_status()? == RuntimeState::Running {
        process_control::stop_runtime(COMMAND_TIMEOUT)?;
    }
    match process_control::runtime_status()? {
        RuntimeState::Stopped => Ok(()),
        RuntimeState::Running => Err("runtime is still running after stop request".to_string()),
    }
}

fn update_runtime_state_token(runtime_was_running: bool) -> &'static str {
    if runtime_was_running {
        "running"
    } else {
        "stopped"
    }
}

fn parse_update_runtime_state(value: &std::ffi::OsStr) -> Result<bool, String> {
    match value.to_str() {
        Some("running") => Ok(true),
        Some("stopped") => Ok(false),
        _ => Err("invalid prior runtime state in update helper arguments".to_string()),
    }
}

fn runtime_state_label(state: RuntimeState) -> String {
    match state {
        RuntimeState::Running => "running".to_string(),
        RuntimeState::Stopped => "stopped".to_string(),
    }
}

fn autostart_label(status: AutoStartStatus) -> String {
    match status {
        AutoStartStatus::Disabled => "disabled".to_string(),
        AutoStartStatus::Enabled => "enabled".to_string(),
        #[cfg(not(target_os = "windows"))]
        AutoStartStatus::RequiresApproval => "requires approval".to_string(),
        AutoStartStatus::Mismatched => "mismatched registration".to_string(),
    }
}

fn path_check(path: &Path) -> String {
    if path.is_file() {
        "present".to_string()
    } else {
        format!("missing ({})", path.display())
    }
}

fn join_paths(paths: &[PathBuf]) -> String {
    if paths.is_empty() {
        "none".to_string()
    } else {
        paths
            .iter()
            .map(|path| path.display().to_string())
            .collect::<Vec<_>>()
            .join(", ")
    }
}

fn same_path(left: &Path, right: &Path) -> bool {
    match (fs::canonicalize(left), fs::canonicalize(right)) {
        (Ok(left), Ok(right)) => left == right,
        _ => left == right,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn selection_result(installed_now: bool, changed: bool) -> CommandResult {
        CommandResult::success("model_selected", "Model large-v3 is now the default")
            .field("selected", "large-v3")
            .field("installed_now", installed_now.to_string())
            .field("previous", if changed { "base.en" } else { "large-v3" })
            .field("changed", changed.to_string())
            .field("model_name", "Large v3 Multilingual")
            .field("runtime_restarted", "true")
    }

    #[test]
    fn terminal_progress_reports_percentage_speed_and_eta() {
        let line = progress_line(
            "Model large-v3",
            512 * 1024 * 1024,
            Some(1024 * 1024 * 1024),
            16.0 * 1024.0 * 1024.0,
            Duration::from_secs(1),
        );
        assert!(line.contains("Model large-v3"));
        assert!(line.contains("50%"));
        assert!(line.contains("512.0 MiB / 1.00 GiB"));
        assert!(line.contains("16.0 MiB/s"));
        assert!(line.contains("ETA 32s"));
    }

    #[test]
    fn terminal_progress_formats_long_eta_compactly() {
        assert_eq!(format_eta(5), "5s");
        assert_eq!(format_eta(65), "1m 05s");
    }

    #[test]
    fn successful_install_and_switch_reports_each_completed_stage() {
        let output = render_model_command_result(&selection_result(true, true));
        assert!(output.contains("Installed and verified successfully"));
        assert!(output.contains("Previous  base.en"));
        assert!(output.contains("Current   large-v3"));
        assert!(output.contains("Runtime restarted successfully"));
        assert!(output.contains("Origin Speak is ready"));
    }

    #[test]
    fn already_installed_default_selection_is_a_clear_no_op() {
        let output = render_model_command_result(&selection_result(false, false));
        assert!(output.contains("Already installed"));
        assert!(output.contains("already the default"));
        assert!(!output.contains("Default model changed"));
    }

    #[test]
    fn inactive_model_removal_reports_measured_bytes_and_remaining_state() {
        let result = CommandResult::success("model_removed", "Removed model tiny.en")
            .field("removed", "true")
            .field("model", "tiny.en")
            .field("model_name", "Tiny English")
            .field("freed_bytes", (75 * 1024 * 1024_u64).to_string())
            .field("remaining_count", "1")
            .field("selected", "base.en");
        let output = render_model_command_result(&result);
        assert!(output.contains("Removed      Tiny English"));
        assert!(output.contains("Freed        75.0 MiB"));
        assert!(output.contains("Installed models remaining: 1"));
        assert!(output.contains("Default model: base.en"));
    }

    #[test]
    fn model_status_distinguishes_gpu_preference_from_active_compute() {
        let result = CommandResult::success("model_status", "Transcription model status")
            .field("selected", "base.en")
            .field("downloaded", "true")
            .field("gpu_preference", "enabled")
            .field("compute", "CPU")
            .field("compute_fallback", "GPU build unavailable");
        let output = render_model_command_result(&result);
        assert!(output.contains("GPU preference   enabled"));
        assert!(output.contains("Active compute   CPU"));
        assert!(output.contains("Fallback reason  GPU build unavailable"));
    }

    #[test]
    fn setup_guidance_requires_human_tty_and_no_explicit_choices() {
        let options = cli::SetupOptions::default();
        assert!(setup_needs_guidance_with_terminal(
            &options,
            OutputFormat::Human,
            true,
            true
        ));
        assert!(!setup_needs_guidance_with_terminal(
            &options,
            OutputFormat::Json,
            true,
            true
        ));
        assert!(!setup_needs_guidance_with_terminal(
            &options,
            OutputFormat::Human,
            false,
            true
        ));

        let explicit = cli::SetupOptions {
            model: Some("tiny.en".to_string()),
            ..cli::SetupOptions::default()
        };
        assert!(!setup_needs_guidance_with_terminal(
            &explicit,
            OutputFormat::Human,
            true,
            true
        ));
    }

    #[test]
    fn setup_stops_running_resident_before_binary_replacement() {
        assert!(setup_preinstall_stop_required(Some(RuntimeState::Running)));
        assert!(!setup_preinstall_stop_required(Some(RuntimeState::Stopped)));
        assert!(!setup_preinstall_stop_required(None));
    }

    #[test]
    fn setup_persistence_snapshot_restores_existing_and_removes_new_files() {
        let root = std::env::temp_dir().join(format!(
            "origin-speak-setup-persistence-test-{}",
            uuid::Uuid::new_v4()
        ));
        fs::create_dir_all(&root).unwrap();
        let config = root.join("config.json");
        let settings = root.join("transcription_settings.json");
        fs::write(&config, b"old-config").unwrap();

        let snapshot = SetupPersistenceSnapshot::capture_from_root(&root).unwrap();
        fs::write(&config, b"new-config").unwrap();
        fs::write(&settings, b"new-settings").unwrap();
        fs::write(root.join("config.json.bak"), b"new-backup").unwrap();
        snapshot.restore().unwrap();

        assert_eq!(fs::read(&config).unwrap(), b"old-config");
        assert!(!settings.exists());
        assert!(!root.join("config.json.bak").exists());
        fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn update_helper_runtime_state_round_trips() {
        for running in [false, true] {
            let token = OsString::from(update_runtime_state_token(running));
            assert_eq!(parse_update_runtime_state(&token).unwrap(), running);
        }
        assert!(parse_update_runtime_state(std::ffi::OsStr::new("unknown")).is_err());
    }

    #[test]
    fn update_result_log_surfaces_latest_record_and_is_consumed() {
        let root = std::env::temp_dir().join(format!(
            "origin-speak-update-result-test-{}",
            uuid::Uuid::new_v4()
        ));
        fs::create_dir_all(&root).unwrap();
        let path = root.join(UPDATE_RESULT_FILE);
        append_update_result_at(
            &path,
            &UpdateResultRecord {
                version: "1.2.3".to_string(),
                status: "pending".to_string(),
                error: None,
            },
        )
        .unwrap();
        append_update_result_at(
            &path,
            &UpdateResultRecord {
                version: "1.2.3".to_string(),
                status: "failed".to_string(),
                error: Some("replacement failed".to_string()),
            },
        )
        .unwrap();

        let record = take_update_result_at(&path).unwrap().unwrap();
        assert_eq!(record.version, "1.2.3");
        assert_eq!(record.status, "failed");
        assert_eq!(record.error.as_deref(), Some("replacement failed"));
        assert!(!path.exists());
        assert!(take_update_result_at(&path).unwrap().is_none());
        fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn update_result_log_keeps_last_complete_record_if_tail_is_torn() {
        let root = std::env::temp_dir().join(format!(
            "origin-speak-update-result-torn-test-{}",
            uuid::Uuid::new_v4()
        ));
        fs::create_dir_all(&root).unwrap();
        let path = root.join(UPDATE_RESULT_FILE);
        append_update_result_at(
            &path,
            &UpdateResultRecord {
                version: "1.2.3".to_string(),
                status: "pending".to_string(),
                error: None,
            },
        )
        .unwrap();
        let mut file = fs::OpenOptions::new().append(true).open(&path).unwrap();
        file.write_all(b"{\"version\":\"1.2.3\"").unwrap();
        file.sync_all().unwrap();

        let record = take_update_result_at(&path).unwrap().unwrap();
        assert_eq!(record.status, "pending");
        assert!(!path.exists());
        fs::remove_dir_all(root).unwrap();
    }

    #[cfg(target_os = "windows")]
    #[test]
    fn update_helpers_use_exclusive_uuid_paths() {
        let root = std::env::temp_dir().join(format!(
            "origin-speak-update-helper-test-{}",
            uuid::Uuid::new_v4()
        ));
        fs::create_dir_all(&root).unwrap();
        let source = root.join("manager.exe");
        fs::write(&source, b"manager").unwrap();
        let first = create_unique_post_exit_helper(&source, "__post-exit-update", &root).unwrap();
        let second = create_unique_post_exit_helper(&source, "__post-exit-update", &root).unwrap();
        assert_ne!(first, second);
        assert_eq!(fs::read(&first).unwrap(), b"manager");
        assert_eq!(fs::read(&second).unwrap(), b"manager");
        fs::remove_dir_all(root).unwrap();
    }

    #[cfg(target_os = "windows")]
    #[test]
    fn stale_helper_sweep_only_removes_old_finished_helpers() {
        let root = std::env::temp_dir().join(format!(
            "origin-speak-helper-sweep-test-{}",
            uuid::Uuid::new_v4()
        ));
        fs::create_dir_all(&root).unwrap();
        let helper = |kind: &str| {
            root.join(format!(
                "origin-speak-{kind}-helper-{}.exe",
                uuid::Uuid::new_v4()
            ))
        };
        let age = |path: &Path| {
            let old = std::time::SystemTime::now() - Duration::from_secs(600);
            fs::File::options()
                .write(true)
                .open(path)
                .unwrap()
                .set_modified(old)
                .unwrap();
        };

        let old_update = helper("update");
        let old_uninstall = helper("uninstall");
        let fresh_update = helper("update");
        let kept = helper("update");
        let unrelated = root.join("origin-speak-update-helper-notes.exe");
        let helper_named_dir = helper("update");
        for path in [
            &old_update,
            &old_uninstall,
            &fresh_update,
            &kept,
            &unrelated,
        ] {
            fs::write(path, b"helper").unwrap();
        }
        for path in [&old_update, &old_uninstall, &kept, &unrelated] {
            age(path);
        }
        fs::create_dir(&helper_named_dir).unwrap();

        let removed = remove_stale_post_exit_helpers(&root, Some(&kept), STALE_HELPER_MIN_AGE);

        assert_eq!(removed, 2);
        assert!(!old_update.exists());
        assert!(!old_uninstall.exists());
        assert!(fresh_update.exists());
        assert!(kept.exists());
        assert!(unrelated.exists());
        assert!(helper_named_dir.is_dir());
        fs::remove_dir_all(root).unwrap();
    }

    #[cfg(target_os = "windows")]
    #[test]
    fn uninstall_helper_commit_marker_is_explicit_and_round_trips() {
        let path = std::env::temp_dir().join(format!(
            "origin-speak-uninstall-commit-test-{}.marker",
            uuid::Uuid::new_v4()
        ));
        assert!(!path.exists());
        commit_post_exit_uninstall(&path).expect("create commit marker");
        wait_for_uninstall_commit(&path, Duration::from_millis(50))
            .expect("helper observes valid commit marker");
        fs::remove_file(path).unwrap();
    }
}