vx_cli/commands/
search.rs

1// Search command implementation
2
3use crate::cli::OutputFormat;
4use crate::ui::UI;
5use std::collections::HashMap;
6use vx_core::PluginRegistry;
7use vx_core::Result;
8
9pub async fn handle(
10    registry: &PluginRegistry,
11    query: Option<String>,
12    category: Option<String>,
13    _installed_only: bool,
14    _available_only: bool,
15    format: OutputFormat,
16    verbose: bool,
17) -> Result<()> {
18    let spinner = UI::new_spinner("Searching tools...");
19
20    // Get all available tools from plugins
21    let mut all_tools = Vec::new();
22    for plugin in registry.get_plugins() {
23        for tool in plugin.tools() {
24            all_tools.push((
25                tool.name().to_string(),
26                tool.description().to_string(),
27                plugin.name().to_string(),
28            ));
29        }
30    }
31
32    spinner.finish_and_clear();
33
34    // Filter tools based on query
35    let filtered_tools: Vec<_> = all_tools
36        .into_iter()
37        .filter(|(name, description, _plugin)| {
38            // Apply query filter
39            if let Some(ref q) = query {
40                let q_lower = q.to_lowercase();
41                if !name.to_lowercase().contains(&q_lower)
42                    && !description.to_lowercase().contains(&q_lower)
43                {
44                    return false;
45                }
46            }
47
48            // Apply category filter
49            if let Some(ref cat) = category {
50                let cat_lower = cat.to_lowercase();
51                // Simple category mapping based on tool names
52                let tool_category = get_tool_category(name);
53                if !tool_category.to_lowercase().contains(&cat_lower) {
54                    return false;
55                }
56            }
57
58            true
59        })
60        .collect();
61
62    // Display results
63    match format {
64        OutputFormat::Table => display_table(&filtered_tools, verbose, &query),
65        OutputFormat::Json => display_json(&filtered_tools),
66        OutputFormat::Yaml => display_yaml(&filtered_tools),
67    }
68
69    Ok(())
70}
71
72fn get_tool_category(tool_name: &str) -> &'static str {
73    match tool_name {
74        "node" | "npm" | "npx" => "javascript",
75        "go" => "go",
76        "cargo" => "rust",
77        "uv" | "uvx" => "python",
78        _ => "utility",
79    }
80}
81
82fn display_table(tools: &[(String, String, String)], verbose: bool, query: &Option<String>) {
83    if let Some(q) = query {
84        UI::info(&format!("Search results for: {}", q));
85    } else {
86        UI::info("Available tools:");
87    }
88
89    if tools.is_empty() {
90        UI::warn("No tools found matching the criteria");
91        return;
92    }
93
94    println!();
95    if verbose {
96        for (name, description, plugin) in tools {
97            let category = get_tool_category(name);
98            println!("{} ({})", name, category);
99            println!("  Plugin: {}", plugin);
100            println!("  Description: {}", description);
101            println!("  Status: Available"); // TODO: Check if installed
102            println!();
103        }
104    } else {
105        // Simple table format
106        println!("┌─────────────┬─────────────┬─────────────────────────────────────┐");
107        println!("│ Tool        │ Category    │ Description                         │");
108        println!("├─────────────┼─────────────┼─────────────────────────────────────┤");
109
110        for (name, description, _plugin) in tools {
111            let category = get_tool_category(name);
112            let truncated_desc = if description.len() > 35 {
113                format!("{}...", &description[..32])
114            } else {
115                description.clone()
116            };
117
118            println!(
119                "│ {:<11} │ {:<11} │ {:<35} │",
120                name, category, truncated_desc
121            );
122        }
123
124        println!("└─────────────┴─────────────┴─────────────────────────────────────┘");
125    }
126
127    UI::info(&format!("Found {} tools", tools.len()));
128}
129
130fn display_json(tools: &[(String, String, String)]) {
131    let mut json_tools = Vec::new();
132
133    for (name, description, plugin) in tools {
134        let mut tool_info = HashMap::new();
135        tool_info.insert("name", name.clone());
136        tool_info.insert("description", description.clone());
137        tool_info.insert("plugin", plugin.clone());
138        tool_info.insert("category", get_tool_category(name).to_string());
139        tool_info.insert("status", "available".to_string()); // TODO: Check if installed
140        json_tools.push(tool_info);
141    }
142
143    match serde_json::to_string_pretty(&json_tools) {
144        Ok(json) => println!("{}", json),
145        Err(e) => UI::error(&format!("Failed to serialize to JSON: {}", e)),
146    }
147}
148
149fn display_yaml(tools: &[(String, String, String)]) {
150    let mut yaml_tools = Vec::new();
151
152    for (name, description, plugin) in tools {
153        let mut tool_info = HashMap::new();
154        tool_info.insert("name", name.clone());
155        tool_info.insert("description", description.clone());
156        tool_info.insert("plugin", plugin.clone());
157        tool_info.insert("category", get_tool_category(name).to_string());
158        tool_info.insert("status", "available".to_string()); // TODO: Check if installed
159        yaml_tools.push(tool_info);
160    }
161
162    match serde_yaml::to_string(&yaml_tools) {
163        Ok(yaml) => println!("{}", yaml),
164        Err(e) => UI::error(&format!("Failed to serialize to YAML: {}", e)),
165    }
166}