use super::*;
use roboticus_core::{ProfileEntry, ProfileRegistry, home_dir};
pub fn cmd_profile_list() -> Result<(), Box<dyn std::error::Error>> {
let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
let (OK, ACTION, WARN, DETAIL, ERR) = icons();
let registry = ProfileRegistry::load()?;
let profiles = registry.list();
heading("Installed Profiles");
if profiles.is_empty() {
empty_state("No profiles found. Run `roboticus profile create <name>` to add one.");
return Ok(());
}
let widths = [20, 24, 12, 8];
table_header(&["ID", "Name", "Source", "Active"], &widths);
for (id, entry) in &profiles {
let active_str = if entry.active {
format!("{GREEN}{OK}{RESET}")
} else {
String::new()
};
let source = entry.source.as_deref().unwrap_or("manual");
table_row(
&[
format!("{ACCENT}{id}{RESET}"),
entry.name.clone(),
source.to_string(),
active_str,
],
&widths,
);
}
eprintln!();
eprintln!(" {DIM}{} profile(s){RESET}", profiles.len());
eprintln!();
Ok(())
}
pub fn cmd_profile_create(
name: &str,
display_name: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
let (OK, ACTION, WARN, DETAIL, ERR) = icons();
validate_profile_id(name)?;
let mut registry = ProfileRegistry::load()?;
if registry.profiles.contains_key(name) {
return Err(format!("profile '{name}' already exists").into());
}
let display = display_name.unwrap_or(name).to_string();
let rel_path = format!("profiles/{name}");
let entry = ProfileEntry {
name: display.clone(),
description: None,
path: rel_path.clone(),
active: false,
installed_at: Some(chrono_now()),
version: None,
source: Some("manual".to_string()),
};
registry.profiles.insert(name.to_string(), entry);
registry.save()?;
registry.ensure_profile_dir(name)?;
eprintln!(" {GREEN}{OK}{RESET} Created profile {ACCENT}{name}{RESET} ({display})");
eprintln!(
" {DIM}Directory: {}{RESET}",
home_dir().join(".roboticus").join(&rel_path).display()
);
eprintln!(
" {DIM}Run {BOLD}roboticus profile switch {name}{RESET}{DIM} to activate it.{RESET}"
);
eprintln!();
Ok(())
}
pub fn cmd_profile_switch(name: &str) -> Result<(), Box<dyn std::error::Error>> {
let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
let (OK, ACTION, WARN, DETAIL, ERR) = icons();
let mut registry = ProfileRegistry::load()?;
if !registry.profiles.contains_key(name) {
return Err(format!(
"profile '{name}' not found. Run `roboticus profile list` to see available profiles."
)
.into());
}
for (id, entry) in registry.profiles.iter_mut() {
entry.active = id == name;
}
registry.save()?;
eprintln!(" {GREEN}{OK}{RESET} Switched to profile {ACCENT}{name}{RESET}");
eprintln!(" {DIM}Restart the daemon for the change to take effect.{RESET}");
eprintln!();
Ok(())
}
pub fn cmd_profile_delete(name: &str, keep_data: bool) -> Result<(), Box<dyn std::error::Error>> {
let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
let (OK, ACTION, WARN, DETAIL, ERR) = icons();
if name == "default" {
return Err("cannot delete the built-in 'default' profile".into());
}
let mut registry = ProfileRegistry::load()?;
let entry = registry
.profiles
.get(name)
.cloned()
.ok_or_else(|| format!("profile '{name}' not found"))?;
if entry.active {
return Err(format!(
"profile '{name}' is currently active. Switch to a different profile first."
)
.into());
}
let profile_dir = registry.resolve_config_dir(name)?;
registry.profiles.remove(name);
registry.save()?;
if !keep_data && profile_dir.exists() {
let safe_root = home_dir().join(".roboticus").join("profiles");
if profile_dir.starts_with(&safe_root) {
std::fs::remove_dir_all(&profile_dir).map_err(|e| {
format!(
"removed from registry but could not delete directory {}: {e}",
profile_dir.display()
)
})?;
eprintln!(" {GREEN}{OK}{RESET} Deleted profile {ACCENT}{name}{RESET} and its data");
} else {
eprintln!(
" {WARN} Removed profile {ACCENT}{name}{RESET} from registry. \
Directory {} is outside the managed tree and was not removed.",
profile_dir.display()
);
}
} else {
eprintln!(
" {GREEN}{OK}{RESET} Removed profile {ACCENT}{name}{RESET} from registry \
(data kept at {})",
profile_dir.display()
);
}
eprintln!();
Ok(())
}
fn validate_profile_id(name: &str) -> Result<(), Box<dyn std::error::Error>> {
if name.is_empty() {
return Err("profile name must not be empty".into());
}
if !name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
return Err(
"profile name may only contain ASCII letters, digits, hyphens, and underscores".into(),
);
}
Ok(())
}
fn chrono_now() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
format!("{secs}")
}