//! Zesdex Gateway — main entry point. //! //! Assembles domain + application + infrastructure layers and dispatches //! to the requested interface: TUI (default), daemon (background IPC), //! API server (REST), WebSocket server, gRPC server, or Web frontend. //! //! CLI flags are parsed via clap; run with `--help` for details. use std::sync::Mutex; use clap::Parser; /// Zesdex — autonomous AI coding agent. #[derive(Parser, Debug)] #[command(name = "zesdex", version, about = "Autonomous AI coding agent with TUI")] struct Cli { /// Run as background daemon with IPC socket #[arg(long)] daemon: bool, /// Attach TUI client to a running daemon session #[arg(long)] attach: Option, /// Run REST API server #[arg(long)] api: bool, /// REST API port #[arg(long, default_value_t = 8080)] api_port: u16, /// Run WebSocket server #[arg(long)] ws: bool, /// WebSocket port #[arg(long, default_value_t = 8081)] ws_port: u16, /// Run gRPC server #[arg(long)] grpc: bool, /// gRPC port #[arg(long, default_value_t = 50051)] grpc_port: u16, /// Serve web frontend #[arg(long)] web: bool, /// Web frontend port #[arg(long, default_value_t = 3000)] web_port: u16, } fn main() -> anyhow::Result<()> { let cli = Cli::parse(); // ── Setup logging ──────────────────────────────────────────────────── let log_dir = dirs::data_dir() .unwrap_or_else(|| std::path::PathBuf::from(".")) .join("zesdex"); let _ = std::fs::create_dir_all(&log_dir); let log_path = log_dir.join("zesdex.log"); let log_file = std::fs::OpenOptions::new() .create(true) .append(true) .open(&log_path) .unwrap_or_else(|_| { std::fs::OpenOptions::new() .write(true) .open("/dev/null") .expect("cannot open /dev/null") }); tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), ) .with_writer(Mutex::new(log_file)) .init(); tracing::info!("zesdex gateway starting"); // ── Dispatch to interface ──────────────────────────────────────────── // Validate mutually exclusive flags let mode_count = [cli.daemon, cli.api, cli.ws, cli.grpc, cli.web] .iter() .filter(|&&b| b) .count() + if cli.attach.is_some() { 1 } else { 0 }; if mode_count > 1 { anyhow::bail!( "Cannot specify multiple modes: --daemon, --attach, --api, --ws, --grpc, --web are mutually exclusive" ); } if cli.daemon { tracing::info!("starting in daemon mode"); zesdex_daemon::server::run_daemon()?; } else if let Some(session_id) = cli.attach { tracing::info!("starting in attach mode for session {session_id}"); zesdex_daemon::client::run_attach(&session_id)?; } else if cli.api { tracing::info!("starting in API server mode"); run_api_server(cli.api_port)?; } else if cli.ws { tracing::info!("starting in WebSocket server mode"); run_ws_server(cli.ws_port)?; } else if cli.grpc { tracing::info!("starting in gRPC server mode"); run_grpc_server(cli.grpc_port)?; } else if cli.web { tracing::info!("starting in web server mode"); run_web_server(cli.web_port)?; } else { // Default: run TUI single-process mode tracing::info!("starting in TUI single-process mode"); run_tui_single_process()?; } Ok(()) } /// Run the TUI in single-process mode (TUI + agent in one process). fn run_tui_single_process() -> anyhow::Result<()> { zesdex_tui::run_single_process() } /// Run the REST API server. fn run_api_server(port: u16) -> anyhow::Result<()> { let rt = tokio::runtime::Runtime::new()?; rt.block_on(async { let store = zesdex_domain::core::Store::new(); let state = zesdex_api::ApiState::new( store.base_dir.clone(), "dev-secret", "", "deepseek-v4-flash-free", Some("https://opencode.ai/zen/v1".to_string()), ); let app = zesdex_api::build_router(state); let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port)); tracing::info!("REST API server listening on http://{addr}/api/v1/health"); let listener = tokio::net::TcpListener::bind(addr).await?; axum::serve(listener, app).await?; Ok::<_, anyhow::Error>(()) })?; Ok(()) } /// Run the WebSocket server. fn run_ws_server(port: u16) -> anyhow::Result<()> { let rt = tokio::runtime::Runtime::new()?; rt.block_on(async { zesdex_ws::run_server(port).await })?; Ok(()) } /// Run the gRPC server. fn run_grpc_server(port: u16) -> anyhow::Result<()> { let rt = tokio::runtime::Runtime::new()?; rt.block_on(async { zesdex_grpc::run_server(port).await })?; Ok(()) } /// Serve the web frontend. fn run_web_server(port: u16) -> anyhow::Result<()> { let rt = tokio::runtime::Runtime::new()?; rt.block_on(async { zesdex_web::run_server(port, None).await })?; Ok(()) }