use std::path::PathBuf;
use clap::Parser;
use khive_mcp::args::{resolve_cli_namespace, Args};
use khive_mcp::server::KhiveMcpServer;
use khive_runtime::{
config_from_env, runtime_config_from_khive_config, KhiveConfig, KhiveRuntime, RuntimeConfig,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_env_filter(args.log.clone())
.with_ansi(false)
.init();
let db_path = match args.db.as_deref() {
Some(":memory:") => None,
Some(path) => Some(PathBuf::from(path)),
None => {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
Some(PathBuf::from(format!("{home}/.khive/khive-graph.db")))
}
};
let (cli_namespace_explicit, cli_namespace) =
resolve_cli_namespace(&args).map_err(|e| anyhow::anyhow!("{e}"))?;
let packs = if args.pack.is_empty() {
RuntimeConfig::default().packs
} else {
args.pack
};
let base_config = RuntimeConfig {
db_path,
default_namespace: cli_namespace,
packs,
..RuntimeConfig::default()
};
let config = if args.no_embed {
let no_embed_base = RuntimeConfig {
embedding_model: None,
additional_embedding_models: vec![],
..base_config
};
resolve_actor_from_config(
args.config.as_deref(),
no_embed_base,
cli_namespace_explicit,
)?
} else {
resolve_config(args.config.as_deref(), base_config, cli_namespace_explicit)?
};
let runtime = KhiveRuntime::new(config)?;
let daemon_mode = args.daemon;
let server = KhiveMcpServer::new(runtime).map_err(|e| anyhow::anyhow!("{e}"))?;
if daemon_mode {
khive_runtime::daemon::run_daemon(server).await?;
} else {
server.serve_stdio().await?;
}
Ok(())
}
fn resolve_config(
config_path: Option<&std::path::Path>,
base: RuntimeConfig,
cli_namespace_explicit: bool,
) -> anyhow::Result<RuntimeConfig> {
match KhiveConfig::load_with_home_fallback(config_path)
.map_err(|e| anyhow::anyhow!("config error: {e}"))?
{
Some(khive_cfg) => {
let env_primary = std::env::var("KHIVE_EMBEDDING_MODEL").ok();
let env_additional = std::env::var("KHIVE_ADDITIONAL_EMBEDDING_MODELS").ok();
if env_primary.is_some() || env_additional.is_some() {
tracing::warn!(
"khive config file is present; KHIVE_EMBEDDING_MODEL and \
KHIVE_ADDITIONAL_EMBEDDING_MODELS env vars are ignored"
);
}
let effective_cfg = if cli_namespace_explicit {
let mut c = khive_cfg;
c.actor.id = None;
c
} else {
khive_cfg
};
Ok(runtime_config_from_khive_config(&effective_cfg, base))
}
None => {
let env_cfg = config_from_env();
if env_cfg.engines.is_empty() {
Ok(base)
} else {
Ok(runtime_config_from_khive_config(&env_cfg, base))
}
}
}
}
fn resolve_actor_from_config(
config_path: Option<&std::path::Path>,
base: RuntimeConfig,
cli_namespace_explicit: bool,
) -> anyhow::Result<RuntimeConfig> {
if cli_namespace_explicit {
return Ok(base);
}
match KhiveConfig::load_with_home_fallback(config_path)
.map_err(|e| anyhow::anyhow!("config error: {e}"))?
{
Some(khive_cfg) => {
let resolved = runtime_config_from_khive_config(&khive_cfg, base);
Ok(RuntimeConfig {
embedding_model: None,
additional_embedding_models: vec![],
..resolved
})
}
None => Ok(base),
}
}