1use crate::ProviderConfig;
2use crate::common::CommonParams;
3use crate::config::Config;
4use crate::instruction_presets::{
5 PresetType, get_instruction_preset_library, list_presets_formatted_by_type,
6};
7use crate::llm::get_available_provider_names;
8use crate::log_debug;
9use crate::mcp::config::{MCPServerConfig, MCPTransportType};
10use crate::mcp::server;
11use crate::ui;
12use anyhow::Context;
13use anyhow::{Result, anyhow};
14use colored::Colorize;
15use std::collections::HashMap;
16
17fn apply_config_changes(
36 config: &mut Config,
37 common: &CommonParams,
38 model: Option<String>,
39 token_limit: Option<usize>,
40 param: Option<Vec<String>>,
41 api_key: Option<String>,
42) -> anyhow::Result<bool> {
43 let mut changes_made = false;
44
45 common.apply_to_config(config)?;
47
48 if let Some(provider) = &common.provider {
50 if !get_available_provider_names().iter().any(|p| p == provider) {
51 return Err(anyhow!("Invalid provider: {}", provider));
52 }
53 if config.default_provider != *provider {
54 config.default_provider.clone_from(provider);
55 changes_made = true;
56 }
57 if !config.providers.contains_key(provider) {
58 config
59 .providers
60 .insert(provider.clone(), ProviderConfig::default());
61 changes_made = true;
62 }
63 }
64
65 let provider_config = config
66 .providers
67 .get_mut(&config.default_provider)
68 .context("Could not get default provider")?;
69
70 if let Some(key) = api_key {
72 if provider_config.api_key != key {
73 provider_config.api_key = key;
74 changes_made = true;
75 }
76 }
77
78 if let Some(model) = model {
80 if provider_config.model != model {
81 provider_config.model = model;
82 changes_made = true;
83 }
84 }
85
86 if let Some(params) = param {
88 let additional_params = parse_additional_params(¶ms);
89 if provider_config.additional_params != additional_params {
90 provider_config.additional_params = additional_params;
91 changes_made = true;
92 }
93 }
94
95 if let Some(use_gitmoji) = common.gitmoji {
97 if config.use_gitmoji != use_gitmoji {
98 config.use_gitmoji = use_gitmoji;
99 changes_made = true;
100 }
101 }
102
103 if let Some(instr) = &common.instructions {
105 if config.instructions != *instr {
106 config.instructions.clone_from(instr);
107 changes_made = true;
108 }
109 }
110
111 if let Some(limit) = token_limit {
113 if provider_config.token_limit != Some(limit) {
114 provider_config.token_limit = Some(limit);
115 changes_made = true;
116 }
117 }
118
119 if let Some(preset) = &common.preset {
121 let preset_library = get_instruction_preset_library();
122 if preset_library.get_preset(preset).is_some() {
123 if config.instruction_preset != *preset {
124 config.instruction_preset.clone_from(preset);
125 changes_made = true;
126 }
127 } else {
128 return Err(anyhow!("Invalid preset: {}", preset));
129 }
130 }
131
132 Ok(changes_made)
133}
134
135#[allow(clippy::too_many_lines)]
137pub fn handle_config_command(
138 common: &CommonParams,
139 api_key: Option<String>,
140 model: Option<String>,
141 token_limit: Option<usize>,
142 param: Option<Vec<String>>,
143) -> anyhow::Result<()> {
144 log_debug!(
145 "Starting 'config' command with common: {:?}, api_key: {:?}, model: {:?}, token_limit: {:?}, param: {:?}",
146 common,
147 api_key,
148 model,
149 token_limit,
150 param
151 );
152
153 let mut config = Config::load()?;
154
155 let changes_made =
157 apply_config_changes(&mut config, common, model, token_limit, param, api_key)?;
158
159 if changes_made {
160 config.save()?;
161 ui::print_success("Configuration updated successfully.");
162 println!();
163 }
164
165 print_configuration(&config);
167
168 Ok(())
169}
170
171fn apply_project_config_changes(
188 config: &mut Config,
189 common: &CommonParams,
190 model: Option<String>,
191 token_limit: Option<usize>,
192 param: Option<Vec<String>>,
193) -> anyhow::Result<bool> {
194 apply_config_changes(config, common, model, token_limit, param, None)
196}
197
198fn print_project_config() {
203 if let Ok(project_config) = Config::load_project_config() {
204 println!(
205 "\n{}",
206 "Current project configuration:".bright_cyan().bold()
207 );
208 print_configuration(&project_config);
209 } else {
210 println!("\n{}", "No project configuration file found.".yellow());
211 println!("You can create one with the project-config command.");
212 }
213}
214
215pub fn handle_project_config_command(
238 common: &CommonParams,
239 model: Option<String>,
240 token_limit: Option<usize>,
241 param: Option<Vec<String>>,
242 print: bool,
243) -> anyhow::Result<()> {
244 log_debug!(
245 "Starting 'project-config' command with common: {:?}, model: {:?}, token_limit: {:?}, param: {:?}, print: {}",
246 common,
247 model,
248 token_limit,
249 param,
250 print
251 );
252
253 let mut config = Config::load()?;
255
256 println!("\n{}", "✨ Project Configuration".bright_magenta().bold());
258
259 if print {
261 print_project_config();
262 return Ok(());
263 }
264
265 let changes_made =
267 apply_project_config_changes(&mut config, common, model, token_limit, param)?;
268
269 if changes_made {
270 config.save_as_project_config()?;
272 ui::print_success("Project configuration created/updated successfully.");
273 println!();
274
275 println!(
277 "{}",
278 "Note: API keys are never stored in project configuration files."
279 .yellow()
280 .italic()
281 );
282 println!();
283
284 println!("{}", "Current project configuration:".bright_cyan().bold());
286 print_configuration(&config);
287 } else {
288 println!("{}", "No changes made to project configuration.".yellow());
289 println!();
290
291 if let Ok(project_config) = Config::load_project_config() {
293 println!("{}", "Current project configuration:".bright_cyan().bold());
294 print_configuration(&project_config);
295 } else {
296 println!("{}", "No project configuration exists yet.".bright_yellow());
297 println!(
298 "{}",
299 "Use this command with options like --model or --provider to create one."
300 .bright_white()
301 );
302 }
303 }
304
305 Ok(())
306}
307
308fn print_configuration(config: &Config) {
310 println!(
312 "\n{}",
313 ui::create_gradient_text("🔮 Git-Iris Configuration 🔮").bold()
314 );
315 println!();
316
317 println!("{}", "Global Settings".bright_magenta().bold().underline());
319 println!();
320
321 let provider_label = "Default Provider:".bright_cyan().bold();
322 let provider_value = config.default_provider.bright_white();
323 println!(" {} {} {}", "🔹".cyan(), provider_label, provider_value);
324
325 let gitmoji_label = "Use Gitmoji:".bright_cyan().bold();
326 let gitmoji_value = if config.use_gitmoji {
327 "Yes".bright_green()
328 } else {
329 "No".bright_red()
330 };
331 println!(" {} {} {}", "🔹".cyan(), gitmoji_label, gitmoji_value);
332
333 let preset_label = "Instruction Preset:".bright_cyan().bold();
334 let preset_value = config.instruction_preset.bright_yellow();
335 println!(" {} {} {}", "🔹".cyan(), preset_label, preset_value);
336
337 println!();
338
339 if !config.instructions.is_empty() {
341 println!("{}", "Custom Instructions".bright_blue().bold().underline());
342 println!();
343
344 config.instructions.lines().for_each(|line| {
346 println!(" {}", line.bright_white().italic());
347 });
348
349 println!();
350 }
351
352 for (provider, provider_config) in &config.providers {
354 println!(
355 "{}",
356 format!("Provider: {provider}")
357 .bright_green()
358 .bold()
359 .underline()
360 );
361 println!();
362
363 let api_key_label = "API Key:".yellow().bold();
365 let api_key_value = if provider_config.api_key.is_empty() {
366 "Not set".bright_red().italic()
367 } else {
368 "Set ✓".bright_green()
369 };
370 println!(" {} {} {}", "🔒".yellow(), api_key_label, api_key_value);
371
372 let model_label = "Model:".yellow().bold();
374 let model_value = provider_config.model.bright_cyan();
375 println!(" {} {} {}", "✨".yellow(), model_label, model_value);
376
377 let token_limit_label = "Token Limit:".yellow().bold();
379 let token_limit_value = provider_config
380 .token_limit
381 .map_or("Default".bright_yellow(), |limit| {
382 limit.to_string().bright_white()
383 });
384 println!(
385 " {} {} {}",
386 "🔢".yellow(),
387 token_limit_label,
388 token_limit_value
389 );
390
391 if !provider_config.additional_params.is_empty() {
393 let params_label = "Additional Parameters:".yellow().bold();
394 println!(" {} {}", "🔧".yellow(), params_label);
395
396 for (key, value) in &provider_config.additional_params {
397 println!(" - {}: {}", key.bright_blue(), value.bright_white());
398 }
399 }
400
401 println!();
402 }
403}
404
405fn parse_additional_params(params: &[String]) -> HashMap<String, String> {
407 params
408 .iter()
409 .filter_map(|param| {
410 let parts: Vec<&str> = param.splitn(2, '=').collect();
411 if parts.len() == 2 {
412 Some((parts[0].to_string(), parts[1].to_string()))
413 } else {
414 None
415 }
416 })
417 .collect()
418}
419
420pub fn handle_list_presets_command() -> Result<()> {
422 let library = get_instruction_preset_library();
423
424 let both_presets = list_presets_formatted_by_type(&library, Some(PresetType::Both));
426 let commit_only_presets = list_presets_formatted_by_type(&library, Some(PresetType::Commit));
427 let review_only_presets = list_presets_formatted_by_type(&library, Some(PresetType::Review));
428
429 println!(
430 "{}",
431 "\nGit-Iris Instruction Presets\n".bright_magenta().bold()
432 );
433
434 println!(
435 "{}",
436 "General Presets (usable for both commit and review):"
437 .bright_cyan()
438 .bold()
439 );
440 println!("{both_presets}\n");
441
442 if !commit_only_presets.is_empty() {
443 println!("{}", "Commit-specific Presets:".bright_green().bold());
444 println!("{commit_only_presets}\n");
445 }
446
447 if !review_only_presets.is_empty() {
448 println!("{}", "Review-specific Presets:".bright_blue().bold());
449 println!("{review_only_presets}\n");
450 }
451
452 println!("{}", "Usage:".bright_yellow().bold());
453 println!(" git-iris gen --preset <preset-key>");
454 println!(" git-iris review --preset <preset-key>");
455 println!("\nPreset types: [B] = Both commands, [C] = Commit only, [R] = Review only");
456
457 Ok(())
458}
459
460pub async fn handle_serve_command(
462 dev: bool,
463 transport: String,
464 port: Option<u16>,
465 listen_address: Option<String>,
466) -> anyhow::Result<()> {
467 log_debug!(
468 "Starting 'serve' command with dev: {}, transport: {}, port: {:?}, listen_address: {:?}",
469 dev,
470 transport,
471 port,
472 listen_address
473 );
474
475 let mut config = MCPServerConfig::default();
477
478 if dev {
480 config = config.with_dev_mode();
481 }
482
483 let transport_type = match transport.to_lowercase().as_str() {
485 "stdio" => MCPTransportType::StdIO,
486 "sse" => MCPTransportType::SSE,
487 _ => {
488 return Err(anyhow::anyhow!(
489 "Invalid transport type: {}. Valid options are: stdio, sse",
490 transport
491 ));
492 }
493 };
494 config = config.with_transport(transport_type);
495
496 if let Some(p) = port {
498 config = config.with_port(p);
499 }
500
501 if let Some(addr) = listen_address {
503 config = config.with_listen_address(addr);
504 }
505
506 server::serve(config).await
508}