terminal_jarvis/tools/
tools_config.rs1use anyhow::{anyhow, Result};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::path::PathBuf;
8
9#[derive(Debug, Clone, Deserialize, Serialize)]
11pub struct ToolDefinition {
12 pub display_name: String,
13 pub config_key: String,
14 pub description: String,
15 pub homepage: String,
16 pub documentation: String,
17 pub cli_command: String,
18 pub requires_npm: bool,
19 pub requires_sudo: bool,
20 pub status: String,
21 pub install: InstallCommand,
22 pub update: InstallCommand,
23 pub auth: AuthDefinition,
24 pub features: Option<ToolFeatures>,
25}
26
27#[derive(Debug, Clone, Deserialize, Serialize)]
29pub struct InstallCommand {
30 pub command: String,
31 pub args: Vec<String>,
32 pub pipe_to: Option<String>, pub verify_command: Option<String>,
34 pub post_install_message: Option<String>,
35}
36
37#[derive(Debug, Clone, Deserialize, Serialize)]
39pub struct AuthDefinition {
40 pub env_vars: Vec<String>,
41 pub setup_url: String,
42 pub browser_auth: bool,
43 pub auth_instructions: Option<String>,
44}
45
46#[derive(Debug, Clone, Deserialize, Serialize)]
48pub struct ToolFeatures {
49 pub supports_files: bool,
50 pub supports_streaming: bool,
51 pub supports_conversation: bool,
52 pub max_context_tokens: Option<u64>,
53 pub supported_languages: Vec<String>,
54}
55
56#[derive(Debug, Clone, Deserialize, Serialize)]
58pub struct ToolPreferences {
59 #[serde(default = "default_true")]
60 pub enabled: bool,
61 #[serde(default = "default_true")]
62 pub auto_update: bool,
63}
64
65fn default_true() -> bool {
66 true
67}
68
69pub struct ToolConfigLoader {
71 tool_definitions: HashMap<String, ToolDefinition>,
73 user_preferences: HashMap<String, ToolPreferences>,
75}
76
77impl Default for ToolConfigLoader {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl ToolConfigLoader {
84 pub fn new() -> Self {
86 let mut loader = Self {
87 tool_definitions: HashMap::new(),
88 user_preferences: HashMap::new(),
89 };
90
91 if let Err(e) = loader.load_builtin_tools() {
93 eprintln!("Warning: Failed to load tool configurations: {}", e);
94 }
95
96 if let Err(e) = loader.load_user_preferences() {
98 eprintln!("Warning: Failed to load user preferences: {}", e);
99 }
100
101 loader
102 }
103
104 fn load_builtin_tools(&mut self) -> Result<()> {
106 let config_dirs = vec![
107 std::env::current_exe()
108 .ok()
109 .and_then(|exe| exe.parent().map(|p| p.join("../config/tools"))),
110 Some(PathBuf::from("./config/tools")),
111 Some(PathBuf::from("../config/tools")),
112 ];
113
114 for config_dir in config_dirs.into_iter().flatten() {
115 if config_dir.exists() && config_dir.is_dir() {
116 if let Ok(entries) = std::fs::read_dir(&config_dir) {
117 for entry in entries.flatten() {
118 if let Some(file_name) = entry.file_name().to_str() {
119 if file_name.ends_with(".toml") {
120 let tool_name = file_name.trim_end_matches(".toml");
121 if let Ok(tool_config) = self.load_tool_config(&entry.path()) {
122 self.tool_definitions
123 .insert(tool_name.to_string(), tool_config);
124 }
125 }
126 }
127 }
128 }
129 break; }
131 }
132
133 Ok(())
134 }
135
136 fn load_tool_config(&self, path: &PathBuf) -> Result<ToolDefinition> {
138 let content = std::fs::read_to_string(path)?;
139
140 #[derive(Deserialize)]
142 struct ToolFile {
143 tool: ToolDefinition,
144 }
145
146 let tool_file: ToolFile = toml::from_str(&content)
147 .map_err(|e| anyhow!("Failed to parse tool config {}: {}", path.display(), e))?;
148
149 Ok(tool_file.tool)
150 }
151
152 fn load_user_preferences(&mut self) -> Result<()> {
154 let config_paths = vec![
155 dirs::config_dir().map(|p| p.join("terminal-jarvis").join("config.toml")),
156 Some(PathBuf::from("./terminal-jarvis.toml")),
157 ];
158
159 for path in config_paths.into_iter().flatten() {
160 if path.exists() {
161 if let Ok(content) = std::fs::read_to_string(&path) {
162 if let Ok(user_config) = toml::from_str::<UserConfigFile>(&content) {
163 if let Some(prefs) = user_config.preferences {
164 if let Some(tools) = prefs.tools {
165 self.user_preferences.extend(tools);
166 }
167 }
168 break; }
170 }
171 }
172 }
173
174 Ok(())
175 }
176
177 pub fn get_tool_definition(&self, tool_name: &str) -> Option<&ToolDefinition> {
179 self.tool_definitions.get(tool_name)
180 }
181
182 pub fn get_tool_names(&self) -> Vec<String> {
184 self.tool_definitions.keys().cloned().collect()
185 }
186
187 #[allow(dead_code)] pub fn is_tool_enabled(&self, tool_name: &str) -> bool {
190 if let Some(tool_def) = self.tool_definitions.get(tool_name) {
191 if let Some(prefs) = self.user_preferences.get(&tool_def.config_key) {
192 return prefs.enabled;
193 }
194 }
195 true }
197
198 pub fn get_install_command(&self, tool_name: &str) -> Option<&InstallCommand> {
200 self.tool_definitions.get(tool_name).map(|t| &t.install)
201 }
202
203 #[allow(dead_code)] pub fn get_update_command(&self, tool_name: &str) -> Option<&InstallCommand> {
206 self.tool_definitions.get(tool_name).map(|t| &t.update)
207 }
208
209 #[allow(dead_code)] pub fn get_auth_info(&self, tool_name: &str) -> Option<&AuthDefinition> {
212 self.tool_definitions.get(tool_name).map(|t| &t.auth)
213 }
214
215 #[allow(dead_code)] pub fn get_display_name_to_config_mapping(&self) -> HashMap<String, String> {
218 self.tool_definitions
219 .values()
220 .map(|tool_def| (tool_def.display_name.clone(), tool_def.config_key.clone()))
221 .collect()
222 }
223
224 #[allow(dead_code)] pub fn requires_sudo(&self, tool_name: &str) -> bool {
227 self.tool_definitions
228 .get(tool_name)
229 .map(|t| t.requires_sudo)
230 .unwrap_or(false)
231 }
232
233 #[allow(dead_code)] pub fn get_npm_tools(&self) -> Vec<String> {
236 self.tool_definitions
237 .iter()
238 .filter(|(_, tool_def)| tool_def.requires_npm)
239 .map(|(name, _)| name.clone())
240 .collect()
241 }
242}
243
244#[derive(Debug, Deserialize)]
246struct UserConfigFile {
247 preferences: Option<UserPreferencesFile>,
248}
249
250#[derive(Debug, Deserialize)]
251struct UserPreferencesFile {
252 tools: Option<HashMap<String, ToolPreferences>>,
253}
254
255static TOOL_CONFIG_LOADER: std::sync::OnceLock<ToolConfigLoader> = std::sync::OnceLock::new();
257
258pub fn get_tool_config_loader() -> &'static ToolConfigLoader {
260 TOOL_CONFIG_LOADER.get_or_init(ToolConfigLoader::new)
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_tool_config_loader_creation() {
269 let loader = ToolConfigLoader::new();
270 let _tool_names = loader.get_tool_names();
272 }
273
274 #[test]
275 fn test_global_config_loader() {
276 let loader = get_tool_config_loader();
277 let _tool_names = loader.get_tool_names();
278 }
279}