Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 44 additions & 33 deletions cecli/tools/ls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,67 +11,78 @@ class Tool(BaseTool):
SCHEMA = {
"type": "function",
"function": {
"name": "Ls",
"description": "List files in a directory.",
"name": "ls",
"description": "List files in a directory. Paths are relative to the project root.",
"parameters": {
"type": "object",
"properties": {
"directory": {
"path": {
"type": "string",
"description": "The directory to list.",
},
"description": (
"The path of the directory to list, relative to the project root. "
"Defaults to the project root."
),
"default": ".",
}
},
"required": ["directory"],
"required": [],
},
},
}

@classmethod
def execute(cls, coder, dir_path=None, directory=None, **kwargs):
# Handle both positional and keyword arguments for backward compatibility
if dir_path is None and directory is not None:
dir_path = directory
elif dir_path is None:
return "Error: Missing directory parameter"
def execute(cls, coder, path=None, directory=None, **kwargs):
"""
List files in directory and optionally add some to context.

This provides information about the structure of the codebase,
similar to how a developer would explore directories.
"""
# Handle both positional and keyword arguments for backward compatibility
dir_path = path or directory or "."

try:
# Make the path relative to root if it's absolute
if dir_path.startswith("/"):
rel_dir = os.path.relpath(dir_path, coder.root)
else:
rel_dir = dir_path
# Create an absolute path from the provided relative path
abs_path = os.path.abspath(os.path.join(coder.root, dir_path))

# Get absolute path
abs_dir = coder.abs_root_path(rel_dir)
# Security check: ensure the resolved path is within the project root
if not abs_path.startswith(os.path.abspath(coder.root)):
coder.io.tool_error(
f"Error: Path '{dir_path}' attempts to access files outside the project root."
)
return "Error: Path is outside the project root."

# Check if path exists
if not os.path.exists(abs_dir):
coder.io.tool_output(f"⚠️ Directory '{dir_path}' not found")
if not os.path.exists(abs_path):
coder.io.tool_output(f"⚠️ Path '{dir_path}' not found")
return "Directory not found"

# Get directory contents
contents = []
try:
with os.scandir(abs_dir) as entries:
for entry in entries:
if entry.is_file() and not entry.name.startswith("."):
rel_path = os.path.join(rel_dir, entry.name)
contents.append(rel_path)
except NotADirectoryError:
# If it's a file, just return the file
contents = [rel_dir]
if os.path.isdir(abs_path):
# It's a directory, list its contents
try:
with os.scandir(abs_path) as entries:
for entry in entries:
if entry.is_file() and not entry.name.startswith("."):
rel_path = os.path.relpath(entry.path, coder.root)
contents.append(rel_path)
except OSError as e:
coder.io.tool_error(f"Error listing directory '{dir_path}': {e}")
return f"Error: {e}"
elif os.path.isfile(abs_path):
# It's a file, just return its relative path
contents.append(os.path.relpath(abs_path, coder.root))

if contents:
coder.io.tool_output(f"📋 Listed {len(contents)} file(s) in '{dir_path}'")
if len(contents) > 10:
return f"Found {len(contents)} files: {', '.join(contents[:10])}..."
sorted_contents = sorted(contents)
if len(sorted_contents) > 10:
return (
f"Found {len(sorted_contents)} files: {', '.join(sorted_contents[:10])}..."
)
else:
return f"Found {len(contents)} files: {', '.join(contents)}"
return f"Found {len(sorted_contents)} files: {', '.join(sorted_contents)}"
else:
coder.io.tool_output(f"📋 No files found in '{dir_path}'")
return "No files found in directory"
Expand Down
Loading