1use anyhow::Result;
2use colored::Colorize;
3use std::io::{self, Write};
4
5use crate::cli::KeyCommands;
6use crate::config;
7
8pub async fn handle(command: KeyCommands) -> Result<()> {
10 match command {
11 KeyCommands::Add { name } => add_key(name).await,
12 KeyCommands::Get { name } => get_key(name).await,
13 KeyCommands::List => list_keys().await,
14 KeyCommands::Remove { name } => remove_key(name).await,
15 }
16}
17
18async fn add_key(name: String) -> Result<()> {
19 let mut config = config::Config::load()?;
20
21 if !config.has_provider(&name) {
22 anyhow::bail!(
23 "Provider '{}' not found. Add it first with 'lc providers add'",
24 name
25 );
26 }
27
28 let provider_cfg = config.get_provider(&name)?;
30 let is_google_sa = provider_cfg.auth_type.as_deref() == Some("google_sa_jwt")
31 || provider_cfg.endpoint.contains("aiplatform.googleapis.com");
32
33 if is_google_sa {
34 println!("Detected Google Vertex AI provider. Please provide the Service Account JSON.");
35 println!("Options:");
36 println!(" 1. Paste the base64 version directly (ex: cat sa.json | base64)");
37 println!(" 2. Provide the path to the JSON file (ex: /path/to/sa.json)");
38 print!("Base64 Service Account JSON or file path for {}: ", name);
39 io::stdout().flush()?;
40
41 let mut input = String::new();
43 io::stdin().read_line(&mut input)?;
44 let input = input.trim();
45
46 let sa_json = if input.starts_with('/') || input.ends_with(".json") {
47 match std::fs::read_to_string(input) {
49 Ok(file_content) => file_content,
50 Err(e) => {
51 anyhow::bail!("Failed to read service account file '{}': {}", input, e)
52 }
53 }
54 } else {
55 let sa_json_b64 = input
57 .trim()
58 .replace("\n", "")
59 .replace("\r", "")
60 .replace(" ", "");
61
62 use base64::{engine::general_purpose, Engine as _};
64 match general_purpose::STANDARD.decode(&sa_json_b64) {
65 Ok(decoded_bytes) => match String::from_utf8(decoded_bytes) {
66 Ok(json_str) => json_str,
67 Err(_) => anyhow::bail!("Invalid UTF-8 in decoded base64 data"),
68 },
69 Err(_) => anyhow::bail!("Invalid base64 format"),
70 }
71 };
72
73 let parsed: serde_json::Value =
75 serde_json::from_str(&sa_json).map_err(|e| anyhow::anyhow!("Invalid JSON: {}", e))?;
76 let sa_type = parsed.get("type").and_then(|v| v.as_str()).unwrap_or("");
77 let client_email = parsed
78 .get("client_email")
79 .and_then(|v| v.as_str())
80 .unwrap_or("");
81 let private_key = parsed
82 .get("private_key")
83 .and_then(|v| v.as_str())
84 .unwrap_or("");
85
86 if sa_type != "service_account" {
87 anyhow::bail!("Service Account JSON must have \"type\": \"service_account\"");
88 }
89 if client_email.is_empty() {
90 anyhow::bail!("Service Account JSON missing 'client_email'");
91 }
92 if private_key.is_empty() {
93 anyhow::bail!("Service Account JSON missing 'private_key'");
94 }
95
96 config.set_api_key(name.clone(), sa_json)?;
98 config.save()?;
99 println!(
100 "{} Service Account stored for provider '{}'",
101 "✓".green(),
102 name
103 );
104 } else {
105 print!("Enter API key for {}: ", name);
106 io::stdout().flush()?;
107 let key = rpassword::read_password()?;
108
109 config.set_api_key(name.clone(), key)?;
110 config.save()?;
111 println!("{} API key set for provider '{}'", "✓".green(), name);
112 }
113
114 Ok(())
115}
116
117async fn get_key(name: String) -> Result<()> {
118 let config = config::Config::load()?;
119
120 if !config.has_provider(&name) {
121 anyhow::bail!("Provider '{}' not found", name);
122 }
123
124 let keys = crate::keys::KeysConfig::load()?;
126 if let Some(auth) = keys.get_auth(&name) {
127 match auth {
128 crate::keys::ProviderAuth::ApiKey(key) => println!("{}", key),
129 crate::keys::ProviderAuth::ServiceAccount(sa_json) => println!("{}", sa_json),
130 crate::keys::ProviderAuth::Token(token) => println!("{}", token),
131 crate::keys::ProviderAuth::OAuthToken(oauth) => println!("{}", oauth),
132 crate::keys::ProviderAuth::Headers(headers) => {
133 for (k, v) in headers {
134 println!("{}={}", k, v);
135 }
136 }
137 }
138 } else {
139 anyhow::bail!("No API key configured for provider '{}'", name);
140 }
141
142 Ok(())
143}
144
145async fn list_keys() -> Result<()> {
146 let config = config::Config::load()?;
147 if config.providers.is_empty() {
148 println!("No providers configured.");
149 return Ok(());
150 }
151
152 println!("\n{}", "API Key Status:".bold().blue());
153
154 let keys = crate::keys::KeysConfig::load().unwrap_or_else(|_| crate::keys::KeysConfig::new());
156
157 for (name, _provider_config) in &config.providers {
158 let has_auth = keys.has_auth(name);
160 let status = if has_auth {
161 "✓ Configured".green()
162 } else {
163 "✗ Missing".red()
164 };
165 println!(" {} {} - {}", "•".blue(), name.bold(), status);
166 }
167
168 Ok(())
169}
170
171async fn remove_key(name: String) -> Result<()> {
172 let mut config = config::Config::load()?;
173
174 if !config.has_provider(&name) {
175 anyhow::bail!("Provider '{}' not found", name);
176 }
177
178 if let Some(provider_config) = config.providers.get_mut(&name) {
179 provider_config.api_key = None;
180 }
181 config.save()?;
182 println!("{} API key removed for provider '{}'", "✓".green(), name);
183
184 Ok(())
185}