use crate::utils::config::Config;
use anyhow::Result;
use colored::Colorize as _;
use dialoguer::Select;
pub async fn execute(config: Config, quiet: bool) -> Result<()> {
let profiles_dir = config.profiles_dir();
if !profiles_dir.exists() {
anyhow::bail!(
"No profiles directory found. Create a profile first with: codexctl save <name>"
);
}
let mut entries = tokio::fs::read_dir(&profiles_dir).await?;
let mut profiles = Vec::new();
while let Some(entry) = entries.next_entry().await? {
let path = entry.path();
if path.is_dir() {
let name = path
.file_name()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_default();
if name.starts_with('.') || name == "backups" {
continue;
}
let meta_path = path.join("profile.json");
let description = if meta_path.exists() {
let content = tokio::fs::read_to_string(&meta_path).await.ok();
content.and_then(|c| {
serde_json::from_str::<serde_json::Value>(&c)
.ok()
.and_then(|v| {
v.get("description")
.and_then(|d| d.as_str())
.map(ToString::to_string)
})
})
} else {
None
};
profiles.push((name, description));
}
}
if profiles.is_empty() {
anyhow::bail!("No profiles found. Create one with: codexctl save <name>");
}
profiles.sort_by(|a, b| a.0.cmp(&b.0));
let display_items: Vec<String> = profiles
.iter()
.map(|(name, desc)| {
desc.as_ref().map_or_else(
|| name.bold().to_string(),
|d| format!("{} - {}", name.bold(), d.dimmed()),
)
})
.collect();
let selection = Select::new()
.with_prompt("Select profile to switch to")
.items(&display_items)
.default(0)
.interact_opt()?;
match selection {
Some(index) => {
let (profile_name, _) = &profiles[index];
if !quiet {
println!(
"{} Switching to profile: {}",
"→".cyan(),
profile_name.green()
);
}
crate::commands::load::execute(config, profile_name.clone(), true, false, quiet, None)
.await?;
}
None => {
println!("Cancelled");
}
}
Ok(())
}