diff options
Diffstat (limited to 'makima/src/bin/makima.rs')
| -rw-r--r-- | makima/src/bin/makima.rs | 187 |
1 files changed, 186 insertions, 1 deletions
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs index ac577b8..c5b7d37 100644 --- a/makima/src/bin/makima.rs +++ b/makima/src/bin/makima.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use makima::daemon::api::{ApiClient, CreateContractRequest}; use makima::daemon::cli::{ - Cli, CliConfig, Commands, ConfigCommand, ContractCommand, RedTeamCommand, SupervisorCommand, ViewArgs, + Cli, CliConfig, Commands, ConfigCommand, ContractCommand, ContractsCommand, RedTeamCommand, SupervisorCommand, ViewArgs, }; use makima::daemon::tui::{self, Action, App, ListItem, ViewType, TuiWsClient, WsEvent, OutputLine, OutputMessageType, WsConnectionState, RepositorySuggestion}; use makima::daemon::config::{DaemonConfig, RepoEntry}; @@ -28,6 +28,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { Commands::Daemon(args) => run_daemon(args).await, Commands::Supervisor(cmd) => run_supervisor(cmd).await, Commands::Contract(cmd) => run_contract(cmd).await, + Commands::Contracts(cmd) => run_contracts(cmd).await, Commands::View(args) => run_view(args).await, Commands::Config(cmd) => run_config(cmd).await, Commands::RedTeam(cmd) => run_red_team(cmd).await, @@ -805,6 +806,190 @@ async fn run_red_team(cmd: RedTeamCommand) -> Result<(), Box<dyn std::error::Err } } +/// Run contracts management helper commands. +async fn run_contracts( + cmd: ContractsCommand, +) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { + match cmd { + ContractsCommand::Pause(args) => { + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + eprintln!("Pausing contract {}...", args.contract_id); + let result = client.pause_contract(args.contract_id, args.reason).await?; + println!("{}", serde_json::to_string_pretty(&result.0)?); + } + ContractsCommand::Resume(args) => { + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + eprintln!("Resuming contract {}...", args.contract_id); + let result = client.resume_contract(args.contract_id).await?; + println!("{}", serde_json::to_string_pretty(&result.0)?); + } + ContractsCommand::Advance(args) => { + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + if args.force { + eprintln!( + "Advancing contract {} to phase '{}' (forced)...", + args.contract_id, args.phase + ); + } else { + eprintln!( + "Advancing contract {} to phase '{}'...", + args.contract_id, args.phase + ); + } + let result = client + .advance_contract_phase(args.contract_id, &args.phase, args.force) + .await?; + println!("{}", serde_json::to_string_pretty(&result.0)?); + } + ContractsCommand::RestartSupervisor(args) => { + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + eprintln!("Restarting supervisor for contract {}...", args.contract_id); + match client.restart_supervisor(args.contract_id).await { + Ok(result) => { + println!("{}", serde_json::to_string_pretty(&result.0)?); + } + Err(e) => { + // If the endpoint doesn't exist, provide a helpful message + eprintln!("Error: {}", e); + eprintln!(); + eprintln!("Note: Supervisor restart may not be implemented yet."); + eprintln!("As a workaround, you can pause and resume the contract:"); + eprintln!(" makima contracts pause {}", args.contract_id); + eprintln!(" makima contracts resume {}", args.contract_id); + std::process::exit(1); + } + } + } + ContractsCommand::Show(args) => { + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + let result = client.get_contract(args.contract_id).await?; + + if args.verbose { + // Full JSON output for verbose mode + println!("{}", serde_json::to_string_pretty(&result.0)?); + } else { + // Formatted summary for normal mode + let contract = &result.0.get("contract"); + let tasks = result.0.get("tasks").and_then(|v| v.as_array()); + let files = result.0.get("files").and_then(|v| v.as_array()); + let repos = result.0.get("repositories").and_then(|v| v.as_array()); + + if let Some(c) = contract { + println!("Contract: {}", c.get("name").and_then(|v| v.as_str()).unwrap_or("Unknown")); + println!(" ID: {}", c.get("id").and_then(|v| v.as_str()).unwrap_or("-")); + println!(" Status: {}", c.get("status").and_then(|v| v.as_str()).unwrap_or("-")); + println!(" Phase: {}", c.get("phase").and_then(|v| v.as_str()).unwrap_or("-")); + println!(" Type: {}", c.get("contractType").and_then(|v| v.as_str()).unwrap_or("-")); + if let Some(desc) = c.get("description").and_then(|v| v.as_str()) { + println!(" Description: {}", desc); + } + println!(); + println!(" Repositories: {}", repos.map(|r| r.len()).unwrap_or(0)); + println!(" Files: {}", files.map(|f| f.len()).unwrap_or(0)); + println!(" Tasks: {}", tasks.map(|t| t.len()).unwrap_or(0)); + + if let Some(tasks) = tasks { + if !tasks.is_empty() { + println!(); + println!(" Task Summary:"); + for task in tasks.iter().take(10) { + let name = task.get("name").and_then(|v| v.as_str()).unwrap_or("Unknown"); + let status = task.get("status").and_then(|v| v.as_str()).unwrap_or("-"); + let is_supervisor = task.get("isSupervisor").and_then(|v| v.as_bool()).unwrap_or(false); + let role = if is_supervisor { " [supervisor]" } else { "" }; + println!(" - {} ({}){}", name, status, role); + } + if tasks.len() > 10 { + println!(" ... and {} more", tasks.len() - 10); + } + } + } + } else { + println!("{}", serde_json::to_string_pretty(&result.0)?); + } + } + } + ContractsCommand::Health(args) => { + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + + // Get contract details + let contract = client.get_contract(args.contract_id).await?; + + // Try to get health endpoint, fall back to constructing from contract data + match client.get_contract_health(args.contract_id).await { + Ok(result) => { + println!("{}", serde_json::to_string_pretty(&result.0)?); + } + Err(_) => { + // Health endpoint may not exist, construct from contract data + let c = contract.0.get("contract"); + let tasks = contract.0.get("tasks").and_then(|v| v.as_array()); + + let status = c + .and_then(|c| c.get("status")) + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + + let phase = c + .and_then(|c| c.get("phase")) + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + + let supervisor_task_id = c + .and_then(|c| c.get("supervisorTaskId")) + .and_then(|v| v.as_str()); + + // Count tasks by status + let mut running = 0; + let mut pending = 0; + let mut done = 0; + let mut failed = 0; + let mut supervisor_status = "unknown"; + + if let Some(tasks) = tasks { + for task in tasks { + let task_status = task.get("status").and_then(|v| v.as_str()).unwrap_or(""); + let task_id = task.get("id").and_then(|v| v.as_str()); + + // Check if this is the supervisor + if task_id == supervisor_task_id { + supervisor_status = task_status; + } + + match task_status { + "running" | "working" => running += 1, + "pending" | "queued" => pending += 1, + "done" | "completed" | "merged" => done += 1, + "failed" | "error" => failed += 1, + _ => {} + } + } + } + + let health = serde_json::json!({ + "contractId": args.contract_id, + "status": status, + "phase": phase, + "supervisorStatus": supervisor_status, + "taskCounts": { + "running": running, + "pending": pending, + "done": done, + "failed": failed, + "total": running + pending + done + failed + }, + "healthy": status == "active" && (supervisor_status == "running" || supervisor_status == "working") + }); + + println!("{}", serde_json::to_string_pretty(&health)?); + } + } + } + } + + Ok(()) +} + /// Load contracts from API async fn load_contracts(client: &ApiClient) -> Result<Vec<ListItem>, Box<dyn std::error::Error + Send + Sync>> { let result = client.list_contracts().await?; |
