use anyhow::Result;
use inquire::{Confirm, Password, Select, Text};
use crate::compose::BotConfig;
use crate::config::{load_current_config, save_updated_config};
use crate::garden;
use crate::ui;
pub fn cmd_add(garden_name: Option<&str>) -> Result<()> {
let name = garden::resolve_garden_name(garden_name)?;
println!();
ui::section_header_no_step("๐ค", &format!("Add Agent ยท {}", name));
let bot_name = ui::retry_prompt(|| {
Text::new(" Agent name (e.g. alex):")
.with_validator(|input: &str| {
if input.is_empty() {
return Err("Please enter a name".into());
}
if input.contains(' ') {
return Err("No spaces allowed".into());
}
Ok(inquire::validator::Validation::Valid)
})
.with_help_message("This will be used as the bot identifier internally")
.prompt()
})?;
let (current_bots, current_providers) = load_current_config(&name)?;
if current_bots.iter().any(|b| b.name == bot_name) {
anyhow::bail!("Agent '{}' already exists in garden '{}'", bot_name, name);
}
println!();
let token = ui::retry_prompt(|| {
Password::new(" Telegram bot token:")
.without_confirmation()
.with_help_message("Get this from @BotFather on Telegram")
.prompt()
})?;
let token_preview = if token.len() > 8 { &token[..8] } else { &token };
println!();
ui::success(&format!(
"{} with token {}...",
bot_name,
token_preview,
));
let confirm = ui::retry_prompt(|| {
Confirm::new(" Add this agent?")
.with_default(true)
.prompt()
})?;
if !confirm {
ui::warn("Cancelled.");
return Ok(());
}
let mut bots = current_bots;
bots.push(BotConfig {
name: bot_name.clone(),
token,
username: String::new(),
enabled: true,
});
save_updated_config(&name, &bots, ¤t_providers)?;
println!();
ui::success(&format!(
"Agent '{}' added to garden '{}'.",
bot_name, name
));
ui::hint(&format!(
"Run `garden up {}` to apply changes.",
name
));
Ok(())
}
pub fn cmd_list(garden_name: Option<&str>) -> Result<()> {
let name = garden::resolve_garden_name(garden_name)?;
let (bots, providers) = load_current_config(&name)?;
println!();
ui::section_header_no_step("๐", &format!("Agents ยท {}", name));
if bots.is_empty() {
println!();
ui::warn("No agents registered yet.");
println!();
ui::hint(&format!("Add one with: garden agent add {}", name));
println!();
return Ok(());
}
let mut rows = vec![(
"๐ค".to_string(),
"Agents".to_string(),
format!("{} registered", bots.len()),
)];
for (i, bot) in bots.iter().enumerate() {
rows.push((
format!(" {}.", i + 1),
bot.name.clone(),
if bot.enabled { "โ enabled" } else { "โ disabled" }.to_string(),
));
}
rows.push((
"๐".to_string(),
"Providers".to_string(),
format!("{} configured", providers.len()),
));
ui::summary_box(&format!("๐ฑ {} โ Agents", name), &rows);
Ok(())
}
pub fn cmd_edit(garden_name: Option<&str>) -> Result<()> {
let name = garden::resolve_garden_name(garden_name)?;
println!();
ui::section_header_no_step("โ๏ธ", &format!("Edit Agent ยท {}", name));
let (current_bots, current_providers) = load_current_config(&name)?;
if current_bots.is_empty() {
println!();
ui::warn("No agents registered.");
ui::hint(&format!("Add one with: garden agent add {}", name));
return Ok(());
}
println!();
println!(" {} Registered agents:", "\x1b[2m");
for (i, bot) in current_bots.iter().enumerate() {
println!(
" {} {}. {}",
"\x1b[2m",
i + 1,
bot.name,
);
}
println!("{}", "\x1b[0m");
let bot_labels: Vec<String> = current_bots
.iter()
.map(|b| b.name.clone())
.collect();
let selection = ui::retry_prompt(|| {
Select::new(" Select an agent to edit:", bot_labels.clone()).prompt()
})?;
let idx = current_bots
.iter()
.position(|b| b.name == selection)
.ok_or_else(|| anyhow::anyhow!("Agent '{}' not found", selection))?;
let entry = ¤t_bots[idx];
let bot_name = entry.name.clone();
let old_token = entry.token.clone();
println!();
ui::divider();
println!(" {} โ current configuration", bot_name);
println!();
let masked_token = if old_token.len() > 8 {
format!("{}...", &old_token[..8])
} else {
"****".to_string()
};
ui::hint(&format!(" Token: {}", masked_token));
println!();
let new_token = ui::retry_prompt(|| {
Password::new(&format!(" Enter new token for {}:", bot_name))
.without_confirmation()
.with_help_message("Get this from @BotFather on Telegram")
.prompt()
})?;
println!();
let new_masked = if new_token.len() > 8 {
format!("{}...", &new_token[..8])
} else {
"****".to_string()
};
ui::success(&format!("Token: updated to {}", new_masked));
let confirm =
ui::retry_prompt(|| Confirm::new(" Apply changes?").with_default(true).prompt())?;
if !confirm {
ui::warn("Cancelled.");
return Ok(());
}
let old_username = current_bots[idx].username.clone();
let old_enabled = current_bots[idx].enabled;
let mut bots = current_bots;
bots[idx] = BotConfig {
name: bot_name.clone(),
token: new_token,
username: old_username,
enabled: old_enabled,
};
save_updated_config(&name, &bots, ¤t_providers)?;
println!();
ui::success(&format!(
"Agent '{}' updated in garden '{}'.",
bot_name, name
));
ui::hint(&format!(
"Run `garden up {}` to apply changes.",
name
));
Ok(())
}
pub fn cmd_remove(garden_name: Option<&str>, agent_name: Option<&str>) -> Result<()> {
let name = garden::resolve_garden_name(garden_name)?;
println!();
ui::section_header_no_step("๐๏ธ", &format!("Remove Agent ยท {}", name));
let (current_bots, current_providers) = load_current_config(&name)?;
if current_bots.is_empty() {
println!();
ui::warn("No agents registered.");
return Ok(());
}
let to_remove = if let Some(agent) = agent_name {
if !current_bots.iter().any(|b| b.name == agent) {
anyhow::bail!(
"Agent '{}' not found in garden '{}'. Registered: {}",
agent,
name,
current_bots
.iter()
.map(|b| b.name.as_str())
.collect::<Vec<_>>()
.join(", ")
);
}
agent.to_string()
} else {
println!();
println!(" {} Registered agents:", "\x1b[2m");
for (i, bot) in current_bots.iter().enumerate() {
println!(
" {} {}. {}",
"\x1b[2m",
i + 1,
bot.name,
);
}
println!("{}", "\x1b[0m");
let bot_names: Vec<&str> = current_bots.iter().map(|b| b.name.as_str()).collect();
let selection = ui::retry_prompt(|| {
Select::new(" Select agent to remove:", bot_names.to_vec()).prompt()
})?;
selection.to_string()
};
let confirm = ui::retry_prompt(|| {
Confirm::new(&format!(" Remove agent '{}'?", to_remove))
.with_default(false)
.prompt()
})?;
if !confirm {
ui::warn("Cancelled.");
return Ok(());
}
let bots: Vec<BotConfig> = current_bots
.into_iter()
.filter(|b| b.name != to_remove)
.collect();
if bots.is_empty() {
ui::warn("Garden will have no agents after removal.");
let proceed =
ui::retry_prompt(|| Confirm::new(" Continue?").with_default(false).prompt())?;
if !proceed {
ui::warn("Cancelled.");
return Ok(());
}
}
save_updated_config(&name, &bots, ¤t_providers)?;
println!();
ui::success(&format!(
"Agent '{}' removed from garden '{}'.",
to_remove, name
));
ui::hint(&format!(
"Run `garden up {}` to apply changes.",
name
));
Ok(())
}