capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
105 lines • 4.14 kB
JavaScript
import { readFile } from 'fs/promises';
import path from 'path';
import { BaseTool } from '../base.js';
export class FileReadTool extends BaseTool {
name = 'file_read';
displayName = '📖 Read File';
description = 'Read file contents. Requires "path" parameter (e.g., {"path": "/src/app.ts"}).';
category = 'file';
icon = '📖';
parameters = [
{
name: 'path',
type: 'string',
description: 'Path to the file to read (absolute or relative to working directory)',
required: true
},
{
name: 'encoding',
type: 'string',
description: 'File encoding (default: utf8)',
default: 'utf8'
}
];
permissions = {
fileSystem: 'read'
};
ui = {
showProgress: false,
collapsible: true,
dangerous: false
};
async run(params, context) {
const { path: filePath, encoding = 'utf8' } = params;
if (!filePath) {
throw new Error('Missing required parameter: path. Example usage: {"path": "/src/index.ts"} or {"path": "./README.md"}');
}
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, encoding);
if (encoding === 'base64' || this.isBinaryFile(resolvedPath)) {
return {
path: resolvedPath,
encoding: 'base64',
content: content.toString('base64'),
size: content.length
};
}
const lines = content.toString().split('\n');
const maxLines = 500;
let displayLines = lines;
let truncated = false;
if (lines.length > maxLines) {
displayLines = [
...lines.slice(0, 250),
`\n... truncated ${lines.length - maxLines} lines ...\n`,
...lines.slice(-250)
];
truncated = true;
}
const numberedLines = displayLines.map((line, i) => {
if (line.includes('... truncated'))
return line;
const lineNum = i < 250 ? i + 1 : lines.length - (displayLines.length - i - 1);
return `${lineNum.toString().padStart(4, ' ')} │ ${line}`;
});
return {
path: resolvedPath,
encoding,
content: truncated ? `[File truncated for token optimization. Showing ${maxLines} of ${lines.length} lines]` : content.toString(),
displayContent: numberedLines.join('\n'),
lines: lines.length,
size: content.length,
truncated
};
}
catch (error) {
if (error.code === 'ENOENT') {
throw new Error(`File not found: ${resolvedPath}. Make sure the path is correct. Use file_list to browse available files.`);
}
else if (error.code === 'EACCES') {
throw new Error(`Permission denied: ${resolvedPath}`);
}
else if (error.code === 'EISDIR') {
throw new Error(`Path is a directory: ${resolvedPath}. Use file_list to see directory contents instead.`);
}
throw error;
}
}
isBinaryFile(filePath) {
const binaryExtensions = [
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.webp',
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
'.zip', '.tar', '.gz', '.rar', '.7z',
'.exe', '.dll', '.so', '.dylib',
'.mp3', '.mp4', '.avi', '.mov', '.wav',
'.woff', '.woff2', '.ttf', '.otf'
];
const ext = path.extname(filePath).toLowerCase();
return binaryExtensions.includes(ext);
}
}
//# sourceMappingURL=file-read.js.map