agtrace_runtime/client/
workspace.rs1use crate::client::{InsightOps, MonitorBuilder, ProjectOps, SessionOps, WatchService};
2use crate::config::Config;
3use crate::init::{InitConfig, InitProgress, InitResult, InitService};
4use crate::ops::{CheckResult, DoctorService, InspectResult};
5use crate::{Error, Result};
6use agtrace_engine::DiagnoseResult;
7use agtrace_index::Database;
8use agtrace_providers::ProviderAdapter;
9use std::path::PathBuf;
10use std::sync::{Arc, Mutex};
11use tokio::task;
12
13pub struct AgTrace {
14 db: Arc<Mutex<Database>>,
15 config: Arc<Config>,
16 provider_configs: Arc<Vec<(String, PathBuf)>>,
17}
18
19impl AgTrace {
20 pub fn setup<F>(config: InitConfig, progress_fn: Option<F>) -> Result<InitResult>
21 where
22 F: FnMut(InitProgress),
23 {
24 InitService::run(config, progress_fn)
25 }
26
27 pub async fn connect_or_create(data_dir: PathBuf) -> Result<Self> {
28 let db_path = data_dir.join("agtrace.db");
29 let config_path = data_dir.join("config.toml");
30
31 if !data_dir.exists() {
32 std::fs::create_dir_all(&data_dir)?;
33 }
34
35 let config = if tokio::fs::try_exists(&config_path).await? {
36 Config::load_from(&config_path)?
37 } else {
38 let detected = Config::detect_providers()?;
39 detected.save_to(&config_path)?;
40 detected
41 };
42
43 let db = task::spawn_blocking(move || Database::open(&db_path).map_err(Error::Index))
44 .await
45 .map_err(|e| Error::InvalidOperation(format!("Task join error: {}", e)))??;
46
47 let provider_configs: Vec<(String, PathBuf)> = config
48 .enabled_providers()
49 .into_iter()
50 .map(|(name, cfg)| (name.clone(), cfg.log_root.clone()))
51 .collect();
52
53 Ok(Self {
54 db: Arc::new(Mutex::new(db)),
55 config: Arc::new(config),
56 provider_configs: Arc::new(provider_configs),
57 })
58 }
59
60 pub async fn open(data_dir: PathBuf) -> Result<Self> {
61 let db_path = data_dir.join("agtrace.db");
62 let config_path = data_dir.join("config.toml");
63
64 let db = task::spawn_blocking(move || {
66 Database::open(&db_path).map_err(|e| {
67 if !db_path.exists() {
68 Error::NotInitialized(format!(
69 "Database not found. Please run 'agtrace init' to initialize the workspace.\n\
70 Database path: {}",
71 db_path.display()
72 ))
73 } else {
74 Error::Index(e)
75 }
76 })
77 })
78 .await
79 .map_err(|e| Error::InvalidOperation(format!("Task join error: {}", e)))??;
80
81 let config = if tokio::fs::try_exists(&config_path).await? {
83 Config::load_from(&config_path)?
84 } else {
85 let detected = Config::detect_providers()?;
86 detected.save_to(&config_path)?;
87 detected
88 };
89
90 let provider_configs: Vec<(String, PathBuf)> = config
91 .enabled_providers()
92 .into_iter()
93 .map(|(name, cfg)| (name.clone(), cfg.log_root.clone()))
94 .collect();
95
96 Ok(Self {
97 db: Arc::new(Mutex::new(db)),
98 config: Arc::new(config),
99 provider_configs: Arc::new(provider_configs),
100 })
101 }
102
103 pub fn diagnose(&self) -> Result<Vec<DiagnoseResult>> {
104 let providers: Vec<(ProviderAdapter, PathBuf)> = self
105 .provider_configs
106 .iter()
107 .filter_map(|(name, path)| {
108 agtrace_providers::create_adapter(name)
109 .ok()
110 .map(|p| (p, path.clone()))
111 })
112 .collect();
113 DoctorService::diagnose_all(&providers)
114 }
115
116 pub fn projects(&self) -> ProjectOps {
117 ProjectOps::new(self.db.clone(), self.provider_configs.clone())
118 }
119
120 pub fn sessions(&self) -> SessionOps {
121 SessionOps::new(self.db.clone(), self.provider_configs.clone())
122 }
123
124 pub fn insights(&self) -> InsightOps {
125 InsightOps::new(self.db.clone(), self.provider_configs.clone())
126 }
127
128 pub fn watch_service(&self) -> WatchService {
129 WatchService::new(
130 self.db.clone(),
131 self.config.clone(),
132 self.provider_configs.clone(),
133 )
134 }
135
136 pub fn workspace_monitor(&self) -> Result<MonitorBuilder> {
137 Ok(MonitorBuilder::new(
138 self.db.clone(),
139 self.provider_configs.clone(),
140 ))
141 }
142
143 pub fn database(&self) -> Arc<Mutex<Database>> {
144 self.db.clone()
145 }
146
147 pub fn config(&self) -> &Config {
148 &self.config
149 }
150
151 pub fn check_file(
152 file_path: &str,
153 provider: &ProviderAdapter,
154 provider_name: &str,
155 ) -> Result<CheckResult> {
156 DoctorService::check_file(file_path, provider, provider_name)
157 }
158
159 pub fn inspect_file(file_path: &str, lines: usize, json_format: bool) -> Result<InspectResult> {
160 DoctorService::inspect_file(file_path, lines, json_format)
161 }
162}