- 22 AI providers (OpenRouter, Anthropic, OpenAI, Mistral, etc.) - 7 channels (CLI, Telegram, Discord, Slack, iMessage, Matrix, Webhook) - 5-step onboarding wizard with Project Context personalization - OpenClaw-aligned system prompt (SOUL.md, IDENTITY.md, USER.md, AGENTS.md, etc.) - SQLite memory backend with auto-save - Skills system with on-demand loading - Security: autonomy levels, command allowlists, cost limits - 532 tests passing, 0 clippy warnings
327 lines
9.0 KiB
Rust
327 lines
9.0 KiB
Rust
#![warn(clippy::all, clippy::pedantic)]
|
|
#![allow(
|
|
clippy::missing_errors_doc,
|
|
clippy::missing_panics_doc,
|
|
clippy::unnecessary_literal_bound,
|
|
clippy::module_name_repetitions,
|
|
clippy::struct_field_names,
|
|
dead_code
|
|
)]
|
|
|
|
use anyhow::Result;
|
|
use clap::{Parser, Subcommand};
|
|
use tracing::{info, Level};
|
|
use tracing_subscriber::FmtSubscriber;
|
|
|
|
mod agent;
|
|
mod channels;
|
|
mod config;
|
|
mod cron;
|
|
mod gateway;
|
|
mod heartbeat;
|
|
mod memory;
|
|
mod observability;
|
|
mod onboard;
|
|
mod providers;
|
|
mod runtime;
|
|
mod security;
|
|
mod integrations;
|
|
mod skills;
|
|
mod tools;
|
|
|
|
use config::Config;
|
|
|
|
/// `ZeroClaw` - Zero overhead. Zero compromise. 100% Rust.
|
|
#[derive(Parser, Debug)]
|
|
#[command(name = "zeroclaw")]
|
|
#[command(author = "theonlyhennygod")]
|
|
#[command(version = "0.1.0")]
|
|
#[command(about = "The fastest, smallest AI assistant.", long_about = None)]
|
|
struct Cli {
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
enum Commands {
|
|
/// Initialize your workspace and configuration
|
|
Onboard,
|
|
|
|
/// Start the AI agent loop
|
|
Agent {
|
|
/// Single message mode (don't enter interactive mode)
|
|
#[arg(short, long)]
|
|
message: Option<String>,
|
|
|
|
/// Provider to use (openrouter, anthropic, openai)
|
|
#[arg(short, long)]
|
|
provider: Option<String>,
|
|
|
|
/// Model to use
|
|
#[arg(short, long)]
|
|
model: Option<String>,
|
|
|
|
/// Temperature (0.0 - 2.0)
|
|
#[arg(short, long, default_value = "0.7")]
|
|
temperature: f64,
|
|
},
|
|
|
|
/// Start the gateway server (webhooks, websockets)
|
|
Gateway {
|
|
/// Port to listen on
|
|
#[arg(short, long, default_value = "8080")]
|
|
port: u16,
|
|
|
|
/// Host to bind to
|
|
#[arg(short, long, default_value = "127.0.0.1")]
|
|
host: String,
|
|
},
|
|
|
|
/// Show system status
|
|
Status {
|
|
/// Show detailed status
|
|
#[arg(short, long)]
|
|
verbose: bool,
|
|
},
|
|
|
|
/// Configure and manage scheduled tasks
|
|
Cron {
|
|
#[command(subcommand)]
|
|
cron_command: CronCommands,
|
|
},
|
|
|
|
/// Manage channels (telegram, discord, slack)
|
|
Channel {
|
|
#[command(subcommand)]
|
|
channel_command: ChannelCommands,
|
|
},
|
|
|
|
/// Tool utilities
|
|
Tools {
|
|
#[command(subcommand)]
|
|
tool_command: ToolCommands,
|
|
},
|
|
|
|
/// Browse 50+ integrations
|
|
Integrations {
|
|
#[command(subcommand)]
|
|
integration_command: IntegrationCommands,
|
|
},
|
|
|
|
/// Manage skills (user-defined capabilities)
|
|
Skills {
|
|
#[command(subcommand)]
|
|
skill_command: SkillCommands,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
enum CronCommands {
|
|
/// List all scheduled tasks
|
|
List,
|
|
/// Add a new scheduled task
|
|
Add {
|
|
/// Cron expression
|
|
expression: String,
|
|
/// Command to run
|
|
command: String,
|
|
},
|
|
/// Remove a scheduled task
|
|
Remove {
|
|
/// Task ID
|
|
id: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
enum ChannelCommands {
|
|
/// List configured channels
|
|
List,
|
|
/// Start all configured channels (Telegram, Discord, Slack)
|
|
Start,
|
|
/// Add a new channel
|
|
Add {
|
|
/// Channel type
|
|
channel_type: String,
|
|
/// Configuration JSON
|
|
config: String,
|
|
},
|
|
/// Remove a channel
|
|
Remove {
|
|
/// Channel name
|
|
name: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
enum SkillCommands {
|
|
/// List installed skills
|
|
List,
|
|
/// Install a skill from a GitHub URL or local path
|
|
Install {
|
|
/// GitHub URL or local path
|
|
source: String,
|
|
},
|
|
/// Remove an installed skill
|
|
Remove {
|
|
/// Skill name
|
|
name: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
enum IntegrationCommands {
|
|
/// List all integrations and their status
|
|
List {
|
|
/// Filter by category (e.g. "chat", "ai", "productivity")
|
|
#[arg(short, long)]
|
|
category: Option<String>,
|
|
},
|
|
/// Show details about a specific integration
|
|
Info {
|
|
/// Integration name
|
|
name: String,
|
|
},
|
|
}
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
enum ToolCommands {
|
|
/// List available tools
|
|
List,
|
|
/// Test a tool
|
|
Test {
|
|
/// Tool name
|
|
tool: String,
|
|
/// Tool arguments (JSON)
|
|
args: String,
|
|
},
|
|
}
|
|
|
|
#[tokio::main]
|
|
#[allow(clippy::too_many_lines)]
|
|
async fn main() -> Result<()> {
|
|
let cli = Cli::parse();
|
|
|
|
// Initialize logging
|
|
let subscriber = FmtSubscriber::builder()
|
|
.with_max_level(Level::INFO)
|
|
.finish();
|
|
|
|
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
|
|
|
// Onboard runs the interactive wizard — no existing config needed
|
|
if matches!(cli.command, Commands::Onboard) {
|
|
let config = onboard::run_wizard()?;
|
|
// Auto-start channels if user said yes during wizard
|
|
if std::env::var("ZEROCLAW_AUTOSTART_CHANNELS").as_deref() == Ok("1") {
|
|
channels::start_channels(config).await?;
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
// All other commands need config loaded first
|
|
let config = Config::load_or_init()?;
|
|
|
|
match cli.command {
|
|
Commands::Onboard => unreachable!(),
|
|
|
|
Commands::Agent {
|
|
message,
|
|
provider,
|
|
model,
|
|
temperature,
|
|
} => agent::run(config, message, provider, model, temperature).await,
|
|
|
|
Commands::Gateway { port, host } => {
|
|
info!("🚀 Starting ZeroClaw Gateway on {host}:{port}");
|
|
info!("POST http://{host}:{port}/webhook — send JSON messages");
|
|
info!("GET http://{host}:{port}/health — health check");
|
|
gateway::run_gateway(&host, port, config).await
|
|
}
|
|
|
|
Commands::Status { verbose } => {
|
|
println!("🦀 ZeroClaw Status");
|
|
println!();
|
|
println!("Version: {}", env!("CARGO_PKG_VERSION"));
|
|
println!("Workspace: {}", config.workspace_dir.display());
|
|
println!("Config: {}", config.config_path.display());
|
|
println!();
|
|
println!(
|
|
"🤖 Provider: {}",
|
|
config.default_provider.as_deref().unwrap_or("openrouter")
|
|
);
|
|
println!(
|
|
" Model: {}",
|
|
config.default_model.as_deref().unwrap_or("(default)")
|
|
);
|
|
println!("📊 Observability: {}", config.observability.backend);
|
|
println!("🛡️ Autonomy: {:?}", config.autonomy.level);
|
|
println!("⚙️ Runtime: {}", config.runtime.kind);
|
|
println!(
|
|
"💓 Heartbeat: {}",
|
|
if config.heartbeat.enabled {
|
|
format!("every {}min", config.heartbeat.interval_minutes)
|
|
} else {
|
|
"disabled".into()
|
|
}
|
|
);
|
|
println!(
|
|
"🧠 Memory: {} (auto-save: {})",
|
|
config.memory.backend,
|
|
if config.memory.auto_save { "on" } else { "off" }
|
|
);
|
|
|
|
if verbose {
|
|
println!();
|
|
println!("Security:");
|
|
println!(" Workspace only: {}", config.autonomy.workspace_only);
|
|
println!(
|
|
" Allowed commands: {}",
|
|
config.autonomy.allowed_commands.join(", ")
|
|
);
|
|
println!(
|
|
" Max actions/hour: {}",
|
|
config.autonomy.max_actions_per_hour
|
|
);
|
|
println!(
|
|
" Max cost/day: ${:.2}",
|
|
f64::from(config.autonomy.max_cost_per_day_cents) / 100.0
|
|
);
|
|
println!();
|
|
println!("Channels:");
|
|
println!(" CLI: ✅ always");
|
|
for (name, configured) in [
|
|
("Telegram", config.channels_config.telegram.is_some()),
|
|
("Discord", config.channels_config.discord.is_some()),
|
|
("Slack", config.channels_config.slack.is_some()),
|
|
("Webhook", config.channels_config.webhook.is_some()),
|
|
] {
|
|
println!(
|
|
" {name:9} {}",
|
|
if configured { "✅ configured" } else { "❌ not configured" }
|
|
);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
Commands::Cron { cron_command } => cron::handle_command(cron_command, config),
|
|
|
|
Commands::Channel { channel_command } => match channel_command {
|
|
ChannelCommands::Start => channels::start_channels(config).await,
|
|
other => channels::handle_command(other, &config),
|
|
},
|
|
|
|
Commands::Tools { tool_command } => tools::handle_command(tool_command, config).await,
|
|
|
|
Commands::Integrations {
|
|
integration_command,
|
|
} => integrations::handle_command(integration_command, &config),
|
|
|
|
Commands::Skills { skill_command } => {
|
|
skills::handle_command(skill_command, &config.workspace_dir)
|
|
}
|
|
}
|
|
}
|