omnyssh 1.0.0

TUI SSH dashboard & server manager
// Suppress dead_code warnings for stub/future-stage modules.
#![allow(dead_code)]

use clap::Parser;

mod app;
mod cli;
mod config;
mod event;
mod ssh;
mod ui;
mod utils;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let cli = cli::Cli::parse();

    // Initialise logging. Output goes to stderr so it does not pollute the
    // TUI. Set RUST_LOG or pass -v to enable debug output.
    let log_level = if cli.verbose { "debug" } else { "warn" };
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(log_level)),
        )
        .with_writer(std::io::stderr)
        .init();

    // Restore the terminal if a panic occurs so the user is not left with a
    // broken shell.
    let default_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |info| {
        // Best-effort terminal restore — ignore errors here.
        let _ = crossterm::terminal::disable_raw_mode();
        let _ = crossterm::execute!(
            std::io::stdout(),
            crossterm::terminal::LeaveAlternateScreen,
            crossterm::event::DisableMouseCapture,
        );
        default_hook(info);
    }));

    // Load application config.
    // A missing file is silently ignored; a malformed file is reported and
    // we fall back to defaults so the app always starts.
    let mut app_config = match config::app_config::load_app_config(cli.config.as_deref()) {
        Ok(cfg) => {
            if let Some(ref path) = cli.config {
                tracing::info!("Loaded config from: {}", path.display());
            }
            cfg
        }
        Err(e) => {
            tracing::warn!("Config load error (using defaults): {}", e);
            config::app_config::AppConfig::default()
        }
    };

    // Apply CLI theme override if provided and save it to config.
    if let Some(ref theme) = cli.theme {
        if config::app_config::UiConfig::is_valid_theme(theme) {
            tracing::info!("Applying theme '{}' and saving to config", theme);
            app_config.ui.theme = theme.clone();

            // Save theme to config file for persistence
            if let Err(e) = config::app_config::save_theme_to_config(theme) {
                eprintln!("Warning: Failed to save theme to config: {}", e);
                eprintln!("Theme will be applied for this session only.");
            } else {
                eprintln!("✓ Theme '{}' saved to config", theme);
            }
        } else {
            eprintln!(
                "Error: Unknown theme '{}'. Available themes: {}",
                theme,
                config::app_config::UiConfig::available_themes().join(", ")
            );
            eprintln!(
                "Falling back to theme from config: '{}'",
                app_config.ui.theme
            );
        }
    }

    let mut app = app::App::new(app_config);
    app.run().await
}