@adversity/coding-tool-x 2.5.0 → 2.6.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.
- package/CHANGELOG.md +22 -0
- package/dist/web/assets/icons-CNM9_Fh0.js +1 -0
- package/dist/web/assets/index-BcmuQT-z.css +41 -0
- package/dist/web/assets/index-Ej0MPDUI.js +14 -0
- package/dist/web/index.html +3 -3
- package/package.json +4 -2
- package/src/commands/plugin.js +585 -0
- package/src/config/default.js +22 -3
- package/src/config/loader.js +6 -1
- package/src/index.js +229 -1
- package/src/server/api/config-export.js +122 -32
- package/src/server/api/dashboard.js +4 -3
- package/src/server/api/mcp.js +63 -0
- package/src/server/api/plugins.js +276 -0
- package/src/server/index.js +1 -0
- package/src/server/proxy-server.js +6 -3
- package/src/server/services/config-export-service.js +331 -5
- package/src/server/services/mcp-client.js +775 -0
- package/src/server/services/mcp-service.js +203 -0
- package/src/server/services/model-detector.js +350 -0
- package/src/server/services/plugins-service.js +177 -0
- package/src/server/services/pty-manager.js +65 -2
- package/src/server/services/speed-test.js +68 -37
- package/src/server/services/ui-config.js +2 -0
- package/src/server/utils/pricing.js +32 -1
- package/src/ui/menu.js +1 -0
- package/dist/web/assets/icons-BALJo7bE.js +0 -1
- package/dist/web/assets/index-CcYz-Mcz.css +0 -41
- package/dist/web/assets/index-k9b43kTe.js +0 -14
|
@@ -0,0 +1,276 @@
|
|
|
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 pluginsService = new PluginsService();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 获取插件列表
|
|
15
|
+
* GET /api/plugins
|
|
16
|
+
*/
|
|
17
|
+
router.get('/', (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const result = pluginsService.listPlugins();
|
|
20
|
+
|
|
21
|
+
res.json({
|
|
22
|
+
success: true,
|
|
23
|
+
...result
|
|
24
|
+
});
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error('[Plugins API] List plugins error:', err);
|
|
27
|
+
res.status(500).json({
|
|
28
|
+
success: false,
|
|
29
|
+
message: err.message
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 安装插件
|
|
36
|
+
* POST /api/plugins/install
|
|
37
|
+
* Body: { gitUrl }
|
|
38
|
+
*/
|
|
39
|
+
router.post('/install', async (req, res) => {
|
|
40
|
+
try {
|
|
41
|
+
const { gitUrl } = req.body;
|
|
42
|
+
|
|
43
|
+
if (!gitUrl) {
|
|
44
|
+
return res.status(400).json({
|
|
45
|
+
success: false,
|
|
46
|
+
message: 'Git URL is required'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = await pluginsService.installPlugin(gitUrl);
|
|
51
|
+
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
return res.status(400).json({
|
|
54
|
+
success: false,
|
|
55
|
+
message: result.error
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
res.json({
|
|
60
|
+
success: true,
|
|
61
|
+
plugin: result.plugin,
|
|
62
|
+
message: `Plugin "${result.plugin.name}" installed successfully`
|
|
63
|
+
});
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error('[Plugins API] Install plugin error:', err);
|
|
66
|
+
res.status(500).json({
|
|
67
|
+
success: false,
|
|
68
|
+
message: err.message
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ==================== 仓库管理 API ====================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 获取插件仓库列表
|
|
77
|
+
* GET /api/plugins/repos
|
|
78
|
+
*/
|
|
79
|
+
router.get('/repos', (req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const repos = pluginsService.getRepos();
|
|
82
|
+
res.json({
|
|
83
|
+
success: true,
|
|
84
|
+
repos
|
|
85
|
+
});
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error('[Plugins API] Get repos error:', err);
|
|
88
|
+
res.status(500).json({
|
|
89
|
+
success: false,
|
|
90
|
+
message: err.message
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 添加插件仓库
|
|
97
|
+
* POST /api/plugins/repos
|
|
98
|
+
* Body: { url, name, description }
|
|
99
|
+
*/
|
|
100
|
+
router.post('/repos', (req, res) => {
|
|
101
|
+
try {
|
|
102
|
+
const repo = req.body;
|
|
103
|
+
|
|
104
|
+
if (!repo || !repo.url) {
|
|
105
|
+
return res.status(400).json({
|
|
106
|
+
success: false,
|
|
107
|
+
message: 'Repository URL is required'
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const repos = pluginsService.addRepo(repo);
|
|
112
|
+
|
|
113
|
+
res.json({
|
|
114
|
+
success: true,
|
|
115
|
+
repos,
|
|
116
|
+
message: 'Repository added successfully'
|
|
117
|
+
});
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.error('[Plugins API] Add repo error:', err);
|
|
120
|
+
res.status(500).json({
|
|
121
|
+
success: false,
|
|
122
|
+
message: err.message
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 删除插件仓库
|
|
129
|
+
* DELETE /api/plugins/repos/:id
|
|
130
|
+
*/
|
|
131
|
+
router.delete('/repos/:id', (req, res) => {
|
|
132
|
+
try {
|
|
133
|
+
const { id } = req.params;
|
|
134
|
+
|
|
135
|
+
const repos = pluginsService.removeRepo(id);
|
|
136
|
+
|
|
137
|
+
res.json({
|
|
138
|
+
success: true,
|
|
139
|
+
repos,
|
|
140
|
+
message: 'Repository removed successfully'
|
|
141
|
+
});
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.error('[Plugins API] Remove repo error:', err);
|
|
144
|
+
res.status(500).json({
|
|
145
|
+
success: false,
|
|
146
|
+
message: err.message
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 获取单个插件详情
|
|
153
|
+
* GET /api/plugins/:name
|
|
154
|
+
*/
|
|
155
|
+
router.get('/:name', (req, res) => {
|
|
156
|
+
try {
|
|
157
|
+
const { name } = req.params;
|
|
158
|
+
|
|
159
|
+
const plugin = pluginsService.getPlugin(name);
|
|
160
|
+
|
|
161
|
+
if (!plugin) {
|
|
162
|
+
return res.status(404).json({
|
|
163
|
+
success: false,
|
|
164
|
+
message: `Plugin "${name}" not found`
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
res.json({
|
|
169
|
+
success: true,
|
|
170
|
+
plugin
|
|
171
|
+
});
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error('[Plugins API] Get plugin error:', err);
|
|
174
|
+
res.status(500).json({
|
|
175
|
+
success: false,
|
|
176
|
+
message: err.message
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 卸载插件
|
|
183
|
+
* DELETE /api/plugins/:name
|
|
184
|
+
*/
|
|
185
|
+
router.delete('/:name', (req, res) => {
|
|
186
|
+
try {
|
|
187
|
+
const { name } = req.params;
|
|
188
|
+
|
|
189
|
+
const result = pluginsService.uninstallPlugin(name);
|
|
190
|
+
|
|
191
|
+
if (!result.success) {
|
|
192
|
+
return res.status(400).json({
|
|
193
|
+
success: false,
|
|
194
|
+
message: result.error
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
res.json({
|
|
199
|
+
success: true,
|
|
200
|
+
message: result.message
|
|
201
|
+
});
|
|
202
|
+
} catch (err) {
|
|
203
|
+
console.error('[Plugins API] Uninstall plugin error:', err);
|
|
204
|
+
res.status(500).json({
|
|
205
|
+
success: false,
|
|
206
|
+
message: err.message
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 切换插件启用状态
|
|
213
|
+
* PUT /api/plugins/:name/toggle
|
|
214
|
+
* Body: { enabled }
|
|
215
|
+
*/
|
|
216
|
+
router.put('/:name/toggle', (req, res) => {
|
|
217
|
+
try {
|
|
218
|
+
const { name } = req.params;
|
|
219
|
+
const { enabled } = req.body;
|
|
220
|
+
|
|
221
|
+
if (typeof enabled !== 'boolean') {
|
|
222
|
+
return res.status(400).json({
|
|
223
|
+
success: false,
|
|
224
|
+
message: 'enabled must be a boolean'
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const plugin = pluginsService.togglePlugin(name, enabled);
|
|
229
|
+
|
|
230
|
+
res.json({
|
|
231
|
+
success: true,
|
|
232
|
+
plugin,
|
|
233
|
+
message: `Plugin "${name}" ${enabled ? 'enabled' : 'disabled'} successfully`
|
|
234
|
+
});
|
|
235
|
+
} catch (err) {
|
|
236
|
+
console.error('[Plugins API] Toggle plugin error:', err);
|
|
237
|
+
res.status(500).json({
|
|
238
|
+
success: false,
|
|
239
|
+
message: err.message
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 更新插件配置
|
|
246
|
+
* PUT /api/plugins/:name/config
|
|
247
|
+
* Body: { config }
|
|
248
|
+
*/
|
|
249
|
+
router.put('/:name/config', (req, res) => {
|
|
250
|
+
try {
|
|
251
|
+
const { name } = req.params;
|
|
252
|
+
const { config } = req.body;
|
|
253
|
+
|
|
254
|
+
if (!config || typeof config !== 'object') {
|
|
255
|
+
return res.status(400).json({
|
|
256
|
+
success: false,
|
|
257
|
+
message: 'config must be an object'
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const result = pluginsService.updatePluginConfig(name, config);
|
|
262
|
+
|
|
263
|
+
res.json({
|
|
264
|
+
success: true,
|
|
265
|
+
message: result.message
|
|
266
|
+
});
|
|
267
|
+
} catch (err) {
|
|
268
|
+
console.error('[Plugins API] Update plugin config error:', err);
|
|
269
|
+
res.status(500).json({
|
|
270
|
+
success: false,
|
|
271
|
+
message: err.message
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
module.exports = router;
|
package/src/server/index.js
CHANGED
|
@@ -134,6 +134,7 @@ async function startServer(port) {
|
|
|
134
134
|
app.use('/api/commands', require('./api/commands'));
|
|
135
135
|
app.use('/api/agents', require('./api/agents'));
|
|
136
136
|
app.use('/api/rules', require('./api/rules'));
|
|
137
|
+
app.use('/api/plugins', require('./api/plugins'));
|
|
137
138
|
|
|
138
139
|
// Web 终端 API
|
|
139
140
|
app.use('/api/terminal', require('./api/terminal'));
|
|
@@ -8,9 +8,10 @@ const { recordSuccess, recordFailure } = require('./services/channel-health');
|
|
|
8
8
|
const { broadcastLog, broadcastSchedulerState } = require('./websocket-server');
|
|
9
9
|
const { loadConfig } = require('../config/loader');
|
|
10
10
|
const DEFAULT_CONFIG = require('../config/default');
|
|
11
|
-
const { resolvePricing } = require('./utils/pricing');
|
|
11
|
+
const { resolvePricing, resolveModelPricing } = require('./utils/pricing');
|
|
12
12
|
const { recordRequest } = require('./services/statistics-service');
|
|
13
13
|
const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
|
|
14
|
+
const eventBus = require('../plugins/event-bus');
|
|
14
15
|
|
|
15
16
|
let proxyServer = null;
|
|
16
17
|
let proxyApp = null;
|
|
@@ -41,8 +42,8 @@ const ONE_MILLION = 1000000;
|
|
|
41
42
|
* @returns {number} 成本(美元)
|
|
42
43
|
*/
|
|
43
44
|
function calculateCost(model, tokens) {
|
|
44
|
-
const
|
|
45
|
-
const pricing =
|
|
45
|
+
const hardcodedPricing = PRICING[model] || {};
|
|
46
|
+
const pricing = resolveModelPricing('claude', model, hardcodedPricing, CLAUDE_BASE_PRICING);
|
|
46
47
|
|
|
47
48
|
const inputRate = typeof pricing.input === 'number' ? pricing.input : CLAUDE_BASE_PRICING.input;
|
|
48
49
|
const outputRate = typeof pricing.output === 'number' ? pricing.output : CLAUDE_BASE_PRICING.output;
|
|
@@ -399,6 +400,7 @@ async function startProxyServer(options = {}) {
|
|
|
399
400
|
proxyServer.listen(port, '127.0.0.1', () => {
|
|
400
401
|
console.log(`✅ Proxy server started on http://127.0.0.1:${port}`);
|
|
401
402
|
saveProxyStartTime('claude', preserveStartTime);
|
|
403
|
+
eventBus.emitSync('proxy:start', { channel: 'claude', port });
|
|
402
404
|
resolve({ success: true, port });
|
|
403
405
|
});
|
|
404
406
|
|
|
@@ -438,6 +440,7 @@ async function stopProxyServer(options = {}) {
|
|
|
438
440
|
if (clearStartTime) {
|
|
439
441
|
clearProxyStartTime('claude');
|
|
440
442
|
}
|
|
443
|
+
eventBus.emitSync('proxy:stop', { channel: 'claude' });
|
|
441
444
|
proxyServer = null;
|
|
442
445
|
proxyApp = null;
|
|
443
446
|
const stoppedPort = currentPort;
|