diff --git a/src/plyngent/tools/file/__init__.py b/src/plyngent/tools/file/__init__.py new file mode 100644 index 0000000..ee36c14 --- /dev/null +++ b/src/plyngent/tools/file/__init__.py @@ -0,0 +1,6 @@ +from .edit_replace import edit_replace as edit_replace +from .listdir import listdir as listdir +from .read import read_file as read_file +from .write import write_file as write_file + +FILE_TOOLS = [read_file, write_file, listdir, edit_replace] diff --git a/src/plyngent/tools/file/edit_replace.py b/src/plyngent/tools/file/edit_replace.py new file mode 100644 index 0000000..c7e8d31 --- /dev/null +++ b/src/plyngent/tools/file/edit_replace.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from plyngent.agent import tool +from plyngent.tools.workspace import resolve_path + + +@tool +def edit_replace(path: str, old_string: str, new_string: str) -> str: + """Replace the first occurrence of ``old_string`` with ``new_string`` in a file.""" + if not old_string: + return "error: old_string must not be empty" + target = resolve_path(path) + if not target.is_file(): + return f"error: not a file: {path}" + text = target.read_text(encoding="utf-8", errors="replace") + if old_string not in text: + return "error: old_string not found in file" + updated = text.replace(old_string, new_string, 1) + _ = target.write_text(updated, encoding="utf-8") + return f"replaced first occurrence in {path}" diff --git a/src/plyngent/tools/file/listdir.py b/src/plyngent/tools/file/listdir.py new file mode 100644 index 0000000..b46877d --- /dev/null +++ b/src/plyngent/tools/file/listdir.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from plyngent.agent import tool +from plyngent.tools.workspace import resolve_path + + +@tool +def listdir(path: str = ".") -> str: + """List entries in a directory under the workspace (name and type).""" + target = resolve_path(path) + if not target.is_dir(): + return f"error: not a directory: {path}" + lines: list[str] = [] + for entry in sorted(target.iterdir(), key=lambda p: p.name): + kind = "dir" if entry.is_dir() else "file" + lines.append(f"{kind}\t{entry.name}") + return "\n".join(lines) if lines else "(empty)" diff --git a/src/plyngent/tools/file/read.py b/src/plyngent/tools/file/read.py new file mode 100644 index 0000000..d7b16c5 --- /dev/null +++ b/src/plyngent/tools/file/read.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from plyngent.agent import tool +from plyngent.tools.workspace import resolve_path + + +@tool +def read_file(path: str, *, offset: int = 0, limit: int | None = None) -> str: + """Read a text file under the workspace. + + ``offset`` is 0-based line start; ``limit`` is max lines (None = rest of file). + """ + target = resolve_path(path) + if not target.is_file(): + return f"error: not a file: {path}" + text = target.read_text(encoding="utf-8", errors="replace") + lines = text.splitlines(keepends=True) + if offset < 0: + return "error: offset must be >= 0" + start = offset + end = len(lines) if limit is None else min(len(lines), start + limit) + if start >= len(lines): + return "" + return "".join(lines[start:end]) diff --git a/src/plyngent/tools/file/write.py b/src/plyngent/tools/file/write.py new file mode 100644 index 0000000..8a1aff1 --- /dev/null +++ b/src/plyngent/tools/file/write.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from plyngent.agent import tool +from plyngent.tools.workspace import resolve_path + + +@tool +def write_file(path: str, content: str) -> str: + """Write text content to a file under the workspace (creates parents).""" + target = resolve_path(path) + target.parent.mkdir(parents=True, exist_ok=True) + _ = target.write_text(content, encoding="utf-8") + return f"wrote {len(content)} characters to {path}"