Skip to main content

git_comma/
setup.rs

1use crate::config::{home_config_path, Config, ConfigError};
2use crate::openrouter::{ApiError, Client};
3use crate::ui;
4use colored::Colorize;
5
6fn print_splash_banner() {
7    let banner = r#"
8 ▄   ▄▄▄▄
9 ▀██████▀                           ▄███████▄
10   ██           ▄        ▄         ██   ▀█▄ ▀█
11   ██     ▄███▄ ███▄███▄ ███▄███▄ ██  ▄█▀██  ██
12   ██     ██ ██ ██ ██ ██ ██ ██ ██ ██  ██ ██ ▄█
13   ▀█████▄▀███▀▄██ ██ ▀█▄██ ██ ▀█ ▀█▄  ▀▀▀▀▀▀
14                                    ▀██████▀▀
15"#;
16    println!("{}", banner.cyan().bold());
17}
18
19fn prompt_api_key(is_first_run: bool, existing_key: Option<&str>) -> String {
20    loop {
21        let mut prompt_text = "Enter OpenRouter API Key (sk-or-v1-...):".to_string();
22        if !is_first_run {
23            prompt_text.push_str(" (Leave blank to keep existing)");
24        }
25
26        let password = inquire::Password::new(&prompt_text)
27            .with_display_mode(inquire::PasswordDisplayMode::Masked)
28            .with_help_message("API key at https://openrouter.ai/keys")
29            .prompt()
30            .expect("User cancelled");
31
32        if password.is_empty() {
33            if is_first_run {
34                ui::error_message("API key is required on first run. Please enter your key.");
35                continue;
36            } else if let Some(key) = existing_key {
37                return key.to_string();
38            } else {
39                ui::error_message("No existing API key found. Please enter your key.");
40                continue;
41            }
42        }
43        break password;
44    }
45}
46
47pub fn validate_max_chars_input(input: &str) -> Result<usize, String> {
48    if input.is_empty() {
49        return Ok(15_000);
50    }
51    match input.parse::<usize>() {
52        Ok(0) => Err("Must be at least 1".to_string()),
53        Ok(n) => Ok(n),
54        Err(_) => Err(format!("'{}' is not a valid number", input)),
55    }
56}
57
58pub fn run_setup_flow(is_first_run: bool) -> Result<Config, ConfigError> {
59    print_splash_banner();
60
61    let existing_key = if !is_first_run {
62        home_config_path()
63            .ok()
64            .and_then(|path| Config::load_from_path(&path).ok())
65            .map(|c| c.api_key)
66    } else {
67        None
68    };
69
70    let api_key = prompt_api_key(is_first_run, existing_key.as_deref());
71    let mut client = Client::new(api_key.clone());
72
73    let models = loop {
74        ui::fetching_models_message();
75        match client.fetch_models() {
76            Ok(m) => break m,
77            Err(ApiError::Unauthorized) => {
78                ui::error_message("Invalid API Key. Make sure you entered the correct key.");
79                let new_key = prompt_api_key(true, None);
80                client = Client::new(new_key);
81                continue;
82            }
83            Err(ApiError::RateLimited) => {
84                ui::rate_limited_message();
85                std::thread::sleep(std::time::Duration::from_secs(2));
86                continue;
87            }
88            Err(ApiError::EmptyResponse) => {
89                ui::error_message("No models available. Please try again.");
90                continue;
91            }
92            Err(ApiError::Forbidden) => {
93                return Err(ConfigError::ApiError(
94                    "API key doesn't have access. Check permissions on OpenRouter.".into(),
95                ));
96            }
97            Err(e) => {
98                return Err(ConfigError::ApiError(format!(
99                    "Failed to fetch models: {}. Please try again.",
100                    e
101                )));
102            }
103        }
104    };
105
106    let model_ids: Vec<String> = models.iter().map(|m| m.id.clone()).collect();
107    ui::models_loaded(model_ids.len());
108
109    let selection = ui::model_select_prompt(&model_ids);
110    let model_id = if selection == "[ Type Manual Model ID... ]" {
111        ui::manual_model_prompt()
112    } else {
113        selection
114    };
115
116    let current_max_chars = if !is_first_run {
117        home_config_path()
118            .ok()
119            .and_then(|path| Config::load_from_path(&path).ok())
120            .map(|c| c.max_chars)
121            .unwrap_or(15_000)
122    } else {
123        15_000
124    };
125
126    let max_chars = loop {
127        let input = inquire::Text::new(&format!(
128            "Max characters for diff (current: {}):",
129            current_max_chars
130        ))
131        .with_default("")
132        .prompt()
133        .expect("User cancelled");
134
135        match validate_max_chars_input(&input) {
136            Ok(n) => break n,
137            Err(msg) => {
138                ui::error_message(&msg);
139                continue;
140            }
141        }
142    };
143
144    let config = Config {
145        api_key,
146        model_id,
147        max_chars,
148    };
149
150    let path = home_config_path()?;
151    config.save(&path)?;
152
153    ui::save_confirmation();
154    Ok(config)
155}