176 lines
5.4 KiB
Rust
176 lines
5.4 KiB
Rust
//! 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<String>,
|
|
|
|
/// 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(())
|
|
}
|