1use anyhow::Result;
2use colored::Colorize;
3use dialoguer::{Confirm, Input, Select};
4
5use crate::cli::SetupCommand;
6use crate::config::Config;
7
8struct ProviderOption {
10 name: &'static str,
11 display: &'static str,
12 default_model: &'static str,
13 requires_key: bool,
14 category: ProviderCategory,
15}
16
17#[derive(Clone, Copy, PartialEq)]
18enum ProviderCategory {
19 Popular,
20 Local,
21 Cloud,
22 Enterprise,
23 Specialized,
24}
25
26impl ProviderOption {
27 fn all() -> Vec<Self> {
28 vec![
29 ProviderOption {
33 name: "openai",
34 display: "OpenAI (GPT-4o, GPT-4o-mini, GPT-5)",
35 default_model: "gpt-4o-mini",
36 requires_key: true,
37 category: ProviderCategory::Popular,
38 },
39 ProviderOption {
40 name: "anthropic",
41 display: "Anthropic (Claude 3.5/4 Sonnet, Haiku, Opus)",
42 default_model: "claude-3-5-haiku-20241022",
43 requires_key: true,
44 category: ProviderCategory::Popular,
45 },
46 ProviderOption {
47 name: "gemini",
48 display: "Google Gemini (2.5 Flash, 2.5 Pro)",
49 default_model: "gemini-2.5-flash",
50 requires_key: true,
51 category: ProviderCategory::Popular,
52 },
53 ProviderOption {
57 name: "ollama",
58 display: "Ollama (Local models - free, private)",
59 default_model: "llama3.2",
60 requires_key: false,
61 category: ProviderCategory::Local,
62 },
63 ProviderOption {
64 name: "lmstudio",
65 display: "LM Studio (Local GUI for LLMs)",
66 default_model: "local-model",
67 requires_key: false,
68 category: ProviderCategory::Local,
69 },
70 ProviderOption {
71 name: "llamacpp",
72 display: "llama.cpp (Local inference)",
73 default_model: "local-model",
74 requires_key: false,
75 category: ProviderCategory::Local,
76 },
77 ProviderOption {
81 name: "groq",
82 display: "Groq (Ultra-fast inference)",
83 default_model: "llama-3.3-70b-versatile",
84 requires_key: true,
85 category: ProviderCategory::Cloud,
86 },
87 ProviderOption {
88 name: "cerebras",
89 display: "Cerebras (Fast inference)",
90 default_model: "llama-3.3-70b",
91 requires_key: true,
92 category: ProviderCategory::Cloud,
93 },
94 ProviderOption {
95 name: "sambanova",
96 display: "SambaNova (Fast inference)",
97 default_model: "Meta-Llama-3.3-70B-Instruct",
98 requires_key: true,
99 category: ProviderCategory::Cloud,
100 },
101 ProviderOption {
102 name: "nebius",
103 display: "Nebius (GPU cloud inference)",
104 default_model: "meta-llama/Llama-3.3-70B-Instruct",
105 requires_key: true,
106 category: ProviderCategory::Cloud,
107 },
108 ProviderOption {
112 name: "xai",
113 display: "xAI (Grok)",
114 default_model: "grok-2",
115 requires_key: true,
116 category: ProviderCategory::Cloud,
117 },
118 ProviderOption {
119 name: "deepseek",
120 display: "DeepSeek (V3, R1 Reasoner)",
121 default_model: "deepseek-chat",
122 requires_key: true,
123 category: ProviderCategory::Cloud,
124 },
125 ProviderOption {
126 name: "openrouter",
127 display: "OpenRouter (Access 100+ models)",
128 default_model: "anthropic/claude-3.5-haiku",
129 requires_key: true,
130 category: ProviderCategory::Cloud,
131 },
132 ProviderOption {
133 name: "mistral",
134 display: "Mistral AI",
135 default_model: "mistral-small-latest",
136 requires_key: true,
137 category: ProviderCategory::Cloud,
138 },
139 ProviderOption {
140 name: "perplexity",
141 display: "Perplexity AI",
142 default_model: "sonar",
143 requires_key: true,
144 category: ProviderCategory::Cloud,
145 },
146 ProviderOption {
147 name: "together",
148 display: "Together AI",
149 default_model: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
150 requires_key: true,
151 category: ProviderCategory::Cloud,
152 },
153 ProviderOption {
154 name: "fireworks",
155 display: "Fireworks AI",
156 default_model: "accounts/fireworks/models/llama-v3p3-70b-instruct",
157 requires_key: true,
158 category: ProviderCategory::Cloud,
159 },
160 ProviderOption {
161 name: "replicate",
162 display: "Replicate",
163 default_model: "meta/meta-llama-3-70b-instruct",
164 requires_key: true,
165 category: ProviderCategory::Cloud,
166 },
167 ProviderOption {
171 name: "azure",
172 display: "Azure OpenAI",
173 default_model: "gpt-4o",
174 requires_key: true,
175 category: ProviderCategory::Enterprise,
176 },
177 ProviderOption {
178 name: "bedrock",
179 display: "AWS Bedrock",
180 default_model: "anthropic.claude-3-haiku-20240307-v1:0",
181 requires_key: true,
182 category: ProviderCategory::Enterprise,
183 },
184 ProviderOption {
185 name: "vertex",
186 display: "Google Vertex AI",
187 default_model: "gemini-2.5-flash-001",
188 requires_key: true,
189 category: ProviderCategory::Enterprise,
190 },
191 ProviderOption {
192 name: "cohere",
193 display: "Cohere",
194 default_model: "command-r",
195 requires_key: true,
196 category: ProviderCategory::Enterprise,
197 },
198 ProviderOption {
199 name: "ai21",
200 display: "AI21 Labs (Jamba)",
201 default_model: "jamba-1.5-mini",
202 requires_key: true,
203 category: ProviderCategory::Enterprise,
204 },
205 ProviderOption {
209 name: "siliconflow",
210 display: "SiliconFlow (China)",
211 default_model: "deepseek-ai/DeepSeek-V3",
212 requires_key: true,
213 category: ProviderCategory::Cloud,
214 },
215 ProviderOption {
216 name: "zhipu",
217 display: "Zhipu AI / ChatGLM (China)",
218 default_model: "glm-4-flash",
219 requires_key: true,
220 category: ProviderCategory::Cloud,
221 },
222 ProviderOption {
223 name: "moonshot",
224 display: "Moonshot AI / Kimi (China)",
225 default_model: "moonshot-v1-8k",
226 requires_key: true,
227 category: ProviderCategory::Cloud,
228 },
229 ProviderOption {
233 name: "jina",
234 display: "Jina AI (Embeddings & LLMs)",
235 default_model: "jina-embeddings-v3",
236 requires_key: true,
237 category: ProviderCategory::Specialized,
238 },
239 ProviderOption {
240 name: "helicone",
241 display: "Helicone (LLM Observability)",
242 default_model: "gpt-4o-mini",
243 requires_key: true,
244 category: ProviderCategory::Specialized,
245 },
246 ]
247 }
248
249 #[allow(dead_code)]
250 fn by_name(name: &str) -> Option<Self> {
251 Self::all().into_iter().find(|p| p.name == name)
252 }
253}
254
255#[derive(Clone, Copy)]
257enum CommitFormat {
258 Conventional,
259 Gitmoji,
260 Simple,
261}
262
263impl CommitFormat {
264 fn display(&self) -> &'static str {
265 match self {
266 CommitFormat::Conventional => "Conventional Commits (feat:, fix:, docs:, etc.)",
267 CommitFormat::Gitmoji => "GitMoji (✨ feat:, 🐛 fix:, 📝 docs:, etc.)",
268 CommitFormat::Simple => "Simple (no prefix)",
269 }
270 }
271
272 fn as_str(&self) -> &'static str {
273 match self {
274 CommitFormat::Conventional => "conventional",
275 CommitFormat::Gitmoji => "gitmoji",
276 CommitFormat::Simple => "simple",
277 }
278 }
279
280 fn all() -> Vec<Self> {
281 vec![
282 CommitFormat::Conventional,
283 CommitFormat::Gitmoji,
284 CommitFormat::Simple,
285 ]
286 }
287}
288
289struct LanguageOption {
291 code: &'static str,
292 display: &'static str,
293}
294
295impl LanguageOption {
296 fn all() -> Vec<Self> {
297 vec![
298 LanguageOption {
299 code: "en",
300 display: "English",
301 },
302 LanguageOption {
303 code: "zh",
304 display: "Chinese (中文)",
305 },
306 LanguageOption {
307 code: "es",
308 display: "Spanish (Español)",
309 },
310 LanguageOption {
311 code: "fr",
312 display: "French (Français)",
313 },
314 LanguageOption {
315 code: "de",
316 display: "German (Deutsch)",
317 },
318 LanguageOption {
319 code: "ja",
320 display: "Japanese (日本語)",
321 },
322 LanguageOption {
323 code: "ko",
324 display: "Korean (한국어)",
325 },
326 LanguageOption {
327 code: "ru",
328 display: "Russian (Русский)",
329 },
330 LanguageOption {
331 code: "pt",
332 display: "Portuguese (Português)",
333 },
334 LanguageOption {
335 code: "it",
336 display: "Italian (Italiano)",
337 },
338 LanguageOption {
339 code: "other",
340 display: "Other (specify)",
341 },
342 ]
343 }
344}
345
346pub async fn execute(cmd: SetupCommand) -> Result<()> {
347 print_welcome_header();
348
349 let is_advanced = if cmd.defaults {
351 return apply_defaults().await;
353 } else if cmd.advanced {
354 true
355 } else {
356 println!();
358 println!("{}", "Choose your setup mode:".bold());
359 println!();
360
361 let modes = vec![
362 "🚀 Quick Setup - Just the essentials (recommended)",
363 "⚙️ Advanced Setup - Full configuration options",
364 ];
365
366 let selection = Select::new()
367 .with_prompt("Select mode")
368 .items(&modes)
369 .default(0)
370 .interact()?;
371
372 selection == 1
373 };
374
375 if is_advanced {
376 run_advanced_setup().await
377 } else {
378 run_quick_setup().await
379 }
380}
381
382fn print_welcome_header() {
383 println!();
384 println!(
385 "{} {}",
386 "🚀".green(),
387 "Welcome to Rusty Commit Setup!".bold().white()
388 );
389 println!();
390 println!(
391 "{}",
392 " Let's get you set up with AI-powered commit messages.".dimmed()
393 );
394 println!();
395}
396
397async fn run_quick_setup() -> Result<()> {
398 let mut config = Config::load()?;
399
400 let provider = select_provider_quick()?;
402 config.ai_provider = Some(provider.name.to_string());
403 config.model = Some(provider.default_model.to_string());
404
405 if provider.requires_key {
407 let api_key = prompt_for_api_key(provider.name)?;
408 if !api_key.is_empty() {
409 config.api_key = Some(api_key);
410 }
411 } else {
412 println!();
413 println!(
414 "{} {} doesn't require an API key - great for privacy!",
415 "ℹ️".blue(),
416 provider.name.bright_cyan()
417 );
418 }
419
420 let format = select_commit_format()?;
422 config.commit_type = Some(format.as_str().to_string());
423 config.emoji = Some(matches!(format, CommitFormat::Gitmoji));
424
425 config.save()?;
427
428 print_completion_message(&config, false);
429 Ok(())
430}
431
432async fn run_advanced_setup() -> Result<()> {
433 let mut config = Config::load()?;
434
435 print_section_header("🤖 AI Provider Configuration");
437
438 let provider = select_provider_advanced()?;
439 config.ai_provider = Some(provider.name.to_string());
440
441 let default_model = provider.default_model;
443 let use_custom_model = Confirm::new()
444 .with_prompt(format!(
445 "Use default model ({}), or specify a custom one?",
446 default_model.bright_cyan()
447 ))
448 .default(true)
449 .interact()?;
450
451 if use_custom_model {
452 config.model = Some(default_model.to_string());
453 } else {
454 let custom_model: String = Input::new()
455 .with_prompt("Enter model name")
456 .default(default_model.to_string())
457 .interact()?;
458 config.model = Some(custom_model);
459 }
460
461 if provider.requires_key {
463 let api_key = prompt_for_api_key(provider.name)?;
464 if !api_key.is_empty() {
465 config.api_key = Some(api_key);
466 }
467
468 let use_custom_url = Confirm::new()
470 .with_prompt("Use a custom API endpoint URL?")
471 .default(false)
472 .interact()?;
473
474 if use_custom_url {
475 let custom_url: String = Input::new()
476 .with_prompt("Enter custom API URL")
477 .default(format!("https://api.{}.com/v1", provider.name))
478 .interact()?;
479 config.api_url = Some(custom_url);
480 }
481 }
482
483 print_section_header("📝 Commit Message Style");
485
486 let format = select_commit_format()?;
487 config.commit_type = Some(format.as_str().to_string());
488 config.emoji = Some(matches!(format, CommitFormat::Gitmoji));
489
490 config.description_capitalize = Some(
492 Confirm::new()
493 .with_prompt("Capitalize the first letter of commit messages?")
494 .default(true)
495 .interact()?,
496 );
497
498 config.description_add_period = Some(
500 Confirm::new()
501 .with_prompt("Add period at the end of commit messages?")
502 .default(false)
503 .interact()?,
504 );
505
506 let max_length: usize = Input::new()
508 .with_prompt("Maximum commit message length")
509 .default(100)
510 .validate_with(|input: &usize| -> Result<(), &str> {
511 if *input >= 50 && *input <= 200 {
512 Ok(())
513 } else {
514 Err("Please enter a value between 50 and 200")
515 }
516 })
517 .interact()?;
518 config.description_max_length = Some(max_length);
519
520 let language = select_language()?;
522 config.language = Some(language.to_string());
523
524 print_section_header("⚙️ Behavior Settings");
526
527 let generate_count: u8 = Input::new()
529 .with_prompt("Number of commit variations to generate (1-5)")
530 .default(1)
531 .validate_with(|input: &u8| -> Result<(), &str> {
532 if *input >= 1 && *input <= 5 {
533 Ok(())
534 } else {
535 Err("Please enter a value between 1 and 5")
536 }
537 })
538 .interact()?;
539 config.generate_count = Some(generate_count);
540
541 config.gitpush = Some(
543 Confirm::new()
544 .with_prompt("Automatically push commits to remote?")
545 .default(false)
546 .interact()?,
547 );
548
549 config.one_line_commit = Some(
551 Confirm::new()
552 .with_prompt("Always use one-line commits (no body)?")
553 .default(false)
554 .interact()?,
555 );
556
557 config.enable_commit_body = Some(
559 Confirm::new()
560 .with_prompt("Allow multi-line commit messages with body?")
561 .default(false)
562 .interact()?,
563 );
564
565 print_section_header("🔧 Advanced Features");
567
568 config.learn_from_history = Some(
570 Confirm::new()
571 .with_prompt("Learn commit style from repository history?")
572 .default(false)
573 .interact()?,
574 );
575
576 if config.learn_from_history == Some(true) {
577 let history_count: usize = Input::new()
578 .with_prompt("Number of commits to analyze for style")
579 .default(50)
580 .validate_with(|input: &usize| -> Result<(), &str> {
581 if *input >= 10 && *input <= 200 {
582 Ok(())
583 } else {
584 Err("Please enter a value between 10 and 200")
585 }
586 })
587 .interact()?;
588 config.history_commits_count = Some(history_count);
589 }
590
591 config.clipboard_on_timeout = Some(
593 Confirm::new()
594 .with_prompt("Copy commit message to clipboard on timeout/error?")
595 .default(true)
596 .interact()?,
597 );
598
599 config.hook_strict = Some(
601 Confirm::new()
602 .with_prompt("Strict hook mode (fail on hook errors)?")
603 .default(true)
604 .interact()?,
605 );
606
607 let hook_timeout: u64 = Input::new()
608 .with_prompt("Hook timeout (milliseconds)")
609 .default(30000)
610 .validate_with(|input: &u64| -> Result<(), &str> {
611 if *input >= 1000 && *input <= 300000 {
612 Ok(())
613 } else {
614 Err("Please enter a value between 1000 and 300000")
615 }
616 })
617 .interact()?;
618 config.hook_timeout_ms = Some(hook_timeout);
619
620 print_section_header("🎯 Token Limits (Optional)");
622
623 let configure_tokens = Confirm::new()
624 .with_prompt("Configure token limits? (Most users can skip this)")
625 .default(false)
626 .interact()?;
627
628 if configure_tokens {
629 let max_input: usize = Input::new()
630 .with_prompt("Maximum input tokens")
631 .default(4096)
632 .interact()?;
633 config.tokens_max_input = Some(max_input);
634
635 let max_output: u32 = Input::new()
636 .with_prompt("Maximum output tokens")
637 .default(500)
638 .interact()?;
639 config.tokens_max_output = Some(max_output);
640 }
641
642 config.save()?;
644
645 print_completion_message(&config, true);
646 Ok(())
647}
648
649fn select_provider_quick() -> Result<ProviderOption> {
650 println!();
651 println!("{}", "Select your AI provider:".bold());
652 println!(
653 "{}",
654 " This determines which AI will generate your commit messages.".dimmed()
655 );
656 println!();
657
658 let providers = ProviderOption::all();
659 let _popular: Vec<_> = providers
660 .iter()
661 .filter(|p| p.category == ProviderCategory::Popular)
662 .map(|p| p.display)
663 .collect();
664
665 let _local: Vec<_> = providers
666 .iter()
667 .filter(|p| p.category == ProviderCategory::Local)
668 .map(|p| p.display)
669 .collect();
670
671 let _cloud: Vec<_> = providers
672 .iter()
673 .filter(|p| p.category == ProviderCategory::Cloud)
674 .map(|p| p.display)
675 .collect();
676
677 let _enterprise: Vec<_> = providers
678 .iter()
679 .filter(|p| p.category == ProviderCategory::Enterprise)
680 .map(|p| p.display)
681 .collect();
682
683 let specialized: Vec<_> = providers
684 .iter()
685 .filter(|p| p.category == ProviderCategory::Specialized)
686 .map(|p| p.display)
687 .collect();
688
689 let mut all_displays = Vec::new();
690 let mut provider_indices: Vec<usize> = Vec::new();
691
692 all_displays.push("─── Popular Providers ───".dimmed().to_string());
694 for (idx, p) in providers.iter().enumerate() {
695 if p.category == ProviderCategory::Popular {
696 all_displays.push(p.display.to_string());
697 provider_indices.push(idx);
698 }
699 }
700
701 all_displays.push("─── Local/Private ───".dimmed().to_string());
703 for (idx, p) in providers.iter().enumerate() {
704 if p.category == ProviderCategory::Local {
705 all_displays.push(p.display.to_string());
706 provider_indices.push(idx);
707 }
708 }
709
710 all_displays.push("─── Cloud Providers ───".dimmed().to_string());
712 for (idx, p) in providers.iter().enumerate() {
713 if p.category == ProviderCategory::Cloud {
714 all_displays.push(p.display.to_string());
715 provider_indices.push(idx);
716 }
717 }
718
719 all_displays.push("─── Enterprise ───".dimmed().to_string());
721 for (idx, p) in providers.iter().enumerate() {
722 if p.category == ProviderCategory::Enterprise {
723 all_displays.push(p.display.to_string());
724 provider_indices.push(idx);
725 }
726 }
727
728 if !specialized.is_empty() {
730 all_displays.push("─── Specialized ───".dimmed().to_string());
731 for (idx, p) in providers.iter().enumerate() {
732 if p.category == ProviderCategory::Specialized {
733 all_displays.push(p.display.to_string());
734 provider_indices.push(idx);
735 }
736 }
737 }
738
739 let selection = Select::new()
740 .with_prompt("AI Provider")
741 .items(&all_displays)
742 .default(1) .interact()?;
744
745 let header_count = all_displays[..=selection]
747 .iter()
748 .filter(|s| s.starts_with('─'))
749 .count();
750
751 let provider_idx = if selection > 0 {
752 selection.saturating_sub(header_count)
753 } else {
754 0
755 };
756
757 let provider = if provider_idx < provider_indices.len() {
758 providers
759 .into_iter()
760 .nth(provider_indices[provider_idx])
761 .unwrap()
762 } else {
763 providers
765 .into_iter()
766 .find(|p| p.category == ProviderCategory::Popular)
767 .unwrap()
768 };
769
770 println!();
771 println!(
772 "{} Selected: {} {}",
773 "✓".green(),
774 provider.name.bright_cyan(),
775 format!("(model: {})", provider.default_model).dimmed()
776 );
777
778 Ok(provider)
779}
780
781fn select_provider_advanced() -> Result<ProviderOption> {
782 println!();
783 println!("{}", "Select your AI provider:".bold());
784 println!();
785
786 let providers = ProviderOption::all();
787 let items: Vec<_> = providers.iter().map(|p| p.display).collect();
788
789 let selection = Select::new()
790 .with_prompt("AI Provider")
791 .items(&items)
792 .default(0)
793 .interact()?;
794
795 let provider = providers.into_iter().nth(selection).unwrap();
796
797 println!();
798 println!("{} Selected: {}", "✓".green(), provider.name.bright_cyan());
799
800 Ok(provider)
801}
802
803fn prompt_for_api_key(provider_name: &str) -> Result<String> {
804 println!();
805 println!("{}", "API Key Configuration".bold());
806 println!(
807 "{}",
808 format!(
809 " Get your API key from the {} dashboard",
810 provider_name.bright_cyan()
811 )
812 .dimmed()
813 );
814 println!(
815 "{}",
816 " Your key will be stored securely in your system's keychain.".dimmed()
817 );
818 println!();
819
820 let api_key: String = Input::new()
821 .with_prompt(format!(
822 "Enter your {} API key",
823 provider_name.bright_cyan()
824 ))
825 .allow_empty(true)
826 .interact()?;
827
828 let trimmed = api_key.trim();
829
830 if trimmed.is_empty() {
831 println!();
832 println!(
833 "{} No API key provided. You can set it later with: {}",
834 "⚠️".yellow(),
835 "rco config set RCO_API_KEY=<your_key>".bright_cyan()
836 );
837 } else {
838 let masked = if trimmed.len() > 4 {
840 format!("{}****", &trimmed[trimmed.len() - 4..])
841 } else {
842 "****".to_string()
843 };
844 println!();
845 println!("{} API key saved: {}", "✓".green(), masked.dimmed());
846 }
847
848 Ok(trimmed.to_string())
849}
850
851fn select_commit_format() -> Result<CommitFormat> {
852 println!();
853 println!("{}", "Commit Message Format".bold());
854 println!(
855 "{}",
856 " Choose how your commit messages should be formatted.".dimmed()
857 );
858 println!();
859
860 let formats = CommitFormat::all();
861 let items: Vec<_> = formats.iter().map(|f| f.display()).collect();
862
863 let selection = Select::new()
864 .with_prompt("Commit format")
865 .items(&items)
866 .default(0)
867 .interact()?;
868
869 let format = formats.into_iter().nth(selection).unwrap();
870
871 println!();
872 println!(
873 "{} Selected: {}",
874 "✓".green(),
875 format.as_str().bright_cyan()
876 );
877
878 let example = match format {
880 CommitFormat::Conventional => "feat(auth): Add login functionality",
881 CommitFormat::Gitmoji => "✨ feat(auth): Add login functionality",
882 CommitFormat::Simple => "Add login functionality",
883 };
884 println!(" Example: {}", example.dimmed());
885
886 Ok(format)
887}
888
889fn select_language() -> Result<String> {
890 println!();
891 println!("{}", "Output Language".bold());
892 println!(
893 "{}",
894 " What language should commit messages be generated in?".dimmed()
895 );
896 println!();
897
898 let languages = LanguageOption::all();
899 let items: Vec<_> = languages.iter().map(|l| l.display).collect();
900
901 let selection = Select::new()
902 .with_prompt("Language")
903 .items(&items)
904 .default(0)
905 .interact()?;
906
907 let lang = &languages[selection];
908
909 if lang.code == "other" {
910 let custom: String = Input::new()
911 .with_prompt("Enter language code (e.g., 'nl' for Dutch)")
912 .interact()?;
913 Ok(custom)
914 } else {
915 Ok(lang.code.to_string())
916 }
917}
918
919fn print_section_header(title: &str) {
920 println!();
921 println!(
922 "{}",
923 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".dimmed()
924 );
925 println!("{}", title.bold());
926 println!(
927 "{}",
928 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".dimmed()
929 );
930}
931
932fn print_completion_message(config: &Config, is_advanced: bool) {
933 println!();
934 println!(
935 "{}",
936 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".dimmed()
937 );
938 println!();
939 println!("{} Setup complete! 🎉", "✓".green().bold());
940 println!();
941
942 println!("{}", "Configuration Summary:".bold());
944 println!();
945
946 if let Some(provider) = &config.ai_provider {
947 println!(" {} Provider: {}", "•".cyan(), provider.bright_white());
948 }
949 if let Some(model) = &config.model {
950 println!(" {} Model: {}", "•".cyan(), model.bright_white());
951 }
952 if let Some(commit_type) = &config.commit_type {
953 println!(
954 " {} Commit format: {}",
955 "•".cyan(),
956 commit_type.bright_white()
957 );
958 }
959 if let Some(language) = &config.language {
960 if language != "en" {
961 println!(" {} Language: {}", "•".cyan(), language.bright_white());
962 }
963 }
964
965 println!();
966 println!("{} You're ready to go!", "→".cyan());
967 println!();
968 println!(" Try it now: {}", "rco".bold().bright_cyan().underline());
969 println!();
970
971 if is_advanced {
972 println!(" Make a commit: {}", "git add . && rco".dimmed());
973 println!();
974 println!(
975 "{} Modify settings anytime: {}",
976 "→".cyan(),
977 "rco setup --advanced".bright_cyan()
978 );
979 println!(
980 "{} Or use: {}",
981 "→".cyan(),
982 "rco config set <key>=<value>".bright_cyan()
983 );
984 } else {
985 println!(
986 " Want more options? Run: {}",
987 "rco setup --advanced".bright_cyan()
988 );
989 }
990
991 println!();
992 println!(
993 "{}",
994 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".dimmed()
995 );
996}
997
998async fn apply_defaults() -> Result<()> {
999 let mut config = Config::load()?;
1000
1001 config.ai_provider = Some("openai".to_string());
1003 config.model = Some("gpt-4o-mini".to_string());
1004 config.commit_type = Some("conventional".to_string());
1005 config.description_capitalize = Some(true);
1006 config.description_add_period = Some(false);
1007 config.description_max_length = Some(100);
1008 config.language = Some("en".to_string());
1009 config.generate_count = Some(1);
1010 config.emoji = Some(false);
1011 config.gitpush = Some(false);
1012 config.one_line_commit = Some(false);
1013 config.enable_commit_body = Some(false);
1014 config.learn_from_history = Some(false);
1015 config.clipboard_on_timeout = Some(true);
1016 config.hook_strict = Some(true);
1017 config.hook_timeout_ms = Some(30000);
1018 config.tokens_max_input = Some(4096);
1019 config.tokens_max_output = Some(500);
1020
1021 config.save()?;
1022
1023 println!();
1024 println!("{} Default configuration applied!", "✓".green().bold());
1025 println!();
1026 println!(" Provider: openai (gpt-4o-mini)");
1027 println!(" Format: conventional commits");
1028 println!();
1029 println!(
1030 " Set your API key: {}",
1031 "rco config set RCO_API_KEY=<your_key>".bright_cyan()
1032 );
1033 println!();
1034
1035 Ok(())
1036}