coding-tool-x 3.2.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 (185) hide show
  1. package/CHANGELOG.md +599 -0
  2. package/LICENSE +21 -0
  3. package/README.md +439 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
  6. package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
  7. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  8. package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
  9. package/dist/web/assets/Home-38JTUlYt.js +1 -0
  10. package/dist/web/assets/Home-CjupSEWE.css +1 -0
  11. package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
  12. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  13. package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
  14. package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
  15. package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
  16. package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
  17. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  18. package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
  19. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  20. package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
  21. package/dist/web/assets/icons-DRrXwWZi.js +1 -0
  22. package/dist/web/assets/index-CetESrXw.css +1 -0
  23. package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
  24. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  25. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  26. package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
  27. package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
  28. package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
  29. package/dist/web/favicon.ico +0 -0
  30. package/dist/web/index.html +20 -0
  31. package/dist/web/logo.png +0 -0
  32. package/docs/bannel.png +0 -0
  33. package/docs/home.png +0 -0
  34. package/docs/logo.png +0 -0
  35. package/docs/model-redirection.md +251 -0
  36. package/docs/multi-channel-load-balancing.md +249 -0
  37. package/package.json +80 -0
  38. package/src/commands/channels.js +551 -0
  39. package/src/commands/cli-type.js +101 -0
  40. package/src/commands/daemon.js +365 -0
  41. package/src/commands/doctor.js +333 -0
  42. package/src/commands/export-config.js +205 -0
  43. package/src/commands/list.js +222 -0
  44. package/src/commands/logs.js +261 -0
  45. package/src/commands/plugin.js +585 -0
  46. package/src/commands/port-config.js +135 -0
  47. package/src/commands/proxy-control.js +264 -0
  48. package/src/commands/proxy.js +152 -0
  49. package/src/commands/resume.js +137 -0
  50. package/src/commands/search.js +190 -0
  51. package/src/commands/security.js +37 -0
  52. package/src/commands/stats.js +398 -0
  53. package/src/commands/switch.js +48 -0
  54. package/src/commands/toggle-proxy.js +247 -0
  55. package/src/commands/ui.js +99 -0
  56. package/src/commands/update.js +97 -0
  57. package/src/commands/workspace.js +454 -0
  58. package/src/config/default.js +69 -0
  59. package/src/config/loader.js +149 -0
  60. package/src/config/model-metadata.js +167 -0
  61. package/src/config/model-metadata.json +125 -0
  62. package/src/config/model-pricing.js +35 -0
  63. package/src/config/paths.js +190 -0
  64. package/src/index.js +680 -0
  65. package/src/plugins/constants.js +15 -0
  66. package/src/plugins/event-bus.js +54 -0
  67. package/src/plugins/manifest-validator.js +129 -0
  68. package/src/plugins/plugin-api.js +128 -0
  69. package/src/plugins/plugin-installer.js +601 -0
  70. package/src/plugins/plugin-loader.js +229 -0
  71. package/src/plugins/plugin-manager.js +170 -0
  72. package/src/plugins/registry.js +152 -0
  73. package/src/plugins/schema/plugin-manifest.json +115 -0
  74. package/src/reset-config.js +94 -0
  75. package/src/server/api/agents.js +826 -0
  76. package/src/server/api/aliases.js +36 -0
  77. package/src/server/api/channels.js +368 -0
  78. package/src/server/api/claude-hooks.js +480 -0
  79. package/src/server/api/codex-channels.js +417 -0
  80. package/src/server/api/codex-projects.js +104 -0
  81. package/src/server/api/codex-proxy.js +195 -0
  82. package/src/server/api/codex-sessions.js +483 -0
  83. package/src/server/api/codex-statistics.js +57 -0
  84. package/src/server/api/commands.js +482 -0
  85. package/src/server/api/config-export.js +212 -0
  86. package/src/server/api/config-registry.js +357 -0
  87. package/src/server/api/config-sync.js +155 -0
  88. package/src/server/api/config-templates.js +248 -0
  89. package/src/server/api/config.js +521 -0
  90. package/src/server/api/convert.js +260 -0
  91. package/src/server/api/dashboard.js +142 -0
  92. package/src/server/api/env.js +144 -0
  93. package/src/server/api/favorites.js +77 -0
  94. package/src/server/api/gemini-channels.js +366 -0
  95. package/src/server/api/gemini-projects.js +91 -0
  96. package/src/server/api/gemini-proxy.js +173 -0
  97. package/src/server/api/gemini-sessions.js +376 -0
  98. package/src/server/api/gemini-statistics.js +57 -0
  99. package/src/server/api/health-check.js +31 -0
  100. package/src/server/api/mcp.js +399 -0
  101. package/src/server/api/opencode-channels.js +419 -0
  102. package/src/server/api/opencode-projects.js +99 -0
  103. package/src/server/api/opencode-proxy.js +207 -0
  104. package/src/server/api/opencode-sessions.js +327 -0
  105. package/src/server/api/opencode-statistics.js +57 -0
  106. package/src/server/api/plugins.js +463 -0
  107. package/src/server/api/pm2-autostart.js +269 -0
  108. package/src/server/api/projects.js +124 -0
  109. package/src/server/api/prompts.js +279 -0
  110. package/src/server/api/proxy.js +306 -0
  111. package/src/server/api/security.js +53 -0
  112. package/src/server/api/sessions.js +514 -0
  113. package/src/server/api/settings.js +142 -0
  114. package/src/server/api/skills.js +570 -0
  115. package/src/server/api/statistics.js +238 -0
  116. package/src/server/api/ui-config.js +64 -0
  117. package/src/server/api/workspaces.js +456 -0
  118. package/src/server/codex-proxy-server.js +681 -0
  119. package/src/server/dev-server.js +26 -0
  120. package/src/server/gemini-proxy-server.js +610 -0
  121. package/src/server/index.js +422 -0
  122. package/src/server/opencode-proxy-server.js +4771 -0
  123. package/src/server/proxy-server.js +669 -0
  124. package/src/server/services/agents-service.js +1137 -0
  125. package/src/server/services/alias.js +71 -0
  126. package/src/server/services/channel-health.js +234 -0
  127. package/src/server/services/channel-scheduler.js +240 -0
  128. package/src/server/services/channels.js +447 -0
  129. package/src/server/services/codex-channels.js +705 -0
  130. package/src/server/services/codex-config.js +90 -0
  131. package/src/server/services/codex-parser.js +322 -0
  132. package/src/server/services/codex-sessions.js +936 -0
  133. package/src/server/services/codex-settings-manager.js +619 -0
  134. package/src/server/services/codex-speed-test-template.json +24 -0
  135. package/src/server/services/codex-statistics-service.js +161 -0
  136. package/src/server/services/commands-service.js +574 -0
  137. package/src/server/services/config-export-service.js +1165 -0
  138. package/src/server/services/config-registry-service.js +828 -0
  139. package/src/server/services/config-sync-manager.js +941 -0
  140. package/src/server/services/config-sync-service.js +504 -0
  141. package/src/server/services/config-templates-service.js +913 -0
  142. package/src/server/services/enhanced-cache.js +196 -0
  143. package/src/server/services/env-checker.js +409 -0
  144. package/src/server/services/env-manager.js +436 -0
  145. package/src/server/services/favorites.js +165 -0
  146. package/src/server/services/format-converter.js +620 -0
  147. package/src/server/services/gemini-channels.js +459 -0
  148. package/src/server/services/gemini-config.js +73 -0
  149. package/src/server/services/gemini-sessions.js +689 -0
  150. package/src/server/services/gemini-settings-manager.js +263 -0
  151. package/src/server/services/gemini-statistics-service.js +157 -0
  152. package/src/server/services/health-check.js +85 -0
  153. package/src/server/services/mcp-client.js +790 -0
  154. package/src/server/services/mcp-service.js +1732 -0
  155. package/src/server/services/model-detector.js +1245 -0
  156. package/src/server/services/network-access.js +80 -0
  157. package/src/server/services/opencode-channels.js +366 -0
  158. package/src/server/services/opencode-gateway-adapters.js +1168 -0
  159. package/src/server/services/opencode-gateway-converter.js +639 -0
  160. package/src/server/services/opencode-sessions.js +931 -0
  161. package/src/server/services/opencode-settings-manager.js +478 -0
  162. package/src/server/services/opencode-statistics-service.js +161 -0
  163. package/src/server/services/plugins-service.js +1268 -0
  164. package/src/server/services/prompts-service.js +534 -0
  165. package/src/server/services/proxy-runtime.js +79 -0
  166. package/src/server/services/repo-scanner-base.js +708 -0
  167. package/src/server/services/request-logger.js +130 -0
  168. package/src/server/services/response-decoder.js +21 -0
  169. package/src/server/services/security-config.js +131 -0
  170. package/src/server/services/session-cache.js +127 -0
  171. package/src/server/services/session-converter.js +577 -0
  172. package/src/server/services/sessions.js +900 -0
  173. package/src/server/services/settings-manager.js +163 -0
  174. package/src/server/services/skill-service.js +1482 -0
  175. package/src/server/services/speed-test.js +1146 -0
  176. package/src/server/services/statistics-service.js +1043 -0
  177. package/src/server/services/ui-config.js +132 -0
  178. package/src/server/services/workspace-service.js +830 -0
  179. package/src/server/utils/pricing.js +73 -0
  180. package/src/server/websocket-server.js +513 -0
  181. package/src/ui/menu.js +139 -0
  182. package/src/ui/prompts.js +100 -0
  183. package/src/utils/format.js +43 -0
  184. package/src/utils/port-helper.js +108 -0
  185. package/src/utils/session.js +240 -0
@@ -0,0 +1,570 @@
1
+ /**
2
+ * Skills API 路由
3
+ */
4
+
5
+ const express = require('express');
6
+ const { SkillService } = require('../services/skill-service');
7
+
8
+ const router = express.Router();
9
+ const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
10
+ const skillServices = new Map();
11
+
12
+ function resolvePlatform(rawPlatform) {
13
+ return SUPPORTED_PLATFORMS.includes(rawPlatform) ? rawPlatform : 'claude';
14
+ }
15
+
16
+ function getPlatform(req) {
17
+ return resolvePlatform(req.query?.platform || req.body?.platform);
18
+ }
19
+
20
+ function getSkillService(req) {
21
+ const platform = getPlatform(req);
22
+ if (!skillServices.has(platform)) {
23
+ skillServices.set(platform, new SkillService(platform));
24
+ }
25
+ return { platform, service: skillServices.get(platform) };
26
+ }
27
+
28
+ /**
29
+ * 获取技能列表
30
+ * GET /api/skills
31
+ * Query: refresh=1 强制刷新缓存
32
+ */
33
+ router.get('/', async (req, res) => {
34
+ try {
35
+ const { platform, service } = getSkillService(req);
36
+ const forceRefresh = req.query.refresh === '1';
37
+ const skills = await service.listSkills(forceRefresh);
38
+ res.json({
39
+ success: true,
40
+ platform,
41
+ skills,
42
+ total: skills.length,
43
+ installed: skills.filter(s => s.installed).length
44
+ });
45
+ } catch (err) {
46
+ console.error('[Skills API] List skills error:', err);
47
+ res.status(500).json({
48
+ success: false,
49
+ message: err.message
50
+ });
51
+ }
52
+ });
53
+
54
+ /**
55
+ * 获取技能详情(完整内容)
56
+ * GET /api/skills/detail/:directory
57
+ */
58
+ router.get('/detail/*', async (req, res) => {
59
+ try {
60
+ const { platform, service } = getSkillService(req);
61
+ const directory = req.params[0]; // 获取通配符匹配的路径
62
+ if (!directory) {
63
+ return res.status(400).json({
64
+ success: false,
65
+ message: 'Missing directory'
66
+ });
67
+ }
68
+
69
+ const result = await service.getSkillDetail(directory);
70
+ res.json({
71
+ success: true,
72
+ platform,
73
+ ...result
74
+ });
75
+ } catch (err) {
76
+ console.error('[Skills API] Get skill detail error:', err);
77
+ res.status(500).json({
78
+ success: false,
79
+ message: err.message
80
+ });
81
+ }
82
+ });
83
+
84
+ /**
85
+ * 获取已安装的技能
86
+ * GET /api/skills/installed
87
+ */
88
+ router.get('/installed', (req, res) => {
89
+ try {
90
+ const { platform, service } = getSkillService(req);
91
+ const skills = service.getInstalledSkills();
92
+ res.json({
93
+ success: true,
94
+ platform,
95
+ skills
96
+ });
97
+ } catch (err) {
98
+ console.error('[Skills API] Get installed skills error:', err);
99
+ res.status(500).json({
100
+ success: false,
101
+ message: err.message
102
+ });
103
+ }
104
+ });
105
+
106
+ /**
107
+ * 安装技能
108
+ * POST /api/skills/install
109
+ * Body: { directory, fullDirectory, repo: { owner, name, branch } }
110
+ * - directory: 本地安装目录(相对路径)
111
+ * - fullDirectory: 仓库中的完整路径(当指定了仓库子目录时使用)
112
+ */
113
+ router.post('/install', async (req, res) => {
114
+ try {
115
+ const { platform, service } = getSkillService(req);
116
+ const { directory, fullDirectory, repo } = req.body;
117
+
118
+ if (!directory) {
119
+ return res.status(400).json({
120
+ success: false,
121
+ message: 'Missing directory'
122
+ });
123
+ }
124
+
125
+ if (!repo || !repo.owner || !repo.name) {
126
+ return res.status(400).json({
127
+ success: false,
128
+ message: 'Missing repo info'
129
+ });
130
+ }
131
+
132
+ const result = await service.installSkill(
133
+ directory,
134
+ {
135
+ owner: repo.owner,
136
+ name: repo.name,
137
+ branch: repo.branch || 'main'
138
+ },
139
+ fullDirectory || null // 传递 fullDirectory 用于从仓库子目录下载
140
+ );
141
+
142
+ res.json({
143
+ success: true,
144
+ platform,
145
+ ...result
146
+ });
147
+ } catch (err) {
148
+ console.error('[Skills API] Install skill error:', err);
149
+ res.status(500).json({
150
+ success: false,
151
+ message: err.message
152
+ });
153
+ }
154
+ });
155
+
156
+ /**
157
+ * 创建自定义技能
158
+ * POST /api/skills/create
159
+ * Body: { name, directory, description, content }
160
+ */
161
+ router.post('/create', (req, res) => {
162
+ try {
163
+ const { platform, service } = getSkillService(req);
164
+ const { name, directory, description, content } = req.body;
165
+
166
+ if (!directory) {
167
+ return res.status(400).json({
168
+ success: false,
169
+ message: '请输入目录名称'
170
+ });
171
+ }
172
+
173
+ // 校验目录名:只允许英文、数字、横杠、下划线
174
+ if (!/^[a-zA-Z0-9_-]+$/.test(directory)) {
175
+ return res.status(400).json({
176
+ success: false,
177
+ message: '目录名只能包含英文、数字、横杠和下划线'
178
+ });
179
+ }
180
+
181
+ if (!content) {
182
+ return res.status(400).json({
183
+ success: false,
184
+ message: '请输入技能内容'
185
+ });
186
+ }
187
+
188
+ const result = service.createCustomSkill({
189
+ name: name || directory,
190
+ directory,
191
+ description: description || '',
192
+ content
193
+ });
194
+
195
+ res.json({
196
+ success: true,
197
+ platform,
198
+ ...result
199
+ });
200
+ } catch (err) {
201
+ console.error('[Skills API] Create skill error:', err);
202
+ res.status(500).json({
203
+ success: false,
204
+ message: err.message
205
+ });
206
+ }
207
+ });
208
+
209
+ /**
210
+ * 卸载技能
211
+ * POST /api/skills/uninstall
212
+ * Body: { directory }
213
+ */
214
+ router.post('/uninstall', (req, res) => {
215
+ try {
216
+ const { platform, service } = getSkillService(req);
217
+ const { directory } = req.body;
218
+
219
+ if (!directory) {
220
+ return res.status(400).json({
221
+ success: false,
222
+ message: 'Missing directory'
223
+ });
224
+ }
225
+
226
+ const result = service.uninstallSkill(directory);
227
+
228
+ res.json({
229
+ success: true,
230
+ platform,
231
+ ...result
232
+ });
233
+ } catch (err) {
234
+ console.error('[Skills API] Uninstall skill error:', err);
235
+ res.status(500).json({
236
+ success: false,
237
+ message: err.message
238
+ });
239
+ }
240
+ });
241
+
242
+ /**
243
+ * 获取仓库列表
244
+ * GET /api/skills/repos
245
+ */
246
+ router.get('/repos', (req, res) => {
247
+ try {
248
+ const { platform, service } = getSkillService(req);
249
+ const repos = service.loadRepos();
250
+ res.json({
251
+ success: true,
252
+ platform,
253
+ repos
254
+ });
255
+ } catch (err) {
256
+ console.error('[Skills API] Get repos error:', err);
257
+ res.status(500).json({
258
+ success: false,
259
+ message: err.message
260
+ });
261
+ }
262
+ });
263
+
264
+ /**
265
+ * 添加仓库
266
+ * POST /api/skills/repos
267
+ * Body: { owner, name, branch, directory, enabled }
268
+ * - directory: 可选,指定扫描的子目录路径
269
+ */
270
+ router.post('/repos', (req, res) => {
271
+ try {
272
+ const { platform, service } = getSkillService(req);
273
+ const { owner, name, branch = 'main', directory = '', enabled = true } = req.body;
274
+
275
+ if (!owner || !name) {
276
+ return res.status(400).json({
277
+ success: false,
278
+ message: 'Missing owner or name'
279
+ });
280
+ }
281
+
282
+ const repos = service.addRepo({ owner, name, branch, directory, enabled });
283
+
284
+ res.json({
285
+ success: true,
286
+ platform,
287
+ repos
288
+ });
289
+ } catch (err) {
290
+ console.error('[Skills API] Add repo error:', err);
291
+ res.status(500).json({
292
+ success: false,
293
+ message: err.message
294
+ });
295
+ }
296
+ });
297
+
298
+ /**
299
+ * 删除仓库
300
+ * DELETE /api/skills/repos/:owner/:name
301
+ * Query: directory - 可选,子目录路径
302
+ */
303
+ router.delete('/repos/:owner/:name', (req, res) => {
304
+ try {
305
+ const { platform, service } = getSkillService(req);
306
+ const { owner, name } = req.params;
307
+ const { directory = '' } = req.query;
308
+ const repos = service.removeRepo(owner, name, directory);
309
+
310
+ res.json({
311
+ success: true,
312
+ platform,
313
+ repos
314
+ });
315
+ } catch (err) {
316
+ console.error('[Skills API] Remove repo error:', err);
317
+ res.status(500).json({
318
+ success: false,
319
+ message: err.message
320
+ });
321
+ }
322
+ });
323
+
324
+ /**
325
+ * 切换仓库启用状态
326
+ * PUT /api/skills/repos/:owner/:name/toggle
327
+ * Body: { enabled, directory }
328
+ * - directory: 可选,子目录路径
329
+ */
330
+ router.put('/repos/:owner/:name/toggle', (req, res) => {
331
+ try {
332
+ const { platform, service } = getSkillService(req);
333
+ const { owner, name } = req.params;
334
+ const { enabled, directory = '' } = req.body;
335
+
336
+ const repos = service.toggleRepo(owner, name, directory, enabled);
337
+
338
+ res.json({
339
+ success: true,
340
+ platform,
341
+ repos
342
+ });
343
+ } catch (err) {
344
+ console.error('[Skills API] Toggle repo error:', err);
345
+ res.status(500).json({
346
+ success: false,
347
+ message: err.message
348
+ });
349
+ }
350
+ });
351
+
352
+ // ==================== 多文件技能管理 API ====================
353
+
354
+ /**
355
+ * 创建带多文件的技能
356
+ * POST /api/skills/create-with-files
357
+ * Body: { directory, files: [{path, content, isBase64?}] }
358
+ */
359
+ router.post('/create-with-files', (req, res) => {
360
+ try {
361
+ const { platform, service } = getSkillService(req);
362
+ const { directory, files } = req.body;
363
+
364
+ if (!directory) {
365
+ return res.status(400).json({
366
+ success: false,
367
+ message: '请输入目录名称'
368
+ });
369
+ }
370
+
371
+ // 校验目录名:只允许英文、数字、横杠、下划线
372
+ if (!/^[a-zA-Z0-9_-]+$/.test(directory)) {
373
+ return res.status(400).json({
374
+ success: false,
375
+ message: '目录名只能包含英文、数字、横杠和下划线'
376
+ });
377
+ }
378
+
379
+ if (!files || !Array.isArray(files) || files.length === 0) {
380
+ return res.status(400).json({
381
+ success: false,
382
+ message: '请提供文件列表'
383
+ });
384
+ }
385
+
386
+ const result = service.createSkillWithFiles({ directory, files });
387
+
388
+ res.json({
389
+ success: true,
390
+ platform,
391
+ ...result
392
+ });
393
+ } catch (err) {
394
+ console.error('[Skills API] Create skill with files error:', err);
395
+ res.status(500).json({
396
+ success: false,
397
+ message: err.message
398
+ });
399
+ }
400
+ });
401
+
402
+ /**
403
+ * 获取技能文件列表
404
+ * GET /api/skills/:directory/files
405
+ */
406
+ router.get('/:directory/files', (req, res) => {
407
+ try {
408
+ const { platform, service } = getSkillService(req);
409
+ const { directory } = req.params;
410
+ const files = service.getSkillFiles(directory);
411
+
412
+ res.json({
413
+ success: true,
414
+ platform,
415
+ directory,
416
+ files
417
+ });
418
+ } catch (err) {
419
+ console.error('[Skills API] Get skill files error:', err);
420
+ res.status(500).json({
421
+ success: false,
422
+ message: err.message
423
+ });
424
+ }
425
+ });
426
+
427
+ /**
428
+ * 获取技能文件内容
429
+ * GET /api/skills/:directory/files/:filePath
430
+ * 注意:filePath 可能包含子目录,使用通配符
431
+ */
432
+ router.get('/:directory/file/*', (req, res) => {
433
+ try {
434
+ const { platform, service } = getSkillService(req);
435
+ const { directory } = req.params;
436
+ const filePath = req.params[0];
437
+
438
+ if (!filePath) {
439
+ return res.status(400).json({
440
+ success: false,
441
+ message: '请指定文件路径'
442
+ });
443
+ }
444
+
445
+ const result = service.getSkillFileContent(directory, filePath);
446
+
447
+ res.json({
448
+ success: true,
449
+ platform,
450
+ ...result
451
+ });
452
+ } catch (err) {
453
+ console.error('[Skills API] Get skill file content error:', err);
454
+ res.status(500).json({
455
+ success: false,
456
+ message: err.message
457
+ });
458
+ }
459
+ });
460
+
461
+ /**
462
+ * 添加文件到技能
463
+ * POST /api/skills/:directory/files
464
+ * Body: { files: [{path, content, isBase64?}] }
465
+ */
466
+ router.post('/:directory/files', (req, res) => {
467
+ try {
468
+ const { platform, service } = getSkillService(req);
469
+ const { directory } = req.params;
470
+ const { files } = req.body;
471
+
472
+ if (!files || !Array.isArray(files) || files.length === 0) {
473
+ return res.status(400).json({
474
+ success: false,
475
+ message: '请提供文件列表'
476
+ });
477
+ }
478
+
479
+ const result = service.addSkillFiles(directory, files);
480
+
481
+ res.json({
482
+ success: true,
483
+ platform,
484
+ ...result
485
+ });
486
+ } catch (err) {
487
+ console.error('[Skills API] Add skill files error:', err);
488
+ res.status(500).json({
489
+ success: false,
490
+ message: err.message
491
+ });
492
+ }
493
+ });
494
+
495
+ /**
496
+ * 删除技能中的文件
497
+ * DELETE /api/skills/:directory/file/*
498
+ */
499
+ router.delete('/:directory/file/*', (req, res) => {
500
+ try {
501
+ const { platform, service } = getSkillService(req);
502
+ const { directory } = req.params;
503
+ const filePath = req.params[0];
504
+
505
+ if (!filePath) {
506
+ return res.status(400).json({
507
+ success: false,
508
+ message: '请指定文件路径'
509
+ });
510
+ }
511
+
512
+ const result = service.deleteSkillFile(directory, filePath);
513
+
514
+ res.json({
515
+ success: true,
516
+ platform,
517
+ ...result
518
+ });
519
+ } catch (err) {
520
+ console.error('[Skills API] Delete skill file error:', err);
521
+ res.status(500).json({
522
+ success: false,
523
+ message: err.message
524
+ });
525
+ }
526
+ });
527
+
528
+ /**
529
+ * 更新技能文件内容
530
+ * PUT /api/skills/:directory/file/*
531
+ * Body: { content, isBase64? }
532
+ */
533
+ router.put('/:directory/file/*', (req, res) => {
534
+ try {
535
+ const { platform, service } = getSkillService(req);
536
+ const { directory } = req.params;
537
+ const filePath = req.params[0];
538
+ const { content, isBase64 = false } = req.body;
539
+
540
+ if (!filePath) {
541
+ return res.status(400).json({
542
+ success: false,
543
+ message: '请指定文件路径'
544
+ });
545
+ }
546
+
547
+ if (content === undefined) {
548
+ return res.status(400).json({
549
+ success: false,
550
+ message: '请提供文件内容'
551
+ });
552
+ }
553
+
554
+ const result = service.updateSkillFile(directory, filePath, content, isBase64);
555
+
556
+ res.json({
557
+ success: true,
558
+ platform,
559
+ ...result
560
+ });
561
+ } catch (err) {
562
+ console.error('[Skills API] Update skill file error:', err);
563
+ res.status(500).json({
564
+ success: false,
565
+ message: err.message
566
+ });
567
+ }
568
+ });
569
+
570
+ module.exports = router;