use std::path::PathBuf;
use std::sync::Arc;
use agentstatedeveloper_core::Engine;
use agentstatedeveloper_mcp::build_router;
use anyhow::{Context, Result};
use tokio::sync::Mutex;
#[tokio::main]
async fn main() -> Result<()> {
let _telemetry = oxtel::builder()
.with_default_service_name("asd-serve")
.with_resource_attribute(oxtel::KeyValue::new(
"service.version",
env!("CARGO_PKG_VERSION"),
))
.with_local_logs(oxtel::sdk::logs::LocalFormat::Text)
.with_local_logs_output(oxtel::LogOutput::Stdout)
.install()?;
let db_path =
PathBuf::from(std::env::var("ASD_DB").unwrap_or_else(|_| "./.asd-state.db".to_string()));
let addr = std::env::var("ASD_SERVE_ADDR").unwrap_or_else(|_| "0.0.0.0:4120".to_string());
let lens_dir = std::env::var("ASD_LENS_DIR").ok().map(PathBuf::from);
let cors_permissive = std::env::var("ASD_CORS_PERMISSIVE")
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
.unwrap_or(false);
tracing::info!(?db_path, %addr, ?lens_dir, "starting asd-serve");
let engine = Engine::open_sqlite(&db_path)
.with_context(|| format!("failed to open ASD db at {}", db_path.display()))?;
let audit_log_path: Option<PathBuf> = std::env::var("ASD_AUDIT_LOG").ok().map(PathBuf::from);
if audit_log_path.is_some() {
tracing::warn!(
"ASD_AUDIT_LOG set but asd-serve is OSS — no new events \
will be written (tamper-evident sink is a commercial \
feature; install asd-pro). Existing logs are readable \
via /audit."
);
}
let shared = Arc::new(Mutex::new(engine));
let app = build_router(shared, db_path, lens_dir, audit_log_path, cors_permissive);
let listener = tokio::net::TcpListener::bind(&addr)
.await
.with_context(|| format!("failed to bind {}", addr))?;
let bound = listener
.local_addr()
.map(|a| a.to_string())
.unwrap_or_else(|_| addr.clone());
tracing::info!("listening on {}", bound);
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?;
Ok(())
}
async fn shutdown_signal() {
let interrupt = async {
let _ = tokio::signal::ctrl_c().await;
};
#[cfg(unix)]
let terminate = async {
use tokio::signal::unix::{SignalKind, signal};
match signal(SignalKind::terminate()) {
Ok(mut terminate) => {
terminate.recv().await;
}
Err(_) => std::future::pending::<()>().await,
}
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
() = interrupt => {}
() = terminate => {}
}
tracing::info!("shutting down");
}