1use crate::cli::ConfigCommands;
4use crate::cli::{DeleteCommands, GetCommands, SetCommands};
5use crate::config;
6use anyhow::Result;
7use colored::Colorize;
8
9pub async fn handle(command: Option<ConfigCommands>) -> Result<()> {
11 match command {
12 Some(ConfigCommands::Set { command }) => handle_set_command(command).await,
13 Some(ConfigCommands::Get { command }) => handle_get_command(command).await,
14 Some(ConfigCommands::Delete { command }) => handle_delete_command(command).await,
15 Some(ConfigCommands::Path) => handle_path_command().await,
16 None => handle_show_current_config().await,
17 }
18}
19
20async fn handle_set_command(command: SetCommands) -> Result<()> {
21 match command {
22 SetCommands::Provider { name } => {
23 let mut config = config::Config::load()?;
24
25 if !config.has_provider(&name) {
26 anyhow::bail!(
27 "Provider '{}' not found. Add it first with 'lc providers add'",
28 name
29 );
30 }
31
32 config.default_provider = Some(name.clone());
33 config.save()?;
34 println!("{} Default provider set to '{}'", "✓".green(), name);
35 }
36 SetCommands::Model { name } => {
37 let mut config = config::Config::load()?;
38 config.default_model = Some(name.clone());
39 config.save()?;
40 println!("{} Default model set to '{}'", "✓".green(), name);
41 }
42 SetCommands::SystemPrompt { prompt } => {
43 let mut config = config::Config::load()?;
44 let resolved_prompt = config.resolve_template_or_prompt(&prompt);
45 config.system_prompt = Some(resolved_prompt);
46 config.save()?;
47 println!("{} System prompt set", "✓".green());
48 }
49 SetCommands::MaxTokens { value } => {
50 let mut config = config::Config::load()?;
51 let parsed_value = config::Config::parse_max_tokens(&value)?;
52 config.max_tokens = Some(parsed_value);
53 config.save()?;
54 println!("{} Max tokens set to {}", "✓".green(), parsed_value);
55 }
56 SetCommands::Temperature { value } => {
57 let mut config = config::Config::load()?;
58 let parsed_value = config::Config::parse_temperature(&value)?;
59 config.temperature = Some(parsed_value);
60 config.save()?;
61 println!("{} Temperature set to {}", "✓".green(), parsed_value);
62 }
63 SetCommands::Search { name } => {
64 let mut search_config = crate::search::SearchConfig::load()?;
65
66 if !search_config.has_provider(&name) {
67 anyhow::bail!(
68 "Search provider '{}' not found. Add it first with 'lc search provider add'",
69 name
70 );
71 }
72
73 search_config.set_default_provider(name.clone())?;
74 search_config.save()?;
75 println!("{} Default search provider set to '{}'", "✓".green(), name);
76 }
77 SetCommands::Stream { value } => {
78 let mut config = config::Config::load()?;
79 let stream_value = match value.to_lowercase().as_str() {
80 "true" | "1" | "yes" | "on" => true,
81 "false" | "0" | "no" | "off" => false,
82 _ => anyhow::bail!("Invalid stream value '{}'. Use 'true' or 'false'", value),
83 };
84 config.stream = Some(stream_value);
85 config.save()?;
86 println!("{} Streaming mode set to {}", "✓".green(), stream_value);
87 }
88 }
89 Ok(())
90}
91
92async fn handle_get_command(command: GetCommands) -> Result<()> {
93 let config = config::Config::load()?;
94 match command {
95 GetCommands::Provider => {
96 if let Some(provider) = &config.default_provider {
97 println!("{}", provider);
98 } else {
99 anyhow::bail!("No default provider configured");
100 }
101 }
102 GetCommands::Model => {
103 if let Some(model) = &config.default_model {
104 println!("{}", model);
105 } else {
106 anyhow::bail!("No default model configured");
107 }
108 }
109 GetCommands::SystemPrompt => {
110 if let Some(system_prompt) = &config.system_prompt {
111 println!("{}", system_prompt);
112 } else {
113 anyhow::bail!("No system prompt configured");
114 }
115 }
116 GetCommands::MaxTokens => {
117 if let Some(max_tokens) = &config.max_tokens {
118 println!("{}", max_tokens);
119 } else {
120 anyhow::bail!("No max tokens configured");
121 }
122 }
123 GetCommands::Temperature => {
124 if let Some(temperature) = &config.temperature {
125 println!("{}", temperature);
126 } else {
127 anyhow::bail!("No temperature configured");
128 }
129 }
130 GetCommands::Search => {
131 let search_config = crate::search::SearchConfig::load()?;
132 if let Some(provider) = search_config.get_default_provider() {
133 println!("{}", provider);
134 } else {
135 anyhow::bail!("No default search provider configured");
136 }
137 }
138 GetCommands::Stream => {
139 if let Some(stream) = &config.stream {
140 println!("{}", stream);
141 } else {
142 anyhow::bail!("No streaming mode configured");
143 }
144 }
145 }
146 Ok(())
147}
148
149async fn handle_delete_command(command: DeleteCommands) -> Result<()> {
150 let mut config = config::Config::load()?;
151 match command {
152 DeleteCommands::Provider => {
153 if config.default_provider.is_some() {
154 config.default_provider = None;
155 config.save()?;
156 println!("{} Default provider deleted", "✓".green());
157 } else {
158 anyhow::bail!("No default provider configured to delete");
159 }
160 }
161 DeleteCommands::Model => {
162 if config.default_model.is_some() {
163 config.default_model = None;
164 config.save()?;
165 println!("{} Default model deleted", "✓".green());
166 } else {
167 anyhow::bail!("No default model configured to delete");
168 }
169 }
170 DeleteCommands::SystemPrompt => {
171 if config.system_prompt.is_some() {
172 config.system_prompt = None;
173 config.save()?;
174 println!("{} System prompt deleted", "✓".green());
175 } else {
176 anyhow::bail!("No system prompt configured to delete");
177 }
178 }
179 DeleteCommands::MaxTokens => {
180 if config.max_tokens.is_some() {
181 config.max_tokens = None;
182 config.save()?;
183 println!("{} Max tokens deleted", "✓".green());
184 } else {
185 anyhow::bail!("No max tokens configured to delete");
186 }
187 }
188 DeleteCommands::Temperature => {
189 if config.temperature.is_some() {
190 config.temperature = None;
191 config.save()?;
192 println!("{} Temperature deleted", "✓".green());
193 } else {
194 anyhow::bail!("No temperature configured to delete");
195 }
196 }
197 DeleteCommands::Search => {
198 let mut search_config = crate::search::SearchConfig::load()?;
199 if search_config.get_default_provider().is_some() {
200 search_config.set_default_provider(String::new())?;
201 search_config.save()?;
202 println!("{} Default search provider deleted", "✓".green());
203 } else {
204 anyhow::bail!("No default search provider configured to delete");
205 }
206 }
207 DeleteCommands::Stream => {
208 let mut config = config::Config::load()?;
209 if config.stream.is_some() {
210 config.stream = None;
211 config.save()?;
212 println!("{} Streaming mode deleted", "✓".green());
213 } else {
214 anyhow::bail!("No streaming mode configured to delete");
215 }
216 }
217 }
218 Ok(())
219}
220
221async fn handle_path_command() -> Result<()> {
222 let config_dir = config::Config::config_dir()?;
223 println!("\n{}", "Configuration Directory:".bold().blue());
224 println!("{}", config_dir.display());
225 println!("\n{}", "Files:".bold().blue());
226 println!(" {} config.toml", "•".blue());
227 println!(" {} logs.db (synced to cloud)", "•".blue());
228 println!("\n{}", "Database Management:".bold().blue());
229 println!(
230 " {} Purge old logs: {}",
231 "•".blue(),
232 "lc logs purge --older-than-days 30".dimmed()
233 );
234 println!(
235 " {} Keep recent logs: {}",
236 "•".blue(),
237 "lc logs purge --keep-recent 1000".dimmed()
238 );
239 println!(
240 " {} Size-based purge: {}",
241 "•".blue(),
242 "lc logs purge --max-size-mb 50".dimmed()
243 );
244 Ok(())
245}
246
247async fn handle_show_current_config() -> Result<()> {
248 let config = config::Config::load()?;
250 println!("\n{}", "Current Configuration:".bold().blue());
251
252 if let Some(provider) = &config.default_provider {
253 println!("provider {}", provider);
254 } else {
255 println!("provider {}", "not set".dimmed());
256 }
257
258 if let Some(model) = &config.default_model {
259 if let Some(provider) = &config.default_provider {
261 match load_provider_enhanced_models(provider).await {
262 Ok(models) => {
263 if let Some(model_metadata) = models.iter().find(|m| m.id == *model) {
265 let _model_info = vec![model.clone()];
267
268 let mut capabilities = Vec::new();
270 if model_metadata.supports_tools || model_metadata.supports_function_calling
271 {
272 capabilities.push("🔧 tools".blue());
273 }
274 if model_metadata.supports_vision {
275 capabilities.push("👁 vision".magenta());
276 }
277 if model_metadata.supports_audio {
278 capabilities.push("🔊 audio".yellow());
279 }
280 if model_metadata.supports_reasoning {
281 capabilities.push("🧠 reasoning".cyan());
282 }
283 if model_metadata.supports_code {
284 capabilities.push("💻 code".green());
285 }
286
287 let mut info_parts = Vec::new();
289 if let Some(ctx) = model_metadata.context_length {
290 if ctx >= 1000000 {
291 info_parts.push(format!("{}m ctx", ctx / 1000000));
292 } else if ctx >= 1000 {
293 info_parts.push(format!("{}k ctx", ctx / 1000));
294 } else {
295 info_parts.push(format!("{} ctx", ctx));
296 }
297 }
298 if let Some(input_price) = model_metadata.input_price_per_m {
299 info_parts.push(format!("${:.2}/M in", input_price));
300 }
301 if let Some(output_price) = model_metadata.output_price_per_m {
302 info_parts.push(format!("${:.2}/M out", output_price));
303 }
304
305 let model_display =
307 if let Some(ref display_name) = model_metadata.display_name {
308 if display_name != &model_metadata.id {
309 format!("{} ({})", model, display_name)
310 } else {
311 model.clone()
312 }
313 } else {
314 model.clone()
315 };
316
317 print!("model {}", model_display);
318
319 if !capabilities.is_empty() {
320 let capability_strings: Vec<String> =
321 capabilities.iter().map(|c| c.to_string()).collect();
322 print!(" [{}]", capability_strings.join(" "));
323 }
324
325 if !info_parts.is_empty() {
326 print!(" ({})", info_parts.join(", ").dimmed());
327 }
328
329 println!();
330 } else {
331 println!("model {}", model);
333 }
334 }
335 Err(_) => {
336 println!("model {}", model);
338 }
339 }
340 } else {
341 println!("model {}", model);
343 }
344 } else {
345 println!("model {}", "not set".dimmed());
346 }
347
348 if let Some(system_prompt) = &config.system_prompt {
349 println!("system_prompt {}", system_prompt);
350 } else {
351 println!("system_prompt {}", "not set".dimmed());
352 }
353
354 if let Some(max_tokens) = &config.max_tokens {
355 println!("max_tokens {}", max_tokens);
356 } else {
357 println!("max_tokens {}", "not set".dimmed());
358 }
359
360 if let Some(temperature) = &config.temperature {
361 println!("temperature {}", temperature);
362 } else {
363 println!("temperature {}", "not set".dimmed());
364 }
365
366 if let Some(stream) = &config.stream {
367 println!("stream {}", stream);
368 } else {
369 println!("stream {}", "not set".dimmed());
370 }
371
372 Ok(())
373}
374
375async fn load_provider_enhanced_models(
377 provider_name: &str,
378) -> Result<Vec<crate::model_metadata::ModelMetadata>> {
379 use crate::model_metadata::MetadataExtractor;
380 use std::fs;
381
382 let filename = format!("models/{}.json", provider_name);
383
384 if !std::path::Path::new(&filename).exists() {
385 return Ok(Vec::new());
386 }
387
388 match fs::read_to_string(&filename) {
389 Ok(json_content) => {
390 match MetadataExtractor::extract_from_provider(provider_name, &json_content) {
391 Ok(models) => Ok(models),
392 Err(e) => {
393 eprintln!(
394 "Warning: Failed to extract metadata from {}: {}",
395 provider_name, e
396 );
397 Ok(Vec::new())
398 }
399 }
400 }
401 Err(e) => {
402 eprintln!("Warning: Failed to read {}: {}", filename, e);
403 Ok(Vec::new())
404 }
405 }
406}