zeroclaw_runtime/rpc/
fs.rs1use std::path::Path;
7use zeroclaw_api::jsonrpc::error_codes::*;
8use zeroclaw_api::jsonrpc::{FsEntry, FsListDirRequest, FsListDirResponse};
9
10pub async fn handle_fs_list_dir(
12 params: &serde_json::Value,
13) -> Result<serde_json::Value, zeroclaw_api::jsonrpc::JsonRpcError> {
14 let req: FsListDirRequest = serde_json::from_value(params.clone())
15 .map_err(|e| rpc_err(INVALID_PARAMS, e.to_string()))?;
16
17 let path = Path::new(&req.path);
18
19 if path.components().any(|c| c.as_os_str() == "..") {
21 return Err(rpc_err(FS_INVALID_PATH, "Path traversal not allowed"));
22 }
23
24 if !path.is_dir() {
25 return Err(rpc_err(
26 FS_NOT_FOUND,
27 format!("Not a directory: {}", req.path),
28 ));
29 }
30
31 let mut entries = Vec::new();
32 let read_dir = match std::fs::read_dir(path) {
33 Ok(rd) => rd,
34 Err(e) => {
35 return Err(rpc_err(
36 FS_NOT_FOUND,
37 format!("Cannot read {}: {e}", req.path),
38 ));
39 }
40 };
41
42 for entry in read_dir {
43 let entry = match entry {
44 Ok(e) => e,
45 Err(_) => continue,
46 };
47 let meta = match entry.metadata() {
48 Ok(m) => m,
49 Err(_) => continue,
50 };
51 let name = entry.file_name().to_string_lossy().to_string();
52 let is_hidden = name.starts_with('.');
53 if is_hidden && !req.show_hidden {
54 continue;
55 }
56
57 let full_path = entry.path().to_string_lossy().to_string();
58 entries.push(FsEntry {
59 name,
60 is_dir: meta.is_dir(),
61 size: meta.len(),
62 is_hidden,
63 full_path,
64 mtime: meta.modified().ok().and_then(|t| {
65 t.duration_since(std::time::UNIX_EPOCH)
66 .ok()
67 .map(|d| d.as_secs())
68 }),
69 });
70 }
71
72 entries.sort_by(|a, b| match (a.is_dir, b.is_dir) {
74 (true, false) => std::cmp::Ordering::Less,
75 (false, true) => std::cmp::Ordering::Greater,
76 _ => a.name.to_lowercase().cmp(&b.name.to_lowercase()),
77 });
78
79 let cwd = path.to_string_lossy().to_string();
80 let resp = FsListDirResponse { entries, cwd };
81 serde_json::to_value(resp).map_err(|e| rpc_err(INTERNAL_ERROR, e.to_string()))
82}
83
84fn rpc_err(code: i32, msg: impl Into<String>) -> zeroclaw_api::jsonrpc::JsonRpcError {
85 zeroclaw_api::jsonrpc::JsonRpcError {
86 code,
87 message: msg.into(),
88 data: None,
89 }
90}