From cf0a25af1d2834bfe6c5ea892ce5769936e5a673 Mon Sep 17 00:00:00 2001 From: soryu Date: Tue, 3 Feb 2026 22:01:29 +0000 Subject: Add makima chain mechanism --- makima/src/bin/makima.rs | 216 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 215 insertions(+), 1 deletion(-) (limited to 'makima/src/bin/makima.rs') diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs index af9832b..2037b47 100644 --- a/makima/src/bin/makima.rs +++ b/makima/src/bin/makima.rs @@ -6,7 +6,8 @@ use std::sync::Arc; use makima::daemon::api::{ApiClient, CreateContractRequest}; use makima::daemon::cli::{ - Cli, CliConfig, Commands, ConfigCommand, ContractCommand, SupervisorCommand, ViewArgs, + Cli, CliConfig, Commands, ConfigCommand, ContractCommand, ChainCommand, + SupervisorCommand, ViewArgs, }; use makima::daemon::tui::{self, Action, App, ListItem, ViewType, TuiWsClient, WsEvent, OutputLine, OutputMessageType, WsConnectionState, RepositorySuggestion}; use makima::daemon::config::{DaemonConfig, RepoEntry}; @@ -30,6 +31,7 @@ async fn main() -> Result<(), Box> { Commands::Contract(cmd) => run_contract(cmd).await, Commands::View(args) => run_view(args).await, Commands::Config(cmd) => run_config(cmd).await, + Commands::Chain(cmd) => run_chain(cmd).await, } } @@ -793,6 +795,218 @@ async fn run_config(cmd: ConfigCommand) -> Result<(), Box Result<(), Box> { + use makima::daemon::chain::{parse_chain_file, validate_dag, ChainRunner}; + + match cmd { + ChainCommand::Run(args) => { + eprintln!("Loading chain from: {}", args.file.display()); + + // Load and validate chain + let chain = parse_chain_file(&args.file)?; + validate_dag(&chain)?; + + if args.dry_run { + eprintln!("\n=== DRY RUN - No changes will be made ===\n"); + } + + let runner = ChainRunner::new(args.common.api_url.clone(), args.common.api_key.clone()); + + // Show execution order + let order = runner.get_execution_order(&chain)?; + eprintln!("Execution order:"); + for (i, name) in order.iter().enumerate() { + eprintln!(" {}. {}", i + 1, name); + } + eprintln!(); + + // Show visualization + eprintln!("{}", runner.visualize_dag(&chain)); + + if args.dry_run { + eprintln!("\n=== DRY RUN COMPLETE ==="); + let request = runner.to_create_request(&chain); + println!("{}", serde_json::to_string_pretty(&request)?); + } else { + // Create chain via API + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + let request = runner.to_create_request(&chain); + let result = client.create_chain(request).await?; + println!("{}", serde_json::to_string(&result.0)?); + } + } + ChainCommand::Status(args) => { + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + let result = client.get_chain(args.chain_id).await?; + println!("{}", serde_json::to_string(&result.0)?); + } + ChainCommand::List(args) => { + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + let result = client.list_chains(args.status.as_deref(), args.limit).await?; + println!("{}", serde_json::to_string(&result.0)?); + } + ChainCommand::Contracts(args) => { + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + let result = client.get_chain_contracts(args.chain_id).await?; + println!("{}", serde_json::to_string(&result.0)?); + } + ChainCommand::Graph(args) => { + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + let result = client.get_chain_graph(args.chain_id).await?; + + // Get the graph data + if args.with_status { + // Enhanced ASCII visualization with status + if let Some(nodes) = result.0.get("nodes").and_then(|v| v.as_array()) { + let mut by_depth: std::collections::HashMap> = + std::collections::HashMap::new(); + + for node in nodes { + let name = node.get("name").and_then(|v| v.as_str()).unwrap_or("?"); + let status = node + .get("status") + .and_then(|v| v.as_str()) + .unwrap_or("pending"); + let depth = node.get("depth").and_then(|v| v.as_i64()).unwrap_or(0) as i32; + by_depth.entry(depth).or_default().push((name, status)); + } + + let chain_name = result + .0 + .get("name") + .and_then(|v| v.as_str()) + .unwrap_or("Chain"); + println!("Chain: {}", chain_name); + println!(); + + let max_depth = by_depth.keys().max().copied().unwrap_or(0); + for depth in 0..=max_depth { + if let Some(contracts) = by_depth.get(&depth) { + let indent = " ".repeat(depth as usize); + for (name, status) in contracts { + let status_icon = match *status { + "completed" | "done" => "\u{2713}", + "active" | "running" | "in_progress" => "\u{21bb}", + "failed" | "error" => "\u{2717}", + _ => "\u{25cb}", + }; + println!("{}[{}] {} {}", indent, name, status_icon, status); + } + if depth < max_depth { + println!("{} |", indent); + println!("{} v", indent); + } + } + } + } + } else { + // Simple JSON output + println!("{}", serde_json::to_string_pretty(&result.0)?); + } + } + ChainCommand::Validate(args) => { + eprintln!("Validating chain file: {}", args.file.display()); + + match parse_chain_file(&args.file) { + Ok(chain) => { + match validate_dag(&chain) { + Ok(()) => { + eprintln!("\u{2713} Chain definition is valid"); + eprintln!(" Name: {}", chain.name); + eprintln!(" Contracts: {}", chain.contracts.len()); + + // Show any warnings + for contract in &chain.contracts { + if contract.tasks.is_none() || contract.tasks.as_ref().map(|t| t.is_empty()).unwrap_or(true) { + eprintln!(" \u{26a0} Contract '{}' has no tasks", contract.name); + } + } + + println!(r#"{{"valid": true, "name": "{}", "contractCount": {}}}"#, + chain.name, chain.contracts.len()); + } + Err(e) => { + eprintln!("\u{2717} DAG validation failed: {}", e); + println!(r#"{{"valid": false, "error": "{}"}}"#, e); + std::process::exit(1); + } + } + } + Err(e) => { + eprintln!("\u{2717} Parse error: {}", e); + println!(r#"{{"valid": false, "error": "{}"}}"#, e); + std::process::exit(1); + } + } + } + ChainCommand::Preview(args) => { + eprintln!("Previewing chain: {}", args.file.display()); + + let chain = parse_chain_file(&args.file)?; + validate_dag(&chain)?; + + let runner = ChainRunner::new(String::new(), String::new()); + + // Show chain info + println!("Chain: {}", chain.name); + if let Some(desc) = &chain.description { + println!("Description: {}", desc); + } + if let Some(repo) = &chain.repository_url { + println!("Repository: {}", repo); + } + println!(); + + // Show execution order + let order = runner.get_execution_order(&chain)?; + println!("Execution Order:"); + for (i, name) in order.iter().enumerate() { + let contract = chain.contracts.iter().find(|c| c.name == *name).unwrap(); + let deps = contract + .depends_on + .as_ref() + .map(|d| d.join(", ")) + .unwrap_or_else(|| "(none)".to_string()); + let task_count = contract.tasks.as_ref().map(|t| t.len()).unwrap_or(0); + println!( + " {}. {} [type: {}, tasks: {}, depends: {}]", + i + 1, + name, + contract.contract_type, + task_count, + deps + ); + } + println!(); + + // Show DAG visualization + println!("{}", runner.visualize_dag(&chain)); + + // Show loop config if enabled + if let Some(lc) = &chain.loop_config { + if lc.enabled { + println!("\nLoop Configuration:"); + println!(" Max iterations: {}", lc.max_iterations); + if let Some(check) = &lc.progress_check { + println!(" Progress check: {}", check); + } + } + } + } + ChainCommand::Archive(args) => { + let client = ApiClient::new(args.common.api_url, args.common.api_key)?; + eprintln!("Archiving chain {}...", args.chain_id); + let result = client.archive_chain(args.chain_id).await?; + println!("{}", serde_json::to_string(&result.0)?); + } + } + + Ok(()) +} + /// Load contracts from API async fn load_contracts(client: &ApiClient) -> Result, Box> { let result = client.list_contracts().await?; -- cgit v1.2.3