src / tools / fileTools.ts
import { rm, writeFile, readFile, cp, rename, readdir, stat, access } from "fs/promises";
import { join, dirname, extname } from "path";
import { existsSync } from "fs";
import { ensureDirectory, resolveInsideBase, targetBase } from "./constants";
export async function executeFileTool(toolName: string, args: any): Promise<any> {
const targetPath = args?.file || args?.path || args?.folder || args?.source || args?.oldName || "";
const p = resolveInsideBase(targetPath);
switch (toolName) {
case "readFile":
if (!existsSync(p)) return { success: false, error: `File ${targetPath} not found.` };
const content = await readFile(p, "utf-8");
return { success: true, content, size: content.length };
case "writeFile":
await ensureDirectory(dirname(p));
await writeFile(p, args?.content || "", "utf-8");
return { success: true, message: `File ${targetPath} written successfully.` };
case "appendFile":
await ensureDirectory(dirname(p));
const current = existsSync(p) ? await readFile(p, "utf-8") : "";
await writeFile(p, current + (args?.content || ""), "utf-8");
return { success: true, message: `Content appended to ${targetPath}.` };
case "renameFile":
case "moveFolder":
const newP = resolveInsideBase(args?.newName || args?.dest || "");
if (!existsSync(p)) return { success: false, error: `Path ${targetPath} not found.` };
await ensureDirectory(dirname(newP));
await rename(p, newP);
return { success: true, message: `Successfully moved/renamed to ${args?.newName || args?.dest}.` };
case "deleteFile":
case "deleteFolder":
if (!existsSync(p)) return { success: false, error: `Path ${targetPath} not found.` };
await rm(p, { recursive: true, force: true });
return { success: true, message: `Successfully deleted ${targetPath}.` };
case "copyFile":
const toP = resolveInsideBase(args?.to || "");
if (!existsSync(p)) return { success: false, error: `File ${targetPath} not found.` };
await ensureDirectory(dirname(toP));
await cp(p, toP, { force: !!args?.overwrite, errorOnExist: !args?.overwrite });
return { success: true, message: `Successfully copied to ${args?.to}.` };
case "listFolder":
case "listDirectoryStructure":
if (!existsSync(p)) return { success: false, error: `Directory not found.` };
const entries = await readdir(p, { withFileTypes: true });
const items = await Promise.all(
entries.map(async (e) => {
const itemPath = join(p, e.name);
const s = await stat(itemPath);
return {
name: e.name,
type: e.isDirectory() ? "folder" : "file",
size: e.isDirectory() ? "-" : `${(s.size / 1024).toFixed(2)} KB`,
modified: s.mtime.toISOString(),
};
})
);
return { success: true, folder: targetPath || "[Root]", items };
case "createFolder":
await ensureDirectory(p);
return { success: true, message: `Folder ${targetPath} created successfully.` };
case "searchFiles":
return await searchFiles(
args?.searchPath || args?.path || targetBase,
args?.query || args?.pattern || "",
args?.searchType || args?.type || "name"
);
default:
return null;
}
}
async function searchFiles(searchPath: string, query: string, searchType: "name" | "content" = "name") {
const results: any[] = [];
let scanned = 0;
const maxDepth = 5;
const excludeDirs = [
'$RECYCLE.BIN', 'System Volume Information', 'Windows',
'Program Files', 'Program Files (x86)', 'node_modules',
'.git', 'temp', 'tmp', 'old_data', 'dist', 'build',
'__pycache__', '.venv', 'venv', 'env', 'Library',
'System32', 'AppData', 'Local Settings'
];
const excludeExtensions = ['.exe', '.dll', '.bin', '.dat', '.log', '.tmp', '.cache'];
const textExtensions = ['.txt', '.md', '.js', '.jsx', '.ts', '.tsx', '.py', '.json', '.html', '.css', '.csv', '.xml', '.yml', '.yaml', '.sh', '.bash', '.go', '.rs', '.rb', '.php'];
async function scan(dir: string, depth: number = 0) {
if (depth > maxDepth) return;
try {
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (excludeDirs.includes(entry.name)) continue;
const fullPath = join(dir, entry.name);
try {
if (entry.isDirectory()) {
await scan(fullPath, depth + 1);
} else {
const stats = await stat(fullPath);
if (stats.size > 10 * 1024 * 1024) continue;
const ext = extname(entry.name).toLowerCase();
if (excludeExtensions.includes(ext)) continue;
scanned++;
let match = false;
if (searchType === "name") {
match = entry.name.toLowerCase().includes(query.toLowerCase());
} else {
if (!textExtensions.includes(ext)) continue;
try {
const content = await readFile(fullPath, "utf-8");
match = content.toLowerCase().includes(query.toLowerCase());
} catch { /* skip */ }
}
if (match) {
results.push({ path: fullPath, name: entry.name, size: stats.size, modified: stats.mtime, extension: ext || 'none' });
}
if (results.length >= 50) return;
}
} catch { /* skip */ }
}
} catch { /* skip */ }
}
try {
const p = resolveInsideBase(searchPath);
await access(p);
await scan(p, 0);
return {
success: true,
results: results.slice(0, 50),
total: results.length,
scanned,
searchType,
query,
path: searchPath,
system_note: `Search completed: scanned ${scanned} files, found ${results.length} results. Use relevant info.`
};
} catch (error: any) {
return { success: false, error: `Path not accessible: ${searchPath}`, details: error.message };
}
}
src / tools / fileTools.ts
import { rm, writeFile, readFile, cp, rename, readdir, stat, access } from "fs/promises";
import { join, dirname, extname } from "path";
import { existsSync } from "fs";
import { ensureDirectory, resolveInsideBase, targetBase } from "./constants";
export async function executeFileTool(toolName: string, args: any): Promise<any> {
const targetPath = args?.file || args?.path || args?.folder || args?.source || args?.oldName || "";
const p = resolveInsideBase(targetPath);
switch (toolName) {
case "readFile":
if (!existsSync(p)) return { success: false, error: `File ${targetPath} not found.` };
const content = await readFile(p, "utf-8");
return { success: true, content, size: content.length };
case "writeFile":
await ensureDirectory(dirname(p));
await writeFile(p, args?.content || "", "utf-8");
return { success: true, message: `File ${targetPath} written successfully.` };
case "appendFile":
await ensureDirectory(dirname(p));
const current = existsSync(p) ? await readFile(p, "utf-8") : "";
await writeFile(p, current + (args?.content || ""), "utf-8");
return { success: true, message: `Content appended to ${targetPath}.` };
case "renameFile":
case "moveFolder":
const newP = resolveInsideBase(args?.newName || args?.dest || "");
if (!existsSync(p)) return { success: false, error: `Path ${targetPath} not found.` };
await ensureDirectory(dirname(newP));
await rename(p, newP);
return { success: true, message: `Successfully moved/renamed to ${args?.newName || args?.dest}.` };
case "deleteFile":
case "deleteFolder":
if (!existsSync(p)) return { success: false, error: `Path ${targetPath} not found.` };
await rm(p, { recursive: true, force: true });
return { success: true, message: `Successfully deleted ${targetPath}.` };
case "copyFile":
const toP = resolveInsideBase(args?.to || "");
if (!existsSync(p)) return { success: false, error: `File ${targetPath} not found.` };
await ensureDirectory(dirname(toP));
await cp(p, toP, { force: !!args?.overwrite, errorOnExist: !args?.overwrite });
return { success: true, message: `Successfully copied to ${args?.to}.` };
case "listFolder":
case "listDirectoryStructure":
if (!existsSync(p)) return { success: false, error: `Directory not found.` };
const entries = await readdir(p, { withFileTypes: true });
const items = await Promise.all(
entries.map(async (e) => {
const itemPath = join(p, e.name);
const s = await stat(itemPath);
return {
name: e.name,
type: e.isDirectory() ? "folder" : "file",
size: e.isDirectory() ? "-" : `${(s.size / 1024).toFixed(2)} KB`,
modified: s.mtime.toISOString(),
};
})
);
return { success: true, folder: targetPath || "[Root]", items };
case "createFolder":
await ensureDirectory(p);
return { success: true, message: `Folder ${targetPath} created successfully.` };
case "searchFiles":
return await searchFiles(
args?.searchPath || args?.path || targetBase,
args?.query || args?.pattern || "",
args?.searchType || args?.type || "name"
);
default:
return null;
}
}
async function searchFiles(searchPath: string, query: string, searchType: "name" | "content" = "name") {
const results: any[] = [];
let scanned = 0;
const maxDepth = 5;
const excludeDirs = [
'$RECYCLE.BIN', 'System Volume Information', 'Windows',
'Program Files', 'Program Files (x86)', 'node_modules',
'.git', 'temp', 'tmp', 'old_data', 'dist', 'build',
'__pycache__', '.venv', 'venv', 'env', 'Library',
'System32', 'AppData', 'Local Settings'
];
const excludeExtensions = ['.exe', '.dll', '.bin', '.dat', '.log', '.tmp', '.cache'];
const textExtensions = ['.txt', '.md', '.js', '.jsx', '.ts', '.tsx', '.py', '.json', '.html', '.css', '.csv', '.xml', '.yml', '.yaml', '.sh', '.bash', '.go', '.rs', '.rb', '.php'];
async function scan(dir: string, depth: number = 0) {
if (depth > maxDepth) return;
try {
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (excludeDirs.includes(entry.name)) continue;
const fullPath = join(dir, entry.name);
try {
if (entry.isDirectory()) {
await scan(fullPath, depth + 1);
} else {
const stats = await stat(fullPath);
if (stats.size > 10 * 1024 * 1024) continue;
const ext = extname(entry.name).toLowerCase();
if (excludeExtensions.includes(ext)) continue;
scanned++;
let match = false;
if (searchType === "name") {
match = entry.name.toLowerCase().includes(query.toLowerCase());
} else {
if (!textExtensions.includes(ext)) continue;
try {
const content = await readFile(fullPath, "utf-8");
match = content.toLowerCase().includes(query.toLowerCase());
} catch { /* skip */ }
}
if (match) {
results.push({ path: fullPath, name: entry.name, size: stats.size, modified: stats.mtime, extension: ext || 'none' });
}
if (results.length >= 50) return;
}
} catch { /* skip */ }
}
} catch { /* skip */ }
}
try {
const p = resolveInsideBase(searchPath);
await access(p);
await scan(p, 0);
return {
success: true,
results: results.slice(0, 50),
total: results.length,
scanned,
searchType,
query,
path: searchPath,
system_note: `Search completed: scanned ${scanned} files, found ${results.length} results. Use relevant info.`
};
} catch (error: any) {
return { success: false, error: `Path not accessible: ${searchPath}`, details: error.message };
}
}