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.
Files changed (237) hide show
  1. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
  2. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
  3. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
  4. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
  5. package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
  6. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
  7. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
  8. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  9. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  10. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  11. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  12. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  13. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  14. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  15. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
  16. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  17. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  18. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  19. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  20. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
  21. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
  22. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  23. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  24. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
  25. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  26. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  27. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
  28. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
  29. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
  30. package/.agent/ARCHITECTURE.md +288 -0
  31. package/.agent/agents/ambient-agent.md +57 -0
  32. package/.agent/agents/debugger.md +55 -0
  33. package/.agent/agents/email-assistant.md +49 -0
  34. package/.agent/agents/file-manager.md +42 -0
  35. package/.agent/agents/python-developer.md +60 -0
  36. package/.agent/agents/scheduler.md +59 -0
  37. package/.agent/agents/web-developer.md +45 -0
  38. package/.agent/data/default.json +29 -0
  39. package/.agent/data/plugins-state.json +255 -0
  40. package/.agent/mcp_config.json +4 -0
  41. package/.agent/mcp_config_updated.json +12 -0
  42. package/.agent/plugins.json +5 -0
  43. package/.agent/rules/GEMINI.md +273 -0
  44. package/.agent/rules/allow-rule.md +77 -0
  45. package/.agent/rules/log-rule.md +83 -0
  46. package/.agent/rules/security-rule.md +93 -0
  47. package/.agent/scripts/auto_preview.py +148 -0
  48. package/.agent/scripts/checklist.py +217 -0
  49. package/.agent/scripts/session_manager.py +120 -0
  50. package/.agent/scripts/verify_all.py +327 -0
  51. package/.agent/skills/api-patterns/SKILL.md +81 -0
  52. package/.agent/skills/api-patterns/api-style.md +42 -0
  53. package/.agent/skills/api-patterns/auth.md +24 -0
  54. package/.agent/skills/api-patterns/documentation.md +26 -0
  55. package/.agent/skills/api-patterns/graphql.md +41 -0
  56. package/.agent/skills/api-patterns/rate-limiting.md +31 -0
  57. package/.agent/skills/api-patterns/response.md +37 -0
  58. package/.agent/skills/api-patterns/rest.md +40 -0
  59. package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
  60. package/.agent/skills/api-patterns/security-testing.md +122 -0
  61. package/.agent/skills/api-patterns/trpc.md +41 -0
  62. package/.agent/skills/api-patterns/versioning.md +22 -0
  63. package/.agent/skills/app-builder/SKILL.md +75 -0
  64. package/.agent/skills/app-builder/agent-coordination.md +71 -0
  65. package/.agent/skills/app-builder/feature-building.md +53 -0
  66. package/.agent/skills/app-builder/project-detection.md +34 -0
  67. package/.agent/skills/app-builder/scaffolding.md +118 -0
  68. package/.agent/skills/app-builder/tech-stack.md +40 -0
  69. package/.agent/skills/app-builder/templates/SKILL.md +39 -0
  70. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
  71. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
  72. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  73. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
  74. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
  75. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
  76. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
  77. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
  78. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
  79. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
  80. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
  81. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
  82. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
  83. package/.agent/skills/architecture/SKILL.md +55 -0
  84. package/.agent/skills/architecture/context-discovery.md +43 -0
  85. package/.agent/skills/architecture/examples.md +94 -0
  86. package/.agent/skills/architecture/pattern-selection.md +68 -0
  87. package/.agent/skills/architecture/patterns-reference.md +50 -0
  88. package/.agent/skills/architecture/trade-off-analysis.md +77 -0
  89. package/.agent/skills/clean-code/SKILL.md +201 -0
  90. package/.agent/skills/doc.md +177 -0
  91. package/.agent/skills/frontend-design/SKILL.md +418 -0
  92. package/.agent/skills/frontend-design/animation-guide.md +331 -0
  93. package/.agent/skills/frontend-design/color-system.md +311 -0
  94. package/.agent/skills/frontend-design/decision-trees.md +418 -0
  95. package/.agent/skills/frontend-design/motion-graphics.md +306 -0
  96. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  97. package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
  98. package/.agent/skills/frontend-design/typography-system.md +345 -0
  99. package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
  100. package/.agent/skills/frontend-design/visual-effects.md +383 -0
  101. package/.agent/skills/i18n-localization/SKILL.md +154 -0
  102. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
  103. package/.agent/skills/mcp-builder/SKILL.md +176 -0
  104. package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
  105. package/.agent/workflows/brainstorm.md +113 -0
  106. package/.agent/workflows/create.md +59 -0
  107. package/.agent/workflows/debug.md +103 -0
  108. package/.agent/workflows/deploy.md +176 -0
  109. package/.agent/workflows/enhance.md +63 -0
  110. package/.agent/workflows/orchestrate.md +237 -0
  111. package/.agent/workflows/plan.md +89 -0
  112. package/.agent/workflows/preview.md +81 -0
  113. package/.agent/workflows/simple-test.md +42 -0
  114. package/.agent/workflows/status.md +86 -0
  115. package/.agent/workflows/structured-orchestrate.md +180 -0
  116. package/.agent/workflows/test.md +144 -0
  117. package/.agent/workflows/ui-ux-pro-max.md +296 -0
  118. package/.claude/settings.local.json +157 -149
  119. package/.editorconfig +56 -0
  120. package/.husky/pre-commit +4 -0
  121. package/.lintstagedrc +7 -0
  122. package/.prettierignore +29 -0
  123. package/.prettierrc +11 -0
  124. package/CLAUDE.md +2 -0
  125. package/README.md +64 -55
  126. package/SPEC.md +102 -61
  127. package/cli/bin/foliko.js +4 -4
  128. package/cli/src/commands/chat.js +53 -51
  129. package/cli/src/commands/list.js +40 -37
  130. package/cli/src/index.js +18 -18
  131. package/cli/src/ui/chat-ui.js +78 -76
  132. package/cli/src/utils/ansi.js +15 -15
  133. package/cli/src/utils/markdown.js +112 -116
  134. package/docker-compose.yml +1 -1
  135. package/docs/ai-sdk-optimization.md +655 -636
  136. package/docs/features.md +80 -80
  137. package/docs/quick-reference.md +49 -46
  138. package/docs/user-manual.md +411 -380
  139. package/examples/ambient-example.js +95 -97
  140. package/examples/basic.js +115 -110
  141. package/examples/bootstrap.js +52 -43
  142. package/examples/mcp-example.js +56 -53
  143. package/examples/skill-example.js +49 -49
  144. package/examples/test-chat.js +60 -58
  145. package/examples/test-mcp.js +49 -43
  146. package/examples/test-reload.js +38 -40
  147. package/examples/test-telegram.js +3 -3
  148. package/examples/test-tg-bot.js +7 -4
  149. package/examples/test-tg-simple.js +4 -3
  150. package/examples/test-tg.js +3 -3
  151. package/examples/test-think.js +13 -7
  152. package/examples/test-web-plugin.js +61 -56
  153. package/examples/test-weixin-feishu.js +40 -37
  154. package/examples/workflow.js +49 -49
  155. package/foliko-1.0.75.tgz +0 -0
  156. package/package.json +37 -3
  157. package/plugins/ai-plugin.js +7 -5
  158. package/plugins/ambient-agent/EventWatcher.js +113 -0
  159. package/plugins/ambient-agent/ExplorerLoop.js +640 -0
  160. package/plugins/ambient-agent/GoalManager.js +197 -0
  161. package/plugins/ambient-agent/Reflector.js +95 -0
  162. package/plugins/ambient-agent/StateStore.js +90 -0
  163. package/plugins/ambient-agent/constants.js +101 -0
  164. package/plugins/ambient-agent/index.js +579 -0
  165. package/plugins/default-plugins.js +62 -49
  166. package/plugins/email/constants.js +64 -0
  167. package/plugins/email/handlers.js +461 -0
  168. package/plugins/email/index.js +278 -0
  169. package/plugins/email/monitor.js +269 -0
  170. package/plugins/email/parser.js +138 -0
  171. package/plugins/email/reply.js +151 -0
  172. package/plugins/email/utils.js +124 -0
  173. package/plugins/feishu-plugin.js +23 -19
  174. package/plugins/file-system-plugin.js +469 -120
  175. package/plugins/install-plugin.js +6 -4
  176. package/plugins/python-executor-plugin.js +3 -1
  177. package/plugins/python-plugin-loader.js +10 -8
  178. package/plugins/rules-plugin.js +5 -3
  179. package/plugins/scheduler-plugin.js +18 -16
  180. package/plugins/session-plugin.js +3 -1
  181. package/plugins/storage-plugin.js +5 -3
  182. package/plugins/subagent-plugin.js +152 -92
  183. package/plugins/telegram-plugin.js +26 -19
  184. package/plugins/think-plugin.js +4 -2
  185. package/plugins/tools-plugin.js +3 -1
  186. package/plugins/web-plugin.js +15 -13
  187. package/plugins/weixin-plugin.js +43 -36
  188. package/reports/system-health-report-20260401.md +79 -0
  189. package/skills/ambient-agent/SKILL.md +49 -39
  190. package/skills/foliko-dev/AGENTS.md +64 -61
  191. package/skills/foliko-dev/SKILL.md +125 -119
  192. package/skills/mcp-usage/SKILL.md +19 -17
  193. package/skills/python-plugin-dev/SKILL.md +16 -15
  194. package/skills/skill-guide/SKILL.md +12 -12
  195. package/skills/subagent-guide/SKILL.md +237 -0
  196. package/skills/workflow-guide/SKILL.md +90 -45
  197. package/skills/workflow-troubleshooting/DEBUGGING.md +36 -21
  198. package/skills/workflow-troubleshooting/SKILL.md +156 -79
  199. package/src/capabilities/index.js +4 -4
  200. package/src/capabilities/skill-manager.js +211 -197
  201. package/src/capabilities/workflow-engine.js +461 -547
  202. package/src/core/agent-chat.js +426 -279
  203. package/src/core/agent.js +453 -248
  204. package/src/core/framework.js +183 -149
  205. package/src/core/index.js +8 -8
  206. package/src/core/plugin-base.js +52 -52
  207. package/src/core/plugin-manager.js +377 -281
  208. package/src/core/provider.js +35 -32
  209. package/src/core/sub-agent-config.js +264 -0
  210. package/src/core/system-prompt-builder.js +120 -0
  211. package/src/core/tool-registry.js +416 -33
  212. package/src/core/tool-router.js +149 -68
  213. package/src/executors/executor-base.js +58 -58
  214. package/src/executors/mcp-executor.js +269 -257
  215. package/src/index.js +5 -17
  216. package/src/utils/circuit-breaker.js +301 -0
  217. package/src/utils/error-boundary.js +363 -0
  218. package/src/utils/error.js +374 -0
  219. package/src/utils/event-emitter.js +20 -20
  220. package/src/utils/id.js +133 -0
  221. package/src/utils/index.js +217 -3
  222. package/src/utils/logger.js +181 -0
  223. package/src/utils/plugin-helpers.js +90 -0
  224. package/src/utils/retry.js +122 -0
  225. package/src/utils/sandbox.js +292 -0
  226. package/test/tool-registry-validation.test.js +218 -0
  227. package/test_report.md +70 -0
  228. package/website/docs/api.html +169 -107
  229. package/website/docs/configuration.html +296 -144
  230. package/website/docs/plugin-development.html +154 -85
  231. package/website/docs/project-structure.html +110 -109
  232. package/website/docs/skill-development.html +117 -61
  233. package/website/index.html +209 -205
  234. package/website/script.js +20 -17
  235. package/website/styles.css +1 -1
  236. package/plugins/ambient-agent-plugin.js +0 -1565
  237. 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().optional().describe('文件路径'),
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 = args.path || args.filePath
97
- const encoding = args.encoding || 'utf8'
98
- const lines = args.lines
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(filePath)) {
124
+ if (!fs.existsSync(pathCheck.resolved)) {
101
125
  return { success: false, error: '文件不存在' }
102
126
  }
103
- const stat = fs.statSync(filePath)
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(filePath, 'utf8')
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(filePath, encoding)
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().optional().describe('文件路径'),
134
- filePath: z.string().optional().describe('文件路径(同path)'),
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 = args.path || args.filePath
139
- const content = args.content
140
- const encoding = args.encoding || 'utf8'
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(filePath)
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(filePath, content, encoding)
147
- return { success: true, message: `文件已写入: ${filePath}`, filePath, size: content.length }
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(targetPath)) {
205
+ if (!fs.existsSync(pathCheck.resolved)) {
168
206
  return { success: false, error: '目标不存在' }
169
207
  }
170
- const stat = fs.statSync(targetPath)
208
+ const stat = fs.statSync(pathCheck.resolved)
171
209
  if (stat.isDirectory()) {
172
210
  if (recursive) {
173
- fs.rmSync(targetPath, { recursive: true, force: true })
211
+ fs.rmSync(pathCheck.resolved, { recursive: true, force: true })
174
212
  } else {
175
- fs.rmdirSync(targetPath)
213
+ fs.rmdirSync(pathCheck.resolved)
176
214
  }
177
215
  } else {
178
- fs.unlinkSync(targetPath)
216
+ fs.unlinkSync(pathCheck.resolved)
179
217
  }
180
- return { success: true, message: `已删除: ${targetPath}` }
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
- framework.registerTool({
189
- name: 'modify_file',
190
- description: '修改文件内容(替换文本)',
191
- inputSchema: z.object({
192
- path: z.string().optional().describe('文件路径'),
193
- filePath: z.string().optional().describe('文件路径(同path)'),
194
- find: z.string().describe('要查找的文本'),
195
- replace: z.string().describe('替换后的文本'),
196
- replaceAll: z.boolean().optional().describe('替换所有匹配')
197
- }),
198
- execute: async (args, framework) => {
199
- const filePath = args.path || args.filePath
200
- const find = args.find
201
- const replace = args.replace
202
- const replaceAll = args.replaceAll || false
203
- try {
204
- if (!fs.existsSync(filePath)) {
205
- return { success: false, error: '文件不存在' }
206
- }
207
- let content = fs.readFileSync(filePath, 'utf8')
208
- if (replaceAll) {
209
- content = content.split(find).join(replace)
210
- } else {
211
- content = content.replace(find, replace)
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
- fs.writeFileSync(filePath, content, 'utf8')
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
- query: z.string().optional().describe('搜索关键词'),
227
- pattern: z.string().optional().describe('搜索模式(支持正则)'),
228
- searchText: z.string().optional().describe('搜索文本'),
229
- path: z.string().optional().describe('搜索目录'),
230
- dirPath: z.string().optional().describe('搜索目录(同path)'),
231
- file: z.string().optional().describe('搜索指定文件'),
232
- fileType: z.string().optional().describe('文件类型过滤'),
233
- maxResults: z.number().optional().describe('最大结果数'),
234
- contextLines: z.number().optional().describe('匹配行的上下文行数')
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.query || args.pattern || args.searchText || ''
238
- const dirPath = args.path || args.dirPath || '.'
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 || 50
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
- const regex = new RegExp(pattern, 'gi')
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 lines = content.split('\n')
256
- for (let i = 0; i < lines.length; i++) {
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 > 5 || results.length >= maxResults) return
280
- const entries = fs.readdirSync(currentPath, { withFileTypes: true })
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
- if (relativePath.includes('node_modules') || relativePath.includes('.git') ||
286
- relativePath.includes('dist') || relativePath.includes('build')) {
287
- continue
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
- const ext = path.extname(entry.name)
294
- if (['.jpg', '.png', '.gif', '.exe', '.dll', '.zip'].includes(ext)) continue
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 lines = content.split('\n')
298
- for (let i = 0; i < lines.length; i++) {
299
- if (regex.test(lines[i])) {
300
- let matchInfo = { file: relativePath, line: i + 1, content: lines[i].substring(0, 200) }
301
- if (contextLines > 0) {
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
- regex.lastIndex = 0
314
- } catch (e) { }
517
+ } catch (e) {
518
+ // 跳过无法读取的文件
519
+ }
315
520
  }
316
521
  }
317
522
  }
523
+
318
524
  searchDir(dirPath)
319
525
  }
320
- return { success: true, pattern, results: results.slice(0, maxResults), total: results.length }
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',