foliko 1.0.73 → 1.0.75
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/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +288 -0
- package/.agent/agents/ambient-agent.md +57 -0
- package/.agent/agents/debugger.md +55 -0
- package/.agent/agents/email-assistant.md +49 -0
- package/.agent/agents/file-manager.md +42 -0
- package/.agent/agents/python-developer.md +60 -0
- package/.agent/agents/scheduler.md +59 -0
- package/.agent/agents/web-developer.md +45 -0
- package/.agent/data/default.json +29 -0
- package/.agent/data/plugins-state.json +255 -0
- package/.agent/mcp_config.json +4 -0
- package/.agent/mcp_config_updated.json +12 -0
- package/.agent/plugins.json +5 -0
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/rules/allow-rule.md +77 -0
- package/.agent/rules/log-rule.md +83 -0
- package/.agent/rules/security-rule.md +93 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/skills/api-patterns/SKILL.md +81 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +55 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/clean-code/SKILL.md +201 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +311 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +237 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +81 -0
- package/.agent/workflows/simple-test.md +42 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/structured-orchestrate.md +180 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +296 -0
- package/.claude/settings.local.json +157 -149
- package/.editorconfig +56 -0
- package/.husky/pre-commit +4 -0
- package/.lintstagedrc +7 -0
- package/.prettierignore +29 -0
- package/.prettierrc +11 -0
- package/CLAUDE.md +2 -0
- package/README.md +64 -55
- package/SPEC.md +102 -61
- package/cli/bin/foliko.js +4 -4
- package/cli/src/commands/chat.js +53 -51
- package/cli/src/commands/list.js +40 -37
- package/cli/src/index.js +18 -18
- package/cli/src/ui/chat-ui.js +78 -76
- package/cli/src/utils/ansi.js +15 -15
- package/cli/src/utils/markdown.js +112 -116
- package/docker-compose.yml +1 -1
- package/docs/ai-sdk-optimization.md +655 -636
- package/docs/features.md +80 -80
- package/docs/quick-reference.md +49 -46
- package/docs/user-manual.md +411 -380
- package/examples/ambient-example.js +95 -97
- package/examples/basic.js +115 -110
- package/examples/bootstrap.js +52 -43
- package/examples/mcp-example.js +56 -53
- package/examples/skill-example.js +49 -49
- package/examples/test-chat.js +60 -58
- package/examples/test-mcp.js +49 -43
- package/examples/test-reload.js +38 -40
- package/examples/test-telegram.js +3 -3
- package/examples/test-tg-bot.js +7 -4
- package/examples/test-tg-simple.js +4 -3
- package/examples/test-tg.js +3 -3
- package/examples/test-think.js +13 -7
- package/examples/test-web-plugin.js +61 -56
- package/examples/test-weixin-feishu.js +40 -37
- package/examples/workflow.js +49 -49
- package/foliko-1.0.75.tgz +0 -0
- package/package.json +37 -3
- package/plugins/ai-plugin.js +7 -5
- package/plugins/ambient-agent/EventWatcher.js +113 -0
- package/plugins/ambient-agent/ExplorerLoop.js +640 -0
- package/plugins/ambient-agent/GoalManager.js +197 -0
- package/plugins/ambient-agent/Reflector.js +95 -0
- package/plugins/ambient-agent/StateStore.js +90 -0
- package/plugins/ambient-agent/constants.js +101 -0
- package/plugins/ambient-agent/index.js +579 -0
- package/plugins/default-plugins.js +62 -49
- package/plugins/email/constants.js +64 -0
- package/plugins/email/handlers.js +461 -0
- package/plugins/email/index.js +278 -0
- package/plugins/email/monitor.js +269 -0
- package/plugins/email/parser.js +138 -0
- package/plugins/email/reply.js +151 -0
- package/plugins/email/utils.js +124 -0
- package/plugins/feishu-plugin.js +23 -19
- package/plugins/file-system-plugin.js +469 -120
- package/plugins/install-plugin.js +6 -4
- package/plugins/python-executor-plugin.js +3 -1
- package/plugins/python-plugin-loader.js +10 -8
- package/plugins/rules-plugin.js +5 -3
- package/plugins/scheduler-plugin.js +18 -16
- package/plugins/session-plugin.js +3 -1
- package/plugins/storage-plugin.js +5 -3
- package/plugins/subagent-plugin.js +152 -92
- package/plugins/telegram-plugin.js +26 -19
- package/plugins/think-plugin.js +4 -2
- package/plugins/tools-plugin.js +3 -1
- package/plugins/web-plugin.js +15 -13
- package/plugins/weixin-plugin.js +43 -36
- package/reports/system-health-report-20260401.md +79 -0
- package/skills/ambient-agent/SKILL.md +49 -39
- package/skills/foliko-dev/AGENTS.md +64 -61
- package/skills/foliko-dev/SKILL.md +125 -119
- package/skills/mcp-usage/SKILL.md +19 -17
- package/skills/python-plugin-dev/SKILL.md +16 -15
- package/skills/skill-guide/SKILL.md +12 -12
- package/skills/subagent-guide/SKILL.md +237 -0
- package/skills/workflow-guide/SKILL.md +90 -45
- package/skills/workflow-troubleshooting/DEBUGGING.md +36 -21
- package/skills/workflow-troubleshooting/SKILL.md +156 -79
- package/src/capabilities/index.js +4 -4
- package/src/capabilities/skill-manager.js +211 -197
- package/src/capabilities/workflow-engine.js +461 -547
- package/src/core/agent-chat.js +426 -279
- package/src/core/agent.js +453 -248
- package/src/core/framework.js +183 -149
- package/src/core/index.js +8 -8
- package/src/core/plugin-base.js +52 -52
- package/src/core/plugin-manager.js +377 -281
- package/src/core/provider.js +35 -32
- package/src/core/sub-agent-config.js +264 -0
- package/src/core/system-prompt-builder.js +120 -0
- package/src/core/tool-registry.js +416 -33
- package/src/core/tool-router.js +149 -68
- package/src/executors/executor-base.js +58 -58
- package/src/executors/mcp-executor.js +269 -257
- package/src/index.js +5 -17
- package/src/utils/circuit-breaker.js +301 -0
- package/src/utils/error-boundary.js +363 -0
- package/src/utils/error.js +374 -0
- package/src/utils/event-emitter.js +20 -20
- package/src/utils/id.js +133 -0
- package/src/utils/index.js +217 -3
- package/src/utils/logger.js +181 -0
- package/src/utils/plugin-helpers.js +90 -0
- package/src/utils/retry.js +122 -0
- package/src/utils/sandbox.js +292 -0
- package/test/tool-registry-validation.test.js +218 -0
- package/test_report.md +70 -0
- package/website/docs/api.html +169 -107
- package/website/docs/configuration.html +296 -144
- package/website/docs/plugin-development.html +154 -85
- package/website/docs/project-structure.html +110 -109
- package/website/docs/skill-development.html +117 -61
- package/website/index.html +209 -205
- package/website/script.js +20 -17
- package/website/styles.css +1 -1
- package/plugins/ambient-agent-plugin.js +0 -1565
- package/plugins/email.js +0 -1142
|
@@ -21,6 +21,24 @@ class FileSystemPlugin extends Plugin {
|
|
|
21
21
|
const path = require('path')
|
|
22
22
|
const { exec } = require('child_process')
|
|
23
23
|
|
|
24
|
+
// 路径安全验证:防止路径穿越攻击
|
|
25
|
+
const validatePath = (filePath, allowOutsideCwd = false) => {
|
|
26
|
+
const resolved = path.resolve(filePath)
|
|
27
|
+
const cwd = process.cwd()
|
|
28
|
+
|
|
29
|
+
// 允许绝对路径且在允许列表中的路径(如果有的话)
|
|
30
|
+
if (allowOutsideCwd) {
|
|
31
|
+
return { valid: true, resolved }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 检查是否在 cwd 目录下
|
|
35
|
+
if (!resolved.startsWith(cwd)) {
|
|
36
|
+
return { valid: false, error: `路径不允许访问: ${filePath}` }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { valid: true, resolved }
|
|
40
|
+
}
|
|
41
|
+
|
|
24
42
|
// 读取目录
|
|
25
43
|
framework.registerTool({
|
|
26
44
|
name: 'read_directory',
|
|
@@ -85,36 +103,42 @@ class FileSystemPlugin extends Plugin {
|
|
|
85
103
|
// 读取文件
|
|
86
104
|
framework.registerTool({
|
|
87
105
|
name: 'read_file',
|
|
88
|
-
description: '
|
|
106
|
+
description: '读取文件内容。path 是必填参数。',
|
|
89
107
|
inputSchema: z.object({
|
|
90
|
-
path: z.string().
|
|
91
|
-
filePath: z.string().optional().describe('文件路径(同path)'),
|
|
92
|
-
encoding: z.enum(['utf8', 'base64', 'binary']).optional().describe('文件编码,默认 utf8'),
|
|
108
|
+
path: z.string().describe('文件路径(必须)'),
|
|
93
109
|
lines: z.number().optional().describe('只读取前 N 行')
|
|
94
110
|
}),
|
|
95
111
|
execute: async (args, framework) => {
|
|
96
|
-
const filePath
|
|
97
|
-
|
|
98
|
-
|
|
112
|
+
const { path: filePath, lines } = args
|
|
113
|
+
if (!filePath) {
|
|
114
|
+
return { success: false, error: 'path 是必填参数' }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 路径安全验证
|
|
118
|
+
const pathCheck = validatePath(filePath)
|
|
119
|
+
if (!pathCheck.valid) {
|
|
120
|
+
return { success: false, error: pathCheck.error }
|
|
121
|
+
}
|
|
122
|
+
|
|
99
123
|
try {
|
|
100
|
-
if (!fs.existsSync(
|
|
124
|
+
if (!fs.existsSync(pathCheck.resolved)) {
|
|
101
125
|
return { success: false, error: '文件不存在' }
|
|
102
126
|
}
|
|
103
|
-
const stat = fs.statSync(
|
|
127
|
+
const stat = fs.statSync(pathCheck.resolved)
|
|
104
128
|
if (stat.size > 1024 * 1024) {
|
|
105
129
|
return { success: false, error: '文件太大,超过 1MB' }
|
|
106
130
|
}
|
|
107
131
|
let content
|
|
108
132
|
if (lines) {
|
|
109
|
-
const fileContent = fs.readFileSync(
|
|
133
|
+
const fileContent = fs.readFileSync(pathCheck.resolved, 'utf8')
|
|
110
134
|
const allLines = fileContent.split('\n')
|
|
111
135
|
content = allLines.slice(0, lines).join('\n')
|
|
112
136
|
} else {
|
|
113
|
-
content = fs.readFileSync(
|
|
137
|
+
content = fs.readFileSync(pathCheck.resolved, 'utf8')
|
|
114
138
|
}
|
|
115
139
|
return {
|
|
116
140
|
success: true,
|
|
117
|
-
filePath,
|
|
141
|
+
filePath: pathCheck.resolved,
|
|
118
142
|
content,
|
|
119
143
|
size: stat.size,
|
|
120
144
|
lines: lines ? null : content.split('\n').length
|
|
@@ -128,23 +152,30 @@ class FileSystemPlugin extends Plugin {
|
|
|
128
152
|
// 写入文件
|
|
129
153
|
framework.registerTool({
|
|
130
154
|
name: 'write_file',
|
|
131
|
-
description: '
|
|
155
|
+
description: '创建或写入文件内容。content 是要写入的文本内容。',
|
|
132
156
|
inputSchema: z.object({
|
|
133
|
-
path: z.string().
|
|
134
|
-
|
|
135
|
-
content: z.string().describe('文件内容')
|
|
157
|
+
path: z.string().describe('文件路径(必须)'),
|
|
158
|
+
content: z.string().describe('文件内容(必须)')
|
|
136
159
|
}),
|
|
137
160
|
execute: async (args, framework) => {
|
|
138
|
-
const filePath
|
|
139
|
-
|
|
140
|
-
|
|
161
|
+
const { path: filePath, content } = args
|
|
162
|
+
if (!filePath || !content) {
|
|
163
|
+
return { success: false, error: 'path 和 content 都是必填参数' }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 路径安全验证
|
|
167
|
+
const pathCheck = validatePath(filePath)
|
|
168
|
+
if (!pathCheck.valid) {
|
|
169
|
+
return { success: false, error: pathCheck.error }
|
|
170
|
+
}
|
|
171
|
+
|
|
141
172
|
try {
|
|
142
|
-
const dir = path.dirname(
|
|
173
|
+
const dir = path.dirname(pathCheck.resolved)
|
|
143
174
|
if (!fs.existsSync(dir)) {
|
|
144
175
|
fs.mkdirSync(dir, { recursive: true })
|
|
145
176
|
}
|
|
146
|
-
fs.writeFileSync(
|
|
147
|
-
return { success: true, message: `文件已写入: ${
|
|
177
|
+
fs.writeFileSync(pathCheck.resolved, content, 'utf8')
|
|
178
|
+
return { success: true, message: `文件已写入: ${pathCheck.resolved}`, filePath: pathCheck.resolved, size: content.length }
|
|
148
179
|
} catch (error) {
|
|
149
180
|
return { success: false, error: error.message }
|
|
150
181
|
}
|
|
@@ -163,21 +194,28 @@ class FileSystemPlugin extends Plugin {
|
|
|
163
194
|
execute: async (args, framework) => {
|
|
164
195
|
const targetPath = args.path || args.targetPath
|
|
165
196
|
const recursive = args.recursive || false
|
|
197
|
+
|
|
198
|
+
// 路径安全验证
|
|
199
|
+
const pathCheck = validatePath(targetPath)
|
|
200
|
+
if (!pathCheck.valid) {
|
|
201
|
+
return { success: false, error: pathCheck.error }
|
|
202
|
+
}
|
|
203
|
+
|
|
166
204
|
try {
|
|
167
|
-
if (!fs.existsSync(
|
|
205
|
+
if (!fs.existsSync(pathCheck.resolved)) {
|
|
168
206
|
return { success: false, error: '目标不存在' }
|
|
169
207
|
}
|
|
170
|
-
const stat = fs.statSync(
|
|
208
|
+
const stat = fs.statSync(pathCheck.resolved)
|
|
171
209
|
if (stat.isDirectory()) {
|
|
172
210
|
if (recursive) {
|
|
173
|
-
fs.rmSync(
|
|
211
|
+
fs.rmSync(pathCheck.resolved, { recursive: true, force: true })
|
|
174
212
|
} else {
|
|
175
|
-
fs.rmdirSync(
|
|
213
|
+
fs.rmdirSync(pathCheck.resolved)
|
|
176
214
|
}
|
|
177
215
|
} else {
|
|
178
|
-
fs.unlinkSync(
|
|
216
|
+
fs.unlinkSync(pathCheck.resolved)
|
|
179
217
|
}
|
|
180
|
-
return { success: true, message: `已删除: ${
|
|
218
|
+
return { success: true, message: `已删除: ${pathCheck.resolved}` }
|
|
181
219
|
} catch (error) {
|
|
182
220
|
return { success: false, error: error.message }
|
|
183
221
|
}
|
|
@@ -185,64 +223,236 @@ class FileSystemPlugin extends Plugin {
|
|
|
185
223
|
})
|
|
186
224
|
|
|
187
225
|
// 修改文件(替换文本)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
226
|
+
framework.registerTool({
|
|
227
|
+
name: 'modify_file',
|
|
228
|
+
description: '修改文件内容(替换文本),支持精确匹配和正则',
|
|
229
|
+
inputSchema: z.object({
|
|
230
|
+
filePath: z.string().describe('文件路径(必须)'),
|
|
231
|
+
find: z.string().min(1).describe('要查找的文本(必填,不能为空)'),
|
|
232
|
+
replace: z.string().describe('替换后的文本'),
|
|
233
|
+
replaceAll: z.boolean().optional().describe('是否替换所有匹配,默认 true'),
|
|
234
|
+
useRegex: z.boolean().optional().describe('是否使用正则表达式匹配,默认 false'),
|
|
235
|
+
backup: z.boolean().default(false).describe('修改前是否创建备份,默认 false')
|
|
236
|
+
}),
|
|
237
|
+
execute: async (args, framework) => {
|
|
238
|
+
const filePath = args.filePath || args.path
|
|
239
|
+
const find = args.find
|
|
240
|
+
const replace = args.replace
|
|
241
|
+
const replaceAll = args.replaceAll !== false // 默认 true
|
|
242
|
+
const useRegex = args.useRegex === true
|
|
243
|
+
const backup = args.backup !== false // 默认 true
|
|
244
|
+
|
|
245
|
+
// 校验 filePath
|
|
246
|
+
if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
|
|
247
|
+
return { success: false, error: 'filePath 是必填参数,不能为空' }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 校验 find
|
|
251
|
+
if (!find || typeof find !== 'string' || find.trim() === '') {
|
|
252
|
+
return { success: false, error: 'find 是必填参数,不能为空字符串' }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 路径安全验证
|
|
256
|
+
const pathCheck = validatePath(filePath)
|
|
257
|
+
if (!pathCheck.valid) {
|
|
258
|
+
return { success: false, error: pathCheck.error }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
if (!fs.existsSync(pathCheck.resolved)) {
|
|
263
|
+
return { success: false, error: '文件不存在' }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// 检查是否是二进制文件
|
|
267
|
+
const stats = fs.statSync(pathCheck.resolved)
|
|
268
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
269
|
+
return { success: false, error: '文件超过 10MB,不支持修改' }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let content = fs.readFileSync(pathCheck.resolved, 'utf8')
|
|
273
|
+
let count = 0
|
|
274
|
+
const matches = []
|
|
275
|
+
|
|
276
|
+
if (useRegex) {
|
|
277
|
+
// 正则表达式模式
|
|
278
|
+
const flags = replaceAll ? 'g' : ''
|
|
279
|
+
const regex = new RegExp(find, flags)
|
|
280
|
+
|
|
281
|
+
let match
|
|
282
|
+
while ((match = regex.exec(content)) !== null) {
|
|
283
|
+
matches.push({
|
|
284
|
+
index: match.index,
|
|
285
|
+
length: match[0].length,
|
|
286
|
+
text: match[0]
|
|
287
|
+
})
|
|
288
|
+
if (!replaceAll) break
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (matches.length > 0) {
|
|
292
|
+
count = replaceAll ? matches.length : 1
|
|
293
|
+
content = content.replace(regex, replace)
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
// 精确字符串匹配
|
|
297
|
+
const escapedFind = find.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
298
|
+
const flags = replaceAll ? 'g' : ''
|
|
299
|
+
const regex = new RegExp(escapedFind, flags)
|
|
300
|
+
|
|
301
|
+
let match
|
|
302
|
+
while ((match = regex.exec(content)) !== null) {
|
|
303
|
+
matches.push({
|
|
304
|
+
index: match.index,
|
|
305
|
+
length: match[0].length,
|
|
306
|
+
text: match[0]
|
|
307
|
+
})
|
|
308
|
+
if (!replaceAll) break
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (matches.length > 0) {
|
|
312
|
+
count = replaceAll ? matches.length : 1
|
|
313
|
+
content = content.replace(regex, replace)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (count === 0) {
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
error: `未找到匹配文本: "${find.substring(0, 50)}${find.length > 50 ? '...' : ''}"`,
|
|
321
|
+
filePath
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 创建备份
|
|
326
|
+
if (backup) {
|
|
327
|
+
const backupPath = pathCheck.resolved + '.bak'
|
|
328
|
+
fs.writeFileSync(backupPath, fs.readFileSync(pathCheck.resolved), 'utf8')
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 原子写入:先写临时文件,再 rename
|
|
332
|
+
const tempPath = pathCheck.resolved + '.tmp.' + Date.now()
|
|
333
|
+
fs.writeFileSync(tempPath, content, 'utf8')
|
|
334
|
+
|
|
335
|
+
// 验证写入内容
|
|
336
|
+
const verifyContent = fs.readFileSync(tempPath, 'utf8')
|
|
337
|
+
if (verifyContent !== content) {
|
|
338
|
+
fs.unlinkSync(tempPath)
|
|
339
|
+
return { success: false, error: '写入验证失败,内容不匹配' }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
fs.renameSync(tempPath, pathCheck.resolved)
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
success: true,
|
|
346
|
+
message: `文件已修改: ${pathCheck.resolved}`,
|
|
347
|
+
filePath: pathCheck.resolved,
|
|
348
|
+
replacements: count,
|
|
349
|
+
matches: matches.slice(0, 5), // 最多返回5个匹配位置
|
|
350
|
+
backupCreated: backup
|
|
351
|
+
}
|
|
352
|
+
} catch (error) {
|
|
353
|
+
return { success: false, error: error.message }
|
|
354
|
+
}
|
|
212
355
|
}
|
|
213
|
-
|
|
214
|
-
return { success: true, message: `文件已修改: ${filePath}`, filePath }
|
|
215
|
-
} catch (error) {
|
|
216
|
-
return { success: false, error: error.message }
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
})
|
|
356
|
+
})
|
|
220
357
|
|
|
221
358
|
// 搜索文件
|
|
222
359
|
framework.registerTool({
|
|
223
360
|
name: 'search_file',
|
|
224
|
-
description: '
|
|
361
|
+
description: '在文件或目录中搜索文本,支持精确匹配和正则表达式',
|
|
225
362
|
inputSchema: z.object({
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
363
|
+
pattern: z.string().describe('搜索模式(关键词或正则表达式)'),
|
|
364
|
+
path: z.string().optional().describe('搜索目录路径'),
|
|
365
|
+
file: z.string().optional().describe('搜索指定文件(与 path 二选一)'),
|
|
366
|
+
fileType: z.string().optional().describe('文件类型过滤,如 .js、.py'),
|
|
367
|
+
maxResults: z.number().optional().describe('最大结果数,默认 100'),
|
|
368
|
+
maxResultsPerFile: z.number().optional().describe('每个文件最大结果数,默认 50'),
|
|
369
|
+
contextLines: z.number().optional().describe('匹配行的上下文行数,默认 0'),
|
|
370
|
+
caseSensitive: z.boolean().optional().describe('是否大小写敏感,默认 false'),
|
|
371
|
+
useRegex: z.boolean().optional().describe('是否使用正则表达式,默认 false'),
|
|
372
|
+
excludeDirs: z.array(z.string()).optional().describe('排除的目录名,默认 ["node_modules", ".git", "dist", "build"]')
|
|
235
373
|
}),
|
|
236
374
|
execute: async (args, framework) => {
|
|
237
|
-
const pattern = args.
|
|
238
|
-
const dirPath = args.path ||
|
|
375
|
+
const pattern = args.pattern
|
|
376
|
+
const dirPath = args.path || process.cwd()
|
|
239
377
|
const targetFile = args.file
|
|
240
378
|
const fileType = args.fileType
|
|
241
|
-
const maxResults = args.maxResults ||
|
|
379
|
+
const maxResults = args.maxResults || 100
|
|
380
|
+
const maxResultsPerFile = args.maxResultsPerFile || 50
|
|
242
381
|
const contextLines = args.contextLines || 0
|
|
382
|
+
const caseSensitive = args.caseSensitive === true
|
|
383
|
+
const useRegex = args.useRegex === true
|
|
384
|
+
const excludeDirs = args.excludeDirs || ['node_modules', '.git', 'dist', 'build', '.claude']
|
|
385
|
+
|
|
386
|
+
if (!pattern || typeof pattern !== 'string' || pattern.trim() === '') {
|
|
387
|
+
return { success: false, error: 'pattern 是必填参数,不能为空' }
|
|
388
|
+
}
|
|
389
|
+
|
|
243
390
|
try {
|
|
244
391
|
const results = []
|
|
245
|
-
|
|
392
|
+
let regex
|
|
393
|
+
|
|
394
|
+
if (useRegex) {
|
|
395
|
+
// 直接使用正则表达式
|
|
396
|
+
try {
|
|
397
|
+
regex = new RegExp(pattern, caseSensitive ? 'g' : 'gi')
|
|
398
|
+
} catch (e) {
|
|
399
|
+
return { success: false, error: `无效的正则表达式: ${e.message}` }
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
// 转义特殊字符作为精确匹配
|
|
403
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
404
|
+
regex = new RegExp(escaped, caseSensitive ? 'g' : 'gi')
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const binaryExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.exe', '.dll', '.so', '.zip', '.tar', '.gz', '.rar', '.7z', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.mp3', '.mp4', '.avi', '.mkv', '.wav', '.flac']
|
|
408
|
+
|
|
409
|
+
const searchInContent = (content, filePath) => {
|
|
410
|
+
const matches = []
|
|
411
|
+
const lines = content.split('\n')
|
|
412
|
+
let fileResultsCount = 0
|
|
413
|
+
|
|
414
|
+
for (let i = 0; i < lines.length; i++) {
|
|
415
|
+
if (fileResultsCount >= maxResultsPerFile) break
|
|
416
|
+
const line = lines[i]
|
|
417
|
+
regex.lastIndex = 0 // 重置 regex 状态
|
|
418
|
+
|
|
419
|
+
if (regex.test(line)) {
|
|
420
|
+
// 找到所有匹配的位置
|
|
421
|
+
const lineMatches = []
|
|
422
|
+
regex.lastIndex = 0
|
|
423
|
+
let match
|
|
424
|
+
while ((match = regex.exec(line)) !== null) {
|
|
425
|
+
lineMatches.push({
|
|
426
|
+
column: match.index,
|
|
427
|
+
length: match[0].length,
|
|
428
|
+
text: match[0]
|
|
429
|
+
})
|
|
430
|
+
if (!regex.global) break
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const matchInfo = {
|
|
434
|
+
file: filePath,
|
|
435
|
+
line: i + 1,
|
|
436
|
+
content: line.substring(0, 300),
|
|
437
|
+
matches: lineMatches
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (contextLines > 0) {
|
|
441
|
+
const start = Math.max(0, i - contextLines)
|
|
442
|
+
const end = Math.min(lines.length, i + contextLines + 1)
|
|
443
|
+
matchInfo.context = lines.slice(start, end).map((l, idx) => ({
|
|
444
|
+
line: start + idx + 1,
|
|
445
|
+
content: l
|
|
446
|
+
}))
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
matches.push(matchInfo)
|
|
450
|
+
fileResultsCount++
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return matches
|
|
455
|
+
}
|
|
246
456
|
|
|
247
457
|
// 搜索单个文件
|
|
248
458
|
if (targetFile) {
|
|
@@ -250,74 +460,85 @@ class FileSystemPlugin extends Plugin {
|
|
|
250
460
|
if (!fs.existsSync(fullPath)) {
|
|
251
461
|
return { success: false, error: `文件不存在: ${targetFile}` }
|
|
252
462
|
}
|
|
463
|
+
|
|
464
|
+
const ext = path.extname(fullPath).toLowerCase()
|
|
465
|
+
if (binaryExtensions.includes(ext)) {
|
|
466
|
+
return { success: false, error: `不支持搜索二进制文件: ${ext}` }
|
|
467
|
+
}
|
|
468
|
+
|
|
253
469
|
try {
|
|
254
470
|
const content = fs.readFileSync(fullPath, 'utf8')
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
if (regex.test(lines[i])) {
|
|
258
|
-
let matchInfo = { file: targetFile, line: i + 1, content: lines[i].substring(0, 200) }
|
|
259
|
-
// 添加上下文行
|
|
260
|
-
if (contextLines > 0) {
|
|
261
|
-
const start = Math.max(0, i - contextLines)
|
|
262
|
-
const end = Math.min(lines.length, i + contextLines + 1)
|
|
263
|
-
matchInfo.context = lines.slice(start, end).map((l, idx) => ({
|
|
264
|
-
line: start + idx + 1,
|
|
265
|
-
content: l
|
|
266
|
-
}))
|
|
267
|
-
}
|
|
268
|
-
results.push(matchInfo)
|
|
269
|
-
if (results.length >= maxResults) break
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
regex.lastIndex = 0
|
|
471
|
+
const fileMatches = searchInContent(content, targetFile)
|
|
472
|
+
results.push(...fileMatches)
|
|
273
473
|
} catch (e) {
|
|
274
474
|
return { success: false, error: `读取文件失败: ${e.message}` }
|
|
275
475
|
}
|
|
276
476
|
} else {
|
|
277
477
|
// 搜索目录
|
|
278
478
|
const searchDir = (currentPath, depth = 0) => {
|
|
279
|
-
if (depth >
|
|
280
|
-
|
|
479
|
+
if (depth > 10 || results.length >= maxResults) return
|
|
480
|
+
|
|
481
|
+
let entries
|
|
482
|
+
try {
|
|
483
|
+
entries = fs.readdirSync(currentPath, { withFileTypes: true })
|
|
484
|
+
} catch (e) {
|
|
485
|
+
return // 跳过无法读取的目录
|
|
486
|
+
}
|
|
487
|
+
|
|
281
488
|
for (const entry of entries) {
|
|
282
489
|
if (results.length >= maxResults) break
|
|
490
|
+
|
|
283
491
|
const fullPath = path.join(currentPath, entry.name)
|
|
284
492
|
const relativePath = path.relative(process.cwd(), fullPath)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
493
|
+
|
|
494
|
+
// 检查是否在排除目录中
|
|
495
|
+
const shouldExclude = excludeDirs.some(exclude =>
|
|
496
|
+
relativePath.includes(exclude)
|
|
497
|
+
)
|
|
498
|
+
if (shouldExclude) continue
|
|
499
|
+
|
|
289
500
|
if (entry.isDirectory()) {
|
|
290
501
|
searchDir(fullPath, depth + 1)
|
|
291
502
|
} else if (entry.isFile()) {
|
|
503
|
+
// 文件类型过滤
|
|
292
504
|
if (fileType && !entry.name.endsWith(fileType)) continue
|
|
293
|
-
|
|
294
|
-
|
|
505
|
+
|
|
506
|
+
const ext = path.extname(entry.name).toLowerCase()
|
|
507
|
+
if (binaryExtensions.includes(ext)) continue
|
|
508
|
+
|
|
295
509
|
try {
|
|
296
510
|
const content = fs.readFileSync(fullPath, 'utf8')
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const start = Math.max(0, i - contextLines)
|
|
303
|
-
const end = Math.min(lines.length, i + contextLines + 1)
|
|
304
|
-
matchInfo.context = lines.slice(start, end).map((l, idx) => ({
|
|
305
|
-
line: start + idx + 1,
|
|
306
|
-
content: l
|
|
307
|
-
}))
|
|
308
|
-
}
|
|
309
|
-
results.push(matchInfo)
|
|
310
|
-
if (results.length >= maxResults) break
|
|
311
|
-
}
|
|
511
|
+
const fileMatches = searchInContent(content, relativePath)
|
|
512
|
+
|
|
513
|
+
for (const match of fileMatches) {
|
|
514
|
+
if (results.length >= maxResults) break
|
|
515
|
+
results.push(match)
|
|
312
516
|
}
|
|
313
|
-
|
|
314
|
-
|
|
517
|
+
} catch (e) {
|
|
518
|
+
// 跳过无法读取的文件
|
|
519
|
+
}
|
|
315
520
|
}
|
|
316
521
|
}
|
|
317
522
|
}
|
|
523
|
+
|
|
318
524
|
searchDir(dirPath)
|
|
319
525
|
}
|
|
320
|
-
|
|
526
|
+
|
|
527
|
+
// 计算统计信息
|
|
528
|
+
const filesWithMatches = new Set(results.map(r => r.file)).size
|
|
529
|
+
const totalMatches = results.reduce((sum, r) => sum + (r.matches?.length || 1), 0)
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
success: true,
|
|
533
|
+
pattern,
|
|
534
|
+
results: results.slice(0, maxResults),
|
|
535
|
+
total: results.length,
|
|
536
|
+
stats: {
|
|
537
|
+
filesWithMatches,
|
|
538
|
+
totalMatches,
|
|
539
|
+
searchPath: targetFile || dirPath
|
|
540
|
+
}
|
|
541
|
+
}
|
|
321
542
|
} catch (error) {
|
|
322
543
|
return { success: false, error: error.message }
|
|
323
544
|
}
|
|
@@ -338,13 +559,45 @@ class FileSystemPlugin extends Plugin {
|
|
|
338
559
|
execute: async (args, framework) => {
|
|
339
560
|
const command = args.cmd || args.command || args.run
|
|
340
561
|
const cwd = args.cwd || process.cwd()
|
|
341
|
-
const timeout = args.timeout || 30000
|
|
562
|
+
const timeout = Math.min(args.timeout || 30000, 120000) // 最多 2 分钟
|
|
563
|
+
|
|
564
|
+
// 验证命令:检查危险的 shell 模式
|
|
565
|
+
const dangerousPatterns = [
|
|
566
|
+
/;\s*rm\s+/i, // ; rm -rf
|
|
567
|
+
/\|\s*rm\s+/i, // | rm -rf
|
|
568
|
+
/&&\s*rm\s+/i, // && rm -rf
|
|
569
|
+
/;\s*del\s+/i, // ; del
|
|
570
|
+
/\|\s*del\s+/i, // | del
|
|
571
|
+
/&\s*rm\s+/i, // & rm -rf
|
|
572
|
+
/;\s*format\s+/i, // ; format
|
|
573
|
+
/&\s*format\s+/i, // & format
|
|
574
|
+
/\$\(/i, // $(command substitution)
|
|
575
|
+
/`[^`]+`/i, // `command substitution`
|
|
576
|
+
/>\s*\/dev\//i, // redirect to /dev/
|
|
577
|
+
/<\s*\/dev\//i, // read from /dev/
|
|
578
|
+
]
|
|
579
|
+
|
|
580
|
+
for (const pattern of dangerousPatterns) {
|
|
581
|
+
if (pattern.test(command)) {
|
|
582
|
+
return {
|
|
583
|
+
success: false,
|
|
584
|
+
error: `命令包含可疑模式,已拒绝执行: ${pattern.toString()}`
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// 验证 cwd 路径
|
|
590
|
+
const resolvedCwd = path.resolve(cwd)
|
|
591
|
+
if (!fs.existsSync(resolvedCwd)) {
|
|
592
|
+
return { success: false, error: `工作目录不存在: ${resolvedCwd}` }
|
|
593
|
+
}
|
|
594
|
+
|
|
342
595
|
return new Promise((resolve) => {
|
|
343
596
|
const startTime = Date.now()
|
|
344
|
-
exec(command, { cwd, timeout }, (error, stdout, stderr) => {
|
|
597
|
+
exec(command, { cwd: resolvedCwd, timeout, shell: true }, (error, stdout, stderr) => {
|
|
345
598
|
const duration = Date.now() - startTime
|
|
346
599
|
if (error) {
|
|
347
|
-
resolve({ success: false, command, error: error.message, stderr, duration })
|
|
600
|
+
resolve({ success: false, command, error: error.message, stderr: stderr.substring(0, 1000), duration })
|
|
348
601
|
} else {
|
|
349
602
|
resolve({
|
|
350
603
|
success: true,
|
|
@@ -359,6 +612,102 @@ class FileSystemPlugin extends Plugin {
|
|
|
359
612
|
}
|
|
360
613
|
})
|
|
361
614
|
|
|
615
|
+
// 执行 Bash 命令
|
|
616
|
+
framework.registerTool({
|
|
617
|
+
name: 'bash',
|
|
618
|
+
description: '执行 Bash 命令(支持 Linux/macOS/Windows Git Bash)',
|
|
619
|
+
inputSchema: z.object({
|
|
620
|
+
cmd: z.string().optional().describe('要执行的 bash 命令'),
|
|
621
|
+
command: z.string().optional().describe('要执行的 bash 命令(同cmd)'),
|
|
622
|
+
cwd: z.string().optional().describe('工作目录'),
|
|
623
|
+
timeout: z.number().optional().describe('超时时间(ms)')
|
|
624
|
+
}),
|
|
625
|
+
execute: async (args, framework) => {
|
|
626
|
+
const command = args.cmd || args.command
|
|
627
|
+
const cwd = args.cwd || process.cwd()
|
|
628
|
+
const timeout = Math.min(args.timeout || 30000, 120000)
|
|
629
|
+
|
|
630
|
+
if (!command) {
|
|
631
|
+
return { success: false, error: 'cmd/command 是必填参数' }
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// 验证命令:检查危险的 shell 模式
|
|
635
|
+
const dangerousPatterns = [
|
|
636
|
+
/;\s*rm\s+-rf/i,
|
|
637
|
+
/\|\s*rm\s+/i,
|
|
638
|
+
/&&\s*rm\s+/i,
|
|
639
|
+
/;\s*del\s+/i,
|
|
640
|
+
/\$\(/i,
|
|
641
|
+
/`[^`]+`/i,
|
|
642
|
+
/>\s*\/dev\//i,
|
|
643
|
+
/<\s*\/dev\//i,
|
|
644
|
+
]
|
|
645
|
+
|
|
646
|
+
for (const pattern of dangerousPatterns) {
|
|
647
|
+
if (pattern.test(command)) {
|
|
648
|
+
return {
|
|
649
|
+
success: false,
|
|
650
|
+
error: `命令包含可疑模式,已拒绝执行: ${pattern.toString()}`
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// 验证 cwd 路径
|
|
656
|
+
const resolvedCwd = path.resolve(cwd)
|
|
657
|
+
if (!fs.existsSync(resolvedCwd)) {
|
|
658
|
+
return { success: false, error: `工作目录不存在: ${resolvedCwd}` }
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// 确定 bash 路径
|
|
662
|
+
let bashPath = 'bash'
|
|
663
|
+
const isWindows = process.platform === 'win32'
|
|
664
|
+
|
|
665
|
+
if (isWindows) {
|
|
666
|
+
// Windows 下尝试查找 Git Bash
|
|
667
|
+
const possiblePaths = [
|
|
668
|
+
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
669
|
+
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
|
670
|
+
process.env.GIT_BASH_PATH || 'bash'
|
|
671
|
+
]
|
|
672
|
+
for (const p of possiblePaths) {
|
|
673
|
+
if (fs.existsSync(p)) {
|
|
674
|
+
bashPath = p
|
|
675
|
+
break
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return new Promise((resolve) => {
|
|
681
|
+
const startTime = Date.now()
|
|
682
|
+
exec(
|
|
683
|
+
`"${bashPath}" -c ${JSON.stringify(command)}`,
|
|
684
|
+
{ cwd: resolvedCwd, timeout, shell: false },
|
|
685
|
+
(error, stdout, stderr) => {
|
|
686
|
+
const duration = Date.now() - startTime
|
|
687
|
+
if (error) {
|
|
688
|
+
resolve({
|
|
689
|
+
success: false,
|
|
690
|
+
command,
|
|
691
|
+
error: error.message,
|
|
692
|
+
stderr: stderr.substring(0, 2000),
|
|
693
|
+
stdout: stdout.substring(0, 2000),
|
|
694
|
+
duration
|
|
695
|
+
})
|
|
696
|
+
} else {
|
|
697
|
+
resolve({
|
|
698
|
+
success: true,
|
|
699
|
+
command,
|
|
700
|
+
stdout: stdout.substring(0, 10000),
|
|
701
|
+
stderr: stderr.substring(0, 1000),
|
|
702
|
+
duration
|
|
703
|
+
})
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
)
|
|
707
|
+
})
|
|
708
|
+
}
|
|
709
|
+
})
|
|
710
|
+
|
|
362
711
|
// 获取北京时间
|
|
363
712
|
framework.registerTool({
|
|
364
713
|
name: 'get_time',
|