Skip to main content

synaps_cli/tools/
ls.rs

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        // Use a dedicated temp dir to avoid races with other tests
76        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        // Cleanup
88        std::fs::remove_dir_all(&dir).ok();
89    }
90}