mod config; mod db; mod ingest; mod merge; mod web; use std::sync::Arc; use clap::Parser; #[tokio::main] async fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let args = config::Args::parse(); args.validate()?; let version = option_env!("FURUMI_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")); tracing::info!("Furumi Agent v{} starting", version); tracing::info!("Inbox directory: {:?}", args.inbox_dir); tracing::info!("Storage directory: {:?}", args.storage_dir); tracing::info!("Ollama: {} (model: {})", args.ollama_url, args.ollama_model); tracing::info!("Confidence threshold: {}", args.confidence_threshold); let system_prompt = args.load_system_prompt()?; tracing::info!("System prompt loaded: {} chars", system_prompt.len()); let merge_prompt = args.load_merge_prompt()?; tracing::info!("Merge prompt loaded: {} chars", merge_prompt.len()); tracing::info!("Connecting to database..."); let pool = db::connect(&args.database_url).await?; tracing::info!("Running database migrations..."); db::migrate(&pool).await?; tracing::info!("Database ready"); let state = Arc::new(web::AppState { pool: pool.clone(), config: Arc::new(args), system_prompt: Arc::new(system_prompt), merge_prompt: Arc::new(merge_prompt), }); // Spawn the ingest pipeline as a background task let ingest_state = state.clone(); tokio::spawn(async move { ingest::run(ingest_state).await; }); // Start the admin web UI let bind_addr: std::net::SocketAddr = state.config.bind.parse().unwrap_or_else(|e| { eprintln!("Error: Invalid bind address '{}': {}", state.config.bind, e); std::process::exit(1); }); tracing::info!("Admin UI: http://{}", bind_addr); let app = web::build_router(state); let listener = tokio::net::TcpListener::bind(bind_addr).await?; axum::serve(listener, app).await?; Ok(()) }