foliko 1.0.74 → 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 -643
  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 +456 -106
  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 -249
  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',
@@ -95,25 +113,32 @@ class FileSystemPlugin extends Plugin {
95
113
  if (!filePath) {
96
114
  return { success: false, error: 'path 是必填参数' }
97
115
  }
116
+
117
+ // 路径安全验证
118
+ const pathCheck = validatePath(filePath)
119
+ if (!pathCheck.valid) {
120
+ return { success: false, error: pathCheck.error }
121
+ }
122
+
98
123
  try {
99
- if (!fs.existsSync(filePath)) {
124
+ if (!fs.existsSync(pathCheck.resolved)) {
100
125
  return { success: false, error: '文件不存在' }
101
126
  }
102
- const stat = fs.statSync(filePath)
127
+ const stat = fs.statSync(pathCheck.resolved)
103
128
  if (stat.size > 1024 * 1024) {
104
129
  return { success: false, error: '文件太大,超过 1MB' }
105
130
  }
106
131
  let content
107
132
  if (lines) {
108
- const fileContent = fs.readFileSync(filePath, 'utf8')
133
+ const fileContent = fs.readFileSync(pathCheck.resolved, 'utf8')
109
134
  const allLines = fileContent.split('\n')
110
135
  content = allLines.slice(0, lines).join('\n')
111
136
  } else {
112
- content = fs.readFileSync(filePath, 'utf8')
137
+ content = fs.readFileSync(pathCheck.resolved, 'utf8')
113
138
  }
114
139
  return {
115
140
  success: true,
116
- filePath,
141
+ filePath: pathCheck.resolved,
117
142
  content,
118
143
  size: stat.size,
119
144
  lines: lines ? null : content.split('\n').length
@@ -137,13 +162,20 @@ class FileSystemPlugin extends Plugin {
137
162
  if (!filePath || !content) {
138
163
  return { success: false, error: 'path 和 content 都是必填参数' }
139
164
  }
165
+
166
+ // 路径安全验证
167
+ const pathCheck = validatePath(filePath)
168
+ if (!pathCheck.valid) {
169
+ return { success: false, error: pathCheck.error }
170
+ }
171
+
140
172
  try {
141
- const dir = path.dirname(filePath)
173
+ const dir = path.dirname(pathCheck.resolved)
142
174
  if (!fs.existsSync(dir)) {
143
175
  fs.mkdirSync(dir, { recursive: true })
144
176
  }
145
- fs.writeFileSync(filePath, content, 'utf8')
146
- 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 }
147
179
  } catch (error) {
148
180
  return { success: false, error: error.message }
149
181
  }
@@ -162,21 +194,28 @@ class FileSystemPlugin extends Plugin {
162
194
  execute: async (args, framework) => {
163
195
  const targetPath = args.path || args.targetPath
164
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
+
165
204
  try {
166
- if (!fs.existsSync(targetPath)) {
205
+ if (!fs.existsSync(pathCheck.resolved)) {
167
206
  return { success: false, error: '目标不存在' }
168
207
  }
169
- const stat = fs.statSync(targetPath)
208
+ const stat = fs.statSync(pathCheck.resolved)
170
209
  if (stat.isDirectory()) {
171
210
  if (recursive) {
172
- fs.rmSync(targetPath, { recursive: true, force: true })
211
+ fs.rmSync(pathCheck.resolved, { recursive: true, force: true })
173
212
  } else {
174
- fs.rmdirSync(targetPath)
213
+ fs.rmdirSync(pathCheck.resolved)
175
214
  }
176
215
  } else {
177
- fs.unlinkSync(targetPath)
216
+ fs.unlinkSync(pathCheck.resolved)
178
217
  }
179
- return { success: true, message: `已删除: ${targetPath}` }
218
+ return { success: true, message: `已删除: ${pathCheck.resolved}` }
180
219
  } catch (error) {
181
220
  return { success: false, error: error.message }
182
221
  }
@@ -184,64 +223,236 @@ class FileSystemPlugin extends Plugin {
184
223
  })
185
224
 
186
225
  // 修改文件(替换文本)
187
- framework.registerTool({
188
- name: 'modify_file',
189
- description: '修改文件内容(替换文本)',
190
- inputSchema: z.object({
191
- path: z.string().optional().describe('文件路径'),
192
- filePath: z.string().optional().describe('文件路径(同path)'),
193
- find: z.string().describe('要查找的文本'),
194
- replace: z.string().describe('替换后的文本'),
195
- replaceAll: z.boolean().optional().describe('替换所有匹配')
196
- }),
197
- execute: async (args, framework) => {
198
- const filePath = args.path || args.filePath
199
- const find = args.find
200
- const replace = args.replace
201
- const replaceAll = args.replaceAll || false
202
- try {
203
- if (!fs.existsSync(filePath)) {
204
- return { success: false, error: '文件不存在' }
205
- }
206
- let content = fs.readFileSync(filePath, 'utf8')
207
- if (replaceAll) {
208
- content = content.split(find).join(replace)
209
- } else {
210
- 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
+ }
211
355
  }
212
- fs.writeFileSync(filePath, content, 'utf8')
213
- return { success: true, message: `文件已修改: ${filePath}`, filePath }
214
- } catch (error) {
215
- return { success: false, error: error.message }
216
- }
217
- }
218
- })
356
+ })
219
357
 
220
358
  // 搜索文件
221
359
  framework.registerTool({
222
360
  name: 'search_file',
223
- description: '在文件或目录中搜索文本',
361
+ description: '在文件或目录中搜索文本,支持精确匹配和正则表达式',
224
362
  inputSchema: z.object({
225
- query: z.string().optional().describe('搜索关键词'),
226
- pattern: z.string().optional().describe('搜索模式(支持正则)'),
227
- searchText: z.string().optional().describe('搜索文本'),
228
- path: z.string().optional().describe('搜索目录'),
229
- dirPath: z.string().optional().describe('搜索目录(同path)'),
230
- file: z.string().optional().describe('搜索指定文件'),
231
- fileType: z.string().optional().describe('文件类型过滤'),
232
- maxResults: z.number().optional().describe('最大结果数'),
233
- 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"]')
234
373
  }),
235
374
  execute: async (args, framework) => {
236
- const pattern = args.query || args.pattern || args.searchText || ''
237
- const dirPath = args.path || args.dirPath || '.'
375
+ const pattern = args.pattern
376
+ const dirPath = args.path || process.cwd()
238
377
  const targetFile = args.file
239
378
  const fileType = args.fileType
240
- const maxResults = args.maxResults || 50
379
+ const maxResults = args.maxResults || 100
380
+ const maxResultsPerFile = args.maxResultsPerFile || 50
241
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
+
242
390
  try {
243
391
  const results = []
244
- 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
+ }
245
456
 
246
457
  // 搜索单个文件
247
458
  if (targetFile) {
@@ -249,74 +460,85 @@ class FileSystemPlugin extends Plugin {
249
460
  if (!fs.existsSync(fullPath)) {
250
461
  return { success: false, error: `文件不存在: ${targetFile}` }
251
462
  }
463
+
464
+ const ext = path.extname(fullPath).toLowerCase()
465
+ if (binaryExtensions.includes(ext)) {
466
+ return { success: false, error: `不支持搜索二进制文件: ${ext}` }
467
+ }
468
+
252
469
  try {
253
470
  const content = fs.readFileSync(fullPath, 'utf8')
254
- const lines = content.split('\n')
255
- for (let i = 0; i < lines.length; i++) {
256
- if (regex.test(lines[i])) {
257
- let matchInfo = { file: targetFile, line: i + 1, content: lines[i].substring(0, 200) }
258
- // 添加上下文行
259
- if (contextLines > 0) {
260
- const start = Math.max(0, i - contextLines)
261
- const end = Math.min(lines.length, i + contextLines + 1)
262
- matchInfo.context = lines.slice(start, end).map((l, idx) => ({
263
- line: start + idx + 1,
264
- content: l
265
- }))
266
- }
267
- results.push(matchInfo)
268
- if (results.length >= maxResults) break
269
- }
270
- }
271
- regex.lastIndex = 0
471
+ const fileMatches = searchInContent(content, targetFile)
472
+ results.push(...fileMatches)
272
473
  } catch (e) {
273
474
  return { success: false, error: `读取文件失败: ${e.message}` }
274
475
  }
275
476
  } else {
276
477
  // 搜索目录
277
478
  const searchDir = (currentPath, depth = 0) => {
278
- if (depth > 5 || results.length >= maxResults) return
279
- 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
+
280
488
  for (const entry of entries) {
281
489
  if (results.length >= maxResults) break
490
+
282
491
  const fullPath = path.join(currentPath, entry.name)
283
492
  const relativePath = path.relative(process.cwd(), fullPath)
284
- if (relativePath.includes('node_modules') || relativePath.includes('.git') ||
285
- relativePath.includes('dist') || relativePath.includes('build')) {
286
- continue
287
- }
493
+
494
+ // 检查是否在排除目录中
495
+ const shouldExclude = excludeDirs.some(exclude =>
496
+ relativePath.includes(exclude)
497
+ )
498
+ if (shouldExclude) continue
499
+
288
500
  if (entry.isDirectory()) {
289
501
  searchDir(fullPath, depth + 1)
290
502
  } else if (entry.isFile()) {
503
+ // 文件类型过滤
291
504
  if (fileType && !entry.name.endsWith(fileType)) continue
292
- const ext = path.extname(entry.name)
293
- 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
+
294
509
  try {
295
510
  const content = fs.readFileSync(fullPath, 'utf8')
296
- const lines = content.split('\n')
297
- for (let i = 0; i < lines.length; i++) {
298
- if (regex.test(lines[i])) {
299
- let matchInfo = { file: relativePath, line: i + 1, content: lines[i].substring(0, 200) }
300
- if (contextLines > 0) {
301
- const start = Math.max(0, i - contextLines)
302
- const end = Math.min(lines.length, i + contextLines + 1)
303
- matchInfo.context = lines.slice(start, end).map((l, idx) => ({
304
- line: start + idx + 1,
305
- content: l
306
- }))
307
- }
308
- results.push(matchInfo)
309
- if (results.length >= maxResults) break
310
- }
511
+ const fileMatches = searchInContent(content, relativePath)
512
+
513
+ for (const match of fileMatches) {
514
+ if (results.length >= maxResults) break
515
+ results.push(match)
311
516
  }
312
- regex.lastIndex = 0
313
- } catch (e) { }
517
+ } catch (e) {
518
+ // 跳过无法读取的文件
519
+ }
314
520
  }
315
521
  }
316
522
  }
523
+
317
524
  searchDir(dirPath)
318
525
  }
319
- 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
+ }
320
542
  } catch (error) {
321
543
  return { success: false, error: error.message }
322
544
  }
@@ -337,13 +559,45 @@ class FileSystemPlugin extends Plugin {
337
559
  execute: async (args, framework) => {
338
560
  const command = args.cmd || args.command || args.run
339
561
  const cwd = args.cwd || process.cwd()
340
- 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
+
341
595
  return new Promise((resolve) => {
342
596
  const startTime = Date.now()
343
- exec(command, { cwd, timeout }, (error, stdout, stderr) => {
597
+ exec(command, { cwd: resolvedCwd, timeout, shell: true }, (error, stdout, stderr) => {
344
598
  const duration = Date.now() - startTime
345
599
  if (error) {
346
- resolve({ success: false, command, error: error.message, stderr, duration })
600
+ resolve({ success: false, command, error: error.message, stderr: stderr.substring(0, 1000), duration })
347
601
  } else {
348
602
  resolve({
349
603
  success: true,
@@ -358,6 +612,102 @@ class FileSystemPlugin extends Plugin {
358
612
  }
359
613
  })
360
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
+
361
711
  // 获取北京时间
362
712
  framework.registerTool({
363
713
  name: 'get_time',