cloudflare-mcp-smart-proxy 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.
- package/README.md +188 -0
- package/index.js +123 -0
- package/package-personal.json +29 -0
- package/package.json +29 -0
- package/publish-now.sh +106 -0
- package/publish-with-2fa.sh +134 -0
- package/publish.sh +90 -0
- package/src/local-tools.js +197 -0
- package/src/router.js +209 -0
- package/src/tools/command-executor.js +82 -0
- package/src/tools/edit-file.js +204 -0
- package/src/tools/file-operations.js +211 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local File Operations - 本地文件操作工具
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs/promises';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
export class FileOperations {
|
|
9
|
+
/**
|
|
10
|
+
* 验证文件路径安全性
|
|
11
|
+
*/
|
|
12
|
+
static validatePath(filePath, workspaceRoot) {
|
|
13
|
+
// 防止路径遍历攻击
|
|
14
|
+
if (filePath.includes('..')) {
|
|
15
|
+
throw new Error('Invalid file path: path traversal not allowed');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 限制在工作目录内
|
|
19
|
+
const resolvedPath = path.resolve(workspaceRoot, filePath);
|
|
20
|
+
const resolvedRoot = path.resolve(workspaceRoot);
|
|
21
|
+
|
|
22
|
+
if (!resolvedPath.startsWith(resolvedRoot)) {
|
|
23
|
+
throw new Error('File path outside workspace root');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return resolvedPath;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 读取文件
|
|
31
|
+
*/
|
|
32
|
+
static async readFile(params, workspaceRoot) {
|
|
33
|
+
const { target_file, offset, limit } = params;
|
|
34
|
+
|
|
35
|
+
if (!target_file) {
|
|
36
|
+
throw new Error('target_file parameter is required');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const filePath = this.validatePath(target_file, workspaceRoot);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
43
|
+
const lines = content.split('\n');
|
|
44
|
+
|
|
45
|
+
const startLine = offset || 0;
|
|
46
|
+
const endLine = limit ? startLine + limit : lines.length;
|
|
47
|
+
const selectedLines = lines.slice(startLine, endLine);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
file: target_file,
|
|
51
|
+
content: selectedLines.join('\n'),
|
|
52
|
+
totalLines: lines.length,
|
|
53
|
+
startLine,
|
|
54
|
+
endLine: startLine + selectedLines.length
|
|
55
|
+
};
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error.code === 'ENOENT') {
|
|
58
|
+
throw new Error(`File not found: ${target_file}`);
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`Read file failed: ${error.message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 写入文件
|
|
66
|
+
*/
|
|
67
|
+
static async writeFile(params, workspaceRoot) {
|
|
68
|
+
const { file_path, contents, overwrite = true } = params;
|
|
69
|
+
|
|
70
|
+
if (!file_path || contents === undefined) {
|
|
71
|
+
throw new Error('file_path and contents parameters are required');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const filePath = this.validatePath(file_path, workspaceRoot);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
// 检查文件是否存在
|
|
78
|
+
let exists = false;
|
|
79
|
+
try {
|
|
80
|
+
await fs.access(filePath);
|
|
81
|
+
exists = true;
|
|
82
|
+
} catch {
|
|
83
|
+
exists = false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (exists && !overwrite) {
|
|
87
|
+
throw new Error(`File already exists: ${file_path}. Use overwrite=true to replace.`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 确保目录存在
|
|
91
|
+
const dir = path.dirname(filePath);
|
|
92
|
+
await fs.mkdir(dir, { recursive: true });
|
|
93
|
+
|
|
94
|
+
// 写入文件
|
|
95
|
+
await fs.writeFile(filePath, contents, 'utf-8');
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
file: file_path,
|
|
100
|
+
size: contents.length,
|
|
101
|
+
created: !exists,
|
|
102
|
+
overwritten: exists
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error.message.includes('already exists')) {
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`Write file failed: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 列出目录
|
|
114
|
+
*/
|
|
115
|
+
static async listDir(params, workspaceRoot) {
|
|
116
|
+
const { target_directory, recursive = false } = params;
|
|
117
|
+
|
|
118
|
+
if (!target_directory) {
|
|
119
|
+
throw new Error('target_directory parameter is required');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const dirPath = this.validatePath(target_directory, workspaceRoot);
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const files = [];
|
|
126
|
+
|
|
127
|
+
async function scanDir(currentPath, relativePath) {
|
|
128
|
+
try {
|
|
129
|
+
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
130
|
+
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
133
|
+
const relPath = path.join(relativePath, entry.name);
|
|
134
|
+
|
|
135
|
+
if (entry.isDirectory()) {
|
|
136
|
+
if (recursive) {
|
|
137
|
+
await scanDir(fullPath, relPath);
|
|
138
|
+
} else {
|
|
139
|
+
files.push({
|
|
140
|
+
name: entry.name,
|
|
141
|
+
path: relPath,
|
|
142
|
+
type: 'directory'
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
const stats = await fs.stat(fullPath);
|
|
147
|
+
files.push({
|
|
148
|
+
name: entry.name,
|
|
149
|
+
path: relPath,
|
|
150
|
+
type: 'file',
|
|
151
|
+
size: stats.size,
|
|
152
|
+
modified: stats.mtime.toISOString()
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
// 忽略权限错误,继续扫描其他目录
|
|
158
|
+
console.error(`Error scanning ${currentPath}:`, error.message);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await scanDir(dirPath, target_directory);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
directory: target_directory,
|
|
166
|
+
files,
|
|
167
|
+
total: files.length
|
|
168
|
+
};
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (error.code === 'ENOENT') {
|
|
171
|
+
throw new Error(`Directory not found: ${target_directory}`);
|
|
172
|
+
}
|
|
173
|
+
throw new Error(`List directory failed: ${error.message}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 删除文件
|
|
179
|
+
*/
|
|
180
|
+
static async deleteFile(params, workspaceRoot) {
|
|
181
|
+
const { file_path } = params;
|
|
182
|
+
|
|
183
|
+
if (!file_path) {
|
|
184
|
+
throw new Error('file_path parameter is required');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const filePath = this.validatePath(file_path, workspaceRoot);
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const stats = await fs.stat(filePath);
|
|
191
|
+
|
|
192
|
+
if (stats.isDirectory()) {
|
|
193
|
+
throw new Error('Cannot delete directory. Use a different method for directories.');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
await fs.unlink(filePath);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
file: file_path,
|
|
201
|
+
deleted: true
|
|
202
|
+
};
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error.code === 'ENOENT') {
|
|
205
|
+
throw new Error(`File not found: ${file_path}`);
|
|
206
|
+
}
|
|
207
|
+
throw new Error(`Delete file failed: ${error.message}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|