matrixcode_core/tools/
ls.rs1use 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 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 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 Ok(out)
75 }
76}