agtrace-runtime 0.7.1

Internal runtime orchestration for the agtrace CLI. Not intended for direct use.
Documentation
use crate::client::{InsightOps, MonitorBuilder, ProjectOps, SessionOps, WatchService};
use crate::config::Config;
use crate::init::{InitConfig, InitProgress, InitResult, InitService};
use crate::ops::{CheckResult, DoctorService, InspectResult};
use crate::{Error, Result};
use agtrace_engine::DiagnoseResult;
use agtrace_index::Database;
use agtrace_providers::ProviderAdapter;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use tokio::task;

pub struct AgTrace {
    db: Arc<Mutex<Database>>,
    config: Arc<Config>,
    provider_configs: Arc<Vec<(String, PathBuf)>>,
}

impl AgTrace {
    pub fn setup<F>(config: InitConfig, progress_fn: Option<F>) -> Result<InitResult>
    where
        F: FnMut(InitProgress),
    {
        InitService::run(config, progress_fn)
    }

    pub async fn connect_or_create(data_dir: PathBuf) -> Result<Self> {
        let db_path = data_dir.join("agtrace.db");
        let config_path = data_dir.join("config.toml");

        if !data_dir.exists() {
            std::fs::create_dir_all(&data_dir)?;
        }

        let config = if tokio::fs::try_exists(&config_path).await? {
            Config::load_from(&config_path)?
        } else {
            let detected = Config::detect_providers()?;
            detected.save_to(&config_path)?;
            detected
        };

        let db = task::spawn_blocking(move || Database::open(&db_path).map_err(Error::Index))
            .await
            .map_err(|e| Error::InvalidOperation(format!("Task join error: {}", e)))??;

        let provider_configs: Vec<(String, PathBuf)> = config
            .enabled_providers()
            .into_iter()
            .map(|(name, cfg)| (name.clone(), cfg.log_root.clone()))
            .collect();

        Ok(Self {
            db: Arc::new(Mutex::new(db)),
            config: Arc::new(config),
            provider_configs: Arc::new(provider_configs),
        })
    }

    pub async fn open(data_dir: PathBuf) -> Result<Self> {
        let db_path = data_dir.join("agtrace.db");
        let config_path = data_dir.join("config.toml");

        // Database operations wrapped in spawn_blocking
        let db = task::spawn_blocking(move || {
            Database::open(&db_path).map_err(|e| {
                if !db_path.exists() {
                    Error::NotInitialized(format!(
                        "Database not found. Please run 'agtrace init' to initialize the workspace.\n\
                         Database path: {}",
                        db_path.display()
                    ))
                } else {
                    Error::Index(e)
                }
            })
        })
        .await
        .map_err(|e| Error::InvalidOperation(format!("Task join error: {}", e)))??;

        // File I/O can use tokio::fs
        let config = if tokio::fs::try_exists(&config_path).await? {
            Config::load_from(&config_path)?
        } else {
            let detected = Config::detect_providers()?;
            detected.save_to(&config_path)?;
            detected
        };

        let provider_configs: Vec<(String, PathBuf)> = config
            .enabled_providers()
            .into_iter()
            .map(|(name, cfg)| (name.clone(), cfg.log_root.clone()))
            .collect();

        Ok(Self {
            db: Arc::new(Mutex::new(db)),
            config: Arc::new(config),
            provider_configs: Arc::new(provider_configs),
        })
    }

    pub fn diagnose(&self) -> Result<Vec<DiagnoseResult>> {
        let providers: Vec<(ProviderAdapter, PathBuf)> = self
            .provider_configs
            .iter()
            .filter_map(|(name, path)| {
                agtrace_providers::create_adapter(name)
                    .ok()
                    .map(|p| (p, path.clone()))
            })
            .collect();
        DoctorService::diagnose_all(&providers)
    }

    pub fn projects(&self) -> ProjectOps {
        ProjectOps::new(self.db.clone(), self.provider_configs.clone())
    }

    pub fn sessions(&self) -> SessionOps {
        SessionOps::new(self.db.clone(), self.provider_configs.clone())
    }

    pub fn insights(&self) -> InsightOps {
        InsightOps::new(self.db.clone(), self.provider_configs.clone())
    }

    pub fn watch_service(&self) -> WatchService {
        WatchService::new(
            self.db.clone(),
            self.config.clone(),
            self.provider_configs.clone(),
        )
    }

    pub fn workspace_monitor(&self) -> Result<MonitorBuilder> {
        Ok(MonitorBuilder::new(
            self.db.clone(),
            self.provider_configs.clone(),
        ))
    }

    pub fn database(&self) -> Arc<Mutex<Database>> {
        self.db.clone()
    }

    pub fn config(&self) -> &Config {
        &self.config
    }

    pub fn check_file(
        file_path: &str,
        provider: &ProviderAdapter,
        provider_name: &str,
    ) -> Result<CheckResult> {
        DoctorService::check_file(file_path, provider, provider_name)
    }

    pub fn inspect_file(file_path: &str, lines: usize, json_format: bool) -> Result<InspectResult> {
        DoctorService::inspect_file(file_path, lines, json_format)
    }
}