1use 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#[derive(Args)]
19pub struct ToolsArgs {
20 #[command(subcommand)]
21 command: ToolsCommand,
22}
23
24#[derive(Subcommand)]
25pub enum ToolsCommand {
26 #[command(name = "list")]
28 List,
29
30 #[command(name = "run")]
32 Run {
33 name: String,
35
36 #[arg(default_value = "{}")]
38 args: String,
39
40 #[arg(long)]
42 raw: bool,
43 },
44
45 #[command(name = "schema")]
47 Schema {
48 name: String,
50 },
51}
52
53pub 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
62fn 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 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
108async fn run_tool(name: &str, args_str: &str, raw: bool) -> Result<()> {
110 let registry = builtin_registry();
111 let executor = ToolExecutor::new(registry);
112
113 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 let result = executor
124 .execute(name, args)
125 .await
126 .with_context(|| format!("Tool '{}' execution failed", name))?;
127
128 if raw {
129 println!("{}", serde_json::to_string_pretty(&result.output)?);
131 } else {
132 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
143fn 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 let params: serde_json::Value =
168 serde_json::from_str(def.parameters).unwrap_or(serde_json::json!({}));
169 println!("{}", serde_json::to_string_pretty(¶ms)?);
170
171 println!();
172 println!("{}", "OpenAI Format:".bold());
173 println!("{}", serde_json::to_string_pretty(&def.to_openai_format())?);
174
175 Ok(())
176}