CheddahBot/cheddahbot/tools/file_ops.py

97 lines
3.4 KiB
Python

"""File operation tools: read, write, edit, search."""
from __future__ import annotations
import os
from pathlib import Path
from . import tool
@tool("read_file", "Read the contents of a file", category="files")
def read_file(path: str) -> str:
p = Path(path).resolve()
if not p.exists():
return f"File not found: {path}"
if not p.is_file():
return f"Not a file: {path}"
try:
content = p.read_text(encoding="utf-8", errors="replace")
if len(content) > 50000:
return content[:50000] + f"\n\n... (truncated, {len(content)} total chars)"
return content
except Exception as e:
return f"Error reading file: {e}"
@tool("write_file", "Write content to a file (creates or overwrites)", category="files")
def write_file(path: str, content: str) -> str:
p = Path(path).resolve()
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(content, encoding="utf-8")
return f"Written {len(content)} chars to {p}"
@tool("edit_file", "Replace text in a file (first occurrence)", category="files")
def edit_file(path: str, old_text: str, new_text: str) -> str:
p = Path(path).resolve()
if not p.exists():
return f"File not found: {path}"
content = p.read_text(encoding="utf-8")
if old_text not in content:
return f"Text not found in {path}"
content = content.replace(old_text, new_text, 1)
p.write_text(content, encoding="utf-8")
return f"Replaced text in {p}"
@tool("list_directory", "List files and folders in a directory", category="files")
def list_directory(path: str = ".") -> str:
p = Path(path).resolve()
if not p.is_dir():
return f"Not a directory: {path}"
entries = sorted(p.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower()))
lines = []
for e in entries[:200]:
prefix = "📁 " if e.is_dir() else "📄 "
size = ""
if e.is_file():
s = e.stat().st_size
if s > 1_000_000:
size = f" ({s / 1_000_000:.1f} MB)"
elif s > 1000:
size = f" ({s / 1000:.1f} KB)"
else:
size = f" ({s} B)"
lines.append(f"{prefix}{e.name}{size}")
return "\n".join(lines) if lines else "(empty directory)"
@tool("search_files", "Search for files matching a glob pattern", category="files")
def search_files(pattern: str, directory: str = ".") -> str:
p = Path(directory).resolve()
matches = list(p.glob(pattern))[:100]
if not matches:
return f"No files matching '{pattern}' in {directory}"
return "\n".join(str(m) for m in matches)
@tool("search_in_files", "Search for text content across files", category="files")
def search_in_files(query: str, directory: str = ".", extension: str = "") -> str:
p = Path(directory).resolve()
pattern = f"**/*{extension}" if extension else "**/*"
results = []
for f in p.glob(pattern):
if not f.is_file() or f.stat().st_size > 1_000_000:
continue
try:
content = f.read_text(encoding="utf-8", errors="ignore")
for i, line in enumerate(content.split("\n"), 1):
if query.lower() in line.lower():
results.append(f"{f}:{i}: {line.strip()[:200]}")
if len(results) >= 50:
return "\n".join(results) + "\n... (truncated)"
except Exception:
continue
return "\n".join(results) if results else f"No matches for '{query}'"