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,463 @@
1
+ /**
2
+ * Plugins API 路由
3
+ *
4
+ * 管理 CTX 插件系统
5
+ */
6
+
7
+ const express = require('express');
8
+ const { PluginsService } = require('../services/plugins-service');
9
+
10
+ const router = express.Router();
11
+ const SUPPORTED_PLATFORMS = ['claude', 'opencode'];
12
+ const pluginServices = new Map();
13
+
14
+ function resolvePlatform(rawPlatform) {
15
+ return SUPPORTED_PLATFORMS.includes(rawPlatform) ? rawPlatform : 'claude';
16
+ }
17
+
18
+ function getPlatform(req) {
19
+ return resolvePlatform(req.query?.platform || req.body?.platform);
20
+ }
21
+
22
+ function getPluginsService(req) {
23
+ const platform = getPlatform(req);
24
+ if (!pluginServices.has(platform)) {
25
+ pluginServices.set(platform, new PluginsService(platform));
26
+ }
27
+ return { platform, service: pluginServices.get(platform) };
28
+ }
29
+
30
+ /**
31
+ * 获取插件列表
32
+ * GET /api/plugins
33
+ */
34
+ router.get('/', (req, res) => {
35
+ try {
36
+ const { platform, service } = getPluginsService(req);
37
+ const result = service.listPlugins();
38
+
39
+ res.json({
40
+ success: true,
41
+ platform,
42
+ ...result
43
+ });
44
+ } catch (err) {
45
+ console.error('[Plugins API] List plugins error:', err);
46
+ res.status(500).json({
47
+ success: false,
48
+ message: err.message
49
+ });
50
+ }
51
+ });
52
+
53
+ /**
54
+ * 获取市场插件列表
55
+ * GET /api/plugins/market
56
+ */
57
+ router.get('/market', async (req, res) => {
58
+ try {
59
+ const { platform, service } = getPluginsService(req);
60
+ const plugins = await service.getMarketPlugins();
61
+
62
+ res.json({
63
+ success: true,
64
+ platform,
65
+ plugins
66
+ });
67
+ } catch (err) {
68
+ console.error('[Plugins API] Get market plugins error:', err);
69
+ res.status(500).json({
70
+ success: false,
71
+ message: err.message
72
+ });
73
+ }
74
+ });
75
+
76
+ /**
77
+ * 安装插件
78
+ * POST /api/plugins/install
79
+ * Body: { directory, repo: { owner, name, branch } } or { source }
80
+ */
81
+ router.post('/install', async (req, res) => {
82
+ try {
83
+ const { platform, service } = getPluginsService(req);
84
+ const { directory, repo, gitUrl, source } = req.body;
85
+
86
+ // Support both new format (directory + repo) and legacy format (gitUrl)
87
+ let installUrl;
88
+ if (source) {
89
+ installUrl = source;
90
+ } else if (directory && repo) {
91
+ installUrl = `https://github.com/${repo.owner}/${repo.name}/tree/${repo.branch || 'main'}/${directory}`;
92
+ } else if (gitUrl) {
93
+ installUrl = gitUrl;
94
+ } else {
95
+ return res.status(400).json({
96
+ success: false,
97
+ message: 'Either source, (directory + repo), or gitUrl is required'
98
+ });
99
+ }
100
+
101
+ const result = await service.installPlugin(installUrl);
102
+
103
+ if (!result.success) {
104
+ return res.status(400).json({
105
+ success: false,
106
+ message: result.error
107
+ });
108
+ }
109
+
110
+ res.json({
111
+ success: true,
112
+ platform,
113
+ plugin: result.plugin,
114
+ message: `Plugin "${result.plugin.name}" installed successfully`
115
+ });
116
+ } catch (err) {
117
+ console.error('[Plugins API] Install plugin error:', err);
118
+ res.status(500).json({
119
+ success: false,
120
+ message: err.message
121
+ });
122
+ }
123
+ });
124
+
125
+ // ==================== 仓库管理 API ====================
126
+
127
+ /**
128
+ * 获取插件仓库列表
129
+ * GET /api/plugins/repos
130
+ */
131
+ router.get('/repos', (req, res) => {
132
+ try {
133
+ const { platform, service } = getPluginsService(req);
134
+ const repos = service.getRepos();
135
+ res.json({
136
+ success: true,
137
+ platform,
138
+ repos
139
+ });
140
+ } catch (err) {
141
+ console.error('[Plugins API] Get repos error:', err);
142
+ res.status(500).json({
143
+ success: false,
144
+ message: err.message
145
+ });
146
+ }
147
+ });
148
+
149
+ /**
150
+ * 添加插件仓库
151
+ * POST /api/plugins/repos
152
+ * Body: { url, name, description }
153
+ */
154
+ router.post('/repos', (req, res) => {
155
+ try {
156
+ const { platform, service } = getPluginsService(req);
157
+ const repo = req.body;
158
+
159
+ if (!repo || !repo.url) {
160
+ return res.status(400).json({
161
+ success: false,
162
+ message: 'Repository URL is required'
163
+ });
164
+ }
165
+
166
+ const repos = service.addRepo(repo);
167
+
168
+ res.json({
169
+ success: true,
170
+ platform,
171
+ repos,
172
+ message: 'Repository added successfully'
173
+ });
174
+ } catch (err) {
175
+ console.error('[Plugins API] Add repo error:', err);
176
+ res.status(500).json({
177
+ success: false,
178
+ message: err.message
179
+ });
180
+ }
181
+ });
182
+
183
+ /**
184
+ * 删除插件仓库
185
+ * DELETE /api/plugins/repos/:owner/:name
186
+ */
187
+ router.delete('/repos/:owner/:name', (req, res) => {
188
+ try {
189
+ const { platform, service } = getPluginsService(req);
190
+ const { owner, name } = req.params;
191
+
192
+ const repos = service.removeRepo(owner, name);
193
+
194
+ res.json({
195
+ success: true,
196
+ platform,
197
+ repos,
198
+ message: 'Repository removed successfully'
199
+ });
200
+ } catch (err) {
201
+ console.error('[Plugins API] Remove repo error:', err);
202
+ res.status(500).json({
203
+ success: false,
204
+ message: err.message
205
+ });
206
+ }
207
+ });
208
+
209
+ /**
210
+ * 切换插件仓库启用状态
211
+ * PUT /api/plugins/repos/:owner/:name/toggle
212
+ * Body: { enabled }
213
+ */
214
+ router.put('/repos/:owner/:name/toggle', (req, res) => {
215
+ try {
216
+ const { platform, service } = getPluginsService(req);
217
+ const { owner, name } = req.params;
218
+ const { enabled } = req.body;
219
+
220
+ if (typeof enabled !== 'boolean') {
221
+ return res.status(400).json({
222
+ success: false,
223
+ message: 'enabled must be a boolean'
224
+ });
225
+ }
226
+
227
+ const repos = service.toggleRepo(owner, name, enabled);
228
+
229
+ res.json({
230
+ success: true,
231
+ platform,
232
+ repos,
233
+ message: `Repository ${enabled ? 'enabled' : 'disabled'} successfully`
234
+ });
235
+ } catch (err) {
236
+ console.error('[Plugins API] Toggle repo error:', err);
237
+ res.status(500).json({
238
+ success: false,
239
+ message: err.message
240
+ });
241
+ }
242
+ });
243
+
244
+ /**
245
+ * 同步仓库到 Claude Code marketplace
246
+ * POST /api/plugins/repos/sync
247
+ */
248
+ router.post('/repos/sync', async (req, res) => {
249
+ try {
250
+ const { platform, service } = getPluginsService(req);
251
+ const result = await service.syncRepos();
252
+
253
+ res.json({
254
+ success: true,
255
+ platform,
256
+ ...result,
257
+ message: 'Repositories synced successfully'
258
+ });
259
+ } catch (err) {
260
+ console.error('[Plugins API] Sync repos error:', err);
261
+ res.status(500).json({
262
+ success: false,
263
+ message: err.message
264
+ });
265
+ }
266
+ });
267
+
268
+ /**
269
+ * 同步本地插件列表
270
+ * POST /api/plugins/sync
271
+ */
272
+ router.post('/sync', async (req, res) => {
273
+ try {
274
+ const { platform, service } = getPluginsService(req);
275
+ const result = await service.syncPlugins();
276
+
277
+ res.json({
278
+ success: true,
279
+ platform,
280
+ ...result,
281
+ message: 'Plugins synced successfully'
282
+ });
283
+ } catch (err) {
284
+ console.error('[Plugins API] Sync plugins error:', err);
285
+ res.status(500).json({
286
+ success: false,
287
+ message: err.message
288
+ });
289
+ }
290
+ });
291
+
292
+ /**
293
+ * 获取插件 README
294
+ * GET /api/plugins/:name/readme
295
+ * Query: repoOwner, repoName, repoBranch, directory, source, repoUrl
296
+ */
297
+ router.get('/:name/readme', async (req, res) => {
298
+ try {
299
+ const { platform, service } = getPluginsService(req);
300
+ const { name } = req.params;
301
+ const { repoOwner, repoName, repoBranch, directory, source, repoUrl } = req.query;
302
+
303
+ const pluginInfo = {
304
+ name,
305
+ repoOwner,
306
+ repoName,
307
+ repoBranch,
308
+ directory,
309
+ source,
310
+ repoUrl
311
+ };
312
+
313
+ const readme = await service.getPluginReadme(pluginInfo);
314
+
315
+ res.json({
316
+ success: true,
317
+ platform,
318
+ readme
319
+ });
320
+ } catch (err) {
321
+ console.error('[Plugins API] Get plugin README error:', err);
322
+ res.status(500).json({
323
+ success: false,
324
+ message: err.message,
325
+ readme: ''
326
+ });
327
+ }
328
+ });
329
+
330
+ /**
331
+ * 获取单个插件详情
332
+ * GET /api/plugins/:name
333
+ */
334
+ router.get('/:name', (req, res) => {
335
+ try {
336
+ const { platform, service } = getPluginsService(req);
337
+ const { name } = req.params;
338
+
339
+ const plugin = service.getPlugin(name);
340
+
341
+ if (!plugin) {
342
+ return res.status(404).json({
343
+ success: false,
344
+ message: `Plugin "${name}" not found`
345
+ });
346
+ }
347
+
348
+ res.json({
349
+ success: true,
350
+ platform,
351
+ plugin
352
+ });
353
+ } catch (err) {
354
+ console.error('[Plugins API] Get plugin error:', err);
355
+ res.status(500).json({
356
+ success: false,
357
+ message: err.message
358
+ });
359
+ }
360
+ });
361
+
362
+ /**
363
+ * 卸载插件
364
+ * DELETE /api/plugins/:name
365
+ */
366
+ router.delete('/:name', (req, res) => {
367
+ try {
368
+ const { platform, service } = getPluginsService(req);
369
+ const { name } = req.params;
370
+
371
+ const result = service.uninstallPlugin(name);
372
+
373
+ if (!result.success) {
374
+ return res.status(400).json({
375
+ success: false,
376
+ message: result.error
377
+ });
378
+ }
379
+
380
+ res.json({
381
+ success: true,
382
+ platform,
383
+ message: result.message
384
+ });
385
+ } catch (err) {
386
+ console.error('[Plugins API] Uninstall plugin error:', err);
387
+ res.status(500).json({
388
+ success: false,
389
+ message: err.message
390
+ });
391
+ }
392
+ });
393
+
394
+ /**
395
+ * 切换插件启用状态
396
+ * PUT /api/plugins/:name/toggle
397
+ * Body: { enabled }
398
+ */
399
+ router.put('/:name/toggle', (req, res) => {
400
+ try {
401
+ const { platform, service } = getPluginsService(req);
402
+ const { name } = req.params;
403
+ const { enabled } = req.body;
404
+
405
+ if (typeof enabled !== 'boolean') {
406
+ return res.status(400).json({
407
+ success: false,
408
+ message: 'enabled must be a boolean'
409
+ });
410
+ }
411
+
412
+ const plugin = service.togglePlugin(name, enabled);
413
+
414
+ res.json({
415
+ success: true,
416
+ platform,
417
+ plugin,
418
+ message: `Plugin "${name}" ${enabled ? 'enabled' : 'disabled'} successfully`
419
+ });
420
+ } catch (err) {
421
+ console.error('[Plugins API] Toggle plugin error:', err);
422
+ res.status(500).json({
423
+ success: false,
424
+ message: err.message
425
+ });
426
+ }
427
+ });
428
+
429
+ /**
430
+ * 更新插件配置
431
+ * PUT /api/plugins/:name/config
432
+ * Body: { config }
433
+ */
434
+ router.put('/:name/config', (req, res) => {
435
+ try {
436
+ const { platform, service } = getPluginsService(req);
437
+ const { name } = req.params;
438
+ const { config } = req.body;
439
+
440
+ if (!config || typeof config !== 'object') {
441
+ return res.status(400).json({
442
+ success: false,
443
+ message: 'config must be an object'
444
+ });
445
+ }
446
+
447
+ const result = service.updatePluginConfig(name, config);
448
+
449
+ res.json({
450
+ success: true,
451
+ platform,
452
+ message: result.message
453
+ });
454
+ } catch (err) {
455
+ console.error('[Plugins API] Update plugin config error:', err);
456
+ res.status(500).json({
457
+ success: false,
458
+ message: err.message
459
+ });
460
+ }
461
+ });
462
+
463
+ module.exports = router;