vex/commands/
tools.rs

1//! Tools command - List and execute VEX tools
2//!
3//! Usage:
4//! ```bash
5//! vex tools list
6//! vex tools run calculator '{"expression": "2+2"}'
7//! vex tools schema calculator
8//! ```
9
10use anyhow::{Context, Result};
11use clap::{Args, Subcommand};
12use colored::Colorize;
13use comfy_table::{modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Cell, Color, Table};
14
15use vex_llm::{tools::builtin_registry, ToolExecutor};
16
17/// Arguments for the tools command
18#[derive(Args)]
19pub struct ToolsArgs {
20    #[command(subcommand)]
21    command: ToolsCommand,
22}
23
24#[derive(Subcommand)]
25pub enum ToolsCommand {
26    /// List all available tools
27    #[command(name = "list")]
28    List,
29
30    /// Run a tool with JSON arguments
31    #[command(name = "run")]
32    Run {
33        /// Name of the tool to run
34        name: String,
35
36        /// JSON arguments for the tool
37        #[arg(default_value = "{}")]
38        args: String,
39
40        /// Output raw JSON (no formatting)
41        #[arg(long)]
42        raw: bool,
43    },
44
45    /// Show the JSON schema for a tool
46    #[command(name = "schema")]
47    Schema {
48        /// Name of the tool
49        name: String,
50    },
51}
52
53/// Run the tools command
54pub async fn run(args: ToolsArgs) -> Result<()> {
55    match args.command {
56        ToolsCommand::List => list_tools(),
57        ToolsCommand::Run { name, args, raw } => run_tool(&name, &args, raw).await,
58        ToolsCommand::Schema { name } => show_schema(&name),
59    }
60}
61
62/// List all available tools
63fn list_tools() -> Result<()> {
64    let registry = builtin_registry();
65
66    println!("{}", "🧰 VEX Built-in Tools".bold().cyan());
67    println!();
68
69    let mut table = Table::new();
70    table
71        .load_preset(UTF8_FULL)
72        .apply_modifier(UTF8_ROUND_CORNERS)
73        .set_header(vec![
74            Cell::new("Name").fg(Color::Cyan),
75            Cell::new("Description").fg(Color::Cyan),
76            Cell::new("Capabilities").fg(Color::Cyan),
77        ]);
78
79    for def in registry.definitions() {
80        // Get capabilities for this tool
81        // Safe: we iterate over definitions that exist in the registry
82        if let Some(tool) = registry.get(def.name) {
83            let caps: Vec<String> = tool
84                .capabilities()
85                .iter()
86                .map(|c| format!("{:?}", c))
87                .collect();
88
89            table.add_row(vec![
90                Cell::new(def.name).fg(Color::Green),
91                Cell::new(def.description),
92                Cell::new(caps.join(", ")).fg(Color::Yellow),
93            ]);
94        }
95    }
96
97    println!("{table}");
98    println!();
99    println!(
100        "Run a tool: {}",
101        "vex tools run <name> '<json_args>'".green()
102    );
103    println!("Show schema: {}", "vex tools schema <name>".green());
104
105    Ok(())
106}
107
108/// Run a tool with JSON arguments
109async fn run_tool(name: &str, args_str: &str, raw: bool) -> Result<()> {
110    let registry = builtin_registry();
111    let executor = ToolExecutor::new(registry);
112
113    // Parse JSON arguments
114    let args: serde_json::Value =
115        serde_json::from_str(args_str).with_context(|| format!("Invalid JSON: {}", args_str))?;
116
117    if !raw {
118        println!("{} Running tool '{}'...", "⚙".blue(), name.green());
119        println!();
120    }
121
122    // Execute the tool
123    let result = executor
124        .execute(name, args)
125        .await
126        .with_context(|| format!("Tool '{}' execution failed", name))?;
127
128    if raw {
129        // Raw JSON output
130        println!("{}", serde_json::to_string_pretty(&result.output)?);
131    } else {
132        // Formatted output
133        println!("{}", "Result:".bold());
134        println!("{}", serde_json::to_string_pretty(&result.output)?);
135        println!();
136        println!("{} {}", "Hash:".dimmed(), result.hash);
137        println!("{} {}ms", "Execution time:".dimmed(), result.execution_ms());
138    }
139
140    Ok(())
141}
142
143/// Show the JSON schema for a tool
144fn show_schema(name: &str) -> Result<()> {
145    let registry = builtin_registry();
146
147    let tool = registry.get(name).ok_or_else(|| {
148        anyhow::anyhow!(
149            "Tool '{}' not found. Run 'vex tools list' to see available tools.",
150            name
151        )
152    })?;
153
154    let def = tool.definition();
155
156    println!("{} Schema for '{}'", "📋".cyan(), name.green().bold());
157    println!();
158    println!("{}", "Name:".bold());
159    println!("  {}", def.name);
160    println!();
161    println!("{}", "Description:".bold());
162    println!("  {}", def.description);
163    println!();
164    println!("{}", "Parameters (JSON Schema):".bold());
165
166    // Pretty print the parameters JSON
167    let params: serde_json::Value =
168        serde_json::from_str(def.parameters).unwrap_or(serde_json::json!({}));
169    println!("{}", serde_json::to_string_pretty(&params)?);
170
171    println!();
172    println!("{}", "OpenAI Format:".bold());
173    println!("{}", serde_json::to_string_pretty(&def.to_openai_format())?);
174
175    Ok(())
176}