@sschepis/robodev 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,141 @@
1
+ // Native file system tools
2
+ // Provides safe file operations with path validation
3
+
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { consoleStyler } from '../ui/console-styler.mjs';
7
+ import { config } from '../config.mjs';
8
+
9
+ export class FileTools {
10
+ constructor(workspaceRoot) {
11
+ this.workspaceRoot = path.resolve(workspaceRoot || config.system.workspaceRoot || process.cwd());
12
+ }
13
+
14
+ // Validate path is within workspace
15
+ validatePath(filePath) {
16
+ const resolvedPath = path.resolve(this.workspaceRoot, filePath);
17
+
18
+ // Check if path starts with workspace root
19
+ if (!resolvedPath.startsWith(this.workspaceRoot)) {
20
+ // Allow access to temporary files if needed, but for now strict workspace confinement
21
+ throw new Error(`Access denied: Path '${filePath}' is outside the workspace root.`);
22
+ }
23
+
24
+ return resolvedPath;
25
+ }
26
+
27
+ // Read file content
28
+ async readFile(args) {
29
+ const { path: filePath, encoding = 'utf8' } = args;
30
+
31
+ consoleStyler.log('working', `Reading file: ${filePath}`);
32
+
33
+ try {
34
+ const resolvedPath = this.validatePath(filePath);
35
+
36
+ if (!fs.existsSync(resolvedPath)) {
37
+ throw new Error(`File not found: ${filePath}`);
38
+ }
39
+
40
+ const content = await fs.promises.readFile(resolvedPath, encoding);
41
+ consoleStyler.log('working', `✓ Read ${content.length} characters`);
42
+
43
+ return content;
44
+ } catch (error) {
45
+ consoleStyler.log('error', `Read file failed: ${error.message}`);
46
+ return `Error reading file: ${error.message}`;
47
+ }
48
+ }
49
+
50
+ // Write content to file
51
+ async writeFile(args) {
52
+ const { path: filePath, content, encoding = 'utf8' } = args;
53
+
54
+ consoleStyler.log('working', `Writing file: ${filePath}`);
55
+
56
+ try {
57
+ const resolvedPath = this.validatePath(filePath);
58
+
59
+ // Ensure directory exists
60
+ const dirPath = path.dirname(resolvedPath);
61
+ if (!fs.existsSync(dirPath)) {
62
+ await fs.promises.mkdir(dirPath, { recursive: true });
63
+ }
64
+
65
+ // Check allowed extensions if configured
66
+ if (config.tools.allowedFileExtensions && config.tools.allowedFileExtensions.length > 0) {
67
+ const ext = path.extname(filePath);
68
+ if (!config.tools.allowedFileExtensions.includes(ext) && !config.tools.enableUnsafeTools) {
69
+ throw new Error(`File extension '${ext}' not allowed. Allowed: ${config.tools.allowedFileExtensions.join(', ')}`);
70
+ }
71
+ }
72
+
73
+ await fs.promises.writeFile(resolvedPath, content, encoding);
74
+ consoleStyler.log('working', `✓ Wrote ${content.length} characters to ${filePath}`);
75
+
76
+ return `Successfully wrote to ${filePath}`;
77
+ } catch (error) {
78
+ consoleStyler.log('error', `Write file failed: ${error.message}`);
79
+ return `Error writing file: ${error.message}`;
80
+ }
81
+ }
82
+
83
+ // List files in directory
84
+ async listFiles(args) {
85
+ const { path: dirPath = '.', recursive = false } = args;
86
+
87
+ consoleStyler.log('working', `Listing files in: ${dirPath} (recursive: ${recursive})`);
88
+
89
+ try {
90
+ const resolvedPath = this.validatePath(dirPath);
91
+
92
+ if (!fs.existsSync(resolvedPath)) {
93
+ throw new Error(`Directory not found: ${dirPath}`);
94
+ }
95
+
96
+ const files = [];
97
+ const MAX_FILES = 5000;
98
+ const MAX_DEPTH = 10;
99
+
100
+ async function scanDir(currentPath, relativePath, depth = 0) {
101
+ if (depth > MAX_DEPTH) return;
102
+ if (files.length >= MAX_FILES) return;
103
+
104
+ const entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
105
+
106
+ for (const entry of entries) {
107
+ if (files.length >= MAX_FILES) break;
108
+
109
+ // Skip hidden files/dirs (starting with .) except specific allowed ones if needed
110
+ // And explicitly skip node_modules and .git
111
+ if (entry.name === 'node_modules' || entry.name === '.git' || (entry.name.startsWith('.') && entry.name !== '.cursorrules' && entry.name !== '.env')) {
112
+ continue;
113
+ }
114
+
115
+ const entryRelativePath = path.join(relativePath, entry.name);
116
+
117
+ if (entry.isDirectory()) {
118
+ files.push(`${entryRelativePath}/`);
119
+ if (recursive) {
120
+ await scanDir(path.join(currentPath, entry.name), entryRelativePath, depth + 1);
121
+ }
122
+ } else {
123
+ files.push(entryRelativePath);
124
+ }
125
+ }
126
+ }
127
+
128
+ await scanDir(resolvedPath, dirPath === '.' ? '' : dirPath);
129
+
130
+ if (files.length >= MAX_FILES) {
131
+ consoleStyler.log('warning', `⚠️ File listing truncated at ${MAX_FILES} entries`);
132
+ }
133
+ consoleStyler.log('working', `✓ Found ${files.length} files/directories`);
134
+ return files.join('\n');
135
+
136
+ } catch (error) {
137
+ consoleStyler.log('error', `List files failed: ${error.message}`);
138
+ return `Error listing files: ${error.message}`;
139
+ }
140
+ }
141
+ }