#![allow(dead_code)]
mod config;
mod provider;
mod skill;
mod plugin;
mod agent;
mod channel;
mod gateway;
mod server;
mod store;
mod cron;
mod hooks;
mod utils;
use anyhow::Result;
use clap::{Parser, Subcommand};
use tracing_subscriber::{fmt, EnvFilter};
#[derive(Parser)]
#[command(name = "rsclaw", version, about = "Rust multi-agent collaboration framework")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Run {
#[arg(long)]
config: Option<String>,
},
Doctor {
#[arg(long, default_value_t = false)]
fix: bool,
},
Init {
#[arg(long, default_value_t = false)]
force: bool,
},
Config,
Agent {
#[command(subcommand)]
action: AgentAction,
},
Skill {
#[command(subcommand)]
action: SkillAction,
},
Plugin {
#[command(subcommand)]
action: PluginAction,
},
Cron {
#[command(subcommand)]
action: CronAction,
},
Hook {
#[command(subcommand)]
action: HookAction,
},
}
#[derive(Subcommand)]
enum AgentAction {
List,
Create {
name: String,
},
Chat {
name: String,
message: String,
},
Destroy {
name: String,
},
}
#[derive(Subcommand)]
enum SkillAction {
List,
Pull {
name: String,
},
Run {
name: String,
args: Vec<String>,
},
}
#[derive(Subcommand)]
enum PluginAction {
List,
Load {
name: String,
},
Run {
plugin: String,
method: String,
params: Option<String>,
},
}
#[derive(Subcommand)]
enum CronAction {
List,
Add {
expression: String,
target: String,
description: String,
},
Run {
id: String,
},
Delete {
id: String,
},
}
#[derive(Subcommand)]
enum HookAction {
List,
Trigger {
event: String,
},
}
static AGENT_MANAGER: std::sync::OnceLock<agent::AgentManager> = std::sync::OnceLock::new();
fn get_manager() -> &'static agent::AgentManager {
AGENT_MANAGER.get_or_init(|| {
let config = config::loader::ConfigLoader::load().unwrap_or_default();
agent::AgentManager::new(config.memory.max_concurrent_agents)
})
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_target(false)
.init();
let cli = Cli::parse();
match cli.command {
Commands::Run { config } => cmd_run(config).await,
Commands::Doctor { fix } => cmd_doctor(fix).await,
Commands::Init { force } => cmd_init(force).await,
Commands::Config => cmd_config().await,
Commands::Agent { action } => match action {
AgentAction::List => cmd_agent_list().await,
AgentAction::Create { name } => cmd_agent_create(name).await,
AgentAction::Chat { name, message } => cmd_agent_chat(name, message).await,
AgentAction::Destroy { name } => cmd_agent_destroy(name).await,
},
Commands::Skill { action } => match action {
SkillAction::List => cmd_skill_list().await,
SkillAction::Pull { name } => cmd_skill_pull(name).await,
SkillAction::Run { name, args } => cmd_skill_run(name, args).await,
},
Commands::Plugin { action } => match action {
PluginAction::List => cmd_plugin_list().await,
PluginAction::Load { name } => cmd_plugin_load(name).await,
PluginAction::Run { plugin, method, params } => cmd_plugin_run(plugin, method, params).await,
},
Commands::Cron { action } => match action {
CronAction::List => cmd_cron_list().await,
CronAction::Add { expression, target, description } => cmd_cron_add(expression, target, description).await,
CronAction::Run { id } => cmd_cron_run(id).await,
CronAction::Delete { id } => cmd_cron_delete(id).await,
},
Commands::Hook { action } => match action {
HookAction::List => cmd_hook_list().await,
HookAction::Trigger { event } => cmd_hook_trigger(event).await,
},
}
}
async fn cmd_run(config_path: Option<String>) -> Result<()> {
use config::loader::ConfigLoader;
use gateway::Gateway;
use server::Server;
let config = if let Some(path) = config_path {
let path = std::path::Path::new(&path);
if path.ends_with(".toml") {
ConfigLoader::load_toml(path)?
} else {
ConfigLoader::load_json5(path)?
}
} else {
ConfigLoader::load()?
};
let host = config.gateway.host.to_string();
let port = config.gateway.port;
let max_concurrent = config.memory.max_concurrent_agents;
let gateway = Gateway::new(max_concurrent);
let server = Server::new(gateway, &host, port);
server.start().await
}
async fn cmd_doctor(fix: bool) -> Result<()> {
use config::loader::ConfigLoader;
use config::validator::ConfigValidator;
let config = ConfigLoader::load()?;
let issues = ConfigValidator::validate(&config)?;
if issues.is_empty() {
println!("Configuration is valid.");
return Ok(());
}
println!("Found {} configuration issues:", issues.len());
for (i, issue) in issues.iter().enumerate() {
println!(" {}. {}", i + 1, issue);
}
if fix {
println!("Configuration issues would be fixed.");
}
Ok(())
}
async fn cmd_init(force: bool) -> Result<()> {
use config::loader::ConfigLoader;
let config_path = dirs::home_dir()
.ok_or_else(|| anyhow::anyhow!("Cannot determine home directory"))?
.join(".rsclaw")
.join("rsclaw.toml");
if config_path.exists() && !force {
anyhow::bail!(
"Configuration already exists at {:?}. Use --force to overwrite.",
config_path
);
}
ConfigLoader::init_default_config(&config_path)?;
println!("Configuration initialized at {:?}", config_path);
Ok(())
}
async fn cmd_config() -> Result<()> {
use config::loader::ConfigLoader;
let config = ConfigLoader::load()?;
let output = toml::to_string_pretty(&config)?;
println!("{}", output);
Ok(())
}
async fn cmd_agent_list() -> Result<()> {
let manager = get_manager();
let agents = manager.list().await;
if agents.is_empty() {
println!("No running agents.");
return Ok(());
}
println!("Running agents ({}/{}):", manager.current_concurrency(), manager.max_concurrency());
for agent in agents {
let state = agent.state().await;
println!(" - {} [{:?}]", agent.name(), state);
}
Ok(())
}
async fn cmd_agent_create(name: String) -> Result<()> {
use std::sync::Arc;
let manager = get_manager();
if manager.is_at_capacity() {
anyhow::bail!(
"Cannot create agent: concurrency limit reached ({}/{})",
manager.current_concurrency(),
manager.max_concurrency()
);
}
let config = config::loader::ConfigLoader::load()?;
let agent_config = agent::AgentConfig {
name: Arc::from(name.as_str()),
model: Arc::from("gpt-4"),
system_prompt: Arc::from("You are a helpful assistant."),
max_tokens: 4096,
memory_limit_mb: config.memory.max_agent_memory_mb,
};
let created_name = manager.create(agent_config).await?;
println!("Agent '{}' created successfully.", created_name);
Ok(())
}
async fn cmd_agent_chat(name: String, message: String) -> Result<()> {
println!("Agent '{}' processing message...", name);
println!("Note: No LLM provider configured, simulating response.");
println!("User: {}", message);
println!("Assistant: This is a simulated response. Configure an LLM provider for real responses.");
Ok(())
}
async fn cmd_agent_destroy(name: String) -> Result<()> {
let manager = get_manager();
if manager.destroy(&name).await? {
println!("Agent '{}' destroyed, memory released.", name);
} else {
println!("Agent '{}' not found.", name);
}
Ok(())
}
async fn cmd_skill_list() -> Result<()> {
use skill::SkillLoader;
let skills_dir = utils::ensure_skills_dir()?;
let mut loader = SkillLoader::new(skills_dir);
let count = loader.load_all()?;
if count == 0 {
println!("No skills installed.");
return Ok(());
}
println!("Installed skills ({}):", count);
for skill in loader.list() {
if let Some(manifest) = &skill.manifest {
println!(" - {} (v{}): {}", skill.name, manifest.version, manifest.description);
} else {
println!(" - {}", skill.name);
}
}
Ok(())
}
async fn cmd_skill_pull(name: String) -> Result<()> {
use skill::ClawHubClient;
let skills_dir = utils::ensure_skills_dir()?;
let client = ClawHubClient::default();
println!("Pulling skill '{}' from ClawHub...", name);
client.install_skill(&name, &skills_dir).await?;
println!("Skill '{}' installed successfully.", name);
Ok(())
}
async fn cmd_skill_run(name: String, args: Vec<String>) -> Result<()> {
use skill::{SkillLoader, ShellRunner};
let skills_dir = utils::ensure_skills_dir()?;
let mut loader = SkillLoader::new(skills_dir);
loader.load_all()?;
let skill = loader.get(&name)
.ok_or_else(|| anyhow::anyhow!("Skill '{}' not found", name))?;
let timeout = skill.manifest
.as_ref()
.and_then(|m| m.timeout)
.unwrap_or(30) as u64;
let runner = ShellRunner::new(timeout);
let result = runner.run(&skill.path, &args).await?;
if result.success {
print!("{}", result.stdout);
} else {
print!("{}", result.stderr);
anyhow::bail!("Skill execution failed with exit code: {:?}", result.exit_code);
}
Ok(())
}
async fn cmd_plugin_list() -> Result<()> {
let plugins_dir = utils::plugins_dir()?;
let manager = plugin::PluginManager::new(plugins_dir);
let plugins = manager.list();
if plugins.is_empty() {
println!("No plugins installed.");
return Ok(());
}
println!("Installed plugins ({}):", plugins.len());
for plugin in plugins {
println!(" - {} (v{}): {}", plugin.name, plugin.manifest.version, plugin.manifest.description);
}
Ok(())
}
async fn cmd_plugin_load(name: String) -> Result<()> {
let plugins_dir = utils::plugins_dir()?;
let mut manager = plugin::PluginManager::new(plugins_dir);
manager.load(&name)?;
println!("Plugin '{}' loaded successfully.", name);
let slots = manager.list_slots();
if !slots.is_empty() {
println!("Registered slots:");
for slot in slots {
println!(" - {} ({})", slot.name, slot.slot_type);
}
}
Ok(())
}
async fn cmd_plugin_run(plugin: String, method: String, params: Option<String>) -> Result<()> {
let plugins_dir = utils::plugins_dir()?;
let mut manager = plugin::PluginManager::new(plugins_dir);
manager.load(&plugin)?;
let params_value = if let Some(p) = params {
Some(serde_json::from_str(&p)?)
} else {
None
};
println!("Running {}.{}...", plugin, method);
let result = manager.execute(&plugin, &method, params_value).await?;
println!("{}", serde_json::to_string_pretty(&result)?);
Ok(())
}
async fn cmd_cron_list() -> Result<()> {
use std::sync::Arc;
use cron::CronStore;
let store = Arc::new(store::default_store()?);
let cron_store = Arc::new(CronStore::new(store));
let tasks = cron_store.list_all()?;
if tasks.is_empty() {
println!("No cron jobs configured.");
return Ok(());
}
println!("Cron jobs ({}):", tasks.len());
for task in tasks {
let status = match task.status {
cron::TaskStatus::Pending => "pending",
cron::TaskStatus::Running => "running",
cron::TaskStatus::Completed => "completed",
cron::TaskStatus::Failed => "failed",
};
println!(" - {} [{}] {}: {} -> {}", task.id, status, task.expression, task.target, task.description);
}
Ok(())
}
async fn cmd_cron_add(expression: String, target: String, description: String) -> Result<()> {
use std::sync::Arc;
use cron::{CronStore, CronTask, TaskType};
let store = Arc::new(store::default_store()?);
let cron_store = Arc::new(CronStore::new(store));
let task = CronTask::new(
Arc::from(expression.as_str()),
TaskType::Agent,
Arc::from(target.as_str()),
Arc::from(description.as_str()),
);
cron_store.save(&task)?;
println!("Cron job added with ID: {}", task.id);
println!(" Expression: {}", expression);
println!(" Target: {}", target);
println!(" Description: {}", description);
Ok(())
}
async fn cmd_cron_run(id: String) -> Result<()> {
use std::sync::Arc;
use cron::CronStore;
let store = Arc::new(store::default_store()?);
let cron_store = Arc::new(CronStore::new(store));
let task = cron_store.load(&id)?
.ok_or_else(|| anyhow::anyhow!("Cron job '{}' not found", id))?;
println!("Running cron job '{}'...", id);
println!("Target: {}", task.target);
println!("Description: {}", task.description);
println!("Note: Task execution simulation (no LLM provider configured)");
Ok(())
}
async fn cmd_cron_delete(id: String) -> Result<()> {
use std::sync::Arc;
use cron::CronStore;
let store = Arc::new(store::default_store()?);
let cron_store = Arc::new(CronStore::new(store));
if cron_store.delete(&id)? {
println!("Cron job '{}' deleted.", id);
} else {
println!("Cron job '{}' not found.", id);
}
Ok(())
}
async fn cmd_hook_list() -> Result<()> {
use std::sync::Arc;
use hooks::{HookRegistry, SystemHooks};
let registry = Arc::new(HookRegistry::new());
SystemHooks::register(®istry).await?;
let hooks = registry.list().await;
if hooks.is_empty() {
println!("No hooks registered.");
return Ok(());
}
println!("Registered hooks ({}):", hooks.len());
for hook in hooks {
let status = if hook.enabled { "enabled" } else { "disabled" };
println!(" - {} [{}] {} (priority: {})", hook.id, status, hook.name, hook.priority);
println!(" Event: {}", hook.event);
}
Ok(())
}
async fn cmd_hook_trigger(event: String) -> Result<()> {
use std::sync::Arc;
use hooks::{HookEvent, HookRegistry, HookEngine, HookTrigger, SystemHooks};
let store = Arc::new(store::default_store()?);
let skills_dir = utils::ensure_skills_dir()?;
let registry = Arc::new(HookRegistry::with_store(store));
SystemHooks::register(®istry).await?;
let engine = Arc::new(HookEngine::new(registry, skills_dir));
let trigger = HookTrigger::new(engine);
let hook_event = HookEvent::from_str(&event)
.ok_or_else(|| anyhow::anyhow!("Invalid event: {}", event))?;
println!("Triggering event '{}'...", event);
let results = trigger.trigger(hook_event, serde_json::json!({})).await;
println!("Execution results:");
for result in results {
let status = if result.success { "ok" } else { "failed" };
println!(" - Hook '{}' [{}] in {}ms", result.hook_id, status, result.duration_ms);
if let Some(error) = &result.error {
println!(" Error: {}", error);
}
}
Ok(())
}