summaryrefslogtreecommitdiff
path: root/makima/src/bin
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-03 22:01:29 +0000
committersoryu <soryu@soryu.co>2026-02-03 22:01:37 +0000
commitcf0a25af1d2834bfe6c5ea892ce5769936e5a673 (patch)
tree476ba326ac1752281a441b5c17d2b3be4b23a2a9 /makima/src/bin
parent8361916ce67f3d2ba191ebf27cb50e79cb42e39c (diff)
downloadsoryu-cf0a25af1d2834bfe6c5ea892ce5769936e5a673.tar.gz
soryu-cf0a25af1d2834bfe6c5ea892ce5769936e5a673.zip
Add makima chain mechanism
Diffstat (limited to 'makima/src/bin')
-rw-r--r--makima/src/bin/makima.rs216
1 files changed, 215 insertions, 1 deletions
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<dyn std::error::Error + Send + Sync>> {
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<dyn std::error::Error
}
}
+/// Run chain commands.
+async fn run_chain(
+ cmd: ChainCommand,
+) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+ 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<i32, Vec<(&str, &str)>> =
+ 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<Vec<ListItem>, Box<dyn std::error::Error + Send + Sync>> {
let result = client.list_contracts().await?;