Skip to main content

matrixcode_core/tools/
ls.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::{Value, json};
4
5use super::{Tool, ToolDefinition};
6
7pub struct LsTool;
8
9#[async_trait]
10impl Tool for LsTool {
11    fn definition(&self) -> ToolDefinition {
12        ToolDefinition {
13            name: "ls".to_string(),
14            description:
15                "List immediate entries of a directory. Returns one entry per line, \
16                 dirs first (suffixed with /), then files with byte size. Does not \
17                 recurse — use 'glob' for recursive discovery."
18                    .to_string(),
19            parameters: json!({
20                "type": "object",
21                "properties": {
22                    "path": {
23                        "type": "string",
24                        "description": "Directory path (defaults to '.')"
25                    }
26                },
27                "required": []
28            }),
29        }
30    }
31
32    async fn execute(&self, params: Value) -> Result<String> {
33        let path = params["path"].as_str().unwrap_or(".").to_string();
34
35        // Show spinner while listing - RAII guard ensures cleanup on error
36        // let mut spinner = ToolSpinner::new(&format!("listing {}", path));
37
38        let mut dirs: Vec<String> = Vec::new();
39        let mut files: Vec<(String, u64)> = Vec::new();
40
41        let mut rd = tokio::fs::read_dir(&path).await?;
42        while let Some(entry) = rd.next_entry().await? {
43            let name = entry.file_name().to_string_lossy().into_owned();
44            let ft = entry.file_type().await?;
45            if ft.is_dir() {
46                dirs.push(format!("{}/", name));
47            } else {
48                let size = entry.metadata().await.map(|m| m.len()).unwrap_or(0);
49                files.push((name, size));
50            }
51        }
52
53        dirs.sort();
54        files.sort_by(|a, b| a.0.cmp(&b.0));
55
56        if dirs.is_empty() && files.is_empty() {
57            // spinner.finish_success("(empty)");
58            return Ok(format!("(empty) {}", path));
59        }
60
61        let mut out = String::new();
62        for d in dirs {
63            out.push_str(&d);
64            out.push('\n');
65        }
66        for (n, s) in files {
67            out.push_str(&format!("{}  ({} B)\n", n, s));
68        }
69        while out.ends_with('\n') {
70            out.pop();
71        }
72
73        // spinner.finish_success(&format!("{} entries", total));
74        Ok(out)
75    }
76}