use anyhow::Result;
use colored::Colorize;
async fn build_embedder() -> Result<std::sync::Arc<dyn crate::core::Embedder>> {
let embedder = crate::core::FastEmbedder::new().await.map_err(|e| {
tracing::error!("FastEmbedder init failed: {e:#}");
anyhow::anyhow!("FastEmbedder init failed: {e}")
})?;
let dim = <crate::core::FastEmbedder as crate::core::Embedder>::dimension(&embedder);
let provider = embedder.provider();
let metal_hint = match provider {
trusty_embedder::ExecutionProvider::CoreML => " (Metal GPU / ANE)",
trusty_embedder::ExecutionProvider::Cuda => " (CUDA GPU)",
trusty_embedder::ExecutionProvider::Cpu => "",
};
tracing::info!(
"embedder initialized: model=AllMiniLML6V2(Q) dim={dim} provider={provider}{metal_hint}"
);
Ok(std::sync::Arc::new(embedder))
}
pub async fn handle_start(port: u16, foreground: bool) -> Result<()> {
let _ = foreground;
if let Some(pid) = crate::service::running_daemon_pid() {
tracing::info!("daemon already running (pid {pid}), exiting cleanly");
eprintln!(
"{} trusty-search daemon already running (pid {pid}); nothing to do",
"✓".green()
);
return Ok(());
}
let orphans = crate::commands::stop::find_daemon_pids();
if !orphans.is_empty() {
tracing::warn!(
"found {} existing trusty-search daemon process(es) not tracked by lockfile: {:?} — terminating before start",
orphans.len(),
orphans
);
eprintln!(
"{} found {} existing trusty-search daemon process(es) not tracked by lockfile — stopping them first",
"⚠".yellow(),
orphans.len()
);
for pid in &orphans {
#[cfg(unix)]
unsafe {
libc::kill(*pid as libc::pid_t, libc::SIGTERM);
}
}
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(3);
loop {
std::thread::sleep(std::time::Duration::from_millis(100));
#[cfg(unix)]
let any_alive = orphans
.iter()
.any(|p| unsafe { libc::kill(*p as libc::pid_t, 0) } == 0);
#[cfg(not(unix))]
let any_alive = false;
if !any_alive || std::time::Instant::now() >= deadline {
break;
}
}
#[cfg(unix)]
for pid in &orphans {
if unsafe { libc::kill(*pid as libc::pid_t, 0) } == 0 {
tracing::warn!("orphan pid {pid} ignored SIGTERM — sending SIGKILL");
unsafe {
libc::kill(*pid as libc::pid_t, libc::SIGKILL);
}
}
}
if let Ok(lock) = crate::service::daemon_lock_path() {
let _ = std::fs::remove_file(&lock);
}
if let Some(port) = crate::daemon_port_path() {
let _ = std::fs::remove_file(&port);
}
}
let cfg = crate::service::load_user_config();
let state = crate::service::SearchAppState::new(crate::core::registry::IndexRegistry::new())
.with_local_model(cfg.local_model)
.with_openrouter_model(cfg.openrouter_model)
.with_openrouter_api_key(cfg.openrouter_api_key);
let install_state = state.clone();
tokio::spawn(async move {
match build_embedder().await {
Ok(embedder) => {
install_state.install_embedder(embedder).await;
tracing::info!("embedder ready — vector lane online");
}
Err(e) => {
tracing::error!(
"embedder failed to initialize: {e:#} — daemon will continue in BM25-only mode"
);
eprintln!(
"{} embedder failed to initialize: {e}\n\
Daemon is up but running BM25-only. Check the model cache at \
~/Library/Caches/trusty-search/models/ and network access.",
"✗".red()
);
}
}
});
match crate::service::run_daemon(state, port).await {
Ok(()) => {}
Err(crate::service::DaemonError::AlreadyRunning(p)) => {
tracing::info!(
"daemon already running (lock at {}), exiting cleanly",
p.display()
);
eprintln!(
"{} trusty-search daemon already running (lock at {}); nothing to do",
"✓".green(),
p.display()
);
return Ok(());
}
Err(e) => {
eprintln!("{} daemon failed: {e}", "✗".red());
std::process::exit(1);
}
}
Ok(())
}