claude_code_switcher/
simple_selector.rs

1use crate::{
2    credentials::{CredentialStore, SavedCredential},
3    templates::{TemplateType, get_template_instance},
4};
5use anyhow::{Result, anyhow};
6use inquire::{Select, Text};
7
8/// Simple credential selector using inquire for consistency with CLI experience
9pub struct SimpleCredentialSelector {
10    credentials: Vec<SavedCredential>,
11    template_type: TemplateType,
12}
13
14impl SimpleCredentialSelector {
15    pub fn new(credentials: Vec<SavedCredential>, template_type: TemplateType) -> Self {
16        Self {
17            credentials,
18            template_type,
19        }
20    }
21
22    pub fn run(&mut self) -> Result<Option<String>> {
23        if self.credentials.is_empty() {
24            // No saved credentials, show API key acquisition URL if available
25            let template_instance = get_template_instance(&self.template_type);
26            if let Some(url) = template_instance.api_key_url() {
27                println!("  💡 Get your API key from: {}", url);
28            }
29
30            // Prompt for new API key directly
31            let prompt_text = format!("Enter your {} API key:", self.template_type);
32            let api_key = Text::new(&prompt_text)
33                .with_placeholder("sk-...")
34                .prompt()
35                .map_err(|e| anyhow!("Failed to get API key: {}", e))?;
36
37            if !api_key.trim().is_empty() {
38                return Ok(Some(api_key));
39            }
40            return Ok(None);
41        }
42
43        // Create selection options
44        let mut options = Vec::new();
45
46        // Add saved credentials
47        for credential in &self.credentials {
48            let masked_key = mask_api_key(credential.api_key());
49            options.push(format!("{} ({})", credential.name(), masked_key));
50        }
51
52        // Add "Enter new API key" option
53        options.push("Enter new API key...".to_string());
54
55        // Show selection prompt
56        let prompt_text = format!("Select {} API key:", self.template_type);
57        match Select::new(&prompt_text, options).prompt() {
58            Ok(choice) => {
59                if choice == "Enter new API key..." {
60                    // Show API key acquisition URL if available
61                    let template_instance = get_template_instance(&self.template_type);
62                    if let Some(url) = template_instance.api_key_url() {
63                        println!("  💡 Get your API key from: {}", url);
64                    }
65
66                    // Prompt for new API key
67                    let prompt_text = format!("Enter your {} API key:", self.template_type);
68                    let api_key = Text::new(&prompt_text)
69                        .with_placeholder("sk-...")
70                        .prompt()
71                        .map_err(|e| anyhow!("Failed to get API key: {}", e))?;
72
73                    if !api_key.trim().is_empty() {
74                        return Ok(Some(api_key));
75                    }
76                    Err(anyhow!("API key cannot be empty"))
77                } else {
78                    // Extract API key from selected credential
79                    let index = self.find_credential_index(&choice)?;
80                    Ok(Some(self.credentials[index].api_key().to_string()))
81                }
82            }
83            Err(_) => Ok(None), // User cancelled
84        }
85    }
86
87    fn find_credential_index(&self, choice: &str) -> Result<usize> {
88        for (i, credential) in self.credentials.iter().enumerate() {
89            let masked_key = mask_api_key(credential.api_key());
90            let option_text = format!("{} ({})", credential.name(), masked_key);
91            if option_text == choice {
92                return Ok(i);
93            }
94        }
95        Err(anyhow!("Credential not found"))
96    }
97}
98
99/// Simple endpoint ID selector using inquire
100pub fn get_endpoint_id_interactively(template_type: &TemplateType) -> Result<String> {
101    // Get saved endpoint IDs
102    let endpoint_ids = if let Ok(credential_store) = CredentialStore::new() {
103        credential_store.get_endpoint_ids(template_type)
104    } else {
105        Vec::new()
106    };
107
108    if endpoint_ids.is_empty() {
109        // No saved endpoint IDs, show API key acquisition URL if available
110        let template_instance = get_template_instance(template_type);
111        if let Some(url) = template_instance.api_key_url() {
112            println!("  💡 Get your API key from: {}", url);
113        }
114
115        // Prompt for new one directly
116        let prompt_text = format!("Enter {} endpoint ID:", template_type);
117        let endpoint_id = Text::new(&prompt_text)
118            .with_placeholder("ep-xxx-xxx")
119            .prompt()
120            .map_err(|e| anyhow!("Failed to get endpoint ID: {}", e))?;
121
122        if !endpoint_id.trim().is_empty() {
123            return Ok(endpoint_id);
124        }
125        return Err(anyhow!("Endpoint ID cannot be empty"));
126    }
127
128    // Create selection options
129    let mut options = Vec::new();
130
131    // Add saved endpoint IDs
132    for (display_name, _endpoint_id) in &endpoint_ids {
133        options.push(display_name.clone());
134    }
135
136    // Add "Enter new endpoint ID" option
137    options.push("Enter new endpoint ID...".to_string());
138
139    // Show selection prompt
140    let prompt_text = format!("Select {} endpoint ID:", template_type);
141    match Select::new(&prompt_text, options).prompt() {
142        Ok(choice) => {
143            if choice == "Enter new endpoint ID..." {
144                // Show API key acquisition URL if available
145                let template_instance = get_template_instance(template_type);
146                if let Some(url) = template_instance.api_key_url() {
147                    println!("  💡 Get your API key from: {}", url);
148                }
149
150                // Prompt for new endpoint ID
151                let prompt_text = format!("Enter {} endpoint ID:", template_type);
152                let endpoint_id = Text::new(&prompt_text)
153                    .with_placeholder("ep-xxx-xxx")
154                    .prompt()
155                    .map_err(|e| anyhow!("Failed to get endpoint ID: {}", e))?;
156
157                if !endpoint_id.trim().is_empty() {
158                    return Ok(endpoint_id);
159                }
160                Err(anyhow!("Endpoint ID cannot be empty"))
161            } else {
162                // Find and return the selected endpoint ID
163                for (display_name, endpoint_id) in &endpoint_ids {
164                    if display_name == &choice {
165                        return Ok(endpoint_id.clone());
166                    }
167                }
168                Err(anyhow!("Endpoint ID not found"))
169            }
170        }
171        Err(_) => Err(anyhow!("No endpoint ID selected")), // User cancelled
172    }
173}
174
175fn mask_api_key(api_key: &str) -> String {
176    if api_key.len() <= 8 {
177        "••••••••".to_string()
178    } else {
179        format!(
180            "{}{}{}",
181            &api_key[..4],
182            "•".repeat(api_key.len() - 8),
183            &api_key[api_key.len() - 4..]
184        )
185    }
186}