116 lines
3.7 KiB
Python
116 lines
3.7 KiB
Python
"""ListDirectoryTool — list directory contents with metadata.
|
|
|
|
Provides a lightweight ``ls``-style listing showing file type, size,
|
|
and modification time. Faster and more structured than Bash(ls) or
|
|
Glob for simple directory orientation.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Any, ClassVar
|
|
|
|
from ..base import Tool, ToolParameter, ToolResult
|
|
|
|
|
|
def _format_size(size: int) -> str:
|
|
"""Format byte count into human-readable size."""
|
|
for unit in ("B", "KB", "MB", "GB"):
|
|
if size < 1024:
|
|
return f"{size:>{6}.1f} {unit}" if unit != "B" else f"{size:>{6}} {unit} "
|
|
break
|
|
size /= 1024 # type: ignore[assignment]
|
|
return f"{size:>{6}.1f} TB"
|
|
|
|
|
|
class ListDirectoryTool(Tool):
|
|
"""List contents of a directory with type, size, and modification time.
|
|
|
|
Returns entries sorted with directories first, then files, each
|
|
group sorted alphabetically.
|
|
"""
|
|
|
|
name: ClassVar[str] = "ls"
|
|
description: ClassVar[str] = (
|
|
"List directory contents showing file type, size, and modification time. "
|
|
"Directories are listed first, then files."
|
|
)
|
|
parameters: ClassVar[list[ToolParameter]] = [
|
|
ToolParameter(
|
|
name="path",
|
|
type="string",
|
|
description="Absolute path to the directory to list",
|
|
),
|
|
ToolParameter(
|
|
name="show_hidden",
|
|
type="boolean",
|
|
description="Include hidden files/directories (names starting with .)",
|
|
required=False,
|
|
default=False,
|
|
),
|
|
]
|
|
|
|
async def execute(self, **kwargs: Any) -> ToolResult:
|
|
dir_path = Path(kwargs["path"])
|
|
show_hidden: bool = kwargs.get("show_hidden", False)
|
|
|
|
if not dir_path.is_absolute():
|
|
return ToolResult.fail(f"Path must be absolute: {dir_path}")
|
|
|
|
if not dir_path.exists():
|
|
return ToolResult.fail(f"Directory not found: {dir_path}")
|
|
|
|
if not dir_path.is_dir():
|
|
return ToolResult.fail(f"Not a directory: {dir_path}")
|
|
|
|
try:
|
|
entries = list(dir_path.iterdir())
|
|
except PermissionError:
|
|
return ToolResult.fail(f"Permission denied: {dir_path}")
|
|
|
|
if not show_hidden:
|
|
entries = [e for e in entries if not e.name.startswith(".")]
|
|
|
|
dirs: list[Path] = sorted(
|
|
[e for e in entries if e.is_dir()], key=lambda p: p.name
|
|
)
|
|
files: list[Path] = sorted(
|
|
[e for e in entries if e.is_file()], key=lambda p: p.name
|
|
)
|
|
other: list[Path] = sorted(
|
|
[e for e in entries if not e.is_dir() and not e.is_file()],
|
|
key=lambda p: p.name,
|
|
)
|
|
|
|
lines: list[str] = []
|
|
|
|
for d in dirs:
|
|
try:
|
|
st = d.stat()
|
|
mtime = time.strftime("%Y-%m-%d %H:%M", time.localtime(st.st_mtime))
|
|
except OSError:
|
|
mtime = " "
|
|
lines.append(f"d {mtime} - {d.name}/")
|
|
|
|
for f in files:
|
|
try:
|
|
st = f.stat()
|
|
mtime = time.strftime("%Y-%m-%d %H:%M", time.localtime(st.st_mtime))
|
|
size = _format_size(st.st_size)
|
|
except OSError:
|
|
mtime = " "
|
|
size = " ? B "
|
|
lines.append(f"f {mtime} {size} {f.name}")
|
|
|
|
for o in other:
|
|
lines.append(f"? {'':16} {'':9} {o.name}")
|
|
|
|
output = "\n".join(lines) if lines else "(empty directory)"
|
|
|
|
return ToolResult.success(
|
|
output,
|
|
total_entries=len(dirs) + len(files) + len(other),
|
|
directories=len(dirs),
|
|
files=len(files),
|
|
)
|