@becrafter/prompt-manager 0.0.19 → 0.1.2
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 +145 -234
- package/app/desktop/assets/app.1.png +0 -0
- package/app/desktop/assets/app.png +0 -0
- package/app/desktop/assets/icons/icon.icns +0 -0
- package/app/desktop/assets/icons/icon.ico +0 -0
- package/app/desktop/assets/icons/icon.png +0 -0
- package/app/desktop/assets/icons/tray.png +0 -0
- package/app/desktop/assets/tray.1.png +0 -0
- package/app/desktop/assets/tray.png +0 -0
- package/app/desktop/main.js +27 -0
- package/app/desktop/package-lock.json +201 -4
- package/app/desktop/package.json +23 -29
- package/app/desktop/src/services/module-loader.js +43 -22
- package/app/desktop/src/services/runtime-manager.js +172 -23
- package/app/desktop/src/services/update-manager.js +6 -7
- package/app/desktop/src/ui/admin-window-manager.js +757 -0
- package/app/desktop/src/ui/splash-manager.js +253 -0
- package/app/desktop/src/ui/tray-manager.js +8 -24
- package/app/desktop/src/utils/icon-manager.js +39 -47
- package/app/desktop/src/utils/resource-paths.js +0 -23
- package/app/desktop/src/utils/resource-sync.js +260 -0
- package/app/desktop/src/utils/runtime-sync.js +241 -0
- package/examples/prompts/recommend/human_3-0_growth_diagnostic_coach_prompt.yaml +105 -0
- package/package.json +16 -13
- package/packages/admin-ui/.babelrc +3 -0
- package/packages/admin-ui/admin.html +237 -4784
- package/packages/admin-ui/css/main.css +2592 -0
- package/packages/admin-ui/css/recommended-prompts.css +610 -0
- package/packages/admin-ui/package-lock.json +6973 -0
- package/packages/admin-ui/package.json +36 -0
- package/packages/admin-ui/src/codemirror.js +53 -0
- package/packages/admin-ui/src/index.js +3188 -0
- package/packages/admin-ui/webpack.config.js +76 -0
- package/packages/resources/tools/chrome-devtools/README.md +310 -0
- package/packages/resources/tools/chrome-devtools/chrome-devtools.tool.js +1703 -0
- package/packages/resources/tools/file-reader/README.md +289 -0
- package/packages/resources/tools/file-reader/file-reader.tool.js +1545 -0
- package/packages/resources/tools/filesystem/README.md +359 -0
- package/packages/resources/tools/filesystem/filesystem.tool.js +514 -160
- package/packages/resources/tools/ollama-remote/README.md +192 -0
- package/packages/resources/tools/ollama-remote/ollama-remote.tool.js +421 -0
- package/packages/resources/tools/pdf-reader/README.md +236 -0
- package/packages/resources/tools/pdf-reader/pdf-reader.tool.js +565 -0
- package/packages/resources/tools/playwright/README.md +306 -0
- package/packages/resources/tools/playwright/playwright.tool.js +1186 -0
- package/packages/resources/tools/todolist/README.md +394 -0
- package/packages/resources/tools/todolist/todolist.tool.js +1312 -0
- package/packages/server/README.md +142 -0
- package/packages/server/api/admin.routes.js +42 -11
- package/packages/server/api/surge.routes.js +43 -0
- package/packages/server/app.js +119 -14
- package/packages/server/index.js +39 -0
- package/packages/server/mcp/mcp.server.js +324 -105
- package/packages/server/mcp/sequential-thinking.handler.js +318 -0
- package/packages/server/mcp/think-plan.handler.js +274 -0
- package/packages/server/middlewares/auth.middleware.js +6 -0
- package/packages/server/package.json +51 -0
- package/packages/server/server.js +37 -1
- package/packages/server/toolm/index.js +9 -0
- package/packages/server/toolm/package-installer.service.js +267 -0
- package/packages/server/toolm/test-tools.js +264 -0
- package/packages/server/toolm/tool-context.service.js +334 -0
- package/packages/server/toolm/tool-dependency.service.js +168 -0
- package/packages/server/toolm/tool-description-generator-optimized.service.js +375 -0
- package/packages/server/toolm/tool-description-generator.service.js +312 -0
- package/packages/server/toolm/tool-environment.service.js +200 -0
- package/packages/server/toolm/tool-execution.service.js +277 -0
- package/packages/server/toolm/tool-loader.service.js +219 -0
- package/packages/server/toolm/tool-logger.service.js +223 -0
- package/packages/server/toolm/tool-manager.handler.js +65 -0
- package/packages/server/toolm/tool-manual-generator.service.js +389 -0
- package/packages/server/toolm/tool-mode-handlers.service.js +224 -0
- package/packages/server/toolm/tool-storage.service.js +111 -0
- package/packages/server/toolm/tool-sync.service.js +138 -0
- package/packages/server/toolm/tool-utils.js +20 -0
- package/packages/server/toolm/tool-yaml-parser.service.js +81 -0
- package/packages/server/toolm/validate-system.js +421 -0
- package/packages/server/utils/config.js +49 -5
- package/packages/server/utils/util.js +65 -10
- package/scripts/build-icons.js +99 -69
- package/scripts/build.sh +57 -0
- package/scripts/surge/CNAME +1 -0
- package/scripts/surge/README.md +47 -0
- package/scripts/surge/package-lock.json +34 -0
- package/scripts/surge/package.json +20 -0
- package/scripts/surge/sync-to-surge.js +151 -0
- package/app/desktop/assets/icons/icon_1024x1024.png +0 -0
- package/app/desktop/assets/icons/icon_128x128.png +0 -0
- package/app/desktop/assets/icons/icon_16x16.png +0 -0
- package/app/desktop/assets/icons/icon_24x24.png +0 -0
- package/app/desktop/assets/icons/icon_256x256.png +0 -0
- package/app/desktop/assets/icons/icon_32x32.png +0 -0
- package/app/desktop/assets/icons/icon_48x48.png +0 -0
- package/app/desktop/assets/icons/icon_512x512.png +0 -0
- package/app/desktop/assets/icons/icon_64x64.png +0 -0
- package/app/desktop/assets/icons/icon_96x96.png +0 -0
- package/packages/admin-ui/js/closebrackets.min.js +0 -8
- package/packages/admin-ui/js/codemirror.min.js +0 -8
- package/packages/admin-ui/js/js-yaml.min.js +0 -2
- package/packages/admin-ui/js/markdown.min.js +0 -8
- package/packages/resources/tools/index.js +0 -16
- package/packages/server/mcp/toolx.handler.js +0 -131
- package/scripts/icns-builder/package.json +0 -12
- /package/packages/server/mcp/{mcp.handler.js → prompt.handler.js} +0 -0
|
@@ -1,184 +1,538 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
1
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* Filesystem Tool - Prompt Manager 体系的文件系统基础设施
|
|
3
|
+
*
|
|
4
|
+
* 战略意义:
|
|
5
|
+
*
|
|
6
|
+
* 1. 架构隔离性
|
|
7
|
+
* 专为 Prompt Manager 体系设计,通过沙箱隔离确保文件操作不会影响
|
|
8
|
+
* Prompt Manager 核心功能。即使 AI Agent 出错,也不会破坏系统稳定性。
|
|
9
|
+
*
|
|
10
|
+
* 2. 平台独立性
|
|
11
|
+
* 虽然很多 AI 平台自带文件工具,但 Prompt Manager 需要自己的实现来保证:
|
|
12
|
+
* - 在无本地工具的 Web Agent 平台上也能工作
|
|
13
|
+
* - 统一的操作语义,不依赖特定 AI 平台
|
|
14
|
+
* - 可移植到任何支持 MCP 协议的环境
|
|
15
|
+
*
|
|
16
|
+
* 3. 生态自主性
|
|
17
|
+
* 作为 Prompt Manager 工具生态的基础组件,filesystem 确保了:
|
|
18
|
+
* - 其他工具可以依赖稳定的文件操作接口
|
|
19
|
+
* - 用户数据始终在 Prompt Manager 控制范围内
|
|
20
|
+
* - 未来可扩展更多存储后端(云存储、分布式等)
|
|
21
|
+
*
|
|
22
|
+
* 这不仅是一个文件操作工具,更是 Prompt Manager 实现平台独立、
|
|
23
|
+
* 生态自主的关键基础设施。
|
|
9
24
|
*/
|
|
10
|
-
export default async function filesystem(params, mode = 'execute') {
|
|
11
|
-
// 根据模式执行不同的操作
|
|
12
|
-
switch (mode) {
|
|
13
|
-
case 'manual':
|
|
14
|
-
// 生成 Markdown 格式的手册
|
|
15
|
-
return generateManual();
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
import path from 'path';
|
|
27
|
+
import os from 'os';
|
|
28
|
+
|
|
29
|
+
export default {
|
|
30
|
+
/**
|
|
31
|
+
* 获取工具依赖
|
|
32
|
+
* 使用 Node.js 内置模块,无需额外依赖
|
|
33
|
+
*/
|
|
34
|
+
getDependencies() {
|
|
35
|
+
return {
|
|
36
|
+
// 使用 Node.js 内置 fs、path 等模块,无需额外依赖
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 获取工具元信息
|
|
42
|
+
*/
|
|
43
|
+
getMetadata() {
|
|
44
|
+
return {
|
|
45
|
+
id: 'filesystem',
|
|
46
|
+
name: 'filesystem',
|
|
47
|
+
description: '基于MCP标准的文件系统操作工具,提供读写、搜索、编辑等功能',
|
|
48
|
+
version: '2.0.0',
|
|
49
|
+
category: 'system',
|
|
50
|
+
author: 'Prompt Manager',
|
|
51
|
+
tags: ['file', 'system', 'io', 'mcp'],
|
|
52
|
+
scenarios: [
|
|
53
|
+
'文件读写操作',
|
|
54
|
+
'目录管理和遍历',
|
|
55
|
+
'文件搜索和批量处理',
|
|
56
|
+
'Prompt Manager资源文件管理',
|
|
57
|
+
'项目文件结构分析'
|
|
58
|
+
],
|
|
59
|
+
limitations: [
|
|
60
|
+
'默认只能访问 ~/.prompt-manager 目录',
|
|
61
|
+
'可通过环境变量配置额外允许的目录',
|
|
62
|
+
'不支持符号链接操作',
|
|
63
|
+
'单文件大小建议不超过10MB'
|
|
64
|
+
]
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 获取参数Schema
|
|
70
|
+
*/
|
|
71
|
+
getSchema() {
|
|
72
|
+
return {
|
|
73
|
+
parameters: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {
|
|
76
|
+
method: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: 'MCP方法名',
|
|
79
|
+
enum: [
|
|
80
|
+
'read_text_file',
|
|
81
|
+
'read_media_file',
|
|
82
|
+
'read_multiple_files',
|
|
83
|
+
'write_file',
|
|
84
|
+
'edit_file',
|
|
85
|
+
'create_directory',
|
|
86
|
+
'list_directory',
|
|
87
|
+
'list_directory_with_sizes',
|
|
88
|
+
'directory_tree',
|
|
89
|
+
'move_file',
|
|
90
|
+
'search_files',
|
|
91
|
+
'get_file_info',
|
|
92
|
+
'list_allowed_directories'
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
// 通用参数,根据method动态使用
|
|
96
|
+
path: { type: 'string', description: '文件或目录路径' },
|
|
97
|
+
paths: { type: 'array', items: { type: 'string' }, description: '多个文件路径' },
|
|
98
|
+
content: { type: 'string', description: '文件内容' },
|
|
99
|
+
head: { type: 'number', description: '读取前N行' },
|
|
100
|
+
tail: { type: 'number', description: '读取后N行' },
|
|
101
|
+
edits: {
|
|
102
|
+
type: 'array',
|
|
103
|
+
description: '编辑操作列表,每个元素为对象: {oldText: "要替换的文本", newText: "新文本"}',
|
|
104
|
+
items: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
oldText: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
description: '要替换的原始文本(必须完全匹配)'
|
|
110
|
+
},
|
|
111
|
+
newText: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
description: '替换后的新文本'
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
required: ['oldText', 'newText']
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
dryRun: { type: 'boolean', description: '仅预览不执行' },
|
|
120
|
+
source: { type: 'string', description: '源路径' },
|
|
121
|
+
destination: { type: 'string', description: '目标路径' },
|
|
122
|
+
pattern: { type: 'string', description: '搜索模式' },
|
|
123
|
+
excludePatterns: { type: 'array', items: { type: 'string' }, description: '排除模式' },
|
|
124
|
+
sortBy: { type: 'string', enum: ['name', 'size'], description: '排序方式' }
|
|
125
|
+
},
|
|
126
|
+
required: ['method']
|
|
127
|
+
},
|
|
128
|
+
environment: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: {
|
|
131
|
+
ALLOWED_DIRECTORIES: {
|
|
132
|
+
type: 'string',
|
|
133
|
+
description: '允许访问的目录列表(JSON数组格式),默认为 ["~/.prompt-manager"]',
|
|
134
|
+
default: '["~/.prompt-manager"]'
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
required: []
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 获取业务错误定义
|
|
144
|
+
*/
|
|
145
|
+
getBusinessErrors() {
|
|
146
|
+
return [
|
|
147
|
+
{
|
|
148
|
+
code: 'PATH_OUTSIDE_SCOPE',
|
|
149
|
+
description: '路径越权访问',
|
|
150
|
+
match: /路径越权/,
|
|
151
|
+
solution: '确保路径在允许的目录范围内',
|
|
152
|
+
retryable: false
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
code: 'FILE_NOT_FOUND',
|
|
156
|
+
description: '文件或目录不存在',
|
|
157
|
+
match: /ENOENT|no such file|cannot find/i,
|
|
158
|
+
solution: '检查文件路径是否正确',
|
|
159
|
+
retryable: false
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
code: 'PERMISSION_DENIED',
|
|
163
|
+
description: '权限不足',
|
|
164
|
+
match: /EACCES|permission denied/i,
|
|
165
|
+
solution: '检查文件或目录的访问权限',
|
|
166
|
+
retryable: false
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
code: 'FILE_TOO_LARGE',
|
|
170
|
+
description: '文件过大',
|
|
171
|
+
match: /File too large|ENOBUFS|too big/i,
|
|
172
|
+
solution: '文件大小不应超过10MB',
|
|
173
|
+
retryable: false
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
code: 'DIRECTORY_NOT_EMPTY',
|
|
177
|
+
description: '目录非空',
|
|
178
|
+
match: /ENOTEMPTY|directory not empty/i,
|
|
179
|
+
solution: '清空目录后再试',
|
|
180
|
+
retryable: false
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
code: 'INVALID_PATH',
|
|
184
|
+
description: '无效路径',
|
|
185
|
+
match: /invalid path|illegal characters/i,
|
|
186
|
+
solution: '检查路径格式是否正确',
|
|
187
|
+
retryable: false
|
|
188
|
+
}
|
|
189
|
+
];
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 执行工具 - 包装MCP实现
|
|
194
|
+
*
|
|
195
|
+
* 注意:文件系统基础能力(getAllowedDirectories、initializeFilesystem、resolvePromptManagerPath)
|
|
196
|
+
* 已由框架层提供,工具可直接通过 this 调用这些方法
|
|
197
|
+
*/
|
|
198
|
+
async execute(params) {
|
|
199
|
+
const { api } = this;
|
|
200
|
+
|
|
201
|
+
// 记录执行开始
|
|
202
|
+
api?.logger?.info('Executing filesystem operation', {
|
|
203
|
+
method: params.method,
|
|
204
|
+
path: params.path || params.paths || params.source
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// 参数验证由 ToolValidator 根据 getSchema() 自动处理
|
|
208
|
+
// 这里进行 method 相关的业务验证
|
|
209
|
+
const methodRequirements = {
|
|
210
|
+
'read_text_file': ['path'],
|
|
211
|
+
'read_media_file': ['path'],
|
|
212
|
+
'read_multiple_files': ['paths'],
|
|
213
|
+
'write_file': ['path', 'content'],
|
|
214
|
+
'edit_file': ['path', 'edits'],
|
|
215
|
+
'create_directory': ['path'],
|
|
216
|
+
'list_directory': ['path'],
|
|
217
|
+
'list_directory_with_sizes': ['path'],
|
|
218
|
+
'directory_tree': ['path'],
|
|
219
|
+
'move_file': ['source', 'destination'],
|
|
220
|
+
'search_files': ['path', 'pattern'],
|
|
221
|
+
'get_file_info': ['path'],
|
|
222
|
+
'list_allowed_directories': []
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const required = methodRequirements[params.method];
|
|
226
|
+
if (!required) {
|
|
227
|
+
throw new Error(`不支持的方法: ${params.method}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const missing = required.filter(field => !params[field]);
|
|
231
|
+
if (missing.length > 0) {
|
|
232
|
+
throw new Error(`方法 ${params.method} 缺少必需参数: ${missing.join(', ')}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
// 初始化文件系统
|
|
237
|
+
await this.initializeFilesystem();
|
|
238
|
+
|
|
239
|
+
// 导入 fs 模块
|
|
240
|
+
const fs = await import('fs');
|
|
241
|
+
const fsPromises = fs.promises;
|
|
20
242
|
|
|
21
|
-
|
|
22
|
-
|
|
243
|
+
// 特殊处理list_allowed_directories
|
|
244
|
+
if (params.method === 'list_allowed_directories') {
|
|
245
|
+
// 使用已初始化的目录列表,如果未初始化则获取
|
|
246
|
+
const dirs = this._allowedDirectories ?? this.getAllowedDirectories();
|
|
247
|
+
api?.logger?.info('Returning allowed directories', { directories: dirs });
|
|
248
|
+
return dirs;
|
|
23
249
|
}
|
|
250
|
+
|
|
251
|
+
// 准备MCP调用参数
|
|
252
|
+
let mcpParams = { ...params };
|
|
24
253
|
|
|
25
|
-
|
|
26
|
-
|
|
254
|
+
// 路径参数转换
|
|
255
|
+
if (params.path) {
|
|
256
|
+
mcpParams.path = this.resolvePromptManagerPath(params.path);
|
|
27
257
|
}
|
|
28
258
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
259
|
+
if (params.paths) {
|
|
260
|
+
mcpParams.paths = params.paths.map(p => this.resolvePromptManagerPath(p));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (params.source) {
|
|
264
|
+
mcpParams.source = this.resolvePromptManagerPath(params.source);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (params.destination) {
|
|
268
|
+
mcpParams.destination = this.resolvePromptManagerPath(params.destination);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 执行对应的文件系统操作
|
|
272
|
+
let result;
|
|
273
|
+
switch (params.method) {
|
|
274
|
+
case 'read_text_file': {
|
|
275
|
+
const content = await fsPromises.readFile(mcpParams.path, 'utf-8');
|
|
42
276
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
277
|
+
if (params.head) {
|
|
278
|
+
// 返回前 N 行
|
|
279
|
+
const lines = content.split('\n');
|
|
280
|
+
result = lines.slice(0, params.head).join('\n');
|
|
281
|
+
} else if (params.tail) {
|
|
282
|
+
// 返回后 N 行
|
|
283
|
+
const lines = content.split('\n');
|
|
284
|
+
result = lines.slice(-params.tail).join('\n');
|
|
285
|
+
} else {
|
|
286
|
+
result = content;
|
|
46
287
|
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
case 'read_media_file': {
|
|
292
|
+
// 读取二进制文件并转base64
|
|
293
|
+
const buffer = await fsPromises.readFile(mcpParams.path);
|
|
294
|
+
const base64 = buffer.toString('base64');
|
|
295
|
+
const ext = path.extname(mcpParams.path).toLowerCase();
|
|
296
|
+
const mimeTypes = {
|
|
297
|
+
'.png': 'image/png',
|
|
298
|
+
'.jpg': 'image/jpeg',
|
|
299
|
+
'.jpeg': 'image/jpeg',
|
|
300
|
+
'.gif': 'image/gif',
|
|
301
|
+
'.webp': 'image/webp',
|
|
302
|
+
'.svg': 'image/svg+xml',
|
|
303
|
+
'.mp3': 'audio/mpeg',
|
|
304
|
+
'.wav': 'audio/wav'
|
|
305
|
+
};
|
|
306
|
+
result = {
|
|
307
|
+
base64: base64,
|
|
308
|
+
mimeType: mimeTypes[ext] || 'application/octet-stream'
|
|
309
|
+
};
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
case 'read_multiple_files':
|
|
314
|
+
result = await Promise.all(
|
|
315
|
+
mcpParams.paths.map(async (filePath, index) => {
|
|
316
|
+
try {
|
|
317
|
+
const content = await fsPromises.readFile(filePath, 'utf-8');
|
|
318
|
+
return {
|
|
319
|
+
path: params.paths[index], // 返回原始相对路径
|
|
320
|
+
content: content,
|
|
321
|
+
success: true
|
|
322
|
+
};
|
|
323
|
+
} catch (error) {
|
|
324
|
+
return {
|
|
325
|
+
path: params.paths[index],
|
|
326
|
+
error: error.message,
|
|
327
|
+
success: false
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
})
|
|
331
|
+
);
|
|
332
|
+
break;
|
|
333
|
+
|
|
334
|
+
case 'write_file': {
|
|
335
|
+
// 自动创建父目录
|
|
336
|
+
const dirPath = path.dirname(mcpParams.path);
|
|
337
|
+
|
|
47
338
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
};
|
|
55
|
-
} catch (error) {
|
|
56
|
-
throw new Error(`写入文件失败: ${error.message}`);
|
|
339
|
+
// 检查目录是否存在,不存在则创建
|
|
340
|
+
await fsPromises.access(dirPath);
|
|
341
|
+
} catch {
|
|
342
|
+
// 目录不存在,创建它
|
|
343
|
+
await fsPromises.mkdir(dirPath, { recursive: true });
|
|
344
|
+
api?.logger?.info('Auto-created directory for write_file', { directory: dirPath });
|
|
57
345
|
}
|
|
58
346
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
347
|
+
await fsPromises.writeFile(mcpParams.path, params.content, 'utf-8');
|
|
348
|
+
result = {
|
|
349
|
+
bytesWritten: Buffer.byteLength(params.content, 'utf-8'),
|
|
350
|
+
path: params.path
|
|
351
|
+
};
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
case 'edit_file': {
|
|
356
|
+
// 读取文件内容
|
|
357
|
+
let content = await fsPromises.readFile(mcpParams.path, 'utf-8');
|
|
358
|
+
|
|
359
|
+
// 应用编辑
|
|
360
|
+
for (const edit of params.edits) {
|
|
361
|
+
content = content.replace(edit.oldText, edit.newText);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (params.dryRun) {
|
|
365
|
+
// 预览模式,返回修改后的内容
|
|
366
|
+
result = content;
|
|
367
|
+
} else {
|
|
368
|
+
// 写回文件
|
|
369
|
+
await fsPromises.writeFile(mcpParams.path, content, 'utf-8');
|
|
370
|
+
result = {
|
|
371
|
+
editsApplied: params.edits.length,
|
|
372
|
+
path: params.path
|
|
67
373
|
};
|
|
68
|
-
} catch (error) {
|
|
69
|
-
throw new Error(`列出目录内容失败: ${error.message}`);
|
|
70
374
|
}
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
71
377
|
|
|
72
|
-
case '
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
378
|
+
case 'create_directory': {
|
|
379
|
+
await fsPromises.mkdir(mcpParams.path, { recursive: true });
|
|
380
|
+
result = { created: mcpParams.path };
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
case 'list_directory':
|
|
385
|
+
case 'list_directory_with_sizes': {
|
|
386
|
+
const entries = await fsPromises.readdir(mcpParams.path, { withFileTypes: true });
|
|
387
|
+
|
|
388
|
+
if (params.method === 'list_directory') {
|
|
389
|
+
result = entries.map(entry => ({
|
|
390
|
+
name: entry.name,
|
|
391
|
+
type: entry.isDirectory() ? 'directory' : 'file'
|
|
392
|
+
}));
|
|
393
|
+
} else {
|
|
394
|
+
result = await Promise.all(
|
|
395
|
+
entries.map(async (entry) => {
|
|
396
|
+
const entryPath = path.join(mcpParams.path, entry.name);
|
|
397
|
+
const stats = await fsPromises.stat(entryPath);
|
|
398
|
+
return {
|
|
399
|
+
name: entry.name,
|
|
400
|
+
type: entry.isDirectory() ? 'directory' : 'file',
|
|
401
|
+
size: stats.size,
|
|
402
|
+
modified: stats.mtime
|
|
403
|
+
};
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
if (params.sortBy === 'size') {
|
|
408
|
+
result.sort((a, b) => b.size - a.size);
|
|
84
409
|
} else {
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
success: true,
|
|
88
|
-
action: 'delete',
|
|
89
|
-
path: filePath,
|
|
90
|
-
type: 'file',
|
|
91
|
-
message: '文件删除成功'
|
|
92
|
-
};
|
|
410
|
+
result.sort((a, b) => a.name.localeCompare(b.name));
|
|
93
411
|
}
|
|
94
|
-
} catch (error) {
|
|
95
|
-
throw new Error(`删除文件或目录失败: ${error.message}`);
|
|
96
412
|
}
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
case 'directory_tree': {
|
|
417
|
+
// 构建目录树
|
|
418
|
+
const buildTree = async (currentPath) => {
|
|
419
|
+
const entries = await fsPromises.readdir(currentPath, { withFileTypes: true });
|
|
420
|
+
const tree = [];
|
|
421
|
+
|
|
422
|
+
for (const entry of entries) {
|
|
423
|
+
const entryPath = path.join(currentPath, entry.name);
|
|
424
|
+
const node = {
|
|
425
|
+
name: entry.name,
|
|
426
|
+
type: entry.isDirectory() ? 'directory' : 'file'
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
if (entry.isDirectory()) {
|
|
430
|
+
try {
|
|
431
|
+
node.children = await buildTree(entryPath);
|
|
432
|
+
} catch (error) {
|
|
433
|
+
node.children = [];
|
|
434
|
+
node.error = error.message;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
tree.push(node);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return tree;
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
result = await buildTree(mcpParams.path);
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
case 'move_file': {
|
|
449
|
+
await fsPromises.rename(mcpParams.source, mcpParams.destination);
|
|
450
|
+
result = {
|
|
451
|
+
from: params.source,
|
|
452
|
+
to: params.destination
|
|
453
|
+
};
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
case 'search_files': {
|
|
458
|
+
// 递归搜索文件(使用内置fs,不依赖glob)
|
|
459
|
+
const searchFiles = async (dir, pattern) => {
|
|
460
|
+
const results = [];
|
|
461
|
+
const entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
462
|
+
|
|
463
|
+
for (const entry of entries) {
|
|
464
|
+
const fullPath = path.join(dir, entry.name);
|
|
465
|
+
|
|
466
|
+
// 检查排除模式
|
|
467
|
+
if (params.excludePatterns) {
|
|
468
|
+
const shouldExclude = params.excludePatterns.some(excludePattern => {
|
|
469
|
+
return entry.name.match(new RegExp(excludePattern.replace(/\*/g, '.*')));
|
|
470
|
+
});
|
|
471
|
+
if (shouldExclude) continue;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (entry.isDirectory()) {
|
|
475
|
+
// 递归搜索子目录
|
|
476
|
+
try {
|
|
477
|
+
const subResults = await searchFiles(fullPath, pattern);
|
|
478
|
+
results.push(...subResults);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
// 忽略无权访问的目录
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
// 检查文件名是否匹配模式
|
|
484
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\?/g, '.'));
|
|
485
|
+
if (regex.test(entry.name)) {
|
|
486
|
+
results.push(fullPath);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return results;
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const foundFiles = await searchFiles(mcpParams.path, params.pattern);
|
|
495
|
+
|
|
496
|
+
// 转换为相对路径(相对于第一个允许的目录)
|
|
497
|
+
const allowedDirs = this._allowedDirectories ?? this.getAllowedDirectories();
|
|
498
|
+
const baseDir = allowedDirs[0];
|
|
499
|
+
result = foundFiles.map(file => path.relative(baseDir, file));
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
case 'get_file_info': {
|
|
504
|
+
const stats = await fsPromises.stat(mcpParams.path);
|
|
505
|
+
result = {
|
|
506
|
+
size: stats.size,
|
|
507
|
+
created: stats.birthtime,
|
|
508
|
+
modified: stats.mtime,
|
|
509
|
+
accessed: stats.atime,
|
|
510
|
+
isDirectory: stats.isDirectory(),
|
|
511
|
+
isFile: stats.isFile(),
|
|
512
|
+
permissions: stats.mode
|
|
513
|
+
};
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
97
516
|
|
|
98
517
|
default:
|
|
99
|
-
throw new Error(
|
|
518
|
+
throw new Error(`不支持的方法: ${params.method}`);
|
|
100
519
|
}
|
|
101
520
|
|
|
102
|
-
|
|
103
|
-
|
|
521
|
+
// 记录执行成功
|
|
522
|
+
api?.logger?.info('Filesystem operation completed', {
|
|
523
|
+
method: params.method,
|
|
524
|
+
success: true
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
return result;
|
|
528
|
+
|
|
529
|
+
} catch (error) {
|
|
530
|
+
// 记录错误
|
|
531
|
+
api?.logger?.error('Filesystem operation failed', {
|
|
532
|
+
method: params.method,
|
|
533
|
+
error: error.message
|
|
534
|
+
});
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
104
537
|
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* 生成 Markdown 格式的手册
|
|
109
|
-
* @returns {string} Markdown 格式的手册
|
|
110
|
-
*/
|
|
111
|
-
function generateManual() {
|
|
112
|
-
return `# 🔧 filesystem
|
|
113
|
-
|
|
114
|
-
> 文件系统工具 - 用于文件操作
|
|
115
|
-
|
|
116
|
-
## 📋 基础信息
|
|
117
|
-
|
|
118
|
-
- **标识**: \`tool://filesystem\`
|
|
119
|
-
- **分类**: 系统工具
|
|
120
|
-
|
|
121
|
-
## ✅ 适用场景
|
|
122
|
-
|
|
123
|
-
- 读取文件内容进行分析
|
|
124
|
-
- 写入文件内容保存数据
|
|
125
|
-
- 列出目录内容查看文件结构
|
|
126
|
-
- 删除文件或目录进行清理
|
|
127
|
-
|
|
128
|
-
## 📝 参数定义
|
|
129
|
-
|
|
130
|
-
### execute 模式参数
|
|
131
|
-
|
|
132
|
-
| 参数 | 类型 | 必需 | 描述 | 默认值 |
|
|
133
|
-
|------|------|------|------|--------|
|
|
134
|
-
| action | string (read|write|list|delete) | ✅ | 操作类型 | - |
|
|
135
|
-
| path | string | ✅ | 文件或目录路径 | - |
|
|
136
|
-
| content | string | ❌ | 写入的文件内容 (仅在action为write时需要) | - |
|
|
137
|
-
|
|
138
|
-
## 💻 使用示例
|
|
139
|
-
|
|
140
|
-
通过 toolx 调用,使用 YAML 格式:
|
|
141
|
-
|
|
142
|
-
\`\`\`yaml
|
|
143
|
-
# 读取文件内容
|
|
144
|
-
tool: tool://filesystem
|
|
145
|
-
mode: execute
|
|
146
|
-
parameters:
|
|
147
|
-
action: read
|
|
148
|
-
path: /path/to/file.txt
|
|
149
|
-
|
|
150
|
-
# 写入文件内容
|
|
151
|
-
tool: tool://filesystem
|
|
152
|
-
mode: execute
|
|
153
|
-
parameters:
|
|
154
|
-
action: write
|
|
155
|
-
path: /path/to/file.txt
|
|
156
|
-
content: "Hello, World!"
|
|
157
|
-
|
|
158
|
-
# 列出目录内容
|
|
159
|
-
tool: tool://filesystem
|
|
160
|
-
mode: execute
|
|
161
|
-
parameters:
|
|
162
|
-
action: list
|
|
163
|
-
path: /path/to/directory
|
|
164
|
-
|
|
165
|
-
# 删除文件或目录
|
|
166
|
-
tool: tool://filesystem
|
|
167
|
-
mode: execute
|
|
168
|
-
parameters:
|
|
169
|
-
action: delete
|
|
170
|
-
path: /path/to/file-or-directory
|
|
171
|
-
\`\`\`
|
|
172
|
-
|
|
173
|
-
## 🚨 业务错误
|
|
174
|
-
|
|
175
|
-
| 错误码 | 描述 | 解决方案 | 可重试 |
|
|
176
|
-
|--------|------|----------|--------|
|
|
177
|
-
| MISSING_ACTION | 缺少必需参数: action | 提供 action 参数 | ❌ |
|
|
178
|
-
| MISSING_PATH | 缺少必需参数: path | 提供 path 参数 | ❌ |
|
|
179
|
-
| READ_FAILED | 读取文件失败 | 检查文件路径是否存在且可读 | ✅ |
|
|
180
|
-
| WRITE_FAILED | 写入文件失败 | 检查文件路径是否可写 | ✅ |
|
|
181
|
-
| LIST_FAILED | 列出目录内容失败 | 检查目录路径是否存在且可读 | ✅ |
|
|
182
|
-
| DELETE_FAILED | 删除文件或目录失败 | 检查文件或目录是否存在且可删除 | ✅ |
|
|
183
|
-
`;
|
|
184
|
-
}
|
|
538
|
+
};
|