coding-tool-x 3.4.3 → 3.4.5
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-CbGxotgz.js → Analytics-DFWyPf5C.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-oP6nrFEb.js → ConfigTemplates-BFE7hmKd.js} +1 -1
- package/dist/web/assets/{Home-DMntmEvh.js → Home-DZUuCrxk.js} +1 -1
- package/dist/web/assets/{PluginManager-BUC_c7nH.js → PluginManager-WyGY2BQN.js} +1 -1
- package/dist/web/assets/{ProjectList-CW8J49n7.js → ProjectList-CBc0QawN.js} +1 -1
- package/dist/web/assets/{ProjectList-oJIyIRkP.css → ProjectList-DL4JK6ci.css} +1 -1
- package/dist/web/assets/{SessionList-7lYnF92v.js → SessionList-CdPR7QLq.js} +1 -1
- package/dist/web/assets/{SkillManager-Cs08216i.js → SkillManager-B5-DxQOS.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-CY-oGtyB.js → WorkspaceManager-C7yqFjpi.js} +1 -1
- package/dist/web/assets/index-BDsmoSfO.js +2 -0
- package/dist/web/assets/{index-5qy5NMIP.css → index-C1pzEgmj.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/src/commands/channels.js +13 -13
- package/src/commands/cli-type.js +5 -5
- package/src/commands/daemon.js +31 -31
- package/src/commands/doctor.js +14 -14
- package/src/commands/export-config.js +23 -23
- package/src/commands/list.js +4 -4
- package/src/commands/logs.js +19 -19
- package/src/commands/plugin.js +62 -62
- package/src/commands/port-config.js +4 -4
- package/src/commands/proxy-control.js +35 -35
- package/src/commands/proxy.js +28 -28
- package/src/commands/resume.js +4 -4
- package/src/commands/search.js +9 -9
- package/src/commands/security.js +5 -5
- package/src/commands/stats.js +18 -18
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +18 -18
- package/src/commands/ui.js +11 -11
- package/src/commands/update.js +9 -9
- package/src/commands/workspace.js +11 -11
- package/src/index.js +24 -24
- package/src/plugins/plugin-installer.js +1 -1
- package/src/reset-config.js +9 -9
- package/src/server/api/channels.js +1 -1
- package/src/server/api/claude-hooks.js +3 -2
- package/src/server/api/plugins.js +165 -14
- package/src/server/api/pm2-autostart.js +2 -2
- package/src/server/api/proxy.js +6 -6
- package/src/server/api/skills.js +66 -7
- package/src/server/codex-proxy-server.js +10 -2
- package/src/server/dev-server.js +2 -2
- package/src/server/gemini-proxy-server.js +10 -2
- package/src/server/index.js +37 -37
- package/src/server/opencode-proxy-server.js +10 -2
- package/src/server/proxy-server.js +14 -6
- package/src/server/services/codex-channels.js +64 -21
- package/src/server/services/codex-env-manager.js +44 -28
- package/src/server/services/config-export-service.js +1 -1
- package/src/server/services/mcp-service.js +2 -1
- package/src/server/services/model-detector.js +2 -2
- package/src/server/services/native-keychain.js +1 -0
- package/src/server/services/plugins-service.js +1066 -261
- package/src/server/services/proxy-runtime.js +129 -5
- package/src/server/services/server-shutdown.js +79 -0
- package/src/server/services/settings-manager.js +3 -3
- package/src/server/services/skill-service.js +146 -29
- package/src/server/websocket-server.js +8 -8
- package/src/ui/menu.js +2 -2
- package/src/ui/prompts.js +5 -5
- package/dist/web/assets/index-ClCqKpvX.js +0 -2
|
@@ -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
|
|
@@ -58,7 +94,11 @@ router.get('/market', async (req, res) => {
|
|
|
58
94
|
try {
|
|
59
95
|
const { platform, service } = getPluginsService(req);
|
|
60
96
|
const forceRefresh = req.query.refresh === '1';
|
|
97
|
+
if (forceRefresh) {
|
|
98
|
+
console.log(`[Plugins API] Refreshing market plugins for ${platform}...`);
|
|
99
|
+
}
|
|
61
100
|
const plugins = await service.getMarketPlugins(forceRefresh);
|
|
101
|
+
console.log(`[Plugins API] ${platform}: ${plugins.length} market plugins loaded (refresh=${forceRefresh})`);
|
|
62
102
|
|
|
63
103
|
res.json({
|
|
64
104
|
success: true,
|
|
@@ -89,7 +129,7 @@ router.post('/install', async (req, res) => {
|
|
|
89
129
|
if (source) {
|
|
90
130
|
installUrl = source;
|
|
91
131
|
} else if (directory && repo) {
|
|
92
|
-
installUrl =
|
|
132
|
+
installUrl = '';
|
|
93
133
|
} else if (gitUrl) {
|
|
94
134
|
installUrl = gitUrl;
|
|
95
135
|
} else {
|
|
@@ -99,7 +139,15 @@ router.post('/install', async (req, res) => {
|
|
|
99
139
|
});
|
|
100
140
|
}
|
|
101
141
|
|
|
102
|
-
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
|
+
);
|
|
103
151
|
|
|
104
152
|
if (!result.success) {
|
|
105
153
|
return res.status(400).json({
|
|
@@ -136,7 +184,7 @@ router.get('/repos', (req, res) => {
|
|
|
136
184
|
res.json({
|
|
137
185
|
success: true,
|
|
138
186
|
platform,
|
|
139
|
-
repos
|
|
187
|
+
repos: sanitizeRepos(service, repos)
|
|
140
188
|
});
|
|
141
189
|
} catch (err) {
|
|
142
190
|
console.error('[Plugins API] Get repos error:', err);
|
|
@@ -155,12 +203,13 @@ router.get('/repos', (req, res) => {
|
|
|
155
203
|
router.post('/repos', (req, res) => {
|
|
156
204
|
try {
|
|
157
205
|
const { platform, service } = getPluginsService(req);
|
|
158
|
-
const repo = req.body;
|
|
206
|
+
const repo = extractRepoPayload(req.body);
|
|
207
|
+
repo.enabled = req.body.enabled !== false;
|
|
159
208
|
|
|
160
|
-
if (!repo || !repo.
|
|
209
|
+
if (!repo.localPath && !repo.projectPath && (!repo.owner || !repo.name) && !repo.repoUrl) {
|
|
161
210
|
return res.status(400).json({
|
|
162
211
|
success: false,
|
|
163
|
-
message: '
|
|
212
|
+
message: 'Missing repo info'
|
|
164
213
|
});
|
|
165
214
|
}
|
|
166
215
|
|
|
@@ -169,7 +218,7 @@ router.post('/repos', (req, res) => {
|
|
|
169
218
|
res.json({
|
|
170
219
|
success: true,
|
|
171
220
|
platform,
|
|
172
|
-
repos,
|
|
221
|
+
repos: sanitizeRepos(service, repos),
|
|
173
222
|
message: 'Repository added successfully'
|
|
174
223
|
});
|
|
175
224
|
} catch (err) {
|
|
@@ -181,6 +230,54 @@ router.post('/repos', (req, res) => {
|
|
|
181
230
|
}
|
|
182
231
|
});
|
|
183
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
|
+
|
|
184
281
|
/**
|
|
185
282
|
* 删除插件仓库
|
|
186
283
|
* DELETE /api/plugins/repos/:owner/:name
|
|
@@ -189,13 +286,14 @@ router.delete('/repos/:owner/:name', (req, res) => {
|
|
|
189
286
|
try {
|
|
190
287
|
const { platform, service } = getPluginsService(req);
|
|
191
288
|
const { owner, name } = req.params;
|
|
289
|
+
const { id = '' } = req.query;
|
|
192
290
|
|
|
193
|
-
const repos = service.removeRepo(owner, name);
|
|
291
|
+
const repos = service.removeRepo(owner, name, id);
|
|
194
292
|
|
|
195
293
|
res.json({
|
|
196
294
|
success: true,
|
|
197
295
|
platform,
|
|
198
|
-
repos,
|
|
296
|
+
repos: sanitizeRepos(service, repos),
|
|
199
297
|
message: 'Repository removed successfully'
|
|
200
298
|
});
|
|
201
299
|
} catch (err) {
|
|
@@ -216,7 +314,7 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
|
216
314
|
try {
|
|
217
315
|
const { platform, service } = getPluginsService(req);
|
|
218
316
|
const { owner, name } = req.params;
|
|
219
|
-
const { enabled } = req.body;
|
|
317
|
+
const { enabled, id = '' } = req.body;
|
|
220
318
|
|
|
221
319
|
if (typeof enabled !== 'boolean') {
|
|
222
320
|
return res.status(400).json({
|
|
@@ -225,12 +323,12 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
|
225
323
|
});
|
|
226
324
|
}
|
|
227
325
|
|
|
228
|
-
const repos = service.toggleRepo(owner, name, enabled);
|
|
326
|
+
const repos = service.toggleRepo(owner, name, enabled, id);
|
|
229
327
|
|
|
230
328
|
res.json({
|
|
231
329
|
success: true,
|
|
232
330
|
platform,
|
|
233
|
-
repos,
|
|
331
|
+
repos: sanitizeRepos(service, repos),
|
|
234
332
|
message: `Repository ${enabled ? 'enabled' : 'disabled'} successfully`
|
|
235
333
|
});
|
|
236
334
|
} catch (err) {
|
|
@@ -242,6 +340,40 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
|
242
340
|
}
|
|
243
341
|
});
|
|
244
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
|
+
|
|
245
377
|
/**
|
|
246
378
|
* 同步仓库到 Claude Code marketplace
|
|
247
379
|
* POST /api/plugins/repos/sync
|
|
@@ -299,16 +431,35 @@ router.get('/:name/readme', async (req, res) => {
|
|
|
299
431
|
try {
|
|
300
432
|
const { platform, service } = getPluginsService(req);
|
|
301
433
|
const { name } = req.params;
|
|
302
|
-
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;
|
|
303
448
|
|
|
304
449
|
const pluginInfo = {
|
|
305
450
|
name,
|
|
451
|
+
repoId,
|
|
452
|
+
repoProvider,
|
|
453
|
+
repoHost,
|
|
306
454
|
repoOwner,
|
|
307
455
|
repoName,
|
|
308
456
|
repoBranch,
|
|
309
457
|
directory,
|
|
310
458
|
source,
|
|
311
|
-
repoUrl
|
|
459
|
+
repoUrl,
|
|
460
|
+
repoProjectPath,
|
|
461
|
+
repoLocalPath,
|
|
462
|
+
installPath
|
|
312
463
|
};
|
|
313
464
|
|
|
314
465
|
const readme = await service.getPluginReadme(pluginInfo);
|
|
@@ -10,9 +10,9 @@ const execAsync = promisify(exec);
|
|
|
10
10
|
|
|
11
11
|
function getExecOptions(timeout = 30000, runtimePlatform = process.platform) {
|
|
12
12
|
if (runtimePlatform === 'win32') {
|
|
13
|
-
return { timeout };
|
|
13
|
+
return { timeout, windowsHide: true };
|
|
14
14
|
}
|
|
15
|
-
return { shell: '/bin/bash', timeout };
|
|
15
|
+
return { shell: '/bin/bash', timeout, windowsHide: true };
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
package/src/server/api/proxy.js
CHANGED
|
@@ -208,7 +208,7 @@ router.post('/start', async (req, res) => {
|
|
|
208
208
|
|
|
209
209
|
// 3. 保存当前激活渠道ID(用于代理模式)
|
|
210
210
|
saveActiveChannelId(currentChannel.id);
|
|
211
|
-
console.log(
|
|
211
|
+
console.log(`[OK] Saved active channel: ${currentChannel.name} (${currentChannel.id})`);
|
|
212
212
|
|
|
213
213
|
// 4. 启动代理服务器
|
|
214
214
|
const proxyResult = await startProxyServer();
|
|
@@ -256,11 +256,11 @@ router.post('/stop', async (req, res) => {
|
|
|
256
256
|
if (restoredChannel) {
|
|
257
257
|
if (hadBackup) {
|
|
258
258
|
deleteBackup();
|
|
259
|
-
console.log('
|
|
259
|
+
console.log('[OK] Discarded backup snapshot');
|
|
260
260
|
}
|
|
261
261
|
} else if (hadBackup) {
|
|
262
262
|
restoreSettings();
|
|
263
|
-
console.log('
|
|
263
|
+
console.log('[OK] Restored settings from backup');
|
|
264
264
|
const channels = getAllChannels();
|
|
265
265
|
const currentSettings = require('../services/channels').getCurrentSettings();
|
|
266
266
|
if (currentSettings) {
|
|
@@ -274,7 +274,7 @@ router.post('/stop', async (req, res) => {
|
|
|
274
274
|
if (restoredChannel) {
|
|
275
275
|
const { applyChannelToSettings } = require('../services/channels');
|
|
276
276
|
applyChannelToSettings(restoredChannel.id);
|
|
277
|
-
console.log(
|
|
277
|
+
console.log(`[OK] Single-channel mode restored: ${restoredChannel.name}`);
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
// 3. 删除备份文件和active-channel.json
|
|
@@ -282,14 +282,14 @@ router.post('/stop', async (req, res) => {
|
|
|
282
282
|
const backupPath = NATIVE_PATHS.claude.settingsBackup;
|
|
283
283
|
if (fs.existsSync(backupPath)) {
|
|
284
284
|
fs.unlinkSync(backupPath);
|
|
285
|
-
console.log('
|
|
285
|
+
console.log('[OK] Removed backup file');
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
const activeChannelPath = PATHS.activeChannel.claude;
|
|
290
290
|
if (fs.existsSync(activeChannelPath)) {
|
|
291
291
|
fs.unlinkSync(activeChannelPath);
|
|
292
|
-
console.log('
|
|
292
|
+
console.log('[OK] Removed active-channel.json');
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
// 4. 通过 WebSocket 推送代理状态更新
|
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
|
|
@@ -50,7 +70,11 @@ router.get('/', async (req, res) => {
|
|
|
50
70
|
try {
|
|
51
71
|
const { platform, service } = getSkillService(req);
|
|
52
72
|
const forceRefresh = req.query.refresh === '1';
|
|
73
|
+
if (forceRefresh) {
|
|
74
|
+
console.log(`[Skills API] Refreshing skills for ${platform}...`);
|
|
75
|
+
}
|
|
53
76
|
const skills = await service.listSkills(forceRefresh);
|
|
77
|
+
console.log(`[Skills API] ${platform}: ${skills.length} skills loaded (refresh=${forceRefresh})`);
|
|
54
78
|
res.json({
|
|
55
79
|
success: true,
|
|
56
80
|
platform,
|
|
@@ -286,7 +310,7 @@ router.get('/repos', (req, res) => {
|
|
|
286
310
|
res.json({
|
|
287
311
|
success: true,
|
|
288
312
|
platform,
|
|
289
|
-
repos
|
|
313
|
+
repos: sanitizeRepos(service, repos)
|
|
290
314
|
});
|
|
291
315
|
} catch (err) {
|
|
292
316
|
console.error('[Skills API] Get repos error:', err);
|
|
@@ -321,7 +345,7 @@ router.post('/repos', (req, res) => {
|
|
|
321
345
|
res.json({
|
|
322
346
|
success: true,
|
|
323
347
|
platform,
|
|
324
|
-
repos
|
|
348
|
+
repos: sanitizeRepos(service, repos)
|
|
325
349
|
});
|
|
326
350
|
} catch (err) {
|
|
327
351
|
console.error('[Skills API] Add repo error:', err);
|
|
@@ -341,7 +365,7 @@ router.delete('/repos', (req, res) => {
|
|
|
341
365
|
res.json({
|
|
342
366
|
success: true,
|
|
343
367
|
platform,
|
|
344
|
-
repos
|
|
368
|
+
repos: sanitizeRepos(service, repos)
|
|
345
369
|
});
|
|
346
370
|
} catch (err) {
|
|
347
371
|
console.error('[Skills API] Remove repo error:', err);
|
|
@@ -362,7 +386,7 @@ router.put('/repos/toggle', (req, res) => {
|
|
|
362
386
|
res.json({
|
|
363
387
|
success: true,
|
|
364
388
|
platform,
|
|
365
|
-
repos
|
|
389
|
+
repos: sanitizeRepos(service, repos)
|
|
366
390
|
});
|
|
367
391
|
} catch (err) {
|
|
368
392
|
console.error('[Skills API] Toggle repo error:', err);
|
|
@@ -373,6 +397,41 @@ router.put('/repos/toggle', (req, res) => {
|
|
|
373
397
|
}
|
|
374
398
|
});
|
|
375
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
|
+
|
|
376
435
|
/**
|
|
377
436
|
* 删除仓库
|
|
378
437
|
* DELETE /api/skills/repos/:owner/:name
|
|
@@ -388,7 +447,7 @@ router.delete('/repos/:owner/:name', (req, res) => {
|
|
|
388
447
|
res.json({
|
|
389
448
|
success: true,
|
|
390
449
|
platform,
|
|
391
|
-
repos
|
|
450
|
+
repos: sanitizeRepos(service, repos)
|
|
392
451
|
});
|
|
393
452
|
} catch (err) {
|
|
394
453
|
console.error('[Skills API] Remove repo error:', err);
|
|
@@ -416,7 +475,7 @@ router.put('/repos/:owner/:name/toggle', (req, res) => {
|
|
|
416
475
|
res.json({
|
|
417
476
|
success: true,
|
|
418
477
|
platform,
|
|
419
|
-
repos
|
|
478
|
+
repos: sanitizeRepos(service, repos)
|
|
420
479
|
});
|
|
421
480
|
} catch (err) {
|
|
422
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,
|
package/src/server/dev-server.js
CHANGED
|
@@ -13,12 +13,12 @@ const chalk = require('chalk');
|
|
|
13
13
|
const config = loadConfig();
|
|
14
14
|
const port = config.ports?.webUI || 19999;
|
|
15
15
|
|
|
16
|
-
console.log(chalk.cyan('\n
|
|
16
|
+
console.log(chalk.cyan('\n[FIX] 开发模式:启动后端 API 服务器...\n'));
|
|
17
17
|
|
|
18
18
|
(async () => {
|
|
19
19
|
await startServer(port);
|
|
20
20
|
|
|
21
|
-
console.log(chalk.yellow('
|
|
21
|
+
console.log(chalk.yellow('[TIP] 开发提示:'));
|
|
22
22
|
console.log(chalk.gray(` - 后端 API: http://localhost:${port}/api`));
|
|
23
23
|
console.log(chalk.gray(' - 前端开发服务器: http://localhost:5000'));
|
|
24
24
|
console.log(chalk.gray(' - 修改后端代码会自动重启 (nodemon)'));
|
|
@@ -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,
|