fileditor-mcp 1.0.2 → 1.0.4

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.
@@ -1,109 +1,109 @@
1
- import { FileUtils } from '../utils/fileUtils.js';
2
-
3
- /**
4
- * read_file tool handler
5
- */
6
- export class ReadFileHandler {
7
-
8
- /**
9
- * Handle read_file request
10
- * @param {Object} args - Request parameters
11
- * @returns {Promise<Object>} Response result
12
- */
13
- static async handle(args) {
14
- const { path, line_range } = args;
15
-
16
- try {
17
- // Check if single file or multiple files
18
- if (Array.isArray(path)) {
19
- // Read multiple files
20
- return await ReadFileHandler.readMultipleFiles(path);
21
- } else {
22
- // Read single file
23
- return await ReadFileHandler.readSingleFile(path, line_range);
24
- }
25
- } catch (error) {
26
- throw new Error(`Failed to read file(s): ${error.message}`);
27
- }
28
- }
29
-
30
- /**
31
- * Read a single file
32
- * @param {string} filePath - File path
33
- * @param {string} line_range - Line range
34
- * @returns {Promise<Object>} Response result
35
- */
36
- static async readSingleFile(filePath, line_range) {
37
- const content = await FileUtils.readFile(filePath);
38
-
39
- if (line_range) {
40
- const selectedContent = FileUtils.getLineRange(content, line_range);
41
- const [startLine] = line_range.split('-').map(Number);
42
- const formattedContent = FileUtils.formatWithLineNumbers(selectedContent, startLine);
43
- return FileUtils.createResponse(formattedContent);
44
- }
45
-
46
- const formattedContent = FileUtils.formatWithLineNumbers(content);
47
- return FileUtils.createResponse(formattedContent);
48
- }
49
-
50
- /**
51
- * Read multiple files
52
- * @param {string[]} filePaths - Array of file paths
53
- * @returns {Promise<Object>} Response result
54
- */
55
- static async readMultipleFiles(filePaths) {
56
- // Check for empty array
57
- if (!Array.isArray(filePaths) || filePaths.length === 0) {
58
- throw new Error('Failed to read any files: No files provided');
59
- }
60
-
61
- const results = [];
62
- const errors = [];
63
-
64
- for (const filePath of filePaths) {
65
- try {
66
- if (!FileUtils.fileExists(filePath)) {
67
- errors.push(`File not found: ${filePath}`);
68
- continue;
69
- }
70
-
71
- const content = await FileUtils.readFile(filePath);
72
- results.push({
73
- file: filePath,
74
- content: content,
75
- lines: FileUtils.countLines(content)
76
- });
77
- } catch (error) {
78
- errors.push(`Error reading ${filePath}: ${error.message}`);
79
- }
80
- }
81
-
82
- // Build return content
83
- let responseText = '';
84
-
85
- if (results.length > 0) {
86
- responseText += `Successfully read ${results.length} file(s):\n\n`;
87
-
88
- for (const result of results) {
89
- responseText += `=== ${result.file} (${result.lines} lines) ===\n`;
90
- const formattedContent = FileUtils.formatWithLineNumbers(result.content);
91
- responseText += formattedContent;
92
- responseText += '\n\n';
93
- }
94
- }
95
-
96
- if (errors.length > 0) {
97
- responseText += `Errors encountered:\n`;
98
- for (const error of errors) {
99
- responseText += `- ${error}\n`;
100
- }
101
- }
102
-
103
- if (results.length === 0 && errors.length > 0) {
104
- throw new Error(`Failed to read any files: ${errors.join(', ')}`);
105
- }
106
-
107
- return FileUtils.createResponse(responseText.trim());
108
- }
109
- }
1
+ import { FileUtils } from '../utils/fileUtils.js';
2
+
3
+ /**
4
+ * read_file tool handler
5
+ */
6
+ export class ReadFileHandler {
7
+
8
+ /**
9
+ * Handle read_file request
10
+ * @param {Object} args - Request parameters
11
+ * @returns {Promise<Object>} Response result
12
+ */
13
+ static async handle(args) {
14
+ const { path, line_range } = args;
15
+
16
+ try {
17
+ // Check if single file or multiple files
18
+ if (Array.isArray(path)) {
19
+ // Read multiple files
20
+ return await ReadFileHandler.readMultipleFiles(path);
21
+ } else {
22
+ // Read single file
23
+ return await ReadFileHandler.readSingleFile(path, line_range);
24
+ }
25
+ } catch (error) {
26
+ throw new Error(`Failed to read file(s): ${error.message}`);
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Read a single file
32
+ * @param {string} filePath - File path
33
+ * @param {string} line_range - Line range
34
+ * @returns {Promise<Object>} Response result
35
+ */
36
+ static async readSingleFile(filePath, line_range) {
37
+ const content = await FileUtils.readFile(filePath);
38
+
39
+ if (line_range) {
40
+ const selectedContent = FileUtils.getLineRange(content, line_range);
41
+ const [startLine] = line_range.split('-').map(Number);
42
+ const formattedContent = FileUtils.formatWithLineNumbers(selectedContent, startLine);
43
+ return FileUtils.createResponse(formattedContent);
44
+ }
45
+
46
+ const formattedContent = FileUtils.formatWithLineNumbers(content);
47
+ return FileUtils.createResponse(formattedContent);
48
+ }
49
+
50
+ /**
51
+ * Read multiple files
52
+ * @param {string[]} filePaths - Array of file paths
53
+ * @returns {Promise<Object>} Response result
54
+ */
55
+ static async readMultipleFiles(filePaths) {
56
+ // Check for empty array
57
+ if (!Array.isArray(filePaths) || filePaths.length === 0) {
58
+ throw new Error('Failed to read any files: No files provided');
59
+ }
60
+
61
+ const results = [];
62
+ const errors = [];
63
+
64
+ for (const filePath of filePaths) {
65
+ try {
66
+ if (!FileUtils.fileExists(filePath)) {
67
+ errors.push(`File not found: ${filePath}`);
68
+ continue;
69
+ }
70
+
71
+ const content = await FileUtils.readFile(filePath);
72
+ results.push({
73
+ file: filePath,
74
+ content: content,
75
+ lines: FileUtils.countLines(content)
76
+ });
77
+ } catch (error) {
78
+ errors.push(`Error reading ${filePath}: ${error.message}`);
79
+ }
80
+ }
81
+
82
+ // Build return content
83
+ let responseText = '';
84
+
85
+ if (results.length > 0) {
86
+ responseText += `Successfully read ${results.length} file(s):\n\n`;
87
+
88
+ for (const result of results) {
89
+ responseText += `=== ${result.file} (${result.lines} lines) ===\n`;
90
+ const formattedContent = FileUtils.formatWithLineNumbers(result.content);
91
+ responseText += formattedContent;
92
+ responseText += '\n\n';
93
+ }
94
+ }
95
+
96
+ if (errors.length > 0) {
97
+ responseText += `Errors encountered:\n`;
98
+ for (const error of errors) {
99
+ responseText += `- ${error}\n`;
100
+ }
101
+ }
102
+
103
+ if (results.length === 0 && errors.length > 0) {
104
+ throw new Error(`Failed to read any files: ${errors.join(', ')}`);
105
+ }
106
+
107
+ return FileUtils.createResponse(responseText.trim());
108
+ }
109
+ }
@@ -1,217 +1,198 @@
1
- import { FileUtils } from '../utils/fileUtils.js';
2
-
3
- /**
4
- * search_and_replace tool handler
5
- */
6
- export class SearchAndReplaceHandler {
7
-
8
- /**
9
- * Handle search_and_replace request
10
- * @param {Object} args - Request parameters
11
- * @returns {Promise<Object>} Response result
12
- */
13
- static async handle(args) {
14
- const {
15
- path,
16
- search,
17
- replace,
18
- use_regex = false,
19
- ignore_case = false,
20
- start_line = null,
21
- end_line = null
22
- } = args;
23
-
24
- try {
25
- // Check if file exists
26
- if (!FileUtils.fileExists(path)) {
27
- throw new Error(`File not found: ${path}`);
28
- }
29
-
30
- // Read file content
31
- const content = await FileUtils.readFile(path);
32
- const lines = content.split('\n');
33
-
34
- // Validate line range
35
- FileUtils.validateLineRange({
36
- start_line,
37
- end_line,
38
- totalLines: lines.length
39
- });
40
-
41
- // Determine search range
42
- const startIdx = start_line !== null ? Math.max(0, start_line - 1) : 0;
43
- const endIdx = end_line !== null ? Math.min(lines.length, end_line) : lines.length;
44
-
45
- // Validate search string
46
- if (!search) {
47
- // Empty search string, do not perform any replacement
48
- const message = SearchAndReplaceHandler._buildResultMessage(path, 0, start_line, end_line, use_regex, ignore_case);
49
- return FileUtils.createResponse(message);
50
- }
51
-
52
- // Perform search and replace
53
- const { processedLines, replacementCount } = SearchAndReplaceHandler._performSearchAndReplace(
54
- lines.slice(startIdx, endIdx),
55
- search,
56
- replace,
57
- use_regex,
58
- ignore_case
59
- );
60
-
61
- // Rebuild file content
62
- const newContent = [
63
- ...lines.slice(0, startIdx),
64
- ...processedLines,
65
- ...lines.slice(endIdx)
66
- ].join('\n');
67
-
68
- // Write back to file
69
- await FileUtils.writeFile(path, newContent);
70
-
71
- // Build response message
72
- const message = SearchAndReplaceHandler._buildResultMessage(path, replacementCount, start_line, end_line, use_regex, ignore_case);
73
- return FileUtils.createResponse(message);
74
-
75
- } catch (error) {
76
- throw new Error(`Failed to search and replace: ${error.message}`);
77
- }
78
- }
79
-
80
- /**
81
- * Perform search and replace operation
82
- * @param {string[]} lines - Lines to process
83
- * @param {string} search - Search pattern
84
- * @param {string} replace - Replacement text
85
- * @param {boolean} use_regex - Whether to use regex
86
- * @param {boolean} ignore_case - Whether to ignore case
87
- * @returns {Object} Processing result
88
- */
89
- static _performSearchAndReplace(lines, search, replace, use_regex, ignore_case) {
90
- let replacementCount = 0;
91
-
92
- const processedLines = lines.map(line => {
93
- let newLine;
94
-
95
- if (use_regex) {
96
- const { processedLine, count } = SearchAndReplaceHandler._regexReplace(line, search, replace, ignore_case);
97
- newLine = processedLine;
98
- replacementCount += count;
99
- } else {
100
- const { processedLine, count } = SearchAndReplaceHandler._stringReplace(line, search, replace, ignore_case);
101
- newLine = processedLine;
102
- replacementCount += count;
103
- }
104
-
105
- return newLine;
106
- });
107
-
108
- return { processedLines, replacementCount };
109
- }
110
-
111
- /**
112
- * Perform regex-based replacement
113
- * @param {string} line - Line to process
114
- * @param {string} search - Regex pattern
115
- * @param {string} replace - Replacement text
116
- * @param {boolean} ignore_case - Whether to ignore case
117
- * @returns {Object} Result with processed line and count
118
- */
119
- static _regexReplace(line, search, replace, ignore_case) {
120
- try {
121
- // Build regex flags
122
- let flags = 'g';
123
- if (ignore_case) {
124
- flags += 'i';
125
- }
126
-
127
- const regex = new RegExp(search, flags);
128
- const matches = line.match(regex);
129
- const count = matches ? matches.length : 0;
130
- const processedLine = line.replace(regex, replace);
131
-
132
- return { processedLine, count };
133
- } catch (error) {
134
- throw new Error(`Invalid regular expression: ${search}. ${error.message}`);
135
- }
136
- }
137
-
138
- /**
139
- * Perform string-based replacement
140
- * @param {string} line - Line to process
141
- * @param {string} search - Search string
142
- * @param {string} replace - Replacement text
143
- * @param {boolean} ignore_case - Whether to ignore case
144
- * @returns {Object} Result with processed line and count
145
- */
146
- static _stringReplace(line, search, replace, ignore_case) {
147
- if (ignore_case) {
148
- // Use regex for case-insensitive string replacement
149
- const escapedSearch = SearchAndReplaceHandler._escapeRegex(search);
150
- const regex = new RegExp(escapedSearch, 'gi');
151
- const matches = line.match(regex);
152
- const count = matches ? matches.length : 0;
153
- const processedLine = line.replace(regex, replace);
154
-
155
- return { processedLine, count };
156
- } else {
157
- // Case-sensitive string replacement
158
- const count = SearchAndReplaceHandler._countOccurrences(line, search);
159
- const processedLine = line.split(search).join(replace);
160
-
161
- return { processedLine, count };
162
- }
163
- }
164
-
165
- /**
166
- * Escape special regex characters in a string
167
- * @param {string} string - String to escape
168
- * @returns {string} Escaped string
169
- */
170
- static _escapeRegex(string) {
171
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
172
- }
173
-
174
- /**
175
- * Count occurrences of a substring in a string
176
- * @param {string} text - Main string
177
- * @param {string} searchText - Substring to search for
178
- * @returns {number} Number of occurrences
179
- */
180
- static _countOccurrences(text, searchText) {
181
- if (!searchText) return 0;
182
- return text.split(searchText).length - 1;
183
- }
184
-
185
- /**
186
- * Build result message
187
- * @param {string} path - File path
188
- * @param {number} replacementCount - Number of replacements
189
- * @param {number|null} start_line - Start line
190
- * @param {number|null} end_line - End line
191
- * @param {boolean} use_regex - Whether regex was used
192
- * @param {boolean} ignore_case - Whether case was ignored
193
- * @returns {string} Result message
194
- */
195
- static _buildResultMessage(path, replacementCount, start_line, end_line, use_regex, ignore_case) {
196
- let message = `Successfully replaced ${replacementCount} occurrence(s) in ${path}`;
197
-
198
- if (start_line || end_line) {
199
- const rangeDesc = start_line && end_line
200
- ? `lines ${start_line}-${end_line}`
201
- : start_line
202
- ? `from line ${start_line}`
203
- : `up to line ${end_line}`;
204
- message += ` (${rangeDesc})`;
205
- }
206
-
207
- if (use_regex) {
208
- message += ` using regex pattern`;
209
- }
210
-
211
- if (ignore_case) {
212
- message += ` (case-insensitive)`;
213
- }
214
-
215
- return message;
216
- }
217
- }
1
+ import { FileUtils } from '../utils/fileUtils.js';
2
+
3
+ /**
4
+ * search_and_replace tool handler
5
+ */
6
+ export class SearchAndReplaceHandler {
7
+
8
+ /**
9
+ * Handle search_and_replace request
10
+ * @param {Object} args - Request parameters
11
+ * @returns {Promise<Object>} Response result
12
+ */
13
+ static async handle(args) {
14
+ const {
15
+ path,
16
+ search,
17
+ replace,
18
+ use_regex = false,
19
+ ignore_case = false,
20
+ start_line = null,
21
+ end_line = null
22
+ } = args;
23
+
24
+ try {
25
+ // Check if file exists
26
+ if (!FileUtils.fileExists(path)) {
27
+ throw new Error(`File not found: ${path}`);
28
+ }
29
+
30
+ // Read file content
31
+ const content = await FileUtils.readFile(path);
32
+ const lines = content.split('\n'); // Determine search range
33
+ const startIdx = start_line !== null ? Math.max(0, start_line - 1) : 0;
34
+ const endIdx = end_line !== null ? Math.min(lines.length, end_line) : lines.length; // Validate search string
35
+ if (!search) {
36
+ // Empty search string, do not perform any replacement
37
+ let message = `Successfully replaced 0 occurrence(s) in ${path}`;
38
+ if (start_line || end_line) {
39
+ const rangeDesc = start_line && end_line
40
+ ? `lines ${start_line}-${end_line}`
41
+ : start_line
42
+ ? `from line ${start_line}`
43
+ : `up to line ${end_line}`;
44
+ message += ` (${rangeDesc})`;
45
+ }
46
+ return FileUtils.createResponse(message);
47
+ }
48
+ if (start_line !== null && start_line < 1) {
49
+ throw new Error(`Invalid start_line: ${start_line}. Line numbers start from 1.`);
50
+ }
51
+ if (end_line !== null && end_line < 1) {
52
+ throw new Error(`Invalid end_line: ${end_line}. Line numbers start from 1.`);
53
+ } if (start_line !== null && end_line !== null && start_line > end_line) {
54
+ throw new Error(`start_line (${start_line}) cannot be greater than end_line (${end_line})`);
55
+ }
56
+ if (start_line !== null && start_line > lines.length) {
57
+ throw new Error(`start_line (${start_line}) exceeds file length (${lines.length} lines)`);
58
+ }
59
+
60
+ // Perform search and replace
61
+ let replacementCount = 0;
62
+ const searchLines = lines.slice(startIdx, endIdx);
63
+ const beforeLines = lines.slice(0, startIdx);
64
+ const afterLines = lines.slice(endIdx);
65
+
66
+ const processedLines = searchLines.map(line => {
67
+ let newLine;
68
+
69
+ if (use_regex) {
70
+ try {
71
+ // Build regex flags
72
+ let flags = 'g';
73
+ if (ignore_case) {
74
+ flags += 'i';
75
+ }
76
+
77
+ const regex = new RegExp(search, flags);
78
+ const matches = line.match(regex);
79
+ if (matches) {
80
+ replacementCount += matches.length;
81
+ }
82
+ newLine = line.replace(regex, replace);
83
+ } catch (error) {
84
+ throw new Error(`Invalid regular expression: ${search}. ${error.message}`);
85
+ }
86
+ } else {
87
+ // Simple string replacement
88
+ if (ignore_case) {
89
+ // Case-insensitive string replacement
90
+ const searchLower = search.toLowerCase();
91
+ const lineLower = line.toLowerCase();
92
+
93
+ // Count occurrences
94
+ const occurrences = SearchAndReplaceHandler.countOccurrencesCaseInsensitive(line, search);
95
+ replacementCount += occurrences;
96
+
97
+ // Perform replacement
98
+ newLine = SearchAndReplaceHandler.replaceAllCaseInsensitive(line, search, replace);
99
+ } else {
100
+ // Case-sensitive string replacement
101
+ const occurrences = SearchAndReplaceHandler.countOccurrences(line, search);
102
+ replacementCount += occurrences;
103
+ newLine = line.split(search).join(replace);
104
+ }
105
+ }
106
+
107
+ return newLine;
108
+ });
109
+
110
+ // Rebuild file content
111
+ const newContent = [...beforeLines, ...processedLines, ...afterLines].join('\n');
112
+
113
+ // Write back to file
114
+ await FileUtils.writeFile(path, newContent);
115
+
116
+ // Build response message
117
+ let message = `Successfully replaced ${replacementCount} occurrence(s) in ${path}`;
118
+
119
+ if (start_line || end_line) {
120
+ const rangeDesc = start_line && end_line
121
+ ? `lines ${start_line}-${end_line}`
122
+ : start_line
123
+ ? `from line ${start_line}`
124
+ : `up to line ${end_line}`;
125
+ message += ` (${rangeDesc})`;
126
+ }
127
+
128
+ if (use_regex) {
129
+ message += ` using regex pattern`;
130
+ }
131
+
132
+ if (ignore_case) {
133
+ message += ` (case-insensitive)`;
134
+ }
135
+
136
+ return FileUtils.createResponse(message);
137
+
138
+ } catch (error) {
139
+ throw new Error(`Failed to search and replace: ${error.message}`);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Count occurrences of a substring in a string
145
+ * @param {string} text - Main string
146
+ * @param {string} searchText - Substring to search for
147
+ * @returns {number} Number of occurrences
148
+ */
149
+ static countOccurrences(text, searchText) {
150
+ if (!searchText) return 0;
151
+ return text.split(searchText).length - 1;
152
+ }
153
+
154
+ /**
155
+ * Count occurrences of a substring in a string (case-insensitive)
156
+ * @param {string} text - Main string
157
+ * @param {string} searchText - Substring to search for
158
+ * @returns {number} Number of occurrences
159
+ */
160
+ static countOccurrencesCaseInsensitive(text, searchText) {
161
+ if (!searchText) return 0;
162
+ const textLower = text.toLowerCase();
163
+ const searchLower = searchText.toLowerCase();
164
+ return textLower.split(searchLower).length - 1;
165
+ } /**
166
+ * Case-insensitive string replacement
167
+ * @param {string} text - Main string
168
+ * @param {string} searchText - Substring to search for
169
+ * @param {string} replaceText - Replacement text
170
+ * @returns {string} Replaced string
171
+ */
172
+ static replaceAllCaseInsensitive(text, searchText, replaceText) {
173
+ if (!searchText) return text;
174
+
175
+ const searchLower = searchText.toLowerCase();
176
+ const textLower = text.toLowerCase();
177
+
178
+ let result = '';
179
+ let lastIndex = 0;
180
+
181
+ let index = textLower.indexOf(searchLower);
182
+ while (index !== -1) {
183
+ // Add part before match
184
+ result += text.slice(lastIndex, index);
185
+ // Add replacement text
186
+ result += replaceText;
187
+ // Update position
188
+ lastIndex = index + searchText.length;
189
+ // Find next match
190
+ index = textLower.indexOf(searchLower, lastIndex);
191
+ }
192
+
193
+ // Add remaining part
194
+ result += text.slice(lastIndex);
195
+
196
+ return result;
197
+ }
198
+ }