Reorganize: Move all skills to skills/ folder
- Created skills/ directory - Moved 272 skills to skills/ subfolder - Kept agents/ at root level - Kept installation scripts and docs at root level Repository structure: - skills/ - All 272 skills from skills.sh - agents/ - Agent definitions - *.sh, *.ps1 - Installation scripts - README.md, etc. - Documentation Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
310
skills/brainstorming/.agent/workspace/file-tool.cjs
Normal file
310
skills/brainstorming/.agent/workspace/file-tool.cjs
Normal file
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* File Operation Tool
|
||||
* Handle file system operations safely
|
||||
*/
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const { BaseTool, ToolResult } = require('./tool-base.cjs');
|
||||
|
||||
class FileOperationTool extends BaseTool {
|
||||
constructor(config = {}) {
|
||||
super({
|
||||
name: 'file',
|
||||
description: 'Perform file system operations (read, write, list, etc.)',
|
||||
parameters: [
|
||||
{
|
||||
name: 'operation',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Operation to perform: read, write, list, delete, move, copy, exists, stat'
|
||||
},
|
||||
{
|
||||
name: 'path',
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'File or directory path'
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Content for write operations'
|
||||
},
|
||||
{
|
||||
name: 'destination',
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Destination path for move/copy operations'
|
||||
},
|
||||
{
|
||||
name: 'encoding',
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'File encoding (default: utf8)'
|
||||
}
|
||||
],
|
||||
...config
|
||||
});
|
||||
|
||||
this.allowedPaths = config.allowedPaths || [];
|
||||
this.maxFileSize = config.maxFileSize || 1024 * 1024; // 1MB
|
||||
}
|
||||
|
||||
async execute(params) {
|
||||
const { operation, path: filePath, content, destination, encoding = 'utf8' } = params;
|
||||
|
||||
// Validate path
|
||||
const validation = this.validatePath(filePath);
|
||||
if (!validation.valid) {
|
||||
throw new Error(`Path validation failed: ${validation.reason}`);
|
||||
}
|
||||
|
||||
try {
|
||||
switch (operation) {
|
||||
case 'read':
|
||||
return await this.readFile(filePath, encoding);
|
||||
|
||||
case 'write':
|
||||
return await this.writeFile(filePath, content, encoding);
|
||||
|
||||
case 'list':
|
||||
return await this.listFiles(filePath);
|
||||
|
||||
case 'delete':
|
||||
return await this.deleteFile(filePath);
|
||||
|
||||
case 'move':
|
||||
return await this.moveFile(filePath, destination);
|
||||
|
||||
case 'copy':
|
||||
return await this.copyFile(filePath, destination);
|
||||
|
||||
case 'exists':
|
||||
return await this.fileExists(filePath);
|
||||
|
||||
case 'stat':
|
||||
return await this.getFileStats(filePath);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown operation: ${operation}`);
|
||||
}
|
||||
} catch (error) {
|
||||
return ToolResult.failure(
|
||||
error,
|
||||
`File operation '${operation}' failed: ${error.message}`,
|
||||
{ operation, path: filePath }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate file path against security rules
|
||||
*/
|
||||
validatePath(filePath) {
|
||||
if (!filePath) {
|
||||
return { valid: false, reason: 'Path is required' };
|
||||
}
|
||||
|
||||
// Resolve absolute path
|
||||
const resolvedPath = path.resolve(filePath);
|
||||
|
||||
// Check against allowed paths if configured
|
||||
if (this.allowedPaths.length > 0) {
|
||||
const isAllowed = this.allowedPaths.some(allowedPath => {
|
||||
const resolvedAllowed = path.resolve(allowedPath);
|
||||
return resolvedPath.startsWith(resolvedAllowed);
|
||||
});
|
||||
|
||||
if (!isAllowed) {
|
||||
return { valid: false, reason: 'Path is outside allowed directories' };
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent path traversal
|
||||
if (filePath.includes('..')) {
|
||||
return { valid: false, reason: 'Path traversal not allowed' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
async readFile(filePath, encoding) {
|
||||
const stats = await fs.stat(filePath);
|
||||
|
||||
if (stats.size > this.maxFileSize) {
|
||||
throw new Error(`File too large (${stats.size} bytes, max ${this.maxFileSize})`);
|
||||
}
|
||||
|
||||
if (!stats.isFile()) {
|
||||
throw new Error('Path is not a file');
|
||||
}
|
||||
|
||||
const content = await fs.readFile(filePath, encoding);
|
||||
|
||||
return ToolResult.success(
|
||||
{ content, size: stats.size },
|
||||
content,
|
||||
{ operation: 'read', path: filePath, size: stats.size }
|
||||
);
|
||||
}
|
||||
|
||||
async writeFile(filePath, content, encoding) {
|
||||
if (content === undefined || content === null) {
|
||||
throw new Error('Content is required for write operation');
|
||||
}
|
||||
|
||||
// Create parent directories if needed
|
||||
const dir = path.dirname(filePath);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
|
||||
await fs.writeFile(filePath, content, encoding);
|
||||
|
||||
const stats = await fs.stat(filePath);
|
||||
|
||||
return ToolResult.success(
|
||||
{ size: stats.size },
|
||||
`Wrote ${stats.size} bytes to ${filePath}`,
|
||||
{ operation: 'write', path: filePath, size: stats.size }
|
||||
);
|
||||
}
|
||||
|
||||
async listFiles(dirPath) {
|
||||
const stats = await fs.stat(dirPath);
|
||||
|
||||
if (!stats.isDirectory()) {
|
||||
throw new Error('Path is not a directory');
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
||||
|
||||
const files = entries.map(entry => ({
|
||||
name: entry.name,
|
||||
type: entry.isDirectory() ? 'directory' : 'file',
|
||||
path: path.join(dirPath, entry.name)
|
||||
}));
|
||||
|
||||
const output = files
|
||||
.map(f => `${f.type === 'directory' ? 'D' : 'F'} ${f.name}`)
|
||||
.join('\n');
|
||||
|
||||
return ToolResult.success(
|
||||
files,
|
||||
output || '[Empty directory]',
|
||||
{ operation: 'list', path: dirPath, count: files.length }
|
||||
);
|
||||
}
|
||||
|
||||
async deleteFile(filePath) {
|
||||
const stats = await fs.stat(filePath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
await fs.rmdir(filePath, { recursive: true });
|
||||
} else {
|
||||
await fs.unlink(filePath);
|
||||
}
|
||||
|
||||
return ToolResult.success(
|
||||
{ deleted: true },
|
||||
`Deleted: ${filePath}`,
|
||||
{ operation: 'delete', path: filePath }
|
||||
);
|
||||
}
|
||||
|
||||
async moveFile(source, destination) {
|
||||
if (!destination) {
|
||||
throw new Error('Destination is required for move operation');
|
||||
}
|
||||
|
||||
const destValidation = this.validatePath(destination);
|
||||
if (!destValidation.valid) {
|
||||
throw new Error(`Destination validation failed: ${destValidation.reason}`);
|
||||
}
|
||||
|
||||
// Create parent directories
|
||||
const destDir = path.dirname(destination);
|
||||
await fs.mkdir(destDir, { recursive: true });
|
||||
|
||||
await fs.rename(source, destination);
|
||||
|
||||
return ToolResult.success(
|
||||
{ moved: true },
|
||||
`Moved ${source} to ${destination}`,
|
||||
{ operation: 'move', source, destination }
|
||||
);
|
||||
}
|
||||
|
||||
async copyFile(source, destination) {
|
||||
if (!destination) {
|
||||
throw new Error('Destination is required for copy operation');
|
||||
}
|
||||
|
||||
const destValidation = this.validatePath(destination);
|
||||
if (!destValidation.valid) {
|
||||
throw new Error(`Destination validation failed: ${destValidation.reason}`);
|
||||
}
|
||||
|
||||
// Create parent directories
|
||||
const destDir = path.dirname(destination);
|
||||
await fs.mkdir(destDir, { recursive: true });
|
||||
|
||||
await fs.copyFile(source, destination);
|
||||
|
||||
const stats = await fs.stat(destination);
|
||||
|
||||
return ToolResult.success(
|
||||
{ size: stats.size },
|
||||
`Copied ${source} to ${destination}`,
|
||||
{ operation: 'copy', source, destination, size: stats.size }
|
||||
);
|
||||
}
|
||||
|
||||
async fileExists(filePath) {
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
return ToolResult.success(
|
||||
{ exists: true },
|
||||
`File exists: ${filePath}`,
|
||||
{ operation: 'exists', path: filePath, exists: true }
|
||||
);
|
||||
} catch {
|
||||
return ToolResult.success(
|
||||
{ exists: false },
|
||||
`File does not exist: ${filePath}`,
|
||||
{ operation: 'exists', path: filePath, exists: false }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async getFileStats(filePath) {
|
||||
const stats = await fs.stat(filePath);
|
||||
|
||||
const info = {
|
||||
size: stats.size,
|
||||
created: stats.birthtime,
|
||||
modified: stats.mtime,
|
||||
accessed: stats.atime,
|
||||
isFile: stats.isFile(),
|
||||
isDirectory: stats.isDirectory(),
|
||||
permissions: stats.mode.toString(8)
|
||||
};
|
||||
|
||||
const output = `
|
||||
Size: ${info.size} bytes
|
||||
Created: ${info.created}
|
||||
Modified: ${info.modified}
|
||||
Type: ${info.isFile ? 'File' : 'Directory'}
|
||||
Permissions: ${info.permissions}
|
||||
`.trim();
|
||||
|
||||
return ToolResult.success(
|
||||
info,
|
||||
output,
|
||||
{ operation: 'stat', path: filePath }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FileOperationTool
|
||||
};
|
||||
Reference in New Issue
Block a user