use crate::agent::{AgentDaemon, DaemonConfig, DaemonManager};
use crate::cli::AgentCommands;
use anyhow::{anyhow, Result};
use std::path::PathBuf;
use std::time::Duration;
use tokio::fs;
use tracing::{error, info, warn};
#[derive(Debug, Clone)]
struct AgentStartConfig {
_project_path: PathBuf,
config_path: Option<PathBuf>,
working_dir: Option<PathBuf>,
pid_file: Option<PathBuf>,
log_file: Option<PathBuf>,
foreground: bool,
health_interval: u64,
max_memory_mb: u64,
auto_restart: bool,
}
pub async fn handle_agent_command(command: AgentCommands) -> Result<()> {
match command {
AgentCommands::Start {
project_path,
config,
working_dir,
pid_file,
log_file,
foreground,
health_interval,
max_memory_mb,
no_auto_restart,
} => {
let start_config = AgentStartConfig {
_project_path: project_path,
config_path: config,
working_dir,
pid_file,
log_file,
foreground,
health_interval,
max_memory_mb,
auto_restart: !no_auto_restart,
};
handle_agent_start(start_config).await
}
AgentCommands::Stop {
pid_file,
force,
timeout,
} => handle_agent_stop(pid_file, force, timeout).await,
AgentCommands::Status { pid_file, format } => handle_agent_status(pid_file, format).await,
AgentCommands::Monitor {
project_path,
project_id,
thresholds,
} => handle_agent_monitor(project_path, project_id, thresholds).await,
AgentCommands::Unmonitor { project_id } => handle_agent_unmonitor(project_id).await,
AgentCommands::Health { pid_file, detailed } => {
handle_agent_health(pid_file, detailed).await
}
AgentCommands::Reload { pid_file, config } => handle_agent_reload(pid_file, config).await,
AgentCommands::QualityGate {
project,
file,
format,
} => handle_agent_quality_gate(project, file, format).await,
AgentCommands::McpServer { config, debug } => handle_agent_mcp_server(config, debug).await,
}
}
async fn handle_agent_start(config: AgentStartConfig) -> Result<()> {
info!("Starting Claude Code Agent daemon");
check_daemon_not_running().await?;
let foreground = config.foreground;
let daemon_config = prepare_daemon_config(config).await?;
let daemon = AgentDaemon::new(daemon_config);
start_daemon_with_mode(daemon, foreground).await
}
async fn check_daemon_not_running() -> Result<()> {
if DaemonManager::is_running().await {
Err(anyhow!(
"Agent daemon is already running. Use 'pmat agent stop' to stop it first."
))
} else {
Ok(())
}
}
async fn prepare_daemon_config(config: AgentStartConfig) -> Result<DaemonConfig> {
let mut daemon_config = load_or_default_config(&config.config_path).await?;
apply_config_overrides(&mut daemon_config, &config);
Ok(daemon_config)
}
async fn load_or_default_config(config_path: &Option<PathBuf>) -> Result<DaemonConfig> {
if let Some(path) = config_path {
load_daemon_config(path).await
} else {
Ok(DaemonConfig::default())
}
}
fn apply_config_overrides(daemon_config: &mut DaemonConfig, config: &AgentStartConfig) {
daemon_config.daemon.health_check_interval = Duration::from_secs(config.health_interval);
daemon_config.daemon.max_memory_mb = config.max_memory_mb;
daemon_config.daemon.auto_restart = config.auto_restart;
apply_optional_overrides(daemon_config, config);
}
fn apply_optional_overrides(daemon_config: &mut DaemonConfig, config: &AgentStartConfig) {
if let Some(working_dir) = &config.working_dir {
daemon_config.daemon.working_directory = working_dir.clone();
}
if let Some(pid_file) = &config.pid_file {
daemon_config.daemon.pid_file = Some(pid_file.clone());
}
if let Some(log_file) = &config.log_file {
daemon_config.daemon.log_file = Some(log_file.clone());
}
}
async fn start_daemon_with_mode(mut daemon: AgentDaemon, foreground: bool) -> Result<()> {
if foreground {
info!("Starting daemon in foreground mode");
daemon.start().await
} else {
start_background_mode(daemon).await
}
}
async fn start_background_mode(mut daemon: AgentDaemon) -> Result<()> {
info!("Starting daemon in background mode");
warn!("Background mode not fully implemented, running in foreground");
daemon.start().await
}
async fn handle_agent_stop(_pid_file: Option<PathBuf>, _force: bool, _timeout: u64) -> Result<()> {
info!("Stopping Claude Code Agent daemon");
if !DaemonManager::is_running().await {
warn!("Agent daemon is not running");
return Ok(());
}
match DaemonManager::shutdown().await {
Ok(()) => {
info!("Agent daemon shut down successfully");
}
Err(e) => {
error!("Failed to shut down daemon: {}", e);
return Err(anyhow::anyhow!("Failed to shut down daemon: {e}"));
}
}
Ok(())
}
async fn handle_agent_status(
_pid_file: Option<PathBuf>,
format: crate::cli::OutputFormat,
) -> Result<()> {
info!("Checking Claude Code Agent daemon status");
let is_running = DaemonManager::is_running().await;
match format {
crate::cli::OutputFormat::Json => {
let status = serde_json::json!({
"running": is_running,
"timestamp": chrono::Utc::now().to_rfc3339(),
"version": env!("CARGO_PKG_VERSION")
});
println!("{}", serde_json::to_string_pretty(&status)?);
}
_ => {
if is_running {
println!("✅ Claude Code Agent daemon is running");
} else {
println!("❌ Claude Code Agent daemon is not running");
}
}
}
Ok(())
}
async fn handle_agent_monitor(
project_path: PathBuf,
project_id: Option<String>,
_thresholds: Option<PathBuf>,
) -> Result<()> {
let project_id = project_id.unwrap_or_else(|| {
project_path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string()
});
info!(
"Starting monitoring for project '{}' at {:?}",
project_id, project_path
);
if !DaemonManager::is_running().await {
return Err(anyhow!(
"Agent daemon is not running. Start it first with 'pmat agent start'"
));
}
match DaemonManager::start_monitoring(&project_path, &project_id).await {
Ok(()) => {
info!("Project monitoring command sent to daemon successfully");
println!(
"✅ Started monitoring project '{project_id}' at {project_path:?}"
);
}
Err(e) => {
error!("Failed to start monitoring: {}", e);
return Err(anyhow::anyhow!("Failed to start monitoring: {e}"));
}
}
Ok(())
}
async fn handle_agent_unmonitor(project_id: String) -> Result<()> {
info!("Stopping monitoring for project '{}'", project_id);
if !DaemonManager::is_running().await {
return Err(anyhow!(
"Agent daemon is not running. Start it first with 'pmat agent start'"
));
}
match DaemonManager::stop_monitoring(&project_id).await {
Ok(()) => {
info!("Stop monitoring command sent to daemon successfully");
println!("✅ Stopped monitoring project '{project_id}'");
}
Err(e) => {
error!("Failed to stop monitoring: {}", e);
return Err(anyhow::anyhow!("Failed to stop monitoring: {e}"));
}
}
Ok(())
}
async fn handle_agent_health(_pid_file: Option<PathBuf>, detailed: bool) -> Result<()> {
if !DaemonManager::is_running().await {
println!("❌ Agent daemon is not running");
return Ok(());
}
if detailed {
match DaemonManager::get_health_info().await {
Ok(health_info) => {
println!("{}", serde_json::to_string_pretty(&health_info)?);
}
Err(e) => {
error!("Failed to get detailed health info: {}", e);
let basic_health = serde_json::json!({
"status": "running",
"error": format!("Could not get detailed info: {}", e),
"last_check": chrono::Utc::now().to_rfc3339()
});
println!("{}", serde_json::to_string_pretty(&basic_health)?);
}
}
} else {
println!("✅ Agent daemon is healthy");
}
Ok(())
}
async fn handle_agent_reload(
_pid_file: Option<PathBuf>,
config_path: Option<PathBuf>,
) -> Result<()> {
info!("Reloading agent daemon configuration");
if !DaemonManager::is_running().await {
return Err(anyhow!(
"Agent daemon is not running. Start it first with 'pmat agent start'"
));
}
if let Some(ref path) = config_path {
info!("Loading configuration from {:?}", path);
let _config = load_daemon_config(path).await?;
}
match DaemonManager::reload_config(config_path.as_ref()).await {
Ok(()) => {
info!("Configuration reload command sent to daemon successfully");
println!("✅ Configuration reloaded successfully");
}
Err(e) => {
error!("Failed to reload configuration: {}", e);
return Err(anyhow::anyhow!("Failed to reload configuration: {e}"));
}
}
Ok(())
}
async fn handle_agent_quality_gate(
project: String,
_file: Option<PathBuf>,
_format: crate::cli::QualityGateOutputFormat,
) -> Result<()> {
info!("Running quality gate for project '{}'", project);
if !DaemonManager::is_running().await {
return Err(anyhow!(
"Agent daemon is not running. Start it first with 'pmat agent start'"
));
}
match DaemonManager::run_quality_gate(&project).await {
Ok(result) => {
info!("Quality gate command sent to daemon successfully");
println!("✅ Quality gate completed");
if let Some(violations) = result.violations {
if violations > 0 {
println!("⚠️ Found {violations} quality violations");
}
}
}
Err(e) => {
error!("Failed to run quality gate: {}", e);
return Err(anyhow::anyhow!("Failed to run quality gate: {e}"));
}
}
Ok(())
}
async fn handle_agent_mcp_server(config_path: Option<PathBuf>, debug: bool) -> Result<()> {
if debug {
eprintln!("Starting MCP server in debug mode");
}
let daemon_config = if let Some(config_path) = config_path {
load_daemon_config(&config_path).await?
} else {
DaemonConfig::default()
};
let mut mcp_server = crate::agent::ClaudeCodeAgentMcpServer::new(daemon_config.agent);
if debug {
eprintln!("MCP Server starting on stdio transport...");
eprintln!("Server capabilities:");
eprintln!(" - start_quality_monitoring: Start monitoring a project");
eprintln!(" - run_quality_gates: Execute quality gates");
eprintln!(" - analyze_complexity: Analyze code complexity");
eprintln!(" - health_check: Check system health");
eprintln!();
eprintln!("Ready for MCP client connections via stdio...");
}
mcp_server.start_stdio().await
}
async fn load_daemon_config(config_path: &PathBuf) -> Result<DaemonConfig> {
if !config_path.exists() {
return Err(anyhow!("Configuration file not found: {config_path:?}"));
}
let config_content = fs::read_to_string(config_path).await?;
let config: DaemonConfig = toml::from_str(&config_content)
.or_else(|_| serde_json::from_str(&config_content))
.map_err(|e| anyhow!("Failed to parse configuration file: {e}"))?;
Ok(config)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[tokio::test]
async fn test_load_daemon_config_missing_file() {
let temp_dir = tempdir().unwrap();
let config_path = temp_dir.path().join("nonexistent.toml");
let result = load_daemon_config(&config_path).await;
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Configuration file not found"));
}
#[tokio::test]
async fn test_daemon_status_json_format() {
let result = handle_agent_status(None, crate::cli::OutputFormat::Json).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_agent_monitor_with_default_project_id() {
let temp_dir = tempdir().unwrap();
let project_path = temp_dir.path().to_path_buf();
let result = handle_agent_monitor(project_path, None, None).await;
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Agent daemon is not running"));
}
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}