Skip to content

Commit abfa8de

Browse files
committed
✨ ✨ Overhaul config display to show all providers with resolved defaults and key status
Redesign print_configuration() to show complete provider information: - Display all known providers, not just those with API keys set - Resolve and show effective model/fast model (including defaults) - Show context window size with custom-override indicator - Show API key status: masked key, env var source, or missing with the expected env var name; warn on bad key format - Add theme name, config file path (~/…), and project config status - Truncate long custom instructions to 3-line preview Extract print_provider_section(), format_token_count(), and mask_api_key() helpers. Rename get_config_path() to get_personal_config_path() and make it public for path display.
1 parent e28883d commit abfa8de

File tree

2 files changed

+175
-54
lines changed

2 files changed

+175
-54
lines changed

src/commands.rs

Lines changed: 172 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -518,8 +518,6 @@ fn display_project_config_result(
518518
fn print_configuration(config: &Config) {
519519
let purple = colors::accent_primary();
520520
let cyan = colors::accent_secondary();
521-
let coral = colors::accent_tertiary();
522-
let yellow = colors::warning();
523521
let green = colors::success();
524522
let dim = colors::text_secondary();
525523
let dim_sep = colors::text_dim();
@@ -539,6 +537,11 @@ fn print_configuration(config: &Config) {
539537
print_section_header("GLOBAL");
540538

541539
print_config_row("Provider", &config.default_provider, cyan, true);
540+
541+
// Theme
542+
let theme = crate::theme::current();
543+
print_config_row("Theme", &theme.meta.name, purple, false);
544+
542545
print_config_row(
543546
"Gitmoji",
544547
if config.use_gitmoji {
@@ -549,79 +552,197 @@ fn print_configuration(config: &Config) {
549552
if config.use_gitmoji { green } else { dim },
550553
false,
551554
);
552-
print_config_row("Preset", &config.instruction_preset, yellow, false);
555+
print_config_row("Preset", &config.instruction_preset, dim, false);
553556
print_config_row(
554-
"Subagent Timeout",
557+
"Timeout",
555558
&format!("{}s", config.subagent_timeout_secs),
556-
coral,
559+
dim,
557560
false,
558561
);
559562

563+
// Config file paths
564+
if let Ok(config_path) = Config::get_personal_config_path() {
565+
let home = dirs::home_dir()
566+
.map(|h| h.to_string_lossy().to_string())
567+
.unwrap_or_default();
568+
let path_str = config_path.to_string_lossy().to_string();
569+
let path_display = if home.is_empty() {
570+
path_str
571+
} else {
572+
path_str.replace(&home, "~")
573+
};
574+
print_config_row("Config", &path_display, dim, false);
575+
}
576+
577+
// Project config status
578+
if let Ok(project_path) = Config::get_project_config_path()
579+
&& project_path.exists()
580+
{
581+
print_config_row("Project", ".irisconfig ✓", green, false);
582+
}
583+
560584
// Custom Instructions (if any)
561585
if !config.instructions.is_empty() {
562586
println!();
563587
print_section_header("INSTRUCTIONS");
564-
for line in config.instructions.lines() {
565-
println!(" {}", line.truecolor(dim.0, dim.1, dim.2).italic());
588+
// Truncate long instructions for display
589+
let preview: String = config.instructions.lines().take(3).collect::<Vec<_>>().join("\n");
590+
for line in preview.lines() {
591+
println!(" {}", line.truecolor(dim.0, dim.1, dim.2).italic());
592+
}
593+
let total_lines = config.instructions.lines().count();
594+
if total_lines > 3 {
595+
println!(
596+
" {}",
597+
format!("… ({} more lines)", total_lines - 3)
598+
.truecolor(dim_sep.0, dim_sep.1, dim_sep.2)
599+
.italic()
600+
);
566601
}
567602
}
568603

569-
// Show all configured providers
570-
// For personal configs: show only those with API keys
571-
// For project configs: show all providers (they never have API keys)
572-
let mut providers: Vec<_> = config
573-
.providers
574-
.iter()
575-
.filter(|(_, cfg)| config.is_project_config || !cfg.api_key.is_empty())
576-
.collect();
577-
providers.sort_by_key(|(name, _)| name.as_str());
604+
// Show ALL providers — active provider first, then rest alphabetically
605+
let mut provider_names: Vec<String> = Provider::ALL.iter().map(|p| p.name().to_string()).collect();
606+
provider_names.sort();
607+
// Move active provider to front
608+
if let Some(pos) = provider_names.iter().position(|n| n == &config.default_provider) {
609+
let active = provider_names.remove(pos);
610+
provider_names.insert(0, active);
611+
}
578612

579-
for (provider_name, provider_config) in providers {
613+
for provider_name in &provider_names {
580614
println!();
581-
let is_active = provider_name == &config.default_provider;
582-
let header = if is_active {
583-
format!("{} ✦", provider_name.to_uppercase())
584-
} else {
585-
provider_name.to_uppercase()
586-
};
587-
print_section_header(&header);
615+
print_provider_section(config, provider_name);
616+
}
617+
618+
println!();
619+
println!(
620+
"{}",
621+
"─".repeat(44).truecolor(dim_sep.0, dim_sep.1, dim_sep.2)
622+
);
623+
println!();
624+
}
588625

589-
// Model
590-
print_config_row("Model", &provider_config.model, cyan, true);
626+
/// Print a single provider section with all its details
627+
fn print_provider_section(config: &Config, provider_name: &str) {
628+
let cyan = colors::accent_secondary();
629+
let coral = colors::accent_tertiary();
630+
let yellow = colors::warning();
631+
let green = colors::success();
632+
let dim = colors::text_secondary();
633+
let error_red: (u8, u8, u8) = (255, 99, 99);
591634

592-
// Fast Model
593-
let fast_model = provider_config.fast_model.as_deref().unwrap_or("(default)");
594-
print_config_row("Fast Model", fast_model, cyan, false);
635+
let is_active = provider_name == config.default_provider;
636+
let provider: Option<Provider> = provider_name.parse().ok();
595637

596-
// Token Limit
597-
if let Some(limit) = provider_config.token_limit {
598-
print_config_row("Token Limit", &limit.to_string(), coral, false);
638+
let header = if is_active {
639+
format!("{} ✦", provider_name.to_uppercase())
640+
} else {
641+
provider_name.to_uppercase()
642+
};
643+
print_section_header(&header);
644+
645+
let provider_config = config.providers.get(provider_name);
646+
647+
// Model (resolve effective model)
648+
let model = provider_config
649+
.and_then(|pc| provider.map(|p| pc.effective_model(p).to_string()))
650+
.or_else(|| provider.map(|p| p.default_model().to_string()))
651+
.unwrap_or_default();
652+
print_config_row("Model", &model, cyan, is_active);
653+
654+
// Fast Model (resolve effective)
655+
let fast_model = provider_config
656+
.and_then(|pc| provider.map(|p| pc.effective_fast_model(p).to_string()))
657+
.or_else(|| provider.map(|p| p.default_fast_model().to_string()))
658+
.unwrap_or_default();
659+
print_config_row("Fast Model", &fast_model, dim, false);
660+
661+
// Context window
662+
if let Some(p) = provider {
663+
let effective_limit = provider_config
664+
.map_or_else(|| p.context_window(), |pc| pc.effective_token_limit(p));
665+
let limit_str = format_token_count(effective_limit);
666+
let is_custom = provider_config.and_then(|pc| pc.token_limit).is_some();
667+
if is_custom {
668+
print_config_row("Context", &format!("{limit_str} (custom)"), coral, false);
669+
} else {
670+
print_config_row("Context", &limit_str, dim, false);
599671
}
672+
}
673+
674+
// API Key status
675+
if let Some(p) = provider {
676+
let has_config_key = provider_config.is_some_and(ProviderConfig::has_api_key);
677+
let has_env_key = std::env::var(p.api_key_env()).is_ok();
678+
let env_var = p.api_key_env();
600679

601-
// Additional Parameters
602-
if !provider_config.additional_params.is_empty() {
680+
let (status, status_color) = if has_config_key {
681+
// Safe: has_config_key guarantees provider_config is Some with a key
682+
let key = &provider_config.expect("checked above").api_key;
683+
let masked = mask_api_key(key);
684+
(format!("✓ {masked}"), green)
685+
} else if has_env_key {
686+
(format!("✓ ${env_var}"), green)
687+
} else {
688+
(format!("✗ not set → ${env_var}"), error_red)
689+
};
690+
print_config_row("API Key", &status, status_color, false);
691+
692+
// Show format warning if key exists but has bad format
693+
let key_value = if has_config_key {
694+
provider_config.map(|pc| pc.api_key.clone())
695+
} else if has_env_key {
696+
std::env::var(p.api_key_env()).ok()
697+
} else {
698+
None
699+
};
700+
if let Some(ref key) = key_value
701+
&& let Err(warning) = p.validate_api_key_format(key)
702+
{
603703
println!(
604-
" {} {}",
605-
"Params".truecolor(dim.0, dim.1, dim.2),
606-
"─".truecolor(dim_sep.0, dim_sep.1, dim_sep.2)
704+
" {}",
705+
format!("⚠ {warning}").truecolor(yellow.0, yellow.1, yellow.2)
607706
);
608-
for (key, value) in &provider_config.additional_params {
609-
println!(
610-
" {} {} {}",
611-
key.truecolor(cyan.0, cyan.1, cyan.2),
612-
"→".truecolor(dim_sep.0, dim_sep.1, dim_sep.2),
613-
value.truecolor(dim.0, dim.1, dim.2)
614-
);
615-
}
616707
}
617708
}
618709

619-
println!();
620-
println!(
621-
"{}",
622-
"─".repeat(40).truecolor(dim_sep.0, dim_sep.1, dim_sep.2)
623-
);
624-
println!();
710+
// Additional Parameters
711+
if let Some(pc) = provider_config
712+
&& !pc.additional_params.is_empty()
713+
{
714+
for (key, value) in &pc.additional_params {
715+
print_config_row(key, value, dim, false);
716+
}
717+
}
718+
}
719+
720+
/// Format a token count in human-readable form (128K, 200K, 1M)
721+
fn format_token_count(count: usize) -> String {
722+
if count >= 1_000_000 && count.is_multiple_of(1_000_000) {
723+
format!("{}M tokens", count / 1_000_000)
724+
} else if count >= 1_000 {
725+
format!("{}K tokens", count / 1_000)
726+
} else {
727+
format!("{count} tokens")
728+
}
729+
}
730+
731+
/// Mask an API key for display, showing only prefix and last 4 chars
732+
fn mask_api_key(key: &str) -> String {
733+
if key.len() <= 8 {
734+
return "••••".to_string();
735+
}
736+
// Show the prefix (e.g. "sk-ant-") and last 4 chars
737+
let prefix_end = key.find('-').map_or(4, |i| {
738+
// Find the last hyphen in the prefix portion (first 12 chars)
739+
key[..12.min(key.len())]
740+
.rfind('-')
741+
.map_or(i + 1, |j| j + 1)
742+
});
743+
let prefix = &key[..prefix_end.min(key.len())];
744+
let suffix = &key[key.len() - 4..];
745+
format!("{prefix}••••{suffix}")
625746
}
626747

627748
/// Print a section header in `SilkCircuit` style

src/config.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ impl Default for Config {
114114
impl Config {
115115
/// Load configuration (personal + project overlay)
116116
pub fn load() -> Result<Self> {
117-
let config_path = Self::get_config_path()?;
117+
let config_path = Self::get_personal_config_path()?;
118118
let mut config = if config_path.exists() {
119119
let content = fs::read_to_string(&config_path)?;
120120
let config: Self = toml::from_str(&content)?;
@@ -242,7 +242,7 @@ impl Config {
242242
return Ok(());
243243
}
244244

245-
let config_path = Self::get_config_path()?;
245+
let config_path = Self::get_personal_config_path()?;
246246
let content = toml::to_string_pretty(self)?;
247247
Self::write_config_file(&config_path, &content)?;
248248
log_debug!("Configuration saved");
@@ -297,7 +297,7 @@ impl Config {
297297
}
298298

299299
/// Get path to personal config file
300-
fn get_config_path() -> Result<PathBuf> {
300+
pub fn get_personal_config_path() -> Result<PathBuf> {
301301
let mut path =
302302
config_dir().ok_or_else(|| anyhow!("Unable to determine config directory"))?;
303303
path.push("git-iris");

0 commit comments

Comments
 (0)