coding-tool-x 3.4.4 → 3.4.6
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/dist/web/assets/{Analytics-_Byi9M6y.js → Analytics-0PgPv5qO.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DIwosdtG.js → ConfigTemplates-pBGoYbCP.js} +1 -1
- package/dist/web/assets/{Home-DdNMuQ9c.js → Home-BRN882om.js} +1 -1
- package/dist/web/assets/{PluginManager-iuY24cnW.js → PluginManager-am97Huts.js} +1 -1
- package/dist/web/assets/{ProjectList-DSkMulzL.js → ProjectList-CXS9KJN1.js} +1 -1
- package/dist/web/assets/{SessionList-B6pGquIr.js → SessionList-BZyrzH7J.js} +1 -1
- package/dist/web/assets/{SkillManager-CHtQX5r8.js → SkillManager-p1CI0tYa.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-gNPs-VaI.js → WorkspaceManager-CUPvLoba.js} +1 -1
- package/dist/web/assets/index-B4Wl3JfR.js +2 -0
- package/dist/web/assets/{index-pMqqe9ei.css → index-Bgt_oqoE.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/src/server/api/claude-hooks.js +1 -0
- package/src/server/api/codex-channels.js +26 -0
- package/src/server/api/oauth-credentials.js +23 -1
- package/src/server/api/opencode-proxy.js +0 -2
- package/src/server/api/plugins.js +161 -14
- package/src/server/api/skills.js +62 -7
- package/src/server/codex-proxy-server.js +10 -2
- package/src/server/gemini-proxy-server.js +10 -2
- package/src/server/opencode-proxy-server.js +10 -2
- package/src/server/proxy-server.js +10 -2
- package/src/server/services/codex-channels.js +64 -21
- package/src/server/services/codex-env-manager.js +44 -28
- package/src/server/services/native-oauth-adapters.js +94 -10
- package/src/server/services/oauth-credentials-service.js +44 -2
- package/src/server/services/opencode-channels.js +0 -2
- package/src/server/services/plugins-service.js +1060 -235
- package/src/server/services/proxy-runtime.js +129 -5
- package/src/server/services/server-shutdown.js +79 -0
- package/src/server/services/skill-service.js +142 -17
- package/dist/web/assets/index-DGjGCo37.js +0 -2
package/dist/web/index.html
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
<link rel="icon" href="/favicon.ico">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>CC-TOOL - ClaudeCode增强工作助手</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-B4Wl3JfR.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/markdown-DyTJGI4N.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vue-vendor-3bf-fPGP.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/vendors-CKPV1OAU.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/naive-ui-Bdxp09n2.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/icons-B5Pl4lrD.js">
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Bgt_oqoE.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coding-tool-x",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.6",
|
|
4
4
|
"description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/ctx.js",
|
|
11
11
|
"test": "npm run test:basic && npm run test:api && npm run test:codex-agents && npm run test:skills && npm run test:plugins-market",
|
|
12
|
-
"test:basic": "node scripts/test-basic.js",
|
|
12
|
+
"test:basic": "node scripts/test-basic.js && npm run test:unit",
|
|
13
13
|
"test:api": "node scripts/test-api-consistency.js",
|
|
14
14
|
"test:codex-agents": "node scripts/test-codex-agents.js",
|
|
15
15
|
"test:skills": "node scripts/test-skill-providers.js",
|
|
@@ -543,6 +543,7 @@ router.post('/test', (req, res) => {
|
|
|
543
543
|
const command = generateSystemNotificationCommand(type || 'notification');
|
|
544
544
|
const { execSync } = require('child_process');
|
|
545
545
|
execSync(command, { stdio: 'ignore', windowsHide: true });
|
|
546
|
+
res.json({ success: true, message: '系统测试通知已发送' });
|
|
546
547
|
}
|
|
547
548
|
} catch (error) {
|
|
548
549
|
console.error('Error testing notification:', error);
|
|
@@ -25,11 +25,26 @@ const { deleteBackup } = require('../services/codex-settings-manager');
|
|
|
25
25
|
const { PATHS } = require('../../config/paths');
|
|
26
26
|
const { getDefaultSpeedTestModelByToolType } = require('../../config/model-metadata');
|
|
27
27
|
const CODEX_GATEWAY_SOURCE_TYPE = 'codex';
|
|
28
|
+
const CODEX_PROVIDER_KEY_PATTERN = /^[a-z0-9_-]+$/i;
|
|
28
29
|
|
|
29
30
|
function getDefaultCodexModel() {
|
|
30
31
|
return getDefaultSpeedTestModelByToolType('codex');
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
function validateCodexProviderKey(value) {
|
|
35
|
+
const normalized = String(value || '').trim();
|
|
36
|
+
if (!normalized) {
|
|
37
|
+
return 'Missing required fields: providerKey';
|
|
38
|
+
}
|
|
39
|
+
if (!CODEX_PROVIDER_KEY_PATTERN.test(normalized)) {
|
|
40
|
+
return 'Invalid providerKey: only letters, numbers, underscores, and hyphens are allowed';
|
|
41
|
+
}
|
|
42
|
+
if (normalized.toLowerCase() === 'openai') {
|
|
43
|
+
return 'Invalid providerKey: "openai" is reserved for the built-in OpenAI provider';
|
|
44
|
+
}
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
module.exports = (config) => {
|
|
34
49
|
/**
|
|
35
50
|
* GET /api/codex/channels
|
|
@@ -137,6 +152,11 @@ module.exports = (config) => {
|
|
|
137
152
|
return res.status(400).json({ error: 'Missing required fields: apiKey' });
|
|
138
153
|
}
|
|
139
154
|
|
|
155
|
+
const providerKeyError = validateCodexProviderKey(providerKey);
|
|
156
|
+
if (providerKeyError) {
|
|
157
|
+
return res.status(400).json({ error: providerKeyError });
|
|
158
|
+
}
|
|
159
|
+
|
|
140
160
|
// wireApi 固定为 'responses' (OpenAI Responses API 格式)
|
|
141
161
|
const channel = createChannel(name, providerKey, baseUrl, apiKey, 'responses', {
|
|
142
162
|
websiteUrl,
|
|
@@ -168,6 +188,12 @@ module.exports = (config) => {
|
|
|
168
188
|
|
|
169
189
|
const { channelId } = req.params;
|
|
170
190
|
const updates = req.body;
|
|
191
|
+
if (Object.prototype.hasOwnProperty.call(updates, 'providerKey')) {
|
|
192
|
+
const providerKeyError = validateCodexProviderKey(updates.providerKey);
|
|
193
|
+
if (providerKeyError) {
|
|
194
|
+
return res.status(400).json({ error: providerKeyError });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
171
197
|
|
|
172
198
|
const channel = updateChannel(channelId, updates);
|
|
173
199
|
// 清除该渠道的模型重定向日志缓存,使下次请求时重新打印
|
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
setDefaultCredential,
|
|
10
10
|
deleteCredential,
|
|
11
11
|
applyStoredCredential,
|
|
12
|
+
disableStoredCredential,
|
|
12
13
|
clearNativeOAuthState,
|
|
13
14
|
fetchCredentialUsage
|
|
14
15
|
} = require('../services/oauth-credentials-service');
|
|
@@ -117,10 +118,31 @@ router.post('/:tool/:credentialId/apply', async (req, res) => {
|
|
|
117
118
|
assertTool(tool);
|
|
118
119
|
const result = await applyStoredCredential(tool, credentialId);
|
|
119
120
|
broadcastToolProxyState(tool);
|
|
121
|
+
const message = tool === 'opencode'
|
|
122
|
+
? 'opencode 已应用 OAuth 凭证,并保留现有 API providers'
|
|
123
|
+
: `${tool} 已切换到 OAuth 凭证控制`;
|
|
120
124
|
res.json({
|
|
121
125
|
tool,
|
|
122
126
|
...result,
|
|
123
|
-
message
|
|
127
|
+
message
|
|
128
|
+
});
|
|
129
|
+
} catch (error) {
|
|
130
|
+
res.status(error.statusCode || 500).json({ error: error.message });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
router.post('/:tool/:credentialId/disable-native', (req, res) => {
|
|
135
|
+
try {
|
|
136
|
+
const { tool, credentialId } = req.params;
|
|
137
|
+
assertTool(tool);
|
|
138
|
+
const result = disableStoredCredential(tool, credentialId);
|
|
139
|
+
broadcastToolProxyState(tool);
|
|
140
|
+
res.json({
|
|
141
|
+
tool,
|
|
142
|
+
...result,
|
|
143
|
+
message: tool === 'opencode'
|
|
144
|
+
? 'opencode OAuth provider 已关闭'
|
|
145
|
+
: `${tool} 本机 OAuth 已关闭`
|
|
124
146
|
});
|
|
125
147
|
} catch (error) {
|
|
126
148
|
res.status(error.statusCode || 500).json({ error: error.message });
|
|
@@ -16,7 +16,6 @@ const {
|
|
|
16
16
|
getCurrentProxyPort
|
|
17
17
|
} = require('../services/opencode-settings-manager');
|
|
18
18
|
const { getChannels, getEnabledChannels, applyChannelToSettings } = require('../services/opencode-channels');
|
|
19
|
-
const { clearNativeOAuth } = require('../services/native-oauth-adapters');
|
|
20
19
|
const { getSchedulerState } = require('../services/channel-scheduler');
|
|
21
20
|
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
22
21
|
const fs = require('fs');
|
|
@@ -195,7 +194,6 @@ router.post('/start', async (req, res) => {
|
|
|
195
194
|
});
|
|
196
195
|
|
|
197
196
|
const activeModel = currentChannel.model || currentChannel.speedTestModel || null;
|
|
198
|
-
clearNativeOAuth('opencode');
|
|
199
197
|
setProxyConfig(proxyResult.port, { channels: channelPayloads, model: activeModel });
|
|
200
198
|
|
|
201
199
|
// 5. 广播状态更新
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const express = require('express');
|
|
8
8
|
const { PluginsService } = require('../services/plugins-service');
|
|
9
|
+
const { maskToken } = require('../services/oauth-utils');
|
|
9
10
|
|
|
10
11
|
const router = express.Router();
|
|
11
12
|
const SUPPORTED_PLATFORMS = ['claude', 'opencode'];
|
|
@@ -27,6 +28,41 @@ function getPluginsService(req) {
|
|
|
27
28
|
return { platform, service: pluginServices.get(platform) };
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
function extractRepoPayload(source = {}) {
|
|
32
|
+
const repo = source.repo && typeof source.repo === 'object' ? source.repo : source;
|
|
33
|
+
return {
|
|
34
|
+
id: repo.id || source.repoId || '',
|
|
35
|
+
provider: repo.provider || source.provider || '',
|
|
36
|
+
host: repo.host || source.host || '',
|
|
37
|
+
owner: repo.owner || source.owner || '',
|
|
38
|
+
name: repo.name || source.name || '',
|
|
39
|
+
branch: repo.branch || source.branch || 'main',
|
|
40
|
+
directory: repo.directory || source.directory || '',
|
|
41
|
+
projectPath: repo.projectPath || source.projectPath || '',
|
|
42
|
+
localPath: repo.localPath || source.localPath || '',
|
|
43
|
+
repoUrl: repo.repoUrl || repo.url || source.repoUrl || source.url || '',
|
|
44
|
+
token: repo.token || source.token || ''
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function sanitizeRepo(repo = {}) {
|
|
49
|
+
const token = String(repo.token || '').trim();
|
|
50
|
+
const sanitized = {
|
|
51
|
+
...repo,
|
|
52
|
+
hasToken: Boolean(token),
|
|
53
|
+
tokenPreview: token ? maskToken(token) : ''
|
|
54
|
+
};
|
|
55
|
+
delete sanitized.token;
|
|
56
|
+
return sanitized;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sanitizeRepos(service, repos = []) {
|
|
60
|
+
if (typeof service.getReposForClient === 'function') {
|
|
61
|
+
return service.getReposForClient(repos);
|
|
62
|
+
}
|
|
63
|
+
return (Array.isArray(repos) ? repos : []).map(sanitizeRepo);
|
|
64
|
+
}
|
|
65
|
+
|
|
30
66
|
/**
|
|
31
67
|
* 获取插件列表
|
|
32
68
|
* GET /api/plugins
|
|
@@ -93,7 +129,7 @@ router.post('/install', async (req, res) => {
|
|
|
93
129
|
if (source) {
|
|
94
130
|
installUrl = source;
|
|
95
131
|
} else if (directory && repo) {
|
|
96
|
-
installUrl =
|
|
132
|
+
installUrl = '';
|
|
97
133
|
} else if (gitUrl) {
|
|
98
134
|
installUrl = gitUrl;
|
|
99
135
|
} else {
|
|
@@ -103,7 +139,15 @@ router.post('/install', async (req, res) => {
|
|
|
103
139
|
});
|
|
104
140
|
}
|
|
105
141
|
|
|
106
|
-
const result = await service.installPlugin(
|
|
142
|
+
const result = await service.installPlugin(
|
|
143
|
+
installUrl,
|
|
144
|
+
directory && repo
|
|
145
|
+
? {
|
|
146
|
+
...extractRepoPayload({ repo }),
|
|
147
|
+
directory
|
|
148
|
+
}
|
|
149
|
+
: null
|
|
150
|
+
);
|
|
107
151
|
|
|
108
152
|
if (!result.success) {
|
|
109
153
|
return res.status(400).json({
|
|
@@ -140,7 +184,7 @@ router.get('/repos', (req, res) => {
|
|
|
140
184
|
res.json({
|
|
141
185
|
success: true,
|
|
142
186
|
platform,
|
|
143
|
-
repos
|
|
187
|
+
repos: sanitizeRepos(service, repos)
|
|
144
188
|
});
|
|
145
189
|
} catch (err) {
|
|
146
190
|
console.error('[Plugins API] Get repos error:', err);
|
|
@@ -159,12 +203,13 @@ router.get('/repos', (req, res) => {
|
|
|
159
203
|
router.post('/repos', (req, res) => {
|
|
160
204
|
try {
|
|
161
205
|
const { platform, service } = getPluginsService(req);
|
|
162
|
-
const repo = req.body;
|
|
206
|
+
const repo = extractRepoPayload(req.body);
|
|
207
|
+
repo.enabled = req.body.enabled !== false;
|
|
163
208
|
|
|
164
|
-
if (!repo || !repo.
|
|
209
|
+
if (!repo.localPath && !repo.projectPath && (!repo.owner || !repo.name) && !repo.repoUrl) {
|
|
165
210
|
return res.status(400).json({
|
|
166
211
|
success: false,
|
|
167
|
-
message: '
|
|
212
|
+
message: 'Missing repo info'
|
|
168
213
|
});
|
|
169
214
|
}
|
|
170
215
|
|
|
@@ -173,7 +218,7 @@ router.post('/repos', (req, res) => {
|
|
|
173
218
|
res.json({
|
|
174
219
|
success: true,
|
|
175
220
|
platform,
|
|
176
|
-
repos,
|
|
221
|
+
repos: sanitizeRepos(service, repos),
|
|
177
222
|
message: 'Repository added successfully'
|
|
178
223
|
});
|
|
179
224
|
} catch (err) {
|
|
@@ -185,6 +230,54 @@ router.post('/repos', (req, res) => {
|
|
|
185
230
|
}
|
|
186
231
|
});
|
|
187
232
|
|
|
233
|
+
router.delete('/repos', (req, res) => {
|
|
234
|
+
try {
|
|
235
|
+
const { platform, service } = getPluginsService(req);
|
|
236
|
+
const { id = '', owner = '', name = '' } = req.query;
|
|
237
|
+
const repos = service.removeRepo(owner, name, id);
|
|
238
|
+
|
|
239
|
+
res.json({
|
|
240
|
+
success: true,
|
|
241
|
+
platform,
|
|
242
|
+
repos: sanitizeRepos(service, repos)
|
|
243
|
+
});
|
|
244
|
+
} catch (err) {
|
|
245
|
+
console.error('[Plugins API] Remove repo error:', err);
|
|
246
|
+
res.status(500).json({
|
|
247
|
+
success: false,
|
|
248
|
+
message: err.message
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
router.put('/repos/toggle', (req, res) => {
|
|
254
|
+
try {
|
|
255
|
+
const { platform, service } = getPluginsService(req);
|
|
256
|
+
const { id = '', owner = '', name = '', enabled } = req.body;
|
|
257
|
+
|
|
258
|
+
if (typeof enabled !== 'boolean') {
|
|
259
|
+
return res.status(400).json({
|
|
260
|
+
success: false,
|
|
261
|
+
message: 'enabled must be a boolean'
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const repos = service.toggleRepo(owner, name, enabled, id);
|
|
266
|
+
|
|
267
|
+
res.json({
|
|
268
|
+
success: true,
|
|
269
|
+
platform,
|
|
270
|
+
repos: sanitizeRepos(service, repos)
|
|
271
|
+
});
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error('[Plugins API] Toggle repo error:', err);
|
|
274
|
+
res.status(500).json({
|
|
275
|
+
success: false,
|
|
276
|
+
message: err.message
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
188
281
|
/**
|
|
189
282
|
* 删除插件仓库
|
|
190
283
|
* DELETE /api/plugins/repos/:owner/:name
|
|
@@ -193,13 +286,14 @@ router.delete('/repos/:owner/:name', (req, res) => {
|
|
|
193
286
|
try {
|
|
194
287
|
const { platform, service } = getPluginsService(req);
|
|
195
288
|
const { owner, name } = req.params;
|
|
289
|
+
const { id = '' } = req.query;
|
|
196
290
|
|
|
197
|
-
const repos = service.removeRepo(owner, name);
|
|
291
|
+
const repos = service.removeRepo(owner, name, id);
|
|
198
292
|
|
|
199
293
|
res.json({
|
|
200
294
|
success: true,
|
|
201
295
|
platform,
|
|
202
|
-
repos,
|
|
296
|
+
repos: sanitizeRepos(service, repos),
|
|
203
297
|
message: 'Repository removed successfully'
|
|
204
298
|
});
|
|
205
299
|
} catch (err) {
|
|
@@ -220,7 +314,7 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
|
220
314
|
try {
|
|
221
315
|
const { platform, service } = getPluginsService(req);
|
|
222
316
|
const { owner, name } = req.params;
|
|
223
|
-
const { enabled } = req.body;
|
|
317
|
+
const { enabled, id = '' } = req.body;
|
|
224
318
|
|
|
225
319
|
if (typeof enabled !== 'boolean') {
|
|
226
320
|
return res.status(400).json({
|
|
@@ -229,12 +323,12 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
|
229
323
|
});
|
|
230
324
|
}
|
|
231
325
|
|
|
232
|
-
const repos = service.toggleRepo(owner, name, enabled);
|
|
326
|
+
const repos = service.toggleRepo(owner, name, enabled, id);
|
|
233
327
|
|
|
234
328
|
res.json({
|
|
235
329
|
success: true,
|
|
236
330
|
platform,
|
|
237
|
-
repos,
|
|
331
|
+
repos: sanitizeRepos(service, repos),
|
|
238
332
|
message: `Repository ${enabled ? 'enabled' : 'disabled'} successfully`
|
|
239
333
|
});
|
|
240
334
|
} catch (err) {
|
|
@@ -246,6 +340,40 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
|
246
340
|
}
|
|
247
341
|
});
|
|
248
342
|
|
|
343
|
+
router.put('/repos/auth', (req, res) => {
|
|
344
|
+
try {
|
|
345
|
+
const { platform, service } = getPluginsService(req);
|
|
346
|
+
const {
|
|
347
|
+
id = '',
|
|
348
|
+
owner = '',
|
|
349
|
+
name = '',
|
|
350
|
+
token = '',
|
|
351
|
+
clearToken = false
|
|
352
|
+
} = req.body;
|
|
353
|
+
|
|
354
|
+
if (!clearToken && !String(token || '').trim()) {
|
|
355
|
+
return res.status(400).json({
|
|
356
|
+
success: false,
|
|
357
|
+
message: 'Missing token'
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const repos = service.updateRepoAuth(owner, name, token, clearToken, id);
|
|
362
|
+
|
|
363
|
+
res.json({
|
|
364
|
+
success: true,
|
|
365
|
+
platform,
|
|
366
|
+
repos: sanitizeRepos(service, repos)
|
|
367
|
+
});
|
|
368
|
+
} catch (err) {
|
|
369
|
+
console.error('[Plugins API] Update repo auth error:', err);
|
|
370
|
+
res.status(500).json({
|
|
371
|
+
success: false,
|
|
372
|
+
message: err.message
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
249
377
|
/**
|
|
250
378
|
* 同步仓库到 Claude Code marketplace
|
|
251
379
|
* POST /api/plugins/repos/sync
|
|
@@ -303,16 +431,35 @@ router.get('/:name/readme', async (req, res) => {
|
|
|
303
431
|
try {
|
|
304
432
|
const { platform, service } = getPluginsService(req);
|
|
305
433
|
const { name } = req.params;
|
|
306
|
-
const {
|
|
434
|
+
const {
|
|
435
|
+
repoId,
|
|
436
|
+
repoProvider,
|
|
437
|
+
repoHost,
|
|
438
|
+
repoOwner,
|
|
439
|
+
repoName,
|
|
440
|
+
repoBranch,
|
|
441
|
+
directory,
|
|
442
|
+
source,
|
|
443
|
+
repoUrl,
|
|
444
|
+
repoProjectPath,
|
|
445
|
+
repoLocalPath,
|
|
446
|
+
installPath
|
|
447
|
+
} = req.query;
|
|
307
448
|
|
|
308
449
|
const pluginInfo = {
|
|
309
450
|
name,
|
|
451
|
+
repoId,
|
|
452
|
+
repoProvider,
|
|
453
|
+
repoHost,
|
|
310
454
|
repoOwner,
|
|
311
455
|
repoName,
|
|
312
456
|
repoBranch,
|
|
313
457
|
directory,
|
|
314
458
|
source,
|
|
315
|
-
repoUrl
|
|
459
|
+
repoUrl,
|
|
460
|
+
repoProjectPath,
|
|
461
|
+
repoLocalPath,
|
|
462
|
+
installPath
|
|
316
463
|
};
|
|
317
464
|
|
|
318
465
|
const readme = await service.getPluginReadme(pluginInfo);
|
package/src/server/api/skills.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const express = require('express');
|
|
6
6
|
const { SkillService } = require('../services/skill-service');
|
|
7
|
+
const { maskToken } = require('../services/oauth-utils');
|
|
7
8
|
|
|
8
9
|
const router = express.Router();
|
|
9
10
|
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
@@ -37,10 +38,29 @@ function extractRepoPayload(source = {}) {
|
|
|
37
38
|
directory: repo.directory || source.directory || '',
|
|
38
39
|
projectPath: repo.projectPath || source.projectPath || '',
|
|
39
40
|
localPath: repo.localPath || source.localPath || '',
|
|
40
|
-
repoUrl: repo.repoUrl || source.repoUrl || ''
|
|
41
|
+
repoUrl: repo.repoUrl || source.repoUrl || '',
|
|
42
|
+
token: repo.token || source.token || ''
|
|
41
43
|
};
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
function sanitizeRepo(repo = {}) {
|
|
47
|
+
const token = String(repo.token || '').trim();
|
|
48
|
+
const sanitized = {
|
|
49
|
+
...repo,
|
|
50
|
+
hasToken: Boolean(token),
|
|
51
|
+
tokenPreview: token ? maskToken(token) : ''
|
|
52
|
+
};
|
|
53
|
+
delete sanitized.token;
|
|
54
|
+
return sanitized;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sanitizeRepos(service, repos = []) {
|
|
58
|
+
if (typeof service.getReposForClient === 'function') {
|
|
59
|
+
return service.getReposForClient(repos);
|
|
60
|
+
}
|
|
61
|
+
return (Array.isArray(repos) ? repos : []).map(sanitizeRepo);
|
|
62
|
+
}
|
|
63
|
+
|
|
44
64
|
/**
|
|
45
65
|
* 获取技能列表
|
|
46
66
|
* GET /api/skills
|
|
@@ -290,7 +310,7 @@ router.get('/repos', (req, res) => {
|
|
|
290
310
|
res.json({
|
|
291
311
|
success: true,
|
|
292
312
|
platform,
|
|
293
|
-
repos
|
|
313
|
+
repos: sanitizeRepos(service, repos)
|
|
294
314
|
});
|
|
295
315
|
} catch (err) {
|
|
296
316
|
console.error('[Skills API] Get repos error:', err);
|
|
@@ -325,7 +345,7 @@ router.post('/repos', (req, res) => {
|
|
|
325
345
|
res.json({
|
|
326
346
|
success: true,
|
|
327
347
|
platform,
|
|
328
|
-
repos
|
|
348
|
+
repos: sanitizeRepos(service, repos)
|
|
329
349
|
});
|
|
330
350
|
} catch (err) {
|
|
331
351
|
console.error('[Skills API] Add repo error:', err);
|
|
@@ -345,7 +365,7 @@ router.delete('/repos', (req, res) => {
|
|
|
345
365
|
res.json({
|
|
346
366
|
success: true,
|
|
347
367
|
platform,
|
|
348
|
-
repos
|
|
368
|
+
repos: sanitizeRepos(service, repos)
|
|
349
369
|
});
|
|
350
370
|
} catch (err) {
|
|
351
371
|
console.error('[Skills API] Remove repo error:', err);
|
|
@@ -366,7 +386,7 @@ router.put('/repos/toggle', (req, res) => {
|
|
|
366
386
|
res.json({
|
|
367
387
|
success: true,
|
|
368
388
|
platform,
|
|
369
|
-
repos
|
|
389
|
+
repos: sanitizeRepos(service, repos)
|
|
370
390
|
});
|
|
371
391
|
} catch (err) {
|
|
372
392
|
console.error('[Skills API] Toggle repo error:', err);
|
|
@@ -377,6 +397,41 @@ router.put('/repos/toggle', (req, res) => {
|
|
|
377
397
|
}
|
|
378
398
|
});
|
|
379
399
|
|
|
400
|
+
router.put('/repos/auth', (req, res) => {
|
|
401
|
+
try {
|
|
402
|
+
const { platform, service } = getSkillService(req);
|
|
403
|
+
const {
|
|
404
|
+
id = '',
|
|
405
|
+
owner = '',
|
|
406
|
+
name = '',
|
|
407
|
+
directory = '',
|
|
408
|
+
token = '',
|
|
409
|
+
clearToken = false
|
|
410
|
+
} = req.body;
|
|
411
|
+
|
|
412
|
+
if (!clearToken && !String(token || '').trim()) {
|
|
413
|
+
return res.status(400).json({
|
|
414
|
+
success: false,
|
|
415
|
+
message: 'Missing token'
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const repos = service.updateRepoAuth(owner, name, directory, token, clearToken, id);
|
|
420
|
+
|
|
421
|
+
res.json({
|
|
422
|
+
success: true,
|
|
423
|
+
platform,
|
|
424
|
+
repos: sanitizeRepos(service, repos)
|
|
425
|
+
});
|
|
426
|
+
} catch (err) {
|
|
427
|
+
console.error('[Skills API] Update repo auth error:', err);
|
|
428
|
+
res.status(500).json({
|
|
429
|
+
success: false,
|
|
430
|
+
message: err.message
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
380
435
|
/**
|
|
381
436
|
* 删除仓库
|
|
382
437
|
* DELETE /api/skills/repos/:owner/:name
|
|
@@ -392,7 +447,7 @@ router.delete('/repos/:owner/:name', (req, res) => {
|
|
|
392
447
|
res.json({
|
|
393
448
|
success: true,
|
|
394
449
|
platform,
|
|
395
|
-
repos
|
|
450
|
+
repos: sanitizeRepos(service, repos)
|
|
396
451
|
});
|
|
397
452
|
} catch (err) {
|
|
398
453
|
console.error('[Skills API] Remove repo error:', err);
|
|
@@ -420,7 +475,7 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
|
420
475
|
res.json({
|
|
421
476
|
success: true,
|
|
422
477
|
platform,
|
|
423
|
-
repos
|
|
478
|
+
repos: sanitizeRepos(service, repos)
|
|
424
479
|
});
|
|
425
480
|
} catch (err) {
|
|
426
481
|
console.error('[Skills API] Toggle repo error:', err);
|
|
@@ -15,6 +15,7 @@ const { getEffectiveApiKey } = require('./services/codex-channels');
|
|
|
15
15
|
const { persistProxyRequestSnapshot } = require('./services/request-logger');
|
|
16
16
|
const { publishUsageLog, publishFailureLog } = require('./services/proxy-log-helper');
|
|
17
17
|
const { redirectModel, resolveTargetUrl } = require('./services/base/proxy-utils');
|
|
18
|
+
const { attachServerShutdownHandling, expediteServerShutdown } = require('./services/server-shutdown');
|
|
18
19
|
|
|
19
20
|
let proxyServer = null;
|
|
20
21
|
let proxyApp = null;
|
|
@@ -519,6 +520,7 @@ async function startCodexProxyServer(options = {}) {
|
|
|
519
520
|
|
|
520
521
|
// 启动服务器
|
|
521
522
|
proxyServer = http.createServer(proxyApp);
|
|
523
|
+
attachServerShutdownHandling(proxyServer);
|
|
522
524
|
|
|
523
525
|
return new Promise((resolve, reject) => {
|
|
524
526
|
proxyServer.listen(port, '127.0.0.1', () => {
|
|
@@ -559,8 +561,13 @@ async function stopCodexProxyServer(options = {}) {
|
|
|
559
561
|
|
|
560
562
|
requestMetadata.clear();
|
|
561
563
|
|
|
564
|
+
const shutdownTimer = expediteServerShutdown(proxyServer);
|
|
565
|
+
|
|
562
566
|
return new Promise((resolve) => {
|
|
563
567
|
proxyServer.close(() => {
|
|
568
|
+
if (shutdownTimer) {
|
|
569
|
+
clearTimeout(shutdownTimer);
|
|
570
|
+
}
|
|
564
571
|
console.log('Codex proxy server stopped');
|
|
565
572
|
|
|
566
573
|
// 清除代理启动时间(仅当明确要求时)
|
|
@@ -580,8 +587,9 @@ async function stopCodexProxyServer(options = {}) {
|
|
|
580
587
|
// 获取代理服务器状态
|
|
581
588
|
function getCodexProxyStatus() {
|
|
582
589
|
const config = loadConfig();
|
|
583
|
-
const
|
|
584
|
-
const
|
|
590
|
+
const allowRecovery = !!proxyServer;
|
|
591
|
+
const startTime = getProxyStartTime('codex', { allowRecovery });
|
|
592
|
+
const runtime = getProxyRuntime('codex', { allowRecovery });
|
|
585
593
|
|
|
586
594
|
return {
|
|
587
595
|
running: !!proxyServer,
|
|
@@ -15,6 +15,7 @@ const { getEffectiveApiKey } = require('./services/gemini-channels');
|
|
|
15
15
|
const { persistProxyRequestSnapshot } = require('./services/request-logger');
|
|
16
16
|
const { publishUsageLog, publishFailureLog } = require('./services/proxy-log-helper');
|
|
17
17
|
const { redirectModel: redirectModelBase, resolveTargetUrl } = require('./services/base/proxy-utils');
|
|
18
|
+
const { attachServerShutdownHandling, expediteServerShutdown } = require('./services/server-shutdown');
|
|
18
19
|
|
|
19
20
|
let proxyServer = null;
|
|
20
21
|
let proxyApp = null;
|
|
@@ -512,6 +513,7 @@ async function startGeminiProxyServer(options = {}) {
|
|
|
512
513
|
|
|
513
514
|
// 启动服务器
|
|
514
515
|
proxyServer = http.createServer(proxyApp);
|
|
516
|
+
attachServerShutdownHandling(proxyServer);
|
|
515
517
|
|
|
516
518
|
return new Promise((resolve, reject) => {
|
|
517
519
|
proxyServer.listen(port, '127.0.0.1', () => {
|
|
@@ -552,8 +554,13 @@ async function stopGeminiProxyServer(options = {}) {
|
|
|
552
554
|
|
|
553
555
|
requestMetadata.clear();
|
|
554
556
|
|
|
557
|
+
const shutdownTimer = expediteServerShutdown(proxyServer);
|
|
558
|
+
|
|
555
559
|
return new Promise((resolve) => {
|
|
556
560
|
proxyServer.close(() => {
|
|
561
|
+
if (shutdownTimer) {
|
|
562
|
+
clearTimeout(shutdownTimer);
|
|
563
|
+
}
|
|
557
564
|
console.log('Gemini proxy server stopped');
|
|
558
565
|
|
|
559
566
|
// 清除代理启动时间(仅当明确要求时)
|
|
@@ -573,8 +580,9 @@ async function stopGeminiProxyServer(options = {}) {
|
|
|
573
580
|
// 获取代理服务器状态
|
|
574
581
|
function getGeminiProxyStatus() {
|
|
575
582
|
const config = loadConfig();
|
|
576
|
-
const
|
|
577
|
-
const
|
|
583
|
+
const allowRecovery = !!proxyServer;
|
|
584
|
+
const startTime = getProxyStartTime('gemini', { allowRecovery });
|
|
585
|
+
const runtime = getProxyRuntime('gemini', { allowRecovery });
|
|
578
586
|
|
|
579
587
|
return {
|
|
580
588
|
running: !!proxyServer,
|