clawgarden-cli 0.8.2

ClawGarden CLI - Multi-bot/multi-agent Garden management tool
//! Agent subcommands โ€” garden agent add/list/remove/edit
//!
//! Standalone CLI commands for managing agents in a garden,
//! decoupled from the interactive `garden config` wizard.

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;

/// Run `garden agent add`
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));

    // โ”€โ”€ 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()
    })?;

    // Check for duplicate
    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!();

    // โ”€โ”€ Telegram bot token โ”€โ”€
    let token = ui::retry_prompt(|| {
        Password::new("  Telegram bot token:")
            .without_confirmation()
            .with_help_message("Get this from @BotFather on Telegram")
            .prompt()
    })?;

    // โ”€โ”€ Confirm โ”€โ”€
    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(());
    }

    // โ”€โ”€ Save โ”€โ”€
    let mut bots = current_bots;
    bots.push(BotConfig {
        name: bot_name.clone(),
        token,
        username: String::new(),
        priority: 100,
        enabled: true,
    });

    save_updated_config(&name, &bots, &current_providers)?;

    println!();
    ui::success(&format!(
        "Agent '{}' added to garden '{}'.",
        bot_name, name
    ));
    ui::hint(&format!(
        "Run `garden up {}` to apply changes.",
        name
    ));

    Ok(())
}

/// Run `garden agent list`
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(())
}

/// Run `garden agent edit` โ€” edit token of an existing agent
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(());
    }

    // Show current agents
    println!();
    println!("  {} Registered agents:", "\x1b[2m");
    for (i, bot) in current_bots.iter().enumerate() {
        println!(
            "    {} {}. {}",
            "\x1b[2m",
            i + 1,
            bot.name,
        );
    }
    println!("{}", "\x1b[0m");

    // Select agent
    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 = &current_bots[idx];
    let bot_name = entry.name.clone();
    let old_token = entry.token.clone();

    // Show current values
    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!();

    // Edit token
    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()
    })?;

    // Summary
    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(());
    }

    // Apply โ€” preserve username/priority/enabled from existing bot
    let old_username = current_bots[idx].username.clone();
    let old_priority = current_bots[idx].priority;
    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,
        priority: old_priority,
        enabled: old_enabled,
    };

    save_updated_config(&name, &bots, &current_providers)?;

    println!();
    ui::success(&format!(
        "Agent '{}' updated in garden '{}'.",
        bot_name, name
    ));
    ui::hint(&format!(
        "Run `garden up {}` to apply changes.",
        name
    ));

    Ok(())
}

/// Run `garden agent remove`
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(());
    }

    // If agent name given via CLI arg, use it directly; otherwise prompt
    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 {
        // Interactive selection
        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()
    };

    // Confirm
    let confirm = ui::retry_prompt(|| {
        Confirm::new(&format!("  Remove agent '{}'?", to_remove))
            .with_default(false)
            .prompt()
    })?;

    if !confirm {
        ui::warn("Cancelled.");
        return Ok(());
    }

    // Remove and save
    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, &current_providers)?;

    println!();
    ui::success(&format!(
        "Agent '{}' removed from garden '{}'.",
        to_remove, name
    ));
    ui::hint(&format!(
        "Run `garden up {}` to apply changes.",
        name
    ));

    Ok(())
}