1use serde_json::{json, Value};
2use tokio::process::Command;
3use crate::{Result, RuntimeError};
4use super::{Tool, ToolContext, expand_path};
5
6pub struct LsTool;
7
8#[async_trait::async_trait]
9impl Tool for LsTool {
10 fn name(&self) -> &str { "ls" }
11
12 fn description(&self) -> &str {
13 "List directory contents with details (permissions, size, modification date). Defaults to current directory."
14 }
15
16 fn parameters(&self) -> Value {
17 json!({
18 "type": "object",
19 "properties": {
20 "path": {
21 "type": "string",
22 "description": "Directory path to list (default: current directory)"
23 }
24 },
25 "required": []
26 })
27 }
28
29 async fn execute(&self, params: Value, _ctx: ToolContext) -> Result<String> {
30 let path = expand_path(params["path"].as_str().unwrap_or("."));
31
32 let output = Command::new("ls")
33 .arg("-lah")
34 .arg(&path)
35 .output()
36 .await
37 .map_err(|e| RuntimeError::Tool(format!("Failed to execute ls: {}", e)))?;
38
39 let stdout = String::from_utf8_lossy(&output.stdout);
40 let stderr = String::from_utf8_lossy(&output.stderr);
41
42 if !output.status.success() {
43 Err(RuntimeError::Tool(format!("ls failed: {}", stderr)))
44 } else if stdout.is_empty() {
45 Ok("Directory is empty.".to_string())
46 } else {
47 Ok(stdout.to_string())
48 }
49 }
50}
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use super::super::test_helpers::create_tool_context;
55 use crate::tools::Tool;
56 use serde_json::json;
57
58 #[test]
59 fn test_ls_tool_schema() {
60 let tool = LsTool;
61 assert_eq!(tool.name(), "ls");
62 assert!(!tool.description().is_empty());
63
64 let params = tool.parameters();
65 assert_eq!(params["type"], "object");
66 assert!(params["properties"].is_object());
67 assert!(params["required"].is_array());
68 }
69
70 #[tokio::test]
71 async fn test_ls_tool_execution() {
72 let tool = LsTool;
73 let ctx = create_tool_context();
74
75 let dir = std::env::temp_dir().join("synaps_test_ls");
77 std::fs::create_dir_all(&dir).unwrap();
78 std::fs::write(dir.join("hello.txt"), "hi").unwrap();
79
80 let params = json!({
81 "path": dir.to_str().unwrap()
82 });
83
84 let result = tool.execute(params, ctx).await.unwrap();
85 assert!(result.contains("hello.txt"));
86
87 std::fs::remove_dir_all(&dir).ok();
89 }
90}