executeImpl method

  1. @override
Future executeImpl(
  1. Map<String, dynamic> params
)
override

Implement the actual tool logic

params are the validated parameters (never null here) Returns the result data to send in the response

Implementation

@override
Future<dynamic> executeImpl(Map<String, dynamic> params) async {
  final filePath = params['file_path'] as String? ?? '';
  final encoding = params['encoding'] as String? ?? 'utf-8';

  if (filePath.isEmpty) {
    return {
      'error': 'Invalid parameter',
      'message': 'file_path is required',
    };
  }

  final project = ServerPodLocator.getProject();
  if (project == null || !project.isValid) {
    return {'error': 'Not a valid ServerPod project'};
  }

  // Resolve file path
  String resolvedPath = filePath;
  if (!p.isAbsolute(filePath)) {
    resolvedPath = p.normalize(p.join(project.rootPath, filePath));
  }

  final file = File(resolvedPath);
  if (!file.existsSync()) {
    return {
      'error': 'File not found',
      'path': resolvedPath,
      'relativePath': filePath,
    };
  }

  try {
    // Security check: ensure file is within project root
    final projectDir = Directory(project.rootPath);
    final projectRealPath = projectDir.resolveSymbolicLinksSync();
    final fileRealPath = file.resolveSymbolicLinksSync();

    // Normalize paths for comparison
    final normalizedProjectPath = p.normalize(projectRealPath);
    final normalizedFilePath = p.normalize(fileRealPath);

    // Check if file is within project directory
    if (!p.isWithin(normalizedProjectPath, normalizedFilePath)) {
      return {
        'error': 'Access denied',
        'message': 'File is outside the project directory',
        'requestedPath': resolvedPath,
      };
    }

    // Read file content
    final content = await file.readAsString();
    final stat = await file.stat();

    // Detect line endings and line count
    final lineCount = content.split('\n').length;
    final hasCrLf = content.contains('\r\n');

    return {
      'path': resolvedPath,
      'relativePath': p.relative(resolvedPath, from: project.rootPath),
      'content': content,
      'size': stat.size,
      'lineCount': lineCount,
      'encoding': encoding,
      'hasCrLfLineEndings': hasCrLf,
      'modified': stat.modified.toIso8601String(),
    };
  } catch (e) {
    return {
      'error': 'Failed to read file',
      'path': resolvedPath,
      'message': e.toString(),
    };
  }
}