UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

132 lines 4.88 kB
import { readFile, writeFile } from 'fs/promises'; import path from 'path'; import { BaseTool } from '../base.js'; export class FileEditTool extends BaseTool { name = 'file_edit'; displayName = '✏️ Edit File'; description = 'Edit a file by replacing specific text content'; category = 'file'; icon = '✏️'; parameters = [ { name: 'path', type: 'string', description: 'Path to the file to edit (absolute or relative to working directory)', required: true }, { name: 'oldText', type: 'string', description: 'Text to find and replace (must match exactly)', required: true }, { name: 'newText', type: 'string', description: 'Text to replace with', required: true }, { name: 'replaceAll', type: 'boolean', description: 'Replace all occurrences (default: false - only first)', default: false } ]; permissions = { fileSystem: 'write' }; ui = { showProgress: false, collapsible: true, dangerous: true }; async run(params, context) { const { path: filePath, oldText, newText, replaceAll = false } = params; if (oldText === newText) { throw new Error('Old text and new text must be different'); } const resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(context.workingDirectory || process.cwd(), filePath); this.reportProgress(context, `Reading file: ${resolvedPath}`); try { const content = await readFile(resolvedPath, 'utf8'); if (!content.includes(oldText)) { throw new Error(`Text not found in file: "${oldText.substring(0, 50)}${oldText.length > 50 ? '...' : ''}"`); } const occurrences = content.split(oldText).length - 1; let newContent; if (replaceAll) { newContent = content.split(oldText).join(newText); } else { const index = content.indexOf(oldText); newContent = content.substring(0, index) + newText + content.substring(index + oldText.length); } this.reportProgress(context, `Writing changes to: ${resolvedPath}`); await writeFile(resolvedPath, newContent, 'utf8'); const linesChanged = this.calculateLinesChanged(content, newContent); const replacements = replaceAll ? occurrences : 1; return { path: resolvedPath, replacements, totalOccurrences: occurrences, linesChanged, preview: this.generatePreview(content, newContent, oldText, newText) }; } catch (error) { if (error.code === 'ENOENT') { throw new Error(`File not found: ${resolvedPath}`); } else if (error.code === 'EACCES') { throw new Error(`Permission denied: ${resolvedPath}`); } throw error; } } calculateLinesChanged(oldContent, newContent) { const oldLines = oldContent.split('\n'); const newLines = newContent.split('\n'); let changes = 0; const maxLength = Math.max(oldLines.length, newLines.length); for (let i = 0; i < maxLength; i++) { if (oldLines[i] !== newLines[i]) { changes++; } } return changes; } generatePreview(oldContent, newContent, _oldText, _newText) { const oldLines = oldContent.split('\n'); const newLines = newContent.split('\n'); let firstChangeLine = -1; for (let i = 0; i < oldLines.length; i++) { if (oldLines[i] !== newLines[i]) { firstChangeLine = i; break; } } if (firstChangeLine === -1) return 'No changes'; const contextLines = 2; const startLine = Math.max(0, firstChangeLine - contextLines); const endLine = Math.min(newLines.length, firstChangeLine + contextLines + 1); const preview = []; for (let i = startLine; i < endLine; i++) { const lineNum = (i + 1).toString().padStart(4, ' '); if (i === firstChangeLine) { if (oldLines[i]) { preview.push(`${lineNum} - │ ${oldLines[i]}`); } preview.push(`${lineNum} + │ ${newLines[i]}`); } else { preview.push(`${lineNum}${newLines[i]}`); } } return preview.join('\n'); } } //# sourceMappingURL=file-edit.js.map