foliko 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/22.txt +10 -0
  3. package/README.md +218 -0
  4. package/SPEC.md +452 -0
  5. package/cli/bin/foliko.js +12 -0
  6. package/cli/src/commands/chat.js +75 -0
  7. package/cli/src/index.js +64 -0
  8. package/cli/src/ui/chat-ui.js +272 -0
  9. package/cli/src/utils/ansi.js +40 -0
  10. package/cli/src/utils/markdown.js +296 -0
  11. package/docs/quick-reference.md +131 -0
  12. package/docs/user-manual.md +1205 -0
  13. package/examples/basic.js +110 -0
  14. package/examples/bootstrap.js +93 -0
  15. package/examples/mcp-example.js +53 -0
  16. package/examples/skill-example.js +49 -0
  17. package/examples/workflow.js +158 -0
  18. package/package.json +36 -0
  19. package/plugins/ai-plugin.js +89 -0
  20. package/plugins/audit-plugin.js +187 -0
  21. package/plugins/default-plugins.js +412 -0
  22. package/plugins/file-system-plugin.js +344 -0
  23. package/plugins/install-plugin.js +93 -0
  24. package/plugins/python-executor-plugin.js +331 -0
  25. package/plugins/rules-plugin.js +292 -0
  26. package/plugins/scheduler-plugin.js +426 -0
  27. package/plugins/session-plugin.js +343 -0
  28. package/plugins/shell-executor-plugin.js +196 -0
  29. package/plugins/storage-plugin.js +237 -0
  30. package/plugins/subagent-plugin.js +395 -0
  31. package/plugins/think-plugin.js +329 -0
  32. package/plugins/tools-plugin.js +114 -0
  33. package/skills/mcp-usage/SKILL.md +198 -0
  34. package/skills/vb-agent-dev/AGENTS.md +162 -0
  35. package/skills/vb-agent-dev/SKILL.md +370 -0
  36. package/src/capabilities/index.js +11 -0
  37. package/src/capabilities/skill-manager.js +319 -0
  38. package/src/capabilities/workflow-engine.js +401 -0
  39. package/src/core/agent-chat.js +311 -0
  40. package/src/core/agent.js +573 -0
  41. package/src/core/framework.js +255 -0
  42. package/src/core/index.js +19 -0
  43. package/src/core/plugin-base.js +205 -0
  44. package/src/core/plugin-manager.js +392 -0
  45. package/src/core/provider.js +108 -0
  46. package/src/core/tool-registry.js +134 -0
  47. package/src/core/tool-router.js +216 -0
  48. package/src/executors/executor-base.js +58 -0
  49. package/src/executors/mcp-executor.js +728 -0
  50. package/src/index.js +37 -0
  51. package/src/utils/event-emitter.js +97 -0
  52. package/test-chat.js +129 -0
  53. package/test-mcp.js +79 -0
  54. package/test-reload.js +61 -0
@@ -0,0 +1,344 @@
1
+ /**
2
+ * FileSystem 插件
3
+ * 提供文件系统操作的常用工具
4
+ */
5
+
6
+ const { Plugin } = require('../src/core/plugin-base')
7
+
8
+ class FileSystemPlugin extends Plugin {
9
+ constructor(config = {}) {
10
+ super()
11
+ this.name = 'file-system'
12
+ this.version = '1.0.0'
13
+ this.description = '文件系统工具插件'
14
+ this.priority = 5
15
+ }
16
+
17
+ install(framework) {
18
+ const { z } = require('zod')
19
+ const fs = require('fs')
20
+ const path = require('path')
21
+ const { exec } = require('child_process')
22
+
23
+ // 读取目录
24
+ framework.registerTool({
25
+ name: 'read_directory',
26
+ description: '读取目录内容,列出目录中的文件和子目录',
27
+ inputSchema: z.object({
28
+ path: z.string().optional().describe('目录路径'),
29
+ dirPath: z.string().optional().describe('目录路径(同path)'),
30
+ recursive: z.boolean().optional().describe('是否递归')
31
+ }),
32
+ execute: async (args, framework) => {
33
+ const dirPath = args.path || args.dirPath || '.'
34
+ const recursive = args.recursive || false
35
+ try {
36
+ const items = []
37
+ const readDir = (currentPath, depth = 0) => {
38
+ if (depth > 3 && recursive) return
39
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true })
40
+ for (const entry of entries) {
41
+ const fullPath = path.join(currentPath, entry.name)
42
+ const relativePath = path.relative(process.cwd(), fullPath)
43
+ if (relativePath.includes('node_modules') || relativePath.includes('.git')) {
44
+ continue
45
+ }
46
+ items.push({
47
+ name: entry.name,
48
+ path: relativePath,
49
+ type: entry.isDirectory() ? 'directory' : 'file',
50
+ size: entry.isFile() ? fs.statSync(fullPath).size : null
51
+ })
52
+ if (entry.isDirectory() && recursive && depth < 3) {
53
+ readDir(fullPath, depth + 1)
54
+ }
55
+ }
56
+ }
57
+ readDir(dirPath)
58
+ return { success: true, dirPath, items: items.slice(0, 100), total: items.length }
59
+ } catch (error) {
60
+ return { success: false, error: error.message }
61
+ }
62
+ }
63
+ })
64
+
65
+ // 创建目录
66
+ framework.registerTool({
67
+ name: 'create_directory',
68
+ description: '创建新目录',
69
+ inputSchema: z.object({
70
+ path: z.string().optional().describe('目录路径'),
71
+ dirPath: z.string().optional().describe('目录路径(同path)')
72
+ }),
73
+ execute: async (args, framework) => {
74
+ const dirPath = args.path || args.dirPath
75
+ try {
76
+ fs.mkdirSync(dirPath, { recursive: true })
77
+ return { success: true, message: `目录已创建: ${dirPath}` }
78
+ } catch (error) {
79
+ return { success: false, error: error.message }
80
+ }
81
+ }
82
+ })
83
+
84
+ // 读取文件
85
+ framework.registerTool({
86
+ name: 'read_file',
87
+ description: '读取文件内容',
88
+ inputSchema: z.object({
89
+ path: z.string().optional().describe('文件路径'),
90
+ filePath: z.string().optional().describe('文件路径(同path)'),
91
+ encoding: z.enum(['utf8', 'base64', 'binary']).optional().describe('文件编码,默认 utf8'),
92
+ lines: z.number().optional().describe('只读取前 N 行')
93
+ }),
94
+ execute: async (args, framework) => {
95
+ const filePath = args.path || args.filePath
96
+ const encoding = args.encoding || 'utf8'
97
+ const lines = args.lines
98
+ try {
99
+ if (!fs.existsSync(filePath)) {
100
+ return { success: false, error: '文件不存在' }
101
+ }
102
+ const stat = fs.statSync(filePath)
103
+ if (stat.size > 1024 * 1024) {
104
+ return { success: false, error: '文件太大,超过 1MB' }
105
+ }
106
+ let content
107
+ if (lines) {
108
+ const fileContent = fs.readFileSync(filePath, 'utf8')
109
+ const allLines = fileContent.split('\n')
110
+ content = allLines.slice(0, lines).join('\n')
111
+ } else {
112
+ content = fs.readFileSync(filePath, encoding)
113
+ }
114
+ return {
115
+ success: true,
116
+ filePath,
117
+ content,
118
+ size: stat.size,
119
+ lines: lines ? null : content.split('\n').length
120
+ }
121
+ } catch (error) {
122
+ return { success: false, error: error.message }
123
+ }
124
+ }
125
+ })
126
+
127
+ // 写入文件
128
+ framework.registerTool({
129
+ name: 'write_file',
130
+ description: '创建或写入文件内容',
131
+ inputSchema: z.object({
132
+ path: z.string().optional().describe('文件路径'),
133
+ filePath: z.string().optional().describe('文件路径(同path)'),
134
+ content: z.string().describe('文件内容')
135
+ }),
136
+ execute: async (args, framework) => {
137
+ const filePath = args.path || args.filePath
138
+ const content = args.content
139
+ const encoding = args.encoding || 'utf8'
140
+ try {
141
+ const dir = path.dirname(filePath)
142
+ if (!fs.existsSync(dir)) {
143
+ fs.mkdirSync(dir, { recursive: true })
144
+ }
145
+ fs.writeFileSync(filePath, content, encoding)
146
+ return { success: true, message: `文件已写入: ${filePath}`, filePath, size: content.length }
147
+ } catch (error) {
148
+ return { success: false, error: error.message }
149
+ }
150
+ }
151
+ })
152
+
153
+ // 删除文件
154
+ framework.registerTool({
155
+ name: 'delete_file',
156
+ description: '删除文件或目录',
157
+ inputSchema: z.object({
158
+ path: z.string().optional().describe('文件或目录路径'),
159
+ targetPath: z.string().optional().describe('文件或目录路径(同path)'),
160
+ recursive: z.boolean().optional().describe('递归删除目录')
161
+ }),
162
+ execute: async (args, framework) => {
163
+ const targetPath = args.path || args.targetPath
164
+ const recursive = args.recursive || false
165
+ try {
166
+ if (!fs.existsSync(targetPath)) {
167
+ return { success: false, error: '目标不存在' }
168
+ }
169
+ const stat = fs.statSync(targetPath)
170
+ if (stat.isDirectory()) {
171
+ if (recursive) {
172
+ fs.rmSync(targetPath, { recursive: true, force: true })
173
+ } else {
174
+ fs.rmdirSync(targetPath)
175
+ }
176
+ } else {
177
+ fs.unlinkSync(targetPath)
178
+ }
179
+ return { success: true, message: `已删除: ${targetPath}` }
180
+ } catch (error) {
181
+ return { success: false, error: error.message }
182
+ }
183
+ }
184
+ })
185
+
186
+ // 修改文件(替换文本)
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)
211
+ }
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
+ })
219
+
220
+ // 搜索文件
221
+ framework.registerTool({
222
+ name: 'search_file',
223
+ description: '在文件中搜索文本',
224
+ 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
+ fileType: z.string().optional().describe('文件类型过滤'),
231
+ maxResults: z.number().optional().describe('最大结果数')
232
+ }),
233
+ execute: async (args, framework) => {
234
+ const pattern = args.query || args.pattern || args.searchText || ''
235
+ const dirPath = args.path || args.dirPath || '.'
236
+ const fileType = args.fileType
237
+ const maxResults = args.maxResults || 50
238
+ try {
239
+ const results = []
240
+ const regex = new RegExp(pattern, 'gi')
241
+ const searchDir = (currentPath, depth = 0) => {
242
+ if (depth > 5 || results.length >= maxResults) return
243
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true })
244
+ for (const entry of entries) {
245
+ if (results.length >= maxResults) break
246
+ const fullPath = path.join(currentPath, entry.name)
247
+ const relativePath = path.relative(process.cwd(), fullPath)
248
+ if (relativePath.includes('node_modules') || relativePath.includes('.git') ||
249
+ relativePath.includes('dist') || relativePath.includes('build')) {
250
+ continue
251
+ }
252
+ if (entry.isDirectory()) {
253
+ searchDir(fullPath, depth + 1)
254
+ } else if (entry.isFile()) {
255
+ if (fileType && !entry.name.endsWith(fileType)) continue
256
+ const ext = path.extname(entry.name)
257
+ if (['.jpg', '.png', '.gif', '.exe', '.dll', '.zip'].includes(ext)) continue
258
+ try {
259
+ const content = fs.readFileSync(fullPath, 'utf8')
260
+ const lines = content.split('\n')
261
+ for (let i = 0; i < lines.length; i++) {
262
+ if (regex.test(lines[i])) {
263
+ results.push({ file: relativePath, line: i + 1, content: lines[i].substring(0, 100) })
264
+ if (results.length >= maxResults) break
265
+ }
266
+ }
267
+ regex.lastIndex = 0
268
+ } catch (e) { }
269
+ }
270
+ }
271
+ }
272
+ searchDir(dirPath)
273
+ return { success: true, pattern, results: results.slice(0, maxResults), total: results.length }
274
+ } catch (error) {
275
+ return { success: false, error: error.message }
276
+ }
277
+ }
278
+ })
279
+
280
+ // 执行命令
281
+ framework.registerTool({
282
+ name: 'execute_command',
283
+ description: '执行终端命令',
284
+ inputSchema: z.object({
285
+ cmd: z.string().optional().describe('要执行的命令'),
286
+ command: z.string().optional().describe('要执行的命令(同cmd)'),
287
+ run: z.string().optional().describe('要执行的命令(同cmd)'),
288
+ cwd: z.string().optional().describe('工作目录'),
289
+ timeout: z.number().optional().describe('超时时间(ms)')
290
+ }),
291
+ execute: async (args, framework) => {
292
+ const command = args.cmd || args.command || args.run
293
+ const cwd = args.cwd || process.cwd()
294
+ const timeout = args.timeout || 30000
295
+ return new Promise((resolve) => {
296
+ const startTime = Date.now()
297
+ exec(command, { cwd, timeout }, (error, stdout, stderr) => {
298
+ const duration = Date.now() - startTime
299
+ if (error) {
300
+ resolve({ success: false, command, error: error.message, stderr, duration })
301
+ } else {
302
+ resolve({
303
+ success: true,
304
+ command,
305
+ stdout: stdout.substring(0, 10000),
306
+ stderr: stderr.substring(0, 1000),
307
+ duration
308
+ })
309
+ }
310
+ })
311
+ })
312
+ }
313
+ })
314
+
315
+ // 获取北京时间
316
+ framework.registerTool({
317
+ name: 'get_time',
318
+ description: '获取当前的时间,需要获取日期和时间的时候需要调用该接口',
319
+ inputSchema: z.object({}),
320
+ execute: async (args, framework) => {
321
+ const now = new Date()
322
+ const beijingTime = new Date(now.getTime() + (8 * 60 * 60 * 1000))
323
+ return {
324
+ success: true,
325
+ beijingTime: beijingTime.toISOString().replace('T', ' ').substring(0, 19),
326
+ timestamp: now.getTime(),
327
+ timezone: 'Asia/Shanghai (UTC+8)',
328
+ formatted: {
329
+ year: beijingTime.getUTCFullYear(),
330
+ month: String(beijingTime.getUTCMonth() + 1).padStart(2, '0'),
331
+ day: String(beijingTime.getUTCDate()).padStart(2, '0'),
332
+ hour: String(beijingTime.getUTCHours()).padStart(2, '0'),
333
+ minute: String(beijingTime.getUTCMinutes()).padStart(2, '0'),
334
+ second: String(beijingTime.getUTCSeconds()).padStart(2, '0')
335
+ }
336
+ }
337
+ }
338
+ })
339
+
340
+ return this
341
+ }
342
+ }
343
+
344
+ module.exports = { FileSystemPlugin }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * InstallPlugin - npm 包安装工具
3
+ * 自动安装缺少的 node 模块到 .agent 目录
4
+ */
5
+
6
+ const { execSync } = require('child_process')
7
+ const fs = require('fs')
8
+ const path = require('path')
9
+ const { Plugin } = require('../src/core/plugin-base')
10
+ const { z } = require('zod')
11
+
12
+ class InstallPlugin extends Plugin {
13
+ constructor(config = {}) {
14
+ super()
15
+ this.name = 'install'
16
+ this.version = '1.0.0'
17
+ this.description = '自动安装 npm 包到 .agent 目录'
18
+
19
+ this._agentDir = config.agentDir || '.agent'
20
+ this._nodeModulesDir = null
21
+ }
22
+
23
+ install(framework) {
24
+ this._framework = framework
25
+ return this
26
+ }
27
+
28
+ start(framework) {
29
+ // 确保 node_modules 目录存在
30
+ this._nodeModulesDir = path.join(this._agentDir, 'node_modules')
31
+ if (!fs.existsSync(this._nodeModulesDir)) {
32
+ fs.mkdirSync(this._nodeModulesDir, { recursive: true })
33
+ }
34
+
35
+ // 注册 install 工具
36
+ framework.registerTool({
37
+ name: 'install',
38
+ description: '安装 npm 包到 .agent 目录,用于解决插件依赖缺失问题',
39
+ inputSchema: z.object({
40
+ package: z.string().describe('包名,如 lodash 或 lodash@4.17.21')
41
+ }),
42
+ execute: async (args) => {
43
+ const { package: packageName } = args
44
+ return this._installPackage(packageName)
45
+ }
46
+ })
47
+
48
+ return this
49
+ }
50
+
51
+ _installPackage(packageName) {
52
+ try {
53
+ console.log(`[InstallPlugin] Installing ${packageName} to ${this._nodeModulesDir}...`)
54
+
55
+ // 使用 npm install 安装到指定目录
56
+ // --prefix 确保安装到 .agent/node_modules
57
+ execSync(`npm install ${packageName} --prefix "${this._agentDir}"`, {
58
+ stdio: 'inherit',
59
+ cwd: this._agentDir
60
+ })
61
+
62
+ console.log(`[InstallPlugin] Successfully installed ${packageName}`)
63
+
64
+ return {
65
+ success: true,
66
+ message: `Successfully installed ${packageName}`
67
+ }
68
+ } catch (err) {
69
+ return {
70
+ success: false,
71
+ error: `Failed to install ${packageName}: ${err.message}`
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * 获取 node_modules 路径,供其他插件使用
78
+ */
79
+ getNodeModulesDir() {
80
+ return this._nodeModulesDir
81
+ }
82
+
83
+ /**
84
+ * 添加到 module.paths(让 require 能找到)
85
+ */
86
+ addToModulePaths() {
87
+ if (this._nodeModulesDir && !module.paths.includes(this._nodeModulesDir)) {
88
+ module.paths.unshift(this._nodeModulesDir)
89
+ }
90
+ }
91
+ }
92
+
93
+ module.exports = { InstallPlugin }