UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

1,005 lines (887 loc) 30.3 kB
import { z } from 'zod'; import { BaseTool } from './base-tool.js'; import { promises as fs, existsSync, statSync, constants } from 'fs'; import { join, relative, isAbsolute, dirname, extname, basename, resolve, normalize, sep, } from 'path'; import { exec, spawn } from 'child_process'; import { promisify } from 'util'; import { platform, EOL } from 'os'; const execAsync = promisify(exec); /** * Cross-platform path utilities */ class CrossPlatformPath { static normalize(path: string): string { // Convert forward slashes to appropriate separator for the platform return normalize(path.replace(/\//g, sep)); } static isWithinDirectory(targetPath: string, basePath: string): boolean { const relativePath = this.getRelativePath(targetPath, basePath); return relativePath.length > 0 && !relativePath.startsWith('..') && !isAbsolute(relativePath); } static getRelativePath(targetPath: string, basePath: string): string { try { return relative(basePath, resolve(basePath, targetPath)); } catch (error) { return ''; } } static resolveSafePath(inputPath: string, workingDirectory: string): string { const normalized = this.normalize(inputPath); if (isAbsolute(normalized)) { // For absolute paths, check if they're within the working directory if (this.isWithinDirectory(normalized, workingDirectory)) { return normalized; } throw new Error(`Absolute path outside working directory: ${inputPath}`); } return resolve(workingDirectory, normalized); } } /** * Cross-platform encoding utilities */ class CrossPlatformEncoding { static readonly DEFAULT_ENCODING = 'utf8'; static async detectEncoding(filePath: string): Promise<BufferEncoding> { try { const buffer = await fs.readFile(filePath); // Simple BOM detection if (buffer.length >= 3 && buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) { return 'utf8'; } if (buffer.length >= 2) { if (buffer[0] === 0xff && buffer[1] === 0xfe) { return 'utf16le'; } if (buffer[0] === 0xfe && buffer[1] === 0xff) { return 'utf16le'; // Use utf16le for compatibility } } return this.DEFAULT_ENCODING; } catch (error) { return this.DEFAULT_ENCODING; } } static normalizeLineEndings(content: string, targetPlatform?: string): string { const target = targetPlatform || platform(); // First normalize all line endings to \n const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); // Then convert to target platform switch (target) { case 'win32': return normalized.replace(/\n/g, '\r\n'); case 'darwin': case 'linux': default: return normalized; } } } /** * Enhanced cross-platform file reader with terminal integration */ export class CrossPlatformFileReader extends BaseTool { constructor(private agentContext: { workingDirectory: string }) { const parameters = z.object({ path: z.string().describe('File path to read'), encoding: z.string().optional().describe('File encoding (auto-detected if not specified)'), maxSize: z .number() .optional() .default(10 * 1024 * 1024) .describe('Maximum file size in bytes (10MB default)'), normalizeLineEndings: z .boolean() .optional() .describe('Normalize line endings for current platform'), includeMetadata: z.boolean().optional().default(true).describe('Include file metadata'), followSymlinks: z.boolean().optional().default(true).describe('Follow symbolic links'), }); super({ name: 'crossPlatformReadFile', description: 'Read files with full cross-platform support, encoding detection, and terminal integration', category: 'File System', parameters, }); } async execute(args: z.infer<typeof this.definition.parameters>): Promise<{ success: boolean; path?: string; content?: string; metadata?: Record<string, unknown>; encoding?: string; error?: string; }> { try { const safePath = CrossPlatformPath.resolveSafePath( args.path, this.agentContext.workingDirectory ); // Check file existence and permissions await this.validateFileAccess(safePath, args.followSymlinks); // Get file stats const stats = await fs.stat(safePath); // Check file size if (stats.size > args.maxSize) { return { success: false, error: `File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB exceeds ${(args.maxSize / 1024 / 1024).toFixed(2)}MB limit`, }; } // Detect or use specified encoding const encoding = (args.encoding as BufferEncoding) || (await CrossPlatformEncoding.detectEncoding(safePath)); // Read file content let content = await fs.readFile(safePath, encoding); // Normalize line endings if requested if (args.normalizeLineEndings !== false) { content = CrossPlatformEncoding.normalizeLineEndings(content); } const result: Record<string, unknown> = { success: true, path: args.path, content, encoding, }; if (args.includeMetadata) { result.metadata = { size: stats.size, modified: stats.mtime, created: stats.birthtime, mode: stats.mode, isDirectory: stats.isDirectory(), isFile: stats.isFile(), isSymbolicLink: stats.isSymbolicLink(), lines: content.split('\n').length, platform: platform(), extension: extname(safePath), basename: basename(safePath), dirname: dirname(safePath), }; } return result as { success: boolean; path?: string; content?: string; metadata?: Record<string, unknown>; encoding?: string; error?: string; }; } catch (error) { return { success: false, error: `Failed to read file '${args.path}': ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private async validateFileAccess(filePath: string, followSymlinks: boolean): Promise<void> { // Check if file exists if (!existsSync(filePath)) { throw new Error(`File not found: ${filePath}`); } // Check read permissions try { await fs.access(filePath, constants.R_OK); } catch (error) { throw new Error(`No read permission for file: ${filePath}`); } const stats = await fs.stat(filePath); // Handle symbolic links if (stats.isSymbolicLink() && !followSymlinks) { throw new Error(`Symbolic link not followed: ${filePath}`); } // Ensure it's a file, not a directory if (stats.isDirectory()) { throw new Error(`Path is a directory, not a file: ${filePath}`); } } } /** * Enhanced cross-platform file writer with terminal integration */ export class CrossPlatformFileWriter extends BaseTool { constructor(private agentContext: { workingDirectory: string }) { const parameters = z.object({ path: z.string().describe('File path to write'), content: z.string().describe('Content to write'), encoding: z.string().optional().default('utf8').describe('File encoding'), mode: z .enum(['write', 'append', 'prepend']) .optional() .default('write') .describe('Write mode'), createDirectories: z .boolean() .optional() .default(true) .describe('Create parent directories if needed'), backup: z.boolean().optional().default(false).describe('Create backup of existing file'), normalizeLineEndings: z .boolean() .optional() .describe('Normalize line endings for current platform'), permissions: z.string().optional().describe('File permissions (Unix octal notation)'), atomicWrite: z.boolean().optional().default(true).describe('Use atomic write operations'), }); super({ name: 'crossPlatformWriteFile', description: 'Write files with full cross-platform support, atomic operations, and terminal integration', category: 'File System', parameters, }); } async execute(args: z.infer<typeof this.definition.parameters>): Promise<{ success: boolean; path?: string; size?: number; backup?: string; error?: string; }> { try { const safePath = CrossPlatformPath.resolveSafePath( args.path, this.agentContext.workingDirectory ); // Create parent directories if needed if (args.createDirectories) { await fs.mkdir(dirname(safePath), { recursive: true }); } // Create backup if requested and file exists let backupPath: string | undefined; if (args.backup && existsSync(safePath)) { backupPath = await this.createBackup(safePath); } // Normalize content line endings let content = args.content; if (args.normalizeLineEndings !== false) { content = CrossPlatformEncoding.normalizeLineEndings(content); } // Handle different write modes let finalContent = content; if (args.mode === 'append' && existsSync(safePath)) { const existingContent = await fs.readFile(safePath, args.encoding as BufferEncoding); finalContent = existingContent + content; } else if (args.mode === 'prepend' && existsSync(safePath)) { const existingContent = await fs.readFile(safePath, args.encoding as BufferEncoding); finalContent = content + existingContent; } // Perform atomic write if (args.atomicWrite) { await this.atomicWrite(safePath, finalContent, args.encoding as BufferEncoding); } else { await fs.writeFile(safePath, finalContent, args.encoding as BufferEncoding); } // Set permissions if specified (Unix-like systems only) if (args.permissions && platform() !== 'win32') { const mode = parseInt(args.permissions, 8); await fs.chmod(safePath, mode); } return { success: true, path: args.path, size: Buffer.byteLength(finalContent, args.encoding as BufferEncoding), backup: backupPath, }; } catch (error) { return { success: false, error: `Failed to write file '${args.path}': ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private async createBackup(filePath: string): Promise<string> { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupPath = `${filePath}.backup.${timestamp}`; await fs.copyFile(filePath, backupPath); return backupPath; } private async atomicWrite( filePath: string, content: string, encoding: BufferEncoding ): Promise<void> { const tempPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).substr(2, 9)}`; try { await fs.writeFile(tempPath, content, encoding); await fs.rename(tempPath, filePath); } catch (error) { // Clean up temp file if it exists try { if (existsSync(tempPath)) { await fs.unlink(tempPath); } } catch (cleanupError) { // Ignore cleanup errors } throw error; } } } /** * Cross-platform terminal command executor for code operations */ export class CrossPlatformTerminalCodeExecutor extends BaseTool { constructor(private agentContext: { workingDirectory: string }) { const parameters = z.object({ operation: z .enum(['execute', 'validate', 'format', 'lint', 'compile']) .describe('Operation to perform'), language: z .string() .optional() .describe('Programming language (auto-detected if not specified)'), code: z.string().optional().describe('Code to execute (for direct execution)'), filePath: z.string().optional().describe('File path for file-based operations'), args: z.array(z.string()).optional().describe('Additional arguments'), timeout: z.number().optional().default(30000).describe('Timeout in milliseconds'), captureOutput: z.boolean().optional().default(true).describe('Capture stdout/stderr'), workingDirectory: z.string().optional().describe('Override working directory'), }); super({ name: 'crossPlatformTerminalCode', description: 'Execute code operations directly from terminal with cross-platform support', category: 'Terminal Code', parameters, }); } async execute(args: z.infer<typeof this.definition.parameters>): Promise<{ success: boolean; output?: string; error?: string; exitCode?: number; command?: string; }> { try { const workingDir = args.workingDirectory || this.agentContext.workingDirectory; let command: string; let tempFile: string | undefined; switch (args.operation) { case 'execute': ({ command, tempFile } = await this.buildExecuteCommand(args, workingDir)); break; case 'validate': command = await this.buildValidateCommand(args, workingDir); break; case 'format': command = await this.buildFormatCommand(args, workingDir); break; case 'lint': command = await this.buildLintCommand(args, workingDir); break; case 'compile': command = await this.buildCompileCommand(args, workingDir); break; default: throw new Error(`Unknown operation: ${args.operation}`); } // Execute command const result = await this.executeCommand(command, workingDir, args.timeout); // Clean up temporary file if created if (tempFile && existsSync(tempFile)) { await fs.unlink(tempFile); } return { success: result.exitCode === 0, output: result.stdout, error: result.stderr, exitCode: result.exitCode, command, }; } catch (error) { return { success: false, error: `Terminal code operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private async buildExecuteCommand( args: any, workingDir: string ): Promise<{ command: string; tempFile?: string }> { const language = args.language || this.detectLanguage(args.filePath, args.code); if (args.code) { // Create temporary file for code execution const tempFile = join(workingDir, `temp_${Date.now()}.${this.getFileExtension(language)}`); await fs.writeFile(tempFile, args.code, 'utf8'); return { command: this.getExecuteCommand(language, tempFile, args.args), tempFile, }; } else if (args.filePath) { const safePath = CrossPlatformPath.resolveSafePath(args.filePath, workingDir); return { command: this.getExecuteCommand(language, safePath, args.args), }; } else { throw new Error('Either code or filePath must be specified for execution'); } } private async buildValidateCommand(args: any, workingDir: string): Promise<string> { const language = args.language || this.detectLanguage(args.filePath, args.code); const filePath = args.filePath ? CrossPlatformPath.resolveSafePath(args.filePath, workingDir) : null; return this.getValidateCommand(language, filePath, args.args); } private async buildFormatCommand(args: any, workingDir: string): Promise<string> { const language = args.language || this.detectLanguage(args.filePath, args.code); const filePath = args.filePath ? CrossPlatformPath.resolveSafePath(args.filePath, workingDir) : null; return this.getFormatCommand(language, filePath, args.args); } private async buildLintCommand(args: any, workingDir: string): Promise<string> { const language = args.language || this.detectLanguage(args.filePath, args.code); const filePath = args.filePath ? CrossPlatformPath.resolveSafePath(args.filePath, workingDir) : null; return this.getLintCommand(language, filePath, args.args); } private async buildCompileCommand(args: any, workingDir: string): Promise<string> { const language = args.language || this.detectLanguage(args.filePath, args.code); const filePath = args.filePath ? CrossPlatformPath.resolveSafePath(args.filePath, workingDir) : null; return this.getCompileCommand(language, filePath, args.args); } private detectLanguage(filePath?: string, code?: string): string { if (filePath) { const ext = extname(filePath).toLowerCase(); switch (ext) { case '.js': return 'javascript'; case '.ts': return 'typescript'; case '.py': return 'python'; case '.java': return 'java'; case '.cpp': case '.cc': case '.cxx': return 'cpp'; case '.c': return 'c'; case '.cs': return 'csharp'; case '.go': return 'go'; case '.rs': return 'rust'; case '.rb': return 'ruby'; case '.php': return 'php'; default: return 'unknown'; } } if (code) { // Simple heuristics for code detection if (code.includes('def ') || code.includes('import ') || code.includes('print(')) return 'python'; if (code.includes('function ') || code.includes('const ') || code.includes('let ')) return 'javascript'; if (code.includes('interface ') || code.includes(': string') || code.includes(': number')) return 'typescript'; if (code.includes('public class') || code.includes('public static void main')) return 'java'; if (code.includes('#include') || code.includes('int main(')) return 'c'; } return 'unknown'; } private getFileExtension(language: string): string { switch (language) { case 'javascript': return 'js'; case 'typescript': return 'ts'; case 'python': return 'py'; case 'java': return 'java'; case 'cpp': return 'cpp'; case 'c': return 'c'; case 'csharp': return 'cs'; case 'go': return 'go'; case 'rust': return 'rs'; case 'ruby': return 'rb'; case 'php': return 'php'; default: return 'txt'; } } private getExecuteCommand(language: string, filePath: string, args: string[] = []): string { const quotedPath = `"${filePath}"`; const additionalArgs = args.join(' '); switch (language) { case 'javascript': return `node ${quotedPath} ${additionalArgs}`.trim(); case 'typescript': return `npx tsx ${quotedPath} ${additionalArgs}`.trim(); case 'python': return `python ${quotedPath} ${additionalArgs}`.trim(); case 'java': return `javac ${quotedPath} && java ${basename(filePath, '.java')} ${additionalArgs}`.trim(); case 'cpp': { const outputName = platform() === 'win32' ? 'output.exe' : 'output'; return `g++ ${quotedPath} -o ${outputName} && ./${outputName} ${additionalArgs}`.trim(); } case 'c': { const cOutputName = platform() === 'win32' ? 'output.exe' : 'output'; return `gcc ${quotedPath} -o ${cOutputName} && ./${cOutputName} ${additionalArgs}`.trim(); } case 'go': return `go run ${quotedPath} ${additionalArgs}`.trim(); case 'rust': return `rustc ${quotedPath} && ./output ${additionalArgs}`.trim(); case 'ruby': return `ruby ${quotedPath} ${additionalArgs}`.trim(); case 'php': return `php ${quotedPath} ${additionalArgs}`.trim(); default: throw new Error(`Execution not supported for language: ${language}`); } } private getValidateCommand( language: string, filePath: string | null, args: string[] = [] ): string { if (!filePath) throw new Error('File path required for validation'); const quotedPath = `"${filePath}"`; const additionalArgs = args.join(' '); switch (language) { case 'javascript': return `node --check ${quotedPath} ${additionalArgs}`.trim(); case 'typescript': return `npx tsc --noEmit ${quotedPath} ${additionalArgs}`.trim(); case 'python': return `python -m py_compile ${quotedPath} ${additionalArgs}`.trim(); case 'java': return `javac -Xlint ${quotedPath} ${additionalArgs}`.trim(); default: throw new Error(`Validation not supported for language: ${language}`); } } private getFormatCommand(language: string, filePath: string | null, args: string[] = []): string { if (!filePath) throw new Error('File path required for formatting'); const quotedPath = `"${filePath}"`; const additionalArgs = args.join(' '); switch (language) { case 'javascript': case 'typescript': return `npx prettier --write ${quotedPath} ${additionalArgs}`.trim(); case 'python': return `python -m black ${quotedPath} ${additionalArgs}`.trim(); case 'java': return `java -jar google-java-format.jar --replace ${quotedPath} ${additionalArgs}`.trim(); case 'go': return `go fmt ${quotedPath} ${additionalArgs}`.trim(); case 'rust': return `rustfmt ${quotedPath} ${additionalArgs}`.trim(); default: throw new Error(`Formatting not supported for language: ${language}`); } } private getLintCommand(language: string, filePath: string | null, args: string[] = []): string { if (!filePath) throw new Error('File path required for linting'); const quotedPath = `"${filePath}"`; const additionalArgs = args.join(' '); switch (language) { case 'javascript': return `npx eslint ${quotedPath} ${additionalArgs}`.trim(); case 'typescript': return `npx eslint ${quotedPath} ${additionalArgs}`.trim(); case 'python': return `python -m flake8 ${quotedPath} ${additionalArgs}`.trim(); default: throw new Error(`Linting not supported for language: ${language}`); } } private getCompileCommand( language: string, filePath: string | null, args: string[] = [] ): string { if (!filePath) throw new Error('File path required for compilation'); const quotedPath = `"${filePath}"`; const additionalArgs = args.join(' '); switch (language) { case 'typescript': return `npx tsc ${quotedPath} ${additionalArgs}`.trim(); case 'java': return `javac ${quotedPath} ${additionalArgs}`.trim(); case 'cpp': return `g++ ${quotedPath} -o output ${additionalArgs}`.trim(); case 'c': return `gcc ${quotedPath} -o output ${additionalArgs}`.trim(); case 'go': return `go build ${quotedPath} ${additionalArgs}`.trim(); case 'rust': return `rustc ${quotedPath} ${additionalArgs}`.trim(); default: throw new Error(`Compilation not supported for language: ${language}`); } } private async executeCommand( command: string, workingDir: string, timeout: number ): Promise<{ stdout: string; stderr: string; exitCode: number; }> { return new Promise(resolve => { const options = { cwd: workingDir, timeout, maxBuffer: 1024 * 1024 * 10, // 10MB encoding: 'utf8' as const, }; exec(command, options, (error: any, stdout: any, stderr: any) => { resolve({ stdout: stdout || '', stderr: stderr || '', exitCode: error ? (error as any).code || 1 : 0, }); }); }); } } /** * Cross-platform directory operations */ export class CrossPlatformDirectoryOperations extends BaseTool { constructor(private agentContext: { workingDirectory: string }) { const parameters = z.object({ operation: z .enum(['list', 'create', 'remove', 'tree', 'search']) .describe('Directory operation'), path: z.string().describe('Directory path'), recursive: z.boolean().optional().default(false).describe('Recursive operation'), pattern: z.string().optional().describe('Search pattern for search operation'), includeHidden: z .boolean() .optional() .default(false) .describe('Include hidden files/directories'), maxDepth: z .number() .optional() .default(10) .describe('Maximum depth for recursive operations'), }); super({ name: 'crossPlatformDirectory', description: 'Cross-platform directory operations with enhanced functionality', category: 'File System', parameters, }); } async execute( args: z.infer<typeof this.definition.parameters> ): Promise<Record<string, unknown>> { try { const safePath = CrossPlatformPath.resolveSafePath( args.path, this.agentContext.workingDirectory ); switch (args.operation) { case 'list': return await this.listDirectory(safePath, args.includeHidden); case 'create': return await this.createDirectory(safePath, args.recursive); case 'remove': return await this.removeDirectory(safePath, args.recursive); case 'tree': return await this.getDirectoryTree(safePath, args.maxDepth, args.includeHidden); case 'search': return await this.searchDirectory( safePath, args.pattern!, args.recursive, args.includeHidden ); default: throw new Error(`Unknown operation: ${args.operation}`); } } catch (error) { return { success: false, error: `Directory operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`, }; } } private async listDirectory( dirPath: string, includeHidden: boolean ): Promise<Record<string, unknown>> { const items = await fs.readdir(dirPath, { withFileTypes: true }); const files: Record<string, unknown>[] = []; const directories: Record<string, unknown>[] = []; for (const item of items) { if (!includeHidden && item.name.startsWith('.')) { continue; } const itemPath = join(dirPath, item.name); const stats = await fs.stat(itemPath); const itemInfo = { name: item.name, path: itemPath, size: stats.size, modified: stats.mtime, permissions: stats.mode, type: item.isDirectory() ? 'directory' : 'file', }; if (item.isDirectory()) { directories.push(itemInfo); } else { files.push(itemInfo); } } return { success: true, path: dirPath, files, directories, total: files.length + directories.length, }; } private async createDirectory( dirPath: string, recursive: boolean ): Promise<Record<string, unknown>> { await fs.mkdir(dirPath, { recursive }); return { success: true, path: dirPath, created: true, recursive, }; } private async removeDirectory( dirPath: string, recursive: boolean ): Promise<Record<string, unknown>> { if (recursive) { await fs.rm(dirPath, { recursive: true, force: true }); } else { await fs.rmdir(dirPath); } return { success: true, path: dirPath, removed: true, recursive, }; } private async getDirectoryTree( dirPath: string, maxDepth: number, includeHidden: boolean, currentDepth = 0 ): Promise<Record<string, unknown>> { if (currentDepth >= maxDepth) { return { name: basename(dirPath), path: dirPath, type: 'directory', children: [], truncated: true, }; } const items = await fs.readdir(dirPath, { withFileTypes: true }); const children: Record<string, unknown>[] = []; for (const item of items) { if (!includeHidden && item.name.startsWith('.')) { continue; } const itemPath = join(dirPath, item.name); if (item.isDirectory()) { const subtree = await this.getDirectoryTree( itemPath, maxDepth, includeHidden, currentDepth + 1 ); children.push(subtree); } else { const stats = await fs.stat(itemPath); children.push({ name: item.name, path: itemPath, type: 'file', size: stats.size, modified: stats.mtime, }); } } return { success: true, name: basename(dirPath), path: dirPath, type: 'directory', children, depth: currentDepth, }; } private async searchDirectory( dirPath: string, pattern: string, recursive: boolean, includeHidden: boolean ): Promise<Record<string, unknown>> { const results: Record<string, unknown>[] = []; const regex = new RegExp(pattern, 'i'); const searchRecursive = async (currentPath: string) => { const items = await fs.readdir(currentPath, { withFileTypes: true }); for (const item of items) { if (!includeHidden && item.name.startsWith('.')) { continue; } const itemPath = join(currentPath, item.name); if (regex.test(item.name)) { const stats = await fs.stat(itemPath); results.push({ name: item.name, path: itemPath, type: item.isDirectory() ? 'directory' : 'file', size: item.isFile() ? stats.size : undefined, modified: stats.mtime, }); } if (item.isDirectory() && recursive) { await searchRecursive(itemPath); } } }; await searchRecursive(dirPath); return { success: true, pattern, searchPath: dirPath, results, count: results.length, }; } }