mod ingest;
mod routes;
mod watcher;
use std::path::PathBuf;
use std::sync::Arc;
use axum::Router;
use axum::extract::DefaultBodyLimit;
use tokio::net::TcpListener;
use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;
use second_brain_core::embedding::Embedder;
use second_brain_core::kuzu_store::KuzuStore;
use second_brain_core::machine::MachineIdentity;
pub struct AppState {
pub store: Arc<KuzuStore>,
pub embedder: Option<Arc<Embedder>>,
pub machine: MachineIdentity,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive("second_brain=info".parse().unwrap())
.add_directive("tower_http=debug".parse().unwrap()),
)
.init();
let db_path = std::env::var("SECOND_BRAIN_DB")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
PathBuf::from(home).join(".second-brain").join("graph.kuzu")
});
if let Some(parent) = db_path.parent() {
std::fs::create_dir_all(parent).ok();
}
let config_dir = MachineIdentity::config_dir()
.expect("cannot determine home directory");
let machine = MachineIdentity::load_or_create(&config_dir)
.expect("failed to load or create machine identity");
tracing::info!("machine identity: {} ({})", machine.name, machine.id);
let store = match KuzuStore::open(&db_path, machine.id.clone()) {
Ok(s) => Arc::new(s),
Err(e) => {
tracing::error!("failed to open database at {}: {e}", db_path.display());
std::process::exit(1);
}
};
store.register_machine(&machine)
.expect("failed to register machine identity");
tracing::info!("database opened at {}", db_path.display());
let embedder = match Embedder::new() {
Ok(e) => {
tracing::info!("embedding model loaded");
Some(Arc::new(e))
}
Err(e) => {
tracing::warn!("embedding model unavailable: {e}");
None
}
};
watcher::spawn(store.clone(), embedder.clone(), machine.id.clone());
let state = Arc::new(AppState { store, embedder, machine });
let app = Router::new()
.merge(routes::router())
.layer(DefaultBodyLimit::max(64 * 1024 * 1024))
.layer(CorsLayer::permissive())
.layer(TraceLayer::new_for_http())
.with_state(state);
let bind = std::env::var("SECOND_BRAIN_BIND").unwrap_or_else(|_| "127.0.0.1:7200".to_string());
let listener = TcpListener::bind(&bind).await.unwrap();
tracing::info!("listening on {bind}");
axum::serve(listener, app).await.unwrap();
}