use std::fs; use serde_json::{json, Value}; use anyhow::{Result, anyhow}; use super::super::Tool; use super::super::ToolCtx; pub struct DirList; impl Tool for DirList { fn name(&self) -> &'static str { "dir_list" } fn description(&self) -> &'static str { "List files and directories in a directory. Use this to explore the workspace structure." } fn parameters(&self) -> Value { json!({ "type": "object", "properties": { "path": { "type": "string", "description": "Directory path to list (relative to workspace root)" } }, "required": ["path"] }) } fn run(&self, ctx: &ToolCtx, args: &Value) -> Result { let rel = args.get("path") .and_then(|v| v.as_str()) .ok_or_else(|| anyhow!("missing required argument: path"))?; let path = super::super::resolve_path(&ctx.workspaces, rel)?; if !path.exists() { return Ok(format!("path '{}' does not exist (resolved to {})", rel, path.display())); } if !path.is_dir() { return Ok(format!("path '{}' is not a directory (resolved to {})", rel, path.display())); } let entries: Vec = fs::read_dir(&path) .map_err(|e| anyhow!("failed to read directory '{}': {}", rel, e))? .filter_map(|e| e.ok()) .map(|e| { let name = e.file_name().to_string_lossy().to_string(); let is_dir = e.file_type().map(|t| t.is_dir()).unwrap_or(false); if is_dir { format!("{}/", name) } else { name } }) .collect(); let canon = path.canonicalize().unwrap_or(path); let header = format!("{} entries in {}:\n", entries.len(), canon.display()); if entries.is_empty() { Ok(format!("{} (empty directory)", header.trim())) } else { Ok(header + &entries.join("\n")) } } }