foliko 1.0.74 → 1.0.76

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 (238) 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 +11 -1
  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 +11 -11
  128. package/cli/src/commands/chat.js +143 -141
  129. package/cli/src/commands/list.js +93 -90
  130. package/cli/src/index.js +75 -75
  131. package/cli/src/ui/chat-ui.js +201 -199
  132. package/cli/src/utils/ansi.js +40 -40
  133. package/cli/src/utils/markdown.js +292 -296
  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 +194 -196
  140. package/examples/basic.js +50 -45
  141. package/examples/bootstrap.js +121 -112
  142. package/examples/mcp-example.js +19 -16
  143. package/examples/skill-example.js +20 -20
  144. package/examples/test-chat.js +137 -135
  145. package/examples/test-mcp.js +85 -79
  146. package/examples/test-reload.js +59 -61
  147. package/examples/test-telegram.js +50 -50
  148. package/examples/test-tg-bot.js +45 -42
  149. package/examples/test-tg-simple.js +47 -46
  150. package/examples/test-tg.js +62 -62
  151. package/examples/test-think.js +43 -37
  152. package/examples/test-web-plugin.js +103 -98
  153. package/examples/test-weixin-feishu.js +103 -100
  154. package/examples/workflow.js +158 -158
  155. package/package.json +37 -3
  156. package/plugins/ai-plugin.js +102 -100
  157. package/plugins/ambient-agent/EventWatcher.js +113 -0
  158. package/plugins/ambient-agent/ExplorerLoop.js +640 -0
  159. package/plugins/ambient-agent/GoalManager.js +197 -0
  160. package/plugins/ambient-agent/Reflector.js +95 -0
  161. package/plugins/ambient-agent/StateStore.js +90 -0
  162. package/plugins/ambient-agent/constants.js +101 -0
  163. package/plugins/ambient-agent/index.js +579 -0
  164. package/plugins/audit-plugin.js +187 -187
  165. package/plugins/default-plugins.js +662 -649
  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 +481 -477
  174. package/plugins/file-system-plugin.js +826 -476
  175. package/plugins/install-plugin.js +199 -197
  176. package/plugins/python-executor-plugin.js +367 -365
  177. package/plugins/python-plugin-loader.js +481 -479
  178. package/plugins/rules-plugin.js +294 -292
  179. package/plugins/scheduler-plugin.js +691 -689
  180. package/plugins/session-plugin.js +369 -367
  181. package/plugins/shell-executor-plugin.js +197 -197
  182. package/plugins/storage-plugin.js +240 -238
  183. package/plugins/subagent-plugin.js +845 -785
  184. package/plugins/telegram-plugin.js +482 -475
  185. package/plugins/think-plugin.js +345 -343
  186. package/plugins/tools-plugin.js +196 -194
  187. package/plugins/web-plugin.js +606 -604
  188. package/plugins/weixin-plugin.js +545 -538
  189. package/reports/system-health-report-20260401.md +79 -0
  190. package/skills/ambient-agent/SKILL.md +49 -39
  191. package/skills/foliko-dev/AGENTS.md +64 -61
  192. package/skills/foliko-dev/SKILL.md +125 -119
  193. package/skills/mcp-usage/SKILL.md +19 -17
  194. package/skills/python-plugin-dev/SKILL.md +16 -15
  195. package/skills/skill-guide/SKILL.md +12 -12
  196. package/skills/subagent-guide/SKILL.md +237 -0
  197. package/skills/workflow-guide/SKILL.md +90 -45
  198. package/skills/workflow-troubleshooting/DEBUGGING.md +36 -21
  199. package/skills/workflow-troubleshooting/SKILL.md +156 -79
  200. package/src/capabilities/index.js +11 -11
  201. package/src/capabilities/skill-manager.js +609 -595
  202. package/src/capabilities/workflow-engine.js +1109 -1195
  203. package/src/core/agent-chat.js +882 -735
  204. package/src/core/agent.js +892 -688
  205. package/src/core/framework.js +465 -431
  206. package/src/core/index.js +19 -19
  207. package/src/core/plugin-base.js +219 -219
  208. package/src/core/plugin-manager.js +863 -767
  209. package/src/core/provider.js +114 -111
  210. package/src/core/sub-agent-config.js +264 -0
  211. package/src/core/system-prompt-builder.js +120 -0
  212. package/src/core/tool-registry.js +517 -134
  213. package/src/core/tool-router.js +297 -216
  214. package/src/executors/executor-base.js +12 -12
  215. package/src/executors/mcp-executor.js +741 -729
  216. package/src/index.js +25 -37
  217. package/src/utils/circuit-breaker.js +301 -0
  218. package/src/utils/error-boundary.js +363 -0
  219. package/src/utils/error.js +374 -0
  220. package/src/utils/event-emitter.js +97 -97
  221. package/src/utils/id.js +133 -0
  222. package/src/utils/index.js +217 -3
  223. package/src/utils/logger.js +181 -0
  224. package/src/utils/plugin-helpers.js +90 -0
  225. package/src/utils/retry.js +122 -0
  226. package/src/utils/sandbox.js +292 -0
  227. package/test/tool-registry-validation.test.js +218 -0
  228. package/test_report.md +70 -0
  229. package/website/docs/api.html +169 -107
  230. package/website/docs/configuration.html +296 -144
  231. package/website/docs/plugin-development.html +154 -85
  232. package/website/docs/project-structure.html +110 -109
  233. package/website/docs/skill-development.html +117 -61
  234. package/website/index.html +209 -205
  235. package/website/script.js +136 -133
  236. package/website/styles.css +1 -1
  237. package/plugins/ambient-agent-plugin.js +0 -1565
  238. package/plugins/email.js +0 -1142
@@ -1,595 +1,609 @@
1
- /**
2
- * SkillManager 技能管理器
3
- * 加载和管理 Skill
4
- */
5
-
6
- const fs = require('fs')
7
- const path = require('path')
8
- const { Plugin } = require('../core/plugin-base')
9
- const { z } = require('zod')
10
-
11
- /**
12
- * 验证 skill 名称
13
- * 1-64字符,字母数字、下划线和连字符,不能以连字符或下划线开头或结尾
14
- */
15
- function isValidSkillName(name) {
16
- if (!name || name.length < 1 || name.length > 64) return false
17
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) return false
18
- if (name.startsWith('-') || name.startsWith('_') || name.endsWith('-') || name.endsWith('_')) return false
19
- if (/--/.test(name) || /__/.test(name)) return false
20
- return true
21
- }
22
-
23
- /**
24
- * 解析 YAML frontmatter
25
- */
26
- function parseFrontmatter(content) {
27
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
28
- if (!match) return null
29
-
30
- const frontmatter = {}
31
- const lines = match[1].split('\n')
32
- let currentKey = null
33
-
34
- for (const line of lines) {
35
- // 处理 metadata 嵌套
36
- if (currentKey === 'metadata') {
37
- if (line.match(/^ {2}[a-zA-Z]/)) {
38
- const colonIndex = line.indexOf(':')
39
- if (colonIndex > 0) {
40
- const key = line.substring(2, colonIndex).trim()
41
- const value = line.substring(colonIndex + 1).trim().replace(/^["']|["']$/g, '')
42
- frontmatter[currentKey][key] = value
43
- continue
44
- }
45
- } else if (line.match(/^[a-zA-Z]/)) {
46
- currentKey = null
47
- continue
48
- }
49
- }
50
-
51
- const colonIndex = line.indexOf(':')
52
- if (colonIndex === -1) continue
53
-
54
- const key = line.substring(0, colonIndex).trim()
55
- let value = line.substring(colonIndex + 1).trim()
56
-
57
- // 移除引号
58
- if ((value.startsWith('"') && value.endsWith('"')) ||
59
- (value.startsWith("'") && value.endsWith("'"))) {
60
- value = value.slice(1, -1)
61
- }
62
-
63
- if (key === 'metadata') {
64
- currentKey = 'metadata'
65
- frontmatter.metadata = {}
66
- } else if (key === 'allowed-tools') {
67
- frontmatter[key] = value.split(',').map(t => t.trim().replace(/^["']|["']$/g, '')).filter(t => t)
68
- } else {
69
- frontmatter[key] = value
70
- }
71
- }
72
-
73
- return frontmatter
74
- }
75
-
76
- /**
77
- * 移除 frontmatter,获取正文
78
- */
79
- function stripFrontmatter(content) {
80
- const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/)
81
- return match ? content.slice(match[0].length).trim() : content.trim()
82
- }
83
-
84
- /**
85
- * Skill 元数据
86
- */
87
- class SkillMetadata {
88
- constructor(data) {
89
- this.name = data.name || ''
90
- this.description = data.description || ''
91
- this.license = data.license || null
92
- this.compatibility = data.compatibility || null
93
- this.metadata = data.metadata || {}
94
- this.allowedTools = data.allowedTools || []
95
- }
96
- }
97
-
98
- /**
99
- * Skill 类
100
- */
101
- class Skill {
102
- constructor(metadata, content) {
103
- this.metadata = metadata
104
- this.content = content
105
- this._framework = null
106
- }
107
-
108
- /**
109
- * 安装 skill
110
- */
111
- install(framework) {
112
- this._framework = framework
113
- return this
114
- }
115
-
116
- /**
117
- * 获取工具定义
118
- */
119
- getTools() {
120
- return []
121
- }
122
- }
123
-
124
- /**
125
- * SkillManager 插件
126
- */
127
- class SkillManagerPlugin extends Plugin {
128
- constructor(config = {}) {
129
- super()
130
- this.name = 'skill-manager'
131
- this.version = '1.0.0'
132
- this.description = '技能管理器,加载和管理 Skill'
133
- this.priority = 5
134
- this.system = true
135
-
136
- this._framework = null
137
- this._skillsDirs = Array.isArray(config.skillsDirs) ? config.skillsDirs : [config.skillsDir || '.agent/skills', 'skills']
138
- this._skills = new Map()
139
- this._loaded = false
140
- }
141
-
142
- install(framework) {
143
- this._framework = framework
144
- return this
145
- }
146
-
147
- start(framework) {
148
- this._loadAllSkills()
149
-
150
- // 注册 loadSkill 工具
151
- framework.registerTool({
152
- name: 'loadSkill',
153
- description: '加载指定技能,获取技能的使用指南和内容',
154
- inputSchema: z.object({
155
- skill: z.string().describe('技能名称')
156
- }),
157
- execute: async (args) => {
158
- const skillName = args.skill
159
- const skill = this.getSkill(skillName)
160
- if (!skill) {
161
- return { success: false, error: `Skill '${skillName}' not found` }
162
- }
163
- return {
164
- success: true,
165
- name: skill.name,
166
- description: skill.metadata?.description || '',
167
- content: skill.content
168
- }
169
- }
170
- })
171
-
172
- // 注册 reloadSkills 工具
173
- framework.registerTool({
174
- name: 'reloadSkills',
175
- description: '重载所有技能,当用户添加新技能或修改技能后调用此工具',
176
- inputSchema: z.object({}),
177
- execute: async () => {
178
- this.reload(this._framework)
179
- return {
180
- success: true,
181
- message: `Skills reloaded. Total: ${this._skills.size}`,
182
- skills: Array.from(this._skills.keys())
183
- }
184
- }
185
- })
186
-
187
- // 注册 loadReference 工具(按需加载 skill 的附加文档)
188
- framework.registerTool({
189
- name: 'loadReference',
190
- description: '加载指定技能的附加参考文档(references 目录下的文件)',
191
- inputSchema: z.object({
192
- skill: z.string().describe('技能名称'),
193
- reference: z.string().describe('参考文档名称(不含 .md 后缀)'),
194
- list: z.boolean().optional().describe('如果为 true,列出该技能的所有可用的 reference 文件')
195
- }),
196
- execute: async (args) => {
197
- if (args.list) {
198
- // 列出该技能的所有 reference
199
- const refs = this.listReferences(args.skill)
200
- return {
201
- success: true,
202
- skill: args.skill,
203
- references: refs
204
- }
205
- }
206
-
207
- const content = this.loadReference(args.skill, args.reference)
208
- if (content === null) {
209
- return {
210
- success: false,
211
- error: `Reference '${args.reference}' not found in skill '${args.skill}'`
212
- }
213
- }
214
- return {
215
- success: true,
216
- skill: args.skill,
217
- reference: args.reference,
218
- content
219
- }
220
- }
221
- })
222
-
223
- // 注册 listScripts 工具(列出 skill 的脚本)
224
- framework.registerTool({
225
- name: 'listScripts',
226
- description: '列出指定技能下的所有可执行脚本',
227
- inputSchema: z.object({
228
- skill: z.string().describe('技能名称')
229
- }),
230
- execute: async (args) => {
231
- const scripts = this.listScripts(args.skill)
232
- return {
233
- success: true,
234
- skill: args.skill,
235
- scripts
236
- }
237
- }
238
- })
239
-
240
- // 注册 loadScript 工具(读取脚本内容)
241
- framework.registerTool({
242
- name: 'loadScript',
243
- description: '读取指定技能下脚本文件的内容',
244
- inputSchema: z.object({
245
- skill: z.string().describe('技能名称'),
246
- script: z.string().describe('脚本名称(包含扩展名)')
247
- }),
248
- execute: async (args) => {
249
- const content = this.loadScript(args.skill, args.script)
250
- if (content === null) {
251
- return {
252
- success: false,
253
- error: `Script '${args.script}' not found in skill '${args.skill}'`
254
- }
255
- }
256
- return {
257
- success: true,
258
- skill: args.skill,
259
- script: args.script,
260
- content
261
- }
262
- }
263
- })
264
-
265
- return this
266
- }
267
-
268
- /**
269
- * 加载所有 skills
270
- */
271
- _loadAllSkills() {
272
- if (this._loaded) return
273
-
274
- let totalLoaded = 0
275
-
276
- for (const skillsDir of this._skillsDirs) {
277
- const resolvedDir = path.resolve(process.cwd(), skillsDir)
278
-
279
- if (!fs.existsSync(resolvedDir)) {
280
- continue
281
- }
282
-
283
- try {
284
- const entries = fs.readdirSync(resolvedDir, { withFileTypes: true })
285
-
286
- for (const entry of entries) {
287
- if (!entry.isDirectory()) continue
288
-
289
- const skillPath = path.join(resolvedDir, entry.name)
290
-
291
- if (!isValidSkillName(entry.name)) {
292
- continue
293
- }
294
-
295
- // 跳过已加载的 skill
296
- if (this._skills.has(entry.name)) {
297
- continue
298
- }
299
-
300
- try {
301
- this._loadSkill(entry.name, skillPath)
302
- totalLoaded++
303
- } catch (err) {
304
- console.error(`[SkillManager] Failed to load skill '${entry.name}':`, err.message)
305
- }
306
- }
307
- } catch (err) {
308
- console.error('[SkillManager] Failed to load skills from:', skillsDir, err.message)
309
- }
310
- }
311
-
312
- console.log(`[SkillManager] Loaded ${this._skills.size} skills`)
313
- this._loaded = true
314
- }
315
-
316
- /**
317
- * 递归查找 SKILL.md AGENTS.md(支持多层嵌套目录)
318
- * @param {string} dir - 要搜索的目录
319
- * @returns {string|null} 找到的 markdown 文件路径
320
- */
321
- _findSkillMarkdown(dir) {
322
- if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
323
- return null
324
- }
325
-
326
- const entries = fs.readdirSync(dir, { withFileTypes: true })
327
-
328
- // 第一遍:优先查找 SKILL.MD
329
- for (const entry of entries) {
330
- if (entry.isFile() && entry.name.toUpperCase() === 'SKILL.MD') {
331
- return path.join(dir, entry.name)
332
- }
333
- }
334
-
335
- // 第二遍:查找 AGENTS.MD
336
- for (const entry of entries) {
337
- if (entry.isFile() && entry.name.toUpperCase() === 'AGENTS.MD') {
338
- return path.join(dir, entry.name)
339
- }
340
- }
341
-
342
- // 如果没找到,递归搜索子目录(排除 node_modules)
343
- for (const entry of entries) {
344
- if (entry.isDirectory() && entry.name !== 'node_modules') {
345
- const subPath = path.join(dir, entry.name)
346
- const found = this._findSkillMarkdown(subPath)
347
- if (found) return found
348
- }
349
- }
350
-
351
- return null
352
- }
353
-
354
- /**
355
- * 加载单个 skill
356
- */
357
- _loadSkill(name, skillPath) {
358
- // 查找 markdown 文件,优先加载 SKILL.md 或 AGENTS.md(支持多层嵌套)
359
- const skillMdPath = this._findSkillMarkdown(skillPath)
360
- if (!skillMdPath) {
361
- throw new Error('No markdown file found')
362
- }
363
-
364
- const mainFile = skillMdPath
365
- const content = fs.readFileSync(mainFile, 'utf-8')
366
- const frontmatter = parseFrontmatter(content)
367
-
368
- if (!frontmatter) {
369
- throw new Error('No valid frontmatter found')
370
- }
371
-
372
- const metadata = new SkillMetadata({
373
- name: frontmatter.name || name,
374
- description: frontmatter.description || '',
375
- license: frontmatter.license,
376
- compatibility: frontmatter.compatibility,
377
- metadata: frontmatter.metadata || {},
378
- allowedTools: frontmatter['allowed-tools'] || []
379
- })
380
-
381
- const skill = new Skill(metadata, stripFrontmatter(content))
382
- skill.install(this._framework)
383
-
384
- // 扫描 references 子目录(按需加载的附加文档)
385
- const references = this._scanReferences(skillPath)
386
-
387
- // 扫描 scripts 子目录(可执行脚本)
388
- const scripts = this._scanScripts(skillPath)
389
-
390
- this._skills.set(name, {
391
- name,
392
- metadata,
393
- content: skill.content,
394
- instance: skill,
395
- path: skillPath,
396
- references,
397
- scripts
398
- })
399
-
400
- const refsInfo = references.size > 0 ? `, ${references.size} refs` : ''
401
- const scriptsInfo = scripts.size > 0 ? `, ${scripts.size} scripts` : ''
402
- //console.log(`[SkillManager] Loaded skill: ${name}${refsInfo}${scriptsInfo}`)
403
- }
404
-
405
- /**
406
- * 扫描 references 子目录,按需加载
407
- * @param {string} skillPath - skill 目录路径
408
- * @returns {Map} references 文件映射 { filename: { path, content } }
409
- */
410
- _scanReferences(skillPath) {
411
- const references = new Map()
412
- const refsDir = path.join(skillPath, 'references')
413
-
414
- if (!fs.existsSync(refsDir) || !fs.statSync(refsDir).isDirectory()) {
415
- return references
416
- }
417
-
418
- try {
419
- const entries = fs.readdirSync(refsDir, { withFileTypes: true })
420
- for (const entry of entries) {
421
- if (entry.isFile() && entry.name.endsWith('.md')) {
422
- const refPath = path.join(refsDir, entry.name)
423
- const refName = entry.name.replace('.md', '')
424
- references.set(refName, {
425
- path: refPath,
426
- content: null // 延迟加载
427
- })
428
- }
429
- }
430
- } catch (err) {
431
- console.warn(`[SkillManager] Failed to scan references for ${skillPath}:`, err.message)
432
- }
433
-
434
- return references
435
- }
436
-
437
- /**
438
- * 扫描 scripts 子目录,获取脚本列表
439
- * @param {string} skillPath - skill 目录路径
440
- * @returns {Map} scripts 映射 { filename: { path, isExecutable } }
441
- */
442
- _scanScripts(skillPath) {
443
- const scripts = new Map()
444
- const scriptsDir = path.join(skillPath, 'scripts')
445
-
446
- if (!fs.existsSync(scriptsDir) || !fs.statSync(scriptsDir).isDirectory()) {
447
- return scripts
448
- }
449
-
450
- try {
451
- const entries = fs.readdirSync(scriptsDir, { withFileTypes: true })
452
- for (const entry of entries) {
453
- if (entry.isFile()) {
454
- const scriptPath = path.join(scriptsDir, entry.name)
455
- // 检查文件是否有执行权限(或检查扩展名)
456
- const isExecutable = entry.name.endsWith('.sh') ||
457
- entry.name.endsWith('.js') ||
458
- entry.name.endsWith('.py') ||
459
- entry.name.endsWith('.ts')
460
- scripts.set(entry.name, {
461
- path: scriptPath,
462
- isExecutable
463
- })
464
- }
465
- }
466
- } catch (err) {
467
- console.warn(`[SkillManager] Failed to scan scripts for ${skillPath}:`, err.message)
468
- }
469
-
470
- return scripts
471
- }
472
-
473
- /**
474
- * 按需加载 reference 文件
475
- * @param {string} skillName - skill 名称
476
- * @param {string} refName - reference 文件名(不含 .md)
477
- * @returns {string|null} 文件内容
478
- */
479
- loadReference(skillName, refName) {
480
- const skill = this._skills.get(skillName)
481
- if (!skill || !skill.references.has(refName)) {
482
- return null
483
- }
484
-
485
- const ref = skill.references.get(refName)
486
- if (!ref.content) {
487
- try {
488
- ref.content = fs.readFileSync(ref.path, 'utf-8')
489
- } catch (err) {
490
- console.error(`[SkillManager] Failed to load reference ${skillName}/${refName}:`, err.message)
491
- return null
492
- }
493
- }
494
-
495
- return ref.content
496
- }
497
-
498
- /**
499
- * 获取 skill 的 reference 列表
500
- * @param {string} skillName
501
- * @returns {string[]} reference 文件名列表
502
- */
503
- listReferences(skillName) {
504
- const skill = this._skills.get(skillName)
505
- if (!skill) return []
506
- return Array.from(skill.references.keys())
507
- }
508
-
509
- /**
510
- * 获取 skill 的 scripts 列表
511
- * @param {string} skillName
512
- * @returns {Object[]} script 信息列表 [{ name, path, isExecutable }]
513
- */
514
- listScripts(skillName) {
515
- const skill = this._skills.get(skillName)
516
- if (!skill) return []
517
- return Array.from(skill.scripts.entries()).map(([name, info]) => ({
518
- name,
519
- path: info.path,
520
- isExecutable: info.isExecutable
521
- }))
522
- }
523
-
524
- /**
525
- * 读取脚本内容
526
- * @param {string} skillName
527
- * @param {string} scriptName
528
- * @returns {string|null}
529
- */
530
- loadScript(skillName, scriptName) {
531
- const skill = this._skills.get(skillName)
532
- if (!skill || !skill.scripts.has(scriptName)) {
533
- return null
534
- }
535
-
536
- const script = skill.scripts.get(scriptName)
537
- try {
538
- return fs.readFileSync(script.path, 'utf-8')
539
- } catch (err) {
540
- console.error(`[SkillManager] Failed to load script ${skillName}/${scriptName}:`, err.message)
541
- return null
542
- }
543
- }
544
-
545
- /**
546
- * 获取所有 skills
547
- */
548
- getAllSkills() {
549
- return Array.from(this._skills.values())
550
- }
551
-
552
- /**
553
- * 获取 skill
554
- */
555
- getSkill(name) {
556
- return this._skills.get(name)
557
- }
558
-
559
- /**
560
- * 检查 skill 是否存在
561
- */
562
- hasSkill(name) {
563
- return this._skills.has(name)
564
- }
565
-
566
- /**
567
- * 获取 skill 数量
568
- */
569
- size() {
570
- return this._skills.size
571
- }
572
-
573
- reload(framework) {
574
- console.log('[SkillManager] Reloading...')
575
- this._skills.clear()
576
- this._loaded = false
577
- this._framework = framework
578
- this._loadAllSkills()
579
- }
580
-
581
- uninstall(framework) {
582
- this._skills.clear()
583
- this._framework = null
584
- this._loaded = false
585
- }
586
- }
587
-
588
- module.exports = {
589
- SkillManagerPlugin,
590
- Skill,
591
- SkillMetadata,
592
- isValidSkillName,
593
- parseFrontmatter,
594
- stripFrontmatter
595
- }
1
+ /**
2
+ * SkillManager 技能管理器
3
+ * 加载和管理 Skill
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { Plugin } = require('../core/plugin-base');
9
+ const { logger } = require('../utils/logger');
10
+ const log = logger.child('SkillManager');
11
+ const { z } = require('zod');
12
+
13
+ /**
14
+ * 验证 skill 名称
15
+ * 1-64字符,字母数字、下划线和连字符,不能以连字符或下划线开头或结尾
16
+ */
17
+ function isValidSkillName(name) {
18
+ if (!name || name.length < 1 || name.length > 64) return false;
19
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) return false;
20
+ if (name.startsWith('-') || name.startsWith('_') || name.endsWith('-') || name.endsWith('_'))
21
+ return false;
22
+ if (/--/.test(name) || /__/.test(name)) return false;
23
+ return true;
24
+ }
25
+
26
+ /**
27
+ * 解析 YAML frontmatter
28
+ */
29
+ function parseFrontmatter(content) {
30
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
31
+ if (!match) return null;
32
+
33
+ const frontmatter = {};
34
+ const lines = match[1].split('\n');
35
+ let currentKey = null;
36
+
37
+ for (const line of lines) {
38
+ // 处理 metadata 嵌套
39
+ if (currentKey === 'metadata') {
40
+ if (line.match(/^ {2}[a-zA-Z]/)) {
41
+ const colonIndex = line.indexOf(':');
42
+ if (colonIndex > 0) {
43
+ const key = line.substring(2, colonIndex).trim();
44
+ const value = line
45
+ .substring(colonIndex + 1)
46
+ .trim()
47
+ .replace(/^["']|["']$/g, '');
48
+ frontmatter[currentKey][key] = value;
49
+ continue;
50
+ }
51
+ } else if (line.match(/^[a-zA-Z]/)) {
52
+ currentKey = null;
53
+ continue;
54
+ }
55
+ }
56
+
57
+ const colonIndex = line.indexOf(':');
58
+ if (colonIndex === -1) continue;
59
+
60
+ const key = line.substring(0, colonIndex).trim();
61
+ let value = line.substring(colonIndex + 1).trim();
62
+
63
+ // 移除引号
64
+ if (
65
+ (value.startsWith('"') && value.endsWith('"')) ||
66
+ (value.startsWith("'") && value.endsWith("'"))
67
+ ) {
68
+ value = value.slice(1, -1);
69
+ }
70
+
71
+ if (key === 'metadata') {
72
+ currentKey = 'metadata';
73
+ frontmatter.metadata = {};
74
+ } else if (key === 'allowed-tools') {
75
+ frontmatter[key] = value
76
+ .split(',')
77
+ .map((t) => t.trim().replace(/^["']|["']$/g, ''))
78
+ .filter((t) => t);
79
+ } else {
80
+ frontmatter[key] = value;
81
+ }
82
+ }
83
+
84
+ return frontmatter;
85
+ }
86
+
87
+ /**
88
+ * 移除 frontmatter,获取正文
89
+ */
90
+ function stripFrontmatter(content) {
91
+ const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
92
+ return match ? content.slice(match[0].length).trim() : content.trim();
93
+ }
94
+
95
+ /**
96
+ * Skill 元数据
97
+ */
98
+ class SkillMetadata {
99
+ constructor(data) {
100
+ this.name = data.name || '';
101
+ this.description = data.description || '';
102
+ this.license = data.license || null;
103
+ this.compatibility = data.compatibility || null;
104
+ this.metadata = data.metadata || {};
105
+ this.allowedTools = data.allowedTools || [];
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Skill 类
111
+ */
112
+ class Skill {
113
+ constructor(metadata, content) {
114
+ this.metadata = metadata;
115
+ this.content = content;
116
+ this._framework = null;
117
+ }
118
+
119
+ /**
120
+ * 安装 skill
121
+ */
122
+ install(framework) {
123
+ this._framework = framework;
124
+ return this;
125
+ }
126
+
127
+ /**
128
+ * 获取工具定义
129
+ */
130
+ getTools() {
131
+ return [];
132
+ }
133
+ }
134
+
135
+ /**
136
+ * SkillManager 插件
137
+ */
138
+ class SkillManagerPlugin extends Plugin {
139
+ constructor(config = {}) {
140
+ super();
141
+ this.name = 'skill-manager';
142
+ this.version = '1.0.0';
143
+ this.description = '技能管理器,加载和管理 Skill';
144
+ this.priority = 5;
145
+ this.system = true;
146
+
147
+ this._framework = null;
148
+ this._skillsDirs = Array.isArray(config.skillsDirs)
149
+ ? config.skillsDirs
150
+ : [config.skillsDir || '.agent/skills', 'skills'];
151
+ this._skills = new Map();
152
+ this._loaded = false;
153
+ }
154
+
155
+ install(framework) {
156
+ this._framework = framework;
157
+ return this;
158
+ }
159
+
160
+ start(framework) {
161
+ this._loadAllSkills();
162
+
163
+ // 注册 loadSkill 工具
164
+ framework.registerTool({
165
+ name: 'loadSkill',
166
+ description: '加载指定技能,获取技能的使用指南和内容',
167
+ inputSchema: z.object({
168
+ skill: z.string().describe('技能名称'),
169
+ }),
170
+ execute: async (args) => {
171
+ const skillName = args.skill;
172
+ const skill = this.getSkill(skillName);
173
+ if (!skill) {
174
+ return { success: false, error: `Skill '${skillName}' not found` };
175
+ }
176
+ return {
177
+ success: true,
178
+ name: skill.name,
179
+ description: skill.metadata?.description || '',
180
+ content: skill.content,
181
+ };
182
+ },
183
+ });
184
+
185
+ // 注册 reloadSkills 工具
186
+ framework.registerTool({
187
+ name: 'reloadSkills',
188
+ description: '重载所有技能,当用户添加新技能或修改技能后调用此工具',
189
+ inputSchema: z.object({}),
190
+ execute: async () => {
191
+ this.reload(this._framework);
192
+ return {
193
+ success: true,
194
+ message: `Skills reloaded. Total: ${this._skills.size}`,
195
+ skills: Array.from(this._skills.keys()),
196
+ };
197
+ },
198
+ });
199
+
200
+ // 注册 loadReference 工具(按需加载 skill 的附加文档)
201
+ framework.registerTool({
202
+ name: 'loadReference',
203
+ description: '加载指定技能的附加参考文档(references 目录下的文件)',
204
+ inputSchema: z.object({
205
+ skill: z.string().describe('技能名称'),
206
+ reference: z.string().describe('参考文档名称(不含 .md 后缀)'),
207
+ list: z.boolean().optional().describe('如果为 true,列出该技能的所有可用的 reference 文件'),
208
+ }),
209
+ execute: async (args) => {
210
+ if (args.list) {
211
+ // 列出该技能的所有 reference
212
+ const refs = this.listReferences(args.skill);
213
+ return {
214
+ success: true,
215
+ skill: args.skill,
216
+ references: refs,
217
+ };
218
+ }
219
+
220
+ const content = this.loadReference(args.skill, args.reference);
221
+ if (content === null) {
222
+ return {
223
+ success: false,
224
+ error: `Reference '${args.reference}' not found in skill '${args.skill}'`,
225
+ };
226
+ }
227
+ return {
228
+ success: true,
229
+ skill: args.skill,
230
+ reference: args.reference,
231
+ content,
232
+ };
233
+ },
234
+ });
235
+
236
+ // 注册 listScripts 工具(列出 skill 的脚本)
237
+ framework.registerTool({
238
+ name: 'listScripts',
239
+ description: '列出指定技能下的所有可执行脚本',
240
+ inputSchema: z.object({
241
+ skill: z.string().describe('技能名称'),
242
+ }),
243
+ execute: async (args) => {
244
+ const scripts = this.listScripts(args.skill);
245
+ return {
246
+ success: true,
247
+ skill: args.skill,
248
+ scripts,
249
+ };
250
+ },
251
+ });
252
+
253
+ // 注册 loadScript 工具(读取脚本内容)
254
+ framework.registerTool({
255
+ name: 'loadScript',
256
+ description: '读取指定技能下脚本文件的内容',
257
+ inputSchema: z.object({
258
+ skill: z.string().describe('技能名称'),
259
+ script: z.string().describe('脚本名称(包含扩展名)'),
260
+ }),
261
+ execute: async (args) => {
262
+ const content = this.loadScript(args.skill, args.script);
263
+ if (content === null) {
264
+ return {
265
+ success: false,
266
+ error: `Script '${args.script}' not found in skill '${args.skill}'`,
267
+ };
268
+ }
269
+ return {
270
+ success: true,
271
+ skill: args.skill,
272
+ script: args.script,
273
+ content,
274
+ };
275
+ },
276
+ });
277
+
278
+ return this;
279
+ }
280
+
281
+ /**
282
+ * 加载所有 skills
283
+ */
284
+ _loadAllSkills() {
285
+ if (this._loaded) return;
286
+
287
+ let totalLoaded = 0;
288
+
289
+ for (const skillsDir of this._skillsDirs) {
290
+ const resolvedDir = path.resolve(process.cwd(), skillsDir);
291
+
292
+ if (!fs.existsSync(resolvedDir)) {
293
+ continue;
294
+ }
295
+
296
+ try {
297
+ const entries = fs.readdirSync(resolvedDir, { withFileTypes: true });
298
+
299
+ for (const entry of entries) {
300
+ if (!entry.isDirectory()) continue;
301
+
302
+ const skillPath = path.join(resolvedDir, entry.name);
303
+
304
+ if (!isValidSkillName(entry.name)) {
305
+ continue;
306
+ }
307
+
308
+ // 跳过已加载的 skill
309
+ if (this._skills.has(entry.name)) {
310
+ continue;
311
+ }
312
+
313
+ try {
314
+ this._loadSkill(entry.name, skillPath);
315
+ totalLoaded++;
316
+ } catch (err) {
317
+ log.error(` Failed to load skill '${entry.name}':`, err.message);
318
+ }
319
+ }
320
+ } catch (err) {
321
+ log.error('Failed to load skills from:', skillsDir, err.message);
322
+ }
323
+ }
324
+
325
+ log.info(` Loaded ${this._skills.size} skills`);
326
+ this._loaded = true;
327
+ }
328
+
329
+ /**
330
+ * 递归查找 SKILL.md AGENTS.md(支持多层嵌套目录)
331
+ * @param {string} dir - 要搜索的目录
332
+ * @returns {string|null} 找到的 markdown 文件路径
333
+ */
334
+ _findSkillMarkdown(dir) {
335
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
336
+ return null;
337
+ }
338
+
339
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
340
+
341
+ // 第一遍:优先查找 SKILL.MD
342
+ for (const entry of entries) {
343
+ if (entry.isFile() && entry.name.toUpperCase() === 'SKILL.MD') {
344
+ return path.join(dir, entry.name);
345
+ }
346
+ }
347
+
348
+ // 第二遍:查找 AGENTS.MD
349
+ for (const entry of entries) {
350
+ if (entry.isFile() && entry.name.toUpperCase() === 'AGENTS.MD') {
351
+ return path.join(dir, entry.name);
352
+ }
353
+ }
354
+
355
+ // 如果没找到,递归搜索子目录(排除 node_modules)
356
+ for (const entry of entries) {
357
+ if (entry.isDirectory() && entry.name !== 'node_modules') {
358
+ const subPath = path.join(dir, entry.name);
359
+ const found = this._findSkillMarkdown(subPath);
360
+ if (found) return found;
361
+ }
362
+ }
363
+
364
+ return null;
365
+ }
366
+
367
+ /**
368
+ * 加载单个 skill
369
+ */
370
+ _loadSkill(name, skillPath) {
371
+ // 查找 markdown 文件,优先加载 SKILL.md 或 AGENTS.md(支持多层嵌套)
372
+ const skillMdPath = this._findSkillMarkdown(skillPath);
373
+ if (!skillMdPath) {
374
+ throw new Error('No markdown file found');
375
+ }
376
+
377
+ const mainFile = skillMdPath;
378
+ const content = fs.readFileSync(mainFile, 'utf-8');
379
+ const frontmatter = parseFrontmatter(content);
380
+
381
+ if (!frontmatter) {
382
+ throw new Error('No valid frontmatter found');
383
+ }
384
+
385
+ const metadata = new SkillMetadata({
386
+ name: frontmatter.name || name,
387
+ description: frontmatter.description || '',
388
+ license: frontmatter.license,
389
+ compatibility: frontmatter.compatibility,
390
+ metadata: frontmatter.metadata || {},
391
+ allowedTools: frontmatter['allowed-tools'] || [],
392
+ });
393
+
394
+ const skill = new Skill(metadata, stripFrontmatter(content));
395
+ skill.install(this._framework);
396
+
397
+ // 扫描 references 子目录(按需加载的附加文档)
398
+ const references = this._scanReferences(skillPath);
399
+
400
+ // 扫描 scripts 子目录(可执行脚本)
401
+ const scripts = this._scanScripts(skillPath);
402
+
403
+ this._skills.set(name, {
404
+ name,
405
+ metadata,
406
+ content: skill.content,
407
+ instance: skill,
408
+ path: skillPath,
409
+ references,
410
+ scripts,
411
+ });
412
+
413
+ const refsInfo = references.size > 0 ? `, ${references.size} refs` : '';
414
+ const scriptsInfo = scripts.size > 0 ? `, ${scripts.size} scripts` : '';
415
+ //log.info(` Loaded skill: ${name}${refsInfo}${scriptsInfo}`)
416
+ }
417
+
418
+ /**
419
+ * 扫描 references 子目录,按需加载
420
+ * @param {string} skillPath - skill 目录路径
421
+ * @returns {Map} references 文件映射 { filename: { path, content } }
422
+ */
423
+ _scanReferences(skillPath) {
424
+ const references = new Map();
425
+ const refsDir = path.join(skillPath, 'references');
426
+
427
+ if (!fs.existsSync(refsDir) || !fs.statSync(refsDir).isDirectory()) {
428
+ return references;
429
+ }
430
+
431
+ try {
432
+ const entries = fs.readdirSync(refsDir, { withFileTypes: true });
433
+ for (const entry of entries) {
434
+ if (entry.isFile() && entry.name.endsWith('.md')) {
435
+ const refPath = path.join(refsDir, entry.name);
436
+ const refName = entry.name.replace('.md', '');
437
+ references.set(refName, {
438
+ path: refPath,
439
+ content: null, // 延迟加载
440
+ });
441
+ }
442
+ }
443
+ } catch (err) {
444
+ log.warn(` Failed to scan references for ${skillPath}:`, err.message);
445
+ }
446
+
447
+ return references;
448
+ }
449
+
450
+ /**
451
+ * 扫描 scripts 子目录,获取脚本列表
452
+ * @param {string} skillPath - skill 目录路径
453
+ * @returns {Map} scripts 映射 { filename: { path, isExecutable } }
454
+ */
455
+ _scanScripts(skillPath) {
456
+ const scripts = new Map();
457
+ const scriptsDir = path.join(skillPath, 'scripts');
458
+
459
+ if (!fs.existsSync(scriptsDir) || !fs.statSync(scriptsDir).isDirectory()) {
460
+ return scripts;
461
+ }
462
+
463
+ try {
464
+ const entries = fs.readdirSync(scriptsDir, { withFileTypes: true });
465
+ for (const entry of entries) {
466
+ if (entry.isFile()) {
467
+ const scriptPath = path.join(scriptsDir, entry.name);
468
+ // 检查文件是否有执行权限(或检查扩展名)
469
+ const isExecutable =
470
+ entry.name.endsWith('.sh') ||
471
+ entry.name.endsWith('.js') ||
472
+ entry.name.endsWith('.py') ||
473
+ entry.name.endsWith('.ts');
474
+ scripts.set(entry.name, {
475
+ path: scriptPath,
476
+ isExecutable,
477
+ });
478
+ }
479
+ }
480
+ } catch (err) {
481
+ log.warn(` Failed to scan scripts for ${skillPath}:`, err.message);
482
+ }
483
+
484
+ return scripts;
485
+ }
486
+
487
+ /**
488
+ * 按需加载 reference 文件
489
+ * @param {string} skillName - skill 名称
490
+ * @param {string} refName - reference 文件名(不含 .md)
491
+ * @returns {string|null} 文件内容
492
+ */
493
+ loadReference(skillName, refName) {
494
+ const skill = this._skills.get(skillName);
495
+ if (!skill || !skill.references.has(refName)) {
496
+ return null;
497
+ }
498
+
499
+ const ref = skill.references.get(refName);
500
+ if (!ref.content) {
501
+ try {
502
+ ref.content = fs.readFileSync(ref.path, 'utf-8');
503
+ } catch (err) {
504
+ log.error(` Failed to load reference ${skillName}/${refName}:`, err.message);
505
+ return null;
506
+ }
507
+ }
508
+
509
+ return ref.content;
510
+ }
511
+
512
+ /**
513
+ * 获取 skill 的 reference 列表
514
+ * @param {string} skillName
515
+ * @returns {string[]} reference 文件名列表
516
+ */
517
+ listReferences(skillName) {
518
+ const skill = this._skills.get(skillName);
519
+ if (!skill) return [];
520
+ return Array.from(skill.references.keys());
521
+ }
522
+
523
+ /**
524
+ * 获取 skill 的 scripts 列表
525
+ * @param {string} skillName
526
+ * @returns {Object[]} script 信息列表 [{ name, path, isExecutable }]
527
+ */
528
+ listScripts(skillName) {
529
+ const skill = this._skills.get(skillName);
530
+ if (!skill) return [];
531
+ return Array.from(skill.scripts.entries()).map(([name, info]) => ({
532
+ name,
533
+ path: info.path,
534
+ isExecutable: info.isExecutable,
535
+ }));
536
+ }
537
+
538
+ /**
539
+ * 读取脚本内容
540
+ * @param {string} skillName
541
+ * @param {string} scriptName
542
+ * @returns {string|null}
543
+ */
544
+ loadScript(skillName, scriptName) {
545
+ const skill = this._skills.get(skillName);
546
+ if (!skill || !skill.scripts.has(scriptName)) {
547
+ return null;
548
+ }
549
+
550
+ const script = skill.scripts.get(scriptName);
551
+ try {
552
+ return fs.readFileSync(script.path, 'utf-8');
553
+ } catch (err) {
554
+ log.error(` Failed to load script ${skillName}/${scriptName}:`, err.message);
555
+ return null;
556
+ }
557
+ }
558
+
559
+ /**
560
+ * 获取所有 skills
561
+ */
562
+ getAllSkills() {
563
+ return Array.from(this._skills.values());
564
+ }
565
+
566
+ /**
567
+ * 获取 skill
568
+ */
569
+ getSkill(name) {
570
+ return this._skills.get(name);
571
+ }
572
+
573
+ /**
574
+ * 检查 skill 是否存在
575
+ */
576
+ hasSkill(name) {
577
+ return this._skills.has(name);
578
+ }
579
+
580
+ /**
581
+ * 获取 skill 数量
582
+ */
583
+ size() {
584
+ return this._skills.size;
585
+ }
586
+
587
+ reload(framework) {
588
+ log.info(' Reloading...');
589
+ this._skills.clear();
590
+ this._loaded = false;
591
+ this._framework = framework;
592
+ this._loadAllSkills();
593
+ }
594
+
595
+ uninstall(framework) {
596
+ this._skills.clear();
597
+ this._framework = null;
598
+ this._loaded = false;
599
+ }
600
+ }
601
+
602
+ module.exports = {
603
+ SkillManagerPlugin,
604
+ Skill,
605
+ SkillMetadata,
606
+ isValidSkillName,
607
+ parseFrontmatter,
608
+ stripFrontmatter,
609
+ };