agentlog 0.1.2

CLI flight recorder for AI coding agents - capture, store, and replay AI sessions
Documentation
use crate::core::config::Config;
use crate::core::policy::{PolicyAction, PolicyEngine, PolicyRule};
use crate::core::{agentlog_path, ensure_initialized};
use anyhow::Result;
use colored::Colorize;

pub async fn list() -> Result<()> {
    let project_root = ensure_initialized()?;
    let config_path = agentlog_path(&project_root).join("config.json");

    let config = if config_path.exists() {
        Config::load(&config_path)?
    } else {
        Config::default()
    };

    println!("{}", "AgentLog Policy Rules".bold().underline());
    println!();
    println!(
        "Policy enforcement: {}",
        if config.policy.enabled {
            "ENABLED".green()
        } else {
            "DISABLED".yellow()
        }
    );
    println!();

    if config.policy.rules.is_empty() {
        println!("{}", "No policy rules configured.".dimmed());
    } else {
        for rule in &config.policy.rules {
            let status = if rule.enabled {
                "".green()
            } else {
                "".dimmed()
            };
            let action = match rule.action {
                PolicyAction::Block => "BLOCK".red(),
                PolicyAction::Warn => "WARN".yellow(),
                PolicyAction::RequireConfirmation => "CONFIRM".cyan(),
                PolicyAction::Allow => "ALLOW".green(),
            };

            println!("{} {}", status, rule.name.bold());
            println!("  Pattern: {}", rule.pattern.cyan());
            println!("  Action: {}", action);
            println!("  Description: {}", rule.description);
            println!();
        }
    }

    Ok(())
}

pub async fn add(
    name: String,
    pattern: String,
    action: String,
    description: Option<String>,
) -> Result<()> {
    let project_root = ensure_initialized()?;
    let config_path = agentlog_path(&project_root).join("config.json");

    let mut config = if config_path.exists() {
        Config::load(&config_path)?
    } else {
        Config::default()
    };

    let action = parse_action(&action)?;

    let rule = PolicyRule {
        name: name.clone(),
        description: description.unwrap_or_else(|| format!("Custom rule for {}", pattern)),
        pattern,
        regex: None,
        action,
        enabled: true,
    };

    // Check if rule already exists
    if let Some(existing) = config.policy.rules.iter_mut().find(|r| r.name == name) {
        *existing = rule;
        println!("{}", format!("Updated rule: {}", name).yellow());
    } else {
        config.policy.rules.push(rule);
        println!("{}", format!("Added rule: {}", name).green());
    }

    config.save(&config_path)?;
    println!("Configuration saved.");

    Ok(())
}

pub async fn remove(name: String) -> Result<()> {
    let project_root = ensure_initialized()?;
    let config_path = agentlog_path(&project_root).join("config.json");

    let mut config = if config_path.exists() {
        Config::load(&config_path)?
    } else {
        Config::default()
    };

    let initial_len = config.policy.rules.len();
    config.policy.rules.retain(|r| r.name != name);

    if config.policy.rules.len() < initial_len {
        config.save(&config_path)?;
        println!("{}", format!("Removed rule: {}", name).green());
    } else {
        println!("{}", format!("Rule not found: {}", name).yellow());
    }

    Ok(())
}

pub async fn toggle(enabled: bool) -> Result<()> {
    let project_root = ensure_initialized()?;
    let config_path = agentlog_path(&project_root).join("config.json");

    let mut config = if config_path.exists() {
        Config::load(&config_path)?
    } else {
        Config::default()
    };

    config.policy.enabled = enabled;
    config.save(&config_path)?;

    if enabled {
        println!("{}", "Policy enforcement ENABLED".green());
    } else {
        println!("{}", "Policy enforcement DISABLED".yellow());
    }

    Ok(())
}

pub async fn check(file_path: String) -> Result<()> {
    let project_root = ensure_initialized()?;
    let config_path = agentlog_path(&project_root).join("config.json");

    let config = if config_path.exists() {
        Config::load(&config_path)?
    } else {
        Config::default()
    };

    let engine = PolicyEngine::new(config.policy)?;
    let path = std::path::Path::new(&file_path);

    println!("{}", format!("Checking: {}", file_path).bold());
    println!();

    if let Some(violation) = engine.check_file(path) {
        println!("{}", violation.format_message());
    } else {
        println!("{}", "No policy violations found.".green());
    }

    Ok(())
}

fn parse_action(action: &str) -> Result<PolicyAction> {
    match action.to_lowercase().as_str() {
        "block" => Ok(PolicyAction::Block),
        "warn" => Ok(PolicyAction::Warn),
        "confirm" | "require_confirmation" => Ok(PolicyAction::RequireConfirmation),
        "allow" => Ok(PolicyAction::Allow),
        _ => Err(anyhow::anyhow!(
            "Invalid action: {}. Use: block, warn, confirm, allow",
            action
        )),
    }
}