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