fileditor-mcp 1.0.2 → 1.0.3

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,153 +1,179 @@
1
- import { FileUtils } from '../utils/fileUtils.js';
2
-
3
- /**
4
- * insert_content tool handler
5
- */
6
- export class InsertContentHandler {
7
-
8
- /**
9
- * Handle insert_content request
10
- * @param {Object} args - Request parameters
11
- * @returns {Promise<Object>} Response result
12
- */
13
- static async handle(args) {
14
- const { path, line, content } = args;
15
-
16
- try {
17
- // Check if single file or multiple files
18
- if (Array.isArray(path)) {
19
- // Insert into multiple files
20
- return await InsertContentHandler.insertContentMultipleFiles(args);
21
- } else {
22
- // Insert into single file
23
- return await InsertContentHandler.insertContentSingleFile(path, line, content);
24
- }
25
- } catch (error) {
26
- throw new Error(`Failed to insert content: ${error.message}`);
27
- }
28
- }
29
-
30
- /**
31
- * Insert content into a single file
32
- * @param {string} filePath - File path
33
- * @param {number} line - Line number
34
- * @param {string} content - Content
35
- * @returns {Promise<Object>} Response result
36
- */
37
- static async insertContentSingleFile(filePath, line, content) {
38
- // Check if file exists
39
- if (!FileUtils.fileExists(filePath)) {
40
- throw new Error(`File not found: ${filePath}`);
41
- }
42
-
43
- // Read existing content
44
- const existingContent = await FileUtils.readFile(filePath);
45
- const lines = existingContent.split('\n');
46
-
47
- // Handle insert position
48
- let insertPosition;
49
- if (line === 0) {
50
- // 0 means end of file
51
- insertPosition = lines.length;
52
- } else if (line > 0) {
53
- // Positive: 1-based index
54
- insertPosition = line - 1; // Convert to 0-based index
55
-
56
- // Check if insert position is valid
57
- if (insertPosition > lines.length) {
58
- throw new Error(`Line number ${line} exceeds file length (${lines.length} lines)`);
59
- }
60
- } else {
61
- // Negative: calculate insert position from end
62
- // -1 means before last line, -2 means before second last line, etc.
63
- insertPosition = lines.length + line;
64
-
65
- if (insertPosition < 0) {
66
- throw new Error(`Negative line number ${line} is out of range for file with ${lines.length} lines`);
67
- }
68
- }
69
-
70
- // Handle content to insert (may contain multiple lines)
71
- const newLines = content.split('\n');
72
-
73
- // Insert new content (both negative and positive are insert operations)
74
- lines.splice(insertPosition, 0, ...newLines);
75
-
76
- // Write back to file
77
- await FileUtils.writeFile(filePath, lines.join('\n'));
78
-
79
- // Build response message
80
- let positionDesc;
81
- if (line === 0) {
82
- positionDesc = 'end of file';
83
- } else if (line > 0) {
84
- positionDesc = `line ${line}`;
85
- } else {
86
- positionDesc = `line ${Math.abs(line)} from end (before current line ${insertPosition + 1})`;
87
- }
88
-
89
- const message = `Successfully inserted ${newLines.length} line(s) at ${positionDesc} in ${filePath}. File now has ${lines.length} lines.`;
90
- return FileUtils.createResponse(message);
91
- }
92
-
93
- /**
94
- * Insert content into multiple files
95
- * @param {Object} args - Original arguments object
96
- * @returns {Promise<Object>} Response result
97
- */
98
- static async insertContentMultipleFiles(args) {
99
- const results = [];
100
- const errors = [];
101
-
102
- try {
103
- // Use FileUtils to normalize parameters
104
- const { paths, contents, lines: lineArray, fileCount } = FileUtils.normalizeMultiFileArgs(args);
105
-
106
- // Perform insert operation for each file
107
- for (let i = 0; i < fileCount; i++) {
108
- const filePath = paths[i];
109
- const line = lineArray[i];
110
- const content = contents[i];
111
-
112
- try {
113
- const result = await InsertContentHandler.insertContentSingleFile(filePath, line, content);
114
- results.push({
115
- file: filePath,
116
- success: true,
117
- message: result.content[0].text
118
- });
119
- } catch (error) {
120
- errors.push({
121
- file: filePath,
122
- error: error.message
123
- });
124
- }
125
- }
126
- } catch (paramError) {
127
- throw new Error(`Parameter validation failed: ${paramError.message}`);
128
- }
129
-
130
- // Build return message
131
- let responseText = '';
132
-
133
- if (results.length > 0) {
134
- responseText += `Successfully processed ${results.length} file(s):\n\n`;
135
- for (const result of results) {
136
- responseText += `✅ ${result.file}: ${result.message}\n`;
137
- }
138
- }
139
-
140
- if (errors.length > 0) {
141
- responseText += `\nErrors encountered:\n`;
142
- for (const error of errors) {
143
- responseText += `❌ ${error.file}: ${error.error}\n`;
144
- }
145
- }
146
-
147
- if (results.length === 0 && errors.length > 0) {
148
- throw new Error(`Failed to insert content in any files: ${errors.map(e => e.error).join(', ')}`);
149
- }
150
-
151
- return FileUtils.createResponse(responseText.trim());
152
- }
153
- }
1
+ import fs from 'fs/promises';
2
+ import { resolve } from 'path';
3
+ import { FileUtils } from '../utils/fileUtils.js';
4
+
5
+ /**
6
+ * insert_content tool handler
7
+ */
8
+ export class InsertContentHandler {
9
+
10
+ /**
11
+ * Handle insert_content request
12
+ * @param {Object} args - Request parameters
13
+ * @returns {Promise<Object>} Response result
14
+ */
15
+ static async handle(args) {
16
+ const { path, line, content } = args;
17
+
18
+ try {
19
+ // Check if single file or multiple files
20
+ if (Array.isArray(path)) {
21
+ // Insert into multiple files
22
+ return await InsertContentHandler.insertContentMultipleFiles(path, line, content);
23
+ } else {
24
+ // Insert into single file
25
+ return await InsertContentHandler.insertContentSingleFile(path, line, content);
26
+ }
27
+ } catch (error) {
28
+ throw new Error(`Failed to insert content: ${error.message}`);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Insert content into a single file
34
+ * @param {string} filePath - File path
35
+ * @param {number} line - Line number
36
+ * @param {string} content - Content
37
+ * @returns {Promise<Object>} Response result
38
+ */
39
+ static async insertContentSingleFile(filePath, line, content) {
40
+ const fullPath = FileUtils.getSecurePath(filePath);
41
+
42
+ // Check if file exists
43
+ if (!FileUtils.fileExists(filePath)) {
44
+ throw new Error(`File not found: ${filePath}`);
45
+ }
46
+
47
+ // Read existing content
48
+ const existingContent = await FileUtils.readFile(filePath);
49
+ const lines = existingContent.split('\n');
50
+
51
+ // Handle insert position
52
+ let insertPosition;
53
+ if (line === 0) {
54
+ // 0 means end of file
55
+ insertPosition = lines.length;
56
+ } else if (line > 0) {
57
+ // Positive: 1-based index
58
+ insertPosition = line - 1; // Convert to 0-based index
59
+
60
+ // Check if insert position is valid
61
+ if (insertPosition > lines.length) {
62
+ throw new Error(`Line number ${line} exceeds file length (${lines.length} lines)`);
63
+ }
64
+ } else {
65
+ // Negative: calculate insert position from end
66
+ // -1 means before last line, -2 means before second last line, etc.
67
+ insertPosition = lines.length + line;
68
+
69
+ if (insertPosition < 0) {
70
+ throw new Error(`Negative line number ${line} is out of range for file with ${lines.length} lines`);
71
+ }
72
+ }
73
+
74
+ // Handle content to insert (may contain multiple lines)
75
+ const newLines = content.split('\n');
76
+
77
+ // Insert new content (both negative and positive are insert operations)
78
+ lines.splice(insertPosition, 0, ...newLines);
79
+
80
+ // Write back to file
81
+ await fs.writeFile(fullPath, lines.join('\n'), 'utf8');
82
+
83
+ // Build response message
84
+ let positionDesc;
85
+ if (line === 0) {
86
+ positionDesc = 'end of file';
87
+ } else if (line > 0) {
88
+ positionDesc = `line ${line}`;
89
+ } else {
90
+ positionDesc = `line ${Math.abs(line)} from end (before current line ${insertPosition + 1})`;
91
+ }
92
+
93
+ const message = `Successfully inserted ${newLines.length} line(s) at ${positionDesc} in ${filePath}. File now has ${lines.length} lines.`;
94
+ return FileUtils.createResponse(message);
95
+ }
96
+
97
+ /**
98
+ * Insert content into multiple files
99
+ * @param {string[]} filePaths - Array of file paths
100
+ * @param {number|number[]} lines - Line number or array of line numbers
101
+ * @param {string|string[]} contents - Content or array of contents
102
+ * @returns {Promise<Object>} Response result
103
+ */
104
+ static async insertContentMultipleFiles(filePaths, lines, contents) {
105
+ const results = [];
106
+ const errors = [];
107
+
108
+ // Parameter validation
109
+ const fileCount = filePaths.length;
110
+
111
+ // Handle line parameter (can be a single number or array)
112
+ let lineArray;
113
+ if (Array.isArray(lines)) {
114
+ if (lines.length !== fileCount) {
115
+ throw new Error(`Line array length (${lines.length}) must match file count (${fileCount})`);
116
+ }
117
+ lineArray = lines;
118
+ } else {
119
+ // If single number, apply to all files
120
+ lineArray = new Array(fileCount).fill(lines);
121
+ }
122
+
123
+ // Handle content parameter (can be a single string or array)
124
+ let contentArray;
125
+ if (Array.isArray(contents)) {
126
+ if (contents.length !== fileCount) {
127
+ throw new Error(`Content array length (${contents.length}) must match file count (${fileCount})`);
128
+ }
129
+ contentArray = contents;
130
+ } else {
131
+ // If single string, apply to all files
132
+ contentArray = new Array(fileCount).fill(contents);
133
+ }
134
+
135
+ // Perform insert operation for each file
136
+ for (let i = 0; i < fileCount; i++) {
137
+ const filePath = filePaths[i];
138
+ const line = lineArray[i];
139
+ const content = contentArray[i];
140
+
141
+ try {
142
+ const result = await InsertContentHandler.insertContentSingleFile(filePath, line, content);
143
+ results.push({
144
+ file: filePath,
145
+ success: true,
146
+ message: result.content[0].text
147
+ });
148
+ } catch (error) {
149
+ errors.push({
150
+ file: filePath,
151
+ error: error.message
152
+ });
153
+ }
154
+ }
155
+
156
+ // Build return message
157
+ let responseText = '';
158
+
159
+ if (results.length > 0) {
160
+ responseText += `Successfully processed ${results.length} file(s):\n\n`;
161
+ for (const result of results) {
162
+ responseText += `✅ ${result.file}: ${result.message}\n`;
163
+ }
164
+ }
165
+
166
+ if (errors.length > 0) {
167
+ responseText += `\nErrors encountered:\n`;
168
+ for (const error of errors) {
169
+ responseText += `❌ ${error.file}: ${error.error}\n`;
170
+ }
171
+ }
172
+
173
+ if (results.length === 0 && errors.length > 0) {
174
+ throw new Error(`Failed to insert content in any files: ${errors.map(e => e.error).join(', ')}`);
175
+ }
176
+
177
+ return FileUtils.createResponse(responseText.trim());
178
+ }
179
+ }
@@ -1,54 +1,65 @@
1
- import { FileUtils } from '../utils/fileUtils.js';
2
-
3
- /**
4
- * list_files tool handler
5
- */
6
- export class ListFilesHandler {
7
-
8
- /**
9
- * Handle list_files request
10
- * @param {Object} args - Request parameters
11
- * @returns {Promise<Object>} Response result
12
- */
13
- static async handle(args) {
14
- const {
15
- path,
16
- recursive = false,
17
- show_hidden = false,
18
- git_filter = 'all'
19
- } = args;
20
-
21
- try {
22
- const result = await FileUtils.readDirectoryAdvanced(path, {
23
- recursive,
24
- show_hidden,
25
- git_filter
26
- }); // Build informative response message
27
- let message = result.join('\n');
28
-
29
- if (result.length === 0) {
30
- message = 'No files found matching the specified criteria.';
31
- } else {
32
- // Add summary information
33
- const summary = [];
34
- if (!show_hidden) {
35
- summary.push('hidden files excluded');
36
- }
37
- if (git_filter !== 'all') {
38
- summary.push(`showing only ${git_filter} files`);
39
- }
40
- if (recursive) {
41
- summary.push('recursive listing');
42
- }
43
-
44
- if (summary.length > 0) {
45
- message += `\n\n--- Listing options: ${summary.join(', ')} ---`;
46
- }
47
- }
48
-
49
- return FileUtils.createResponse(message);
50
- } catch (error) {
51
- throw new Error(`Failed to list files: ${error.message}`);
52
- }
53
- }
54
- }
1
+ import fs from 'fs/promises';
2
+ import { resolve, join } from 'path';
3
+ import { existsSync } from 'fs';
4
+ import { FileUtils } from '../utils/fileUtils.js';
5
+
6
+ /**
7
+ * list_files tool handler
8
+ */
9
+ export class ListFilesHandler {
10
+
11
+ /**
12
+ * Handle list_files request
13
+ * @param {Object} args - Request parameters
14
+ * @returns {Promise<Object>} Response result
15
+ */
16
+ static async handle(args) {
17
+ const { path, recursive = false } = args;
18
+
19
+ try {
20
+ const fullPath = FileUtils.getSecurePath(path);
21
+
22
+ if (!existsSync(fullPath)) {
23
+ throw new Error(`Directory not found: ${path}`);
24
+ }
25
+
26
+ const result = [];
27
+
28
+ if (recursive) {
29
+ await ListFilesHandler.listFilesRecursive(fullPath, result, '');
30
+ } else {
31
+ const items = await fs.readdir(fullPath, { withFileTypes: true });
32
+ for (const item of items) {
33
+ const type = item.isDirectory() ? 'directory' : 'file';
34
+ result.push(`${type}: ${item.name}`);
35
+ }
36
+ }
37
+
38
+ return FileUtils.createResponse(result.join('\n'));
39
+ } catch (error) {
40
+ throw new Error(`Failed to list files: ${error.message}`);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Recursively list files
46
+ * @param {string} dirPath - Directory path
47
+ * @param {string[]} result - Result array
48
+ * @param {string} prefix - Path prefix
49
+ */
50
+ static async listFilesRecursive(dirPath, result, prefix) {
51
+ const items = await fs.readdir(dirPath, { withFileTypes: true });
52
+
53
+ for (const item of items) {
54
+ const itemPath = join(dirPath, item.name);
55
+ const displayPath = prefix ? `${prefix}/${item.name}` : item.name;
56
+
57
+ if (item.isDirectory()) {
58
+ result.push(`directory: ${displayPath}`);
59
+ await ListFilesHandler.listFilesRecursive(itemPath, result, displayPath);
60
+ } else {
61
+ result.push(`file: ${displayPath}`);
62
+ }
63
+ }
64
+ }
65
+ }