import { readdir } from "node:fs/promises"; import { z } from "zod"; import type { ToolDefinition } from "../types/index.js"; import { canonicalize } from "./path-utils.js"; export function createListFilesTool(workingDirectory: string): ToolDefinition { return { name: "list_files", description: "List files and directories at a path relative to the working directory.", parameters: z.object({ path: z .string() .optional() .describe("Path to list, relative to the working directory. Defaults to '.'"), }), execute: async (args: Record): Promise => { const relPath = (args.path as string | undefined) ?? "."; // Canonicalize so a symlink-in-workdir pointing outside is detected. // See `canonicalize` in ./path-utils.ts for the resolution semantics. const absolutePath = await canonicalize(workingDirectory, relPath); const absoluteWorkDir = await canonicalize(workingDirectory); if (absolutePath !== absoluteWorkDir && !absolutePath.startsWith(`${absoluteWorkDir}/`)) { return `Error: Path "${relPath}" is outside the working directory.`; } try { const entries = await readdir(absolutePath, { withFileTypes: true }); if (entries.length === 0) { return "(empty directory)"; } return entries .map((entry) => (entry.isDirectory() ? `${entry.name}/` : entry.name)) .join("\n"); } catch (err) { return `Error listing files: ${err instanceof Error ? err.message : String(err)}`; } }, }; }