#![allow(dead_code, clippy::if_same_then_else)]
mod api;
mod cli;
mod commands;
mod compact;
mod config;
mod context;
mod cost;
mod db;
mod permissions;
mod plugin;
mod query;
mod repl;
mod session;
mod theme;
mod tools;
mod tui;
mod utils;
use anyhow::Result;
use clap::Parser;
#[tokio::main]
async fn main() -> Result<()> {
let args = cli::Cli::parse();
let filter = if args.debug {
"claux=debug"
} else if args.verbose {
"claux=info"
} else {
"claux=warn"
};
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.init();
let config = config::Config::load()?;
let mut plugin_registry = plugin::PluginRegistry::new();
for plugin_config in &config.plugins {
plugin_registry.add(Box::new(plugin::CommandPlugin::new(
&plugin_config.name,
&plugin_config.command,
&plugin_config.args,
plugin_config.trigger.clone(),
)));
}
if !plugin_registry.is_empty() {
tracing::info!(
"Loaded {} plugin(s): {} context, {} tool-start, {} tool-complete, {} session-start",
plugin_registry.len(),
plugin_registry.get_by_trigger(&config::HookTrigger::OnContextBuild),
plugin_registry.get_by_trigger(&config::HookTrigger::OnToolStart),
plugin_registry.get_by_trigger(&config::HookTrigger::OnToolComplete),
plugin_registry.get_by_trigger(&config::HookTrigger::OnSessionStart),
);
}
let model = args.model.as_deref().unwrap_or(&config.model).to_string();
tracing::debug!(
"Config loaded: openai_base_url={:?} openai_api_key_cmd={:?} model={}",
config.openai_base_url,
config.openai_api_key_cmd,
config.model
);
let provider = build_provider(&config, &model)?;
let provider_name = provider.name().to_string();
tracing::info!("Provider: {} ({})", provider_name, model);
let config_for_factory = config.clone();
let model_for_factory = model.clone();
let agent_factory: tools::agent::ProviderFactory = Box::new(move || {
build_provider(&config_for_factory, &model_for_factory)
.expect("failed to build agent provider")
});
if let Some(ref prompt) = args.prompt {
let tool_registry =
tools::ToolRegistry::new_with_agent_factory(agent_factory, model.clone());
let permission_checker = permissions::PermissionChecker::new(config.permission_mode);
let mut engine = query::Engine::new(provider, tool_registry, permission_checker, &model);
engine.set_auto_compact_threshold(config.auto_compact_threshold);
let system_prompt = context::build_system_prompt_for_model(
&model,
Some(&plugin_registry),
&config::HookTrigger::OnContextBuild,
config.is_anthropic(),
)
.await?;
engine.set_system_prompt(system_prompt);
let response = engine.submit(prompt).await?;
print!("{response}");
return Ok(());
}
let tool_registry = tools::ToolRegistry::new_with_agent_factory(agent_factory, model.clone());
let permission_checker = permissions::PermissionChecker::new(config.permission_mode);
let mut engine = query::Engine::new(provider, tool_registry, permission_checker, &model);
engine.set_auto_compact_threshold(config.auto_compact_threshold);
let system_prompt = context::build_system_prompt_for_model(
&model,
Some(&plugin_registry),
&config::HookTrigger::OnContextBuild,
config.is_anthropic(),
)
.await?;
engine.set_system_prompt(system_prompt);
plugin::PluginRegistry::execute_side_effects(
&plugin_registry,
&config::HookTrigger::OnSessionStart,
None,
)?;
if let Some(ref session_id) = args.resume {
let sessions = session::list_sessions()?;
let found = sessions
.iter()
.find(|(sid, _)| sid == session_id || sid.starts_with(session_id));
match found {
Some((_, path)) => {
let (meta, messages) = session::load_session(path)?;
engine.set_messages(messages);
eprintln!(
"Resumed session {} ({}, {} messages)",
meta.id,
meta.model,
engine.message_count()
);
}
None => {
eprintln!("Session not found: {session_id}. Starting new session.");
}
}
}
if args.tui {
tui::run(engine, &config, &plugin_registry).await
} else {
repl::run(engine, &config, &plugin_registry).await
}
}
fn build_provider(config: &config::Config, model: &str) -> Result<Box<dyn api::Provider>> {
if let Some(ref base_url) = config.openai_base_url {
let api_key = config.resolve_openai_key().unwrap_or_default();
let name = config.openai_provider_name.as_deref().unwrap_or("openai");
return Ok(Box::new(api::OpenAICompatProvider::new(
base_url, &api_key, model, name,
)));
}
let auth = config
.resolve_auth()
.ok_or_else(|| anyhow::anyhow!(
"No authentication found. Set ANTHROPIC_API_KEY, configure ~/.config/claux/config.toml, or run `claude login`."
))?;
Ok(Box::new(api::AnthropicProvider::new(auth, model)))
}