coding-tool-x 3.5.6 → 3.5.8
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/README.md +17 -0
- package/bin/ctx.js +6 -1
- package/dist/web/assets/{Analytics-CRNCHeui.js → Analytics-BzoNzfbi.js} +2 -2
- package/dist/web/assets/Analytics-vQS5IWvs.css +1 -0
- package/dist/web/assets/{ConfigTemplates-C0erJdo2.js → ConfigTemplates-O4ikBt1o.js} +1 -1
- package/dist/web/assets/{Home-CL5z6Q4d.js → Home-BQjsnblU.js} +1 -1
- package/dist/web/assets/Home-qzk118Of.css +1 -0
- package/dist/web/assets/{PluginManager-hDx0XMO_.js → PluginManager-DS_DJnVc.js} +1 -1
- package/dist/web/assets/ProjectList-CqYDtsHx.js +1 -0
- package/dist/web/assets/ProjectList-GCC2QOmq.css +1 -0
- package/dist/web/assets/SessionList-CfPtcq6Y.css +1 -0
- package/dist/web/assets/SessionList-DMlLtMCz.js +1 -0
- package/dist/web/assets/{SkillManager-D6Vwpajh.js → SkillManager-DpNE02r0.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-C3TjeOPy.js → WorkspaceManager-DMY7_SHh.js} +1 -1
- package/dist/web/assets/icons-CEq2hYB-.js +1 -0
- package/dist/web/assets/index-Clf0l3wc.js +2 -0
- package/dist/web/assets/index-Dih_bOsv.css +1 -0
- package/dist/web/assets/{naive-ui-BaTCPPL5.js → naive-ui-Cg4_ZeoT.js} +1 -1
- package/dist/web/assets/{vendors-Fza9uSYn.js → vendors-Bsp-dq2d.js} +1 -1
- package/dist/web/assets/vue-vendor-BxIT0uQq.js +45 -0
- package/dist/web/index.html +7 -7
- package/docs/Caddyfile.example +19 -0
- package/docs/reverse-proxy-https.md +57 -0
- package/package.json +2 -1
- package/src/commands/daemon.js +33 -5
- package/src/commands/export-config.js +6 -6
- package/src/commands/ui.js +12 -3
- package/src/config/default.js +2 -6
- package/src/config/loader.js +2 -2
- package/src/config/paths.js +166 -33
- package/src/index.js +124 -34
- package/src/server/api/agents.js +52 -2
- package/src/server/api/commands.js +38 -2
- package/src/server/api/plugins.js +104 -1
- package/src/server/api/sessions.js +5 -5
- package/src/server/index.js +25 -5
- package/src/server/services/agents-service.js +269 -62
- package/src/server/services/commands-service.js +281 -81
- package/src/server/services/config-export-service.js +7 -7
- package/src/server/services/config-registry-service.js +4 -5
- package/src/server/services/config-sync-manager.js +61 -41
- package/src/server/services/config-sync-service.js +3 -3
- package/src/server/services/gemini-channels.js +5 -5
- package/src/server/services/gemini-config.js +3 -4
- package/src/server/services/gemini-sessions.js +23 -20
- package/src/server/services/gemini-settings-manager.js +2 -3
- package/src/server/services/https-cert.js +171 -0
- package/src/server/services/mcp-service.js +9 -14
- package/src/server/services/native-oauth-adapters.js +3 -3
- package/src/server/services/network-access.js +47 -2
- package/src/server/services/notification-hooks.js +11 -5
- package/src/server/services/opencode-sessions.js +4 -4
- package/src/server/services/opencode-settings-manager.js +3 -3
- package/src/server/services/plugins-service.js +499 -23
- package/src/server/services/prompts-service.js +5 -9
- package/src/server/services/sessions.js +2 -2
- package/src/server/services/skill-service.js +155 -18
- package/src/server/services/web-ui-runtime.js +54 -0
- package/src/server/websocket-server.js +11 -4
- package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +0 -1
- package/dist/web/assets/ProjectList-BNsz96av.js +0 -1
- package/dist/web/assets/ProjectList-DL4JK6ci.css +0 -1
- package/dist/web/assets/SessionList-B8dXVXfi.css +0 -1
- package/dist/web/assets/SessionList-CG1UhFo3.js +0 -1
- package/dist/web/assets/icons-CQuif85v.js +0 -1
- package/dist/web/assets/index-GuER-BmS.js +0 -2
- package/dist/web/assets/index-VGAxnLqi.css +0 -1
- package/dist/web/assets/vue-vendor-aWwwFAao.js +0 -45
package/src/index.js
CHANGED
|
@@ -19,6 +19,7 @@ const eventBus = require('./plugins/event-bus');
|
|
|
19
19
|
const chalk = require('chalk');
|
|
20
20
|
const path = require('path');
|
|
21
21
|
const fs = require('fs');
|
|
22
|
+
let processHandlersRegistered = false;
|
|
22
23
|
|
|
23
24
|
function getInquirer() {
|
|
24
25
|
return require('inquirer');
|
|
@@ -36,35 +37,66 @@ function showHelp() {
|
|
|
36
37
|
const version = getVersion();
|
|
37
38
|
console.log(chalk.cyan.bold(`\nCODING-TOOL v${version}`));
|
|
38
39
|
console.log(chalk.gray('Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控\n'));
|
|
40
|
+
console.log(chalk.gray('用法: ctx [command] [options]'));
|
|
41
|
+
console.log(chalk.gray('不带参数时进入交互菜单;输入未知命令时会直接报错。\n'));
|
|
39
42
|
|
|
40
43
|
console.log(chalk.yellow('[START] 服务管理:'));
|
|
41
44
|
console.log(' ctx start 启动所有服务(后台运行)');
|
|
42
45
|
console.log(' ctx start --host 启动所有服务(后台运行,允许 LAN 访问)');
|
|
46
|
+
console.log(' ctx start --https 启动所有服务(后台运行,原生 HTTPS)');
|
|
43
47
|
console.log(' ctx stop 停止所有服务');
|
|
44
48
|
console.log(' ctx restart 重启所有服务');
|
|
45
49
|
console.log(' ctx status 查看服务状态\n');
|
|
46
50
|
|
|
47
51
|
console.log(chalk.yellow('[UI] UI 管理:'));
|
|
48
52
|
console.log(' ctx ui 前台启动 Web UI(仅本地访问)');
|
|
53
|
+
console.log(' ctx ui --https 前台启动 Web UI(原生 HTTPS)');
|
|
49
54
|
console.log(' ctx ui --host 前台启动 Web UI(允许 LAN 访问)');
|
|
50
|
-
console.log(' ctx ui start 后台启动 Web UI');
|
|
55
|
+
console.log(' ctx ui start 后台启动 Web UI(等同于 ctx start)');
|
|
56
|
+
console.log(' ctx ui start --https 后台启动 Web UI(原生 HTTPS)');
|
|
51
57
|
console.log(' ctx ui start --host 后台启动 Web UI(允许 LAN 访问)');
|
|
52
58
|
console.log(' ctx ui stop 停止 Web UI');
|
|
53
59
|
console.log(' ctx ui restart 重启 Web UI\n');
|
|
54
60
|
|
|
55
|
-
console.log(chalk.yellow('[
|
|
61
|
+
console.log(chalk.yellow('[CHANNEL] 渠道代理管理:'));
|
|
56
62
|
console.log(' ctx claude start 启动 Claude 代理');
|
|
57
63
|
console.log(' ctx claude stop 停止 Claude 代理');
|
|
64
|
+
console.log(' ctx claude restart 重启 Claude 代理');
|
|
58
65
|
console.log(' ctx claude status 查看 Claude 代理状态');
|
|
59
66
|
console.log(' ctx codex start 启动 Codex 代理');
|
|
67
|
+
console.log(' ctx codex stop 停止 Codex 代理');
|
|
68
|
+
console.log(' ctx codex restart 重启 Codex 代理');
|
|
69
|
+
console.log(' ctx codex status 查看 Codex 代理状态');
|
|
60
70
|
console.log(' ctx gemini start 启动 Gemini 代理');
|
|
71
|
+
console.log(' ctx gemini stop 停止 Gemini 代理');
|
|
72
|
+
console.log(' ctx gemini restart 重启 Gemini 代理');
|
|
73
|
+
console.log(' ctx gemini status 查看 Gemini 代理状态');
|
|
61
74
|
console.log(' ctx opencode start 启动 OpenCode 代理');
|
|
62
|
-
console.log(
|
|
75
|
+
console.log(' ctx opencode stop 停止 OpenCode 代理');
|
|
76
|
+
console.log(' ctx opencode restart 重启 OpenCode 代理');
|
|
77
|
+
console.log(' ctx opencode status 查看 OpenCode 代理状态\n');
|
|
78
|
+
|
|
79
|
+
console.log(chalk.yellow('[PROXY] 旧版代理命令:'));
|
|
80
|
+
console.log(' ctx proxy 启动旧版 Claude 代理');
|
|
81
|
+
console.log(' ctx proxy start 启动旧版 Claude 代理');
|
|
82
|
+
console.log(' ctx proxy stop 停止旧版 Claude 代理并恢复配置');
|
|
83
|
+
console.log(' ctx proxy status 查看旧版 Claude 代理状态\n');
|
|
84
|
+
|
|
85
|
+
console.log(chalk.yellow('[COMPAT] 兼容命令:'));
|
|
86
|
+
console.log(' ctx daemon start 等同于 ctx start');
|
|
87
|
+
console.log(' ctx daemon stop 等同于 ctx stop');
|
|
88
|
+
console.log(' ctx daemon restart 等同于 ctx restart');
|
|
89
|
+
console.log(' ctx daemon status 等同于 ctx status');
|
|
90
|
+
console.log(' ctx daemon logs 查看 UI 日志');
|
|
91
|
+
console.log(' ctx daemon logs --follow 实时跟踪 UI 日志\n');
|
|
63
92
|
|
|
64
93
|
console.log(chalk.yellow('[LOG] 日志管理:'));
|
|
65
94
|
console.log(' ctx logs 查看所有日志');
|
|
66
95
|
console.log(' ctx logs ui 查看 UI 日志');
|
|
67
96
|
console.log(' ctx logs claude 查看 Claude 日志');
|
|
97
|
+
console.log(' ctx logs codex 查看 Codex 日志');
|
|
98
|
+
console.log(' ctx logs gemini 查看 Gemini 日志');
|
|
99
|
+
console.log(' ctx logs opencode 查看 OpenCode 日志');
|
|
68
100
|
console.log(' ctx logs --lines 100 查看最近 100 行');
|
|
69
101
|
console.log(' ctx logs --follow 实时跟踪日志');
|
|
70
102
|
console.log(' ctx logs --clear 清空日志\n');
|
|
@@ -73,18 +105,24 @@ function showHelp() {
|
|
|
73
105
|
console.log(' ctx stats 查看总体统计');
|
|
74
106
|
console.log(' ctx stats claude 查看 Claude 统计');
|
|
75
107
|
console.log(' ctx stats --today 查看今日统计');
|
|
108
|
+
console.log(' ctx stats --week 查看最近 7 天统计');
|
|
109
|
+
console.log(' ctx stats --month 查看最近 30 天统计');
|
|
76
110
|
console.log(' ctx stats export 导出统计数据\n');
|
|
77
111
|
|
|
78
112
|
console.log(chalk.yellow('[TOOL] 其他命令:'));
|
|
79
113
|
console.log(' ctx update 检查并更新到最新版本');
|
|
114
|
+
console.log(' ctx update --check 仅检查更新,不执行安装');
|
|
80
115
|
console.log(' ctx doctor 系统诊断');
|
|
81
116
|
console.log(' ctx port 配置端口');
|
|
82
117
|
console.log(' ctx reset 重置配置');
|
|
83
118
|
console.log(' ctx security reset 关闭访问密码');
|
|
119
|
+
console.log(' ctx security disable 关闭访问密码(别名)');
|
|
120
|
+
console.log(' ctx security off 关闭访问密码(别名)');
|
|
121
|
+
console.log(' ctx help 显示帮助');
|
|
84
122
|
console.log(' ctx --version, -v 显示版本');
|
|
85
123
|
console.log(' ctx --help, -h 显示帮助\n');
|
|
86
124
|
|
|
87
|
-
console.log(chalk.yellow('[
|
|
125
|
+
console.log(chalk.yellow('[PLUGIN] 插件管理:'));
|
|
88
126
|
console.log(' ctx plugin list 列出已安装插件');
|
|
89
127
|
console.log(' ctx plugin install <url> 从 Git 安装插件');
|
|
90
128
|
console.log(' ctx plugin remove <name> 卸载插件');
|
|
@@ -112,21 +150,44 @@ function showHelp() {
|
|
|
112
150
|
console.log(chalk.gray(' 问题: https://github.com/ZeaoZhang/coding-tool/issues\n'));
|
|
113
151
|
}
|
|
114
152
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (err.code === 'EIO' || err.code === 'ENOTTY' || err.code === 'EPIPE') {
|
|
119
|
-
process.exit(0);
|
|
153
|
+
function registerProcessHandlers() {
|
|
154
|
+
if (processHandlersRegistered) {
|
|
155
|
+
return;
|
|
120
156
|
}
|
|
121
|
-
throw err;
|
|
122
|
-
});
|
|
123
157
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
158
|
+
processHandlersRegistered = true;
|
|
159
|
+
|
|
160
|
+
process.on('uncaughtException', (err) => {
|
|
161
|
+
// 忽略终端相关的错误(通常在 Ctrl+C 时发生)
|
|
162
|
+
if (err.code === 'EIO' || err.code === 'ENOTTY' || err.code === 'EPIPE') {
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
|
165
|
+
throw err;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
process.on('SIGINT', async () => {
|
|
169
|
+
eventBus.emitSync('cli:shutdown', {});
|
|
170
|
+
PluginManager.shutdownPlugins();
|
|
171
|
+
process.exit(0);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function exitWithError(code = 1) {
|
|
176
|
+
process.exit(code);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function showUnknownCommand(command) {
|
|
180
|
+
console.error(chalk.red(`\n[ERROR] 未知命令: ${command}\n`));
|
|
181
|
+
console.log(chalk.gray('使用 ctx --help 查看完整帮助\n'));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function showUnknownSubcommand(command, subCommand, supportedText) {
|
|
185
|
+
console.error(chalk.red(`\n[ERROR] 未知 ${command} 子命令: ${subCommand}\n`));
|
|
186
|
+
if (supportedText) {
|
|
187
|
+
console.log(chalk.gray(`${supportedText}\n`));
|
|
188
|
+
}
|
|
189
|
+
console.log(chalk.gray('使用 ctx --help 查看完整帮助\n'));
|
|
190
|
+
}
|
|
130
191
|
|
|
131
192
|
/**
|
|
132
193
|
* 主函数
|
|
@@ -144,7 +205,7 @@ async function main() {
|
|
|
144
205
|
}
|
|
145
206
|
|
|
146
207
|
// --help 或 -h - 显示帮助信息
|
|
147
|
-
if (args[0] === '--help' || args[0] === '-h') {
|
|
208
|
+
if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
|
|
148
209
|
showHelp();
|
|
149
210
|
return;
|
|
150
211
|
}
|
|
@@ -185,8 +246,8 @@ async function main() {
|
|
|
185
246
|
return;
|
|
186
247
|
}
|
|
187
248
|
|
|
188
|
-
|
|
189
|
-
|
|
249
|
+
showUnknownSubcommand('daemon', subCommand, '支持的命令: start, stop, restart, status, logs');
|
|
250
|
+
exitWithError(1);
|
|
190
251
|
return;
|
|
191
252
|
}
|
|
192
253
|
|
|
@@ -236,16 +297,18 @@ async function main() {
|
|
|
236
297
|
// ui 命令 - Web UI 管理
|
|
237
298
|
if (args[0] === 'ui') {
|
|
238
299
|
const subCommand = args[1];
|
|
239
|
-
if (subCommand
|
|
300
|
+
if (!subCommand || subCommand.startsWith('--')) {
|
|
301
|
+
const { handleUI } = require('./commands/ui');
|
|
302
|
+
await handleUI();
|
|
303
|
+
} else if (subCommand === 'start') {
|
|
240
304
|
await handleStart(); // UI start 实际上就是启动整个服务
|
|
241
305
|
} else if (subCommand === 'stop') {
|
|
242
306
|
await handleStop();
|
|
243
307
|
} else if (subCommand === 'restart') {
|
|
244
308
|
await handleRestart();
|
|
245
309
|
} else {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
await handleUI();
|
|
310
|
+
showUnknownSubcommand('ui', subCommand, '支持的命令: start, stop, restart,或直接使用 ctx ui [--host] [--https]');
|
|
311
|
+
exitWithError(1);
|
|
249
312
|
}
|
|
250
313
|
return;
|
|
251
314
|
}
|
|
@@ -272,6 +335,7 @@ async function main() {
|
|
|
272
335
|
default:
|
|
273
336
|
console.log(chalk.red(`\n[ERROR] 未知操作: ${action}\n`));
|
|
274
337
|
console.log(chalk.gray('支持的操作: start, stop, restart, status\n'));
|
|
338
|
+
exitWithError(1);
|
|
275
339
|
}
|
|
276
340
|
return;
|
|
277
341
|
}
|
|
@@ -357,8 +421,8 @@ async function main() {
|
|
|
357
421
|
return;
|
|
358
422
|
|
|
359
423
|
default:
|
|
360
|
-
|
|
361
|
-
|
|
424
|
+
showUnknownSubcommand('proxy', subCommand, '支持的命令: start, stop, status');
|
|
425
|
+
exitWithError(1);
|
|
362
426
|
return;
|
|
363
427
|
}
|
|
364
428
|
}
|
|
@@ -375,15 +439,17 @@ async function main() {
|
|
|
375
439
|
|
|
376
440
|
// 初始化插件系统
|
|
377
441
|
const pluginResult = PluginManager.initializePlugins({ config, args });
|
|
378
|
-
|
|
442
|
+
const isPluginCommand = args[0] && PluginManager.isPluginCommand(args[0]);
|
|
443
|
+
const shouldShowPluginInitSummary = !args[0] || isPluginCommand;
|
|
444
|
+
if (shouldShowPluginInitSummary && pluginResult.loaded > 0) {
|
|
379
445
|
console.log(chalk.gray(`[Plugin] 已加载 ${pluginResult.loaded} 个插件`));
|
|
380
446
|
}
|
|
381
|
-
if (pluginResult.failed.length > 0) {
|
|
447
|
+
if (shouldShowPluginInitSummary && pluginResult.failed.length > 0) {
|
|
382
448
|
console.log(chalk.yellow(`[Plugin] ${pluginResult.failed.length} 个插件加载失败`));
|
|
383
449
|
}
|
|
384
450
|
|
|
385
451
|
// 检查是否为插件注册的命令
|
|
386
|
-
if (
|
|
452
|
+
if (isPluginCommand) {
|
|
387
453
|
eventBus.emitSync('cli:command:before', { command: args[0], args: args.slice(1), config });
|
|
388
454
|
const result = await PluginManager.executePluginCommand(args[0], args.slice(1));
|
|
389
455
|
eventBus.emitSync('cli:command:after', { command: args[0], args: args.slice(1), result });
|
|
@@ -394,6 +460,12 @@ async function main() {
|
|
|
394
460
|
return;
|
|
395
461
|
}
|
|
396
462
|
|
|
463
|
+
if (args[0]) {
|
|
464
|
+
showUnknownCommand(args[0]);
|
|
465
|
+
exitWithError(1);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
397
469
|
while (true) {
|
|
398
470
|
// 显示主菜单
|
|
399
471
|
const { showMainMenu } = require('./ui/menu');
|
|
@@ -685,8 +757,26 @@ async function main() {
|
|
|
685
757
|
}
|
|
686
758
|
}
|
|
687
759
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
760
|
+
async function runCli() {
|
|
761
|
+
registerProcessHandlers();
|
|
762
|
+
await main();
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (require.main === module) {
|
|
766
|
+
runCli().catch((error) => {
|
|
767
|
+
console.error('程序出错:', error);
|
|
768
|
+
process.exit(1);
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
module.exports = {
|
|
773
|
+
main,
|
|
774
|
+
runCli,
|
|
775
|
+
showHelp,
|
|
776
|
+
getVersion,
|
|
777
|
+
_test: {
|
|
778
|
+
showUnknownCommand,
|
|
779
|
+
showUnknownSubcommand,
|
|
780
|
+
registerProcessHandlers
|
|
781
|
+
}
|
|
782
|
+
};
|
package/src/server/api/agents.js
CHANGED
|
@@ -228,7 +228,7 @@ router.use((req, res, next) => {
|
|
|
228
228
|
router.get('/', (req, res) => {
|
|
229
229
|
try {
|
|
230
230
|
const { platform, service } = getAgentsService(req);
|
|
231
|
-
const { projectPath } = req.query;
|
|
231
|
+
const { projectPath, refresh } = req.query;
|
|
232
232
|
const normalizedProjectPath = normalizeOptionalProjectPath(projectPath);
|
|
233
233
|
if (normalizedProjectPath.error) {
|
|
234
234
|
return res.status(400).json({
|
|
@@ -236,7 +236,10 @@ router.get('/', (req, res) => {
|
|
|
236
236
|
message: normalizedProjectPath.error
|
|
237
237
|
});
|
|
238
238
|
}
|
|
239
|
-
const
|
|
239
|
+
const forceRefresh = refresh === '1';
|
|
240
|
+
const result = service.listAgents(normalizedProjectPath.projectPath, {
|
|
241
|
+
syncManagedLocalAgents: forceRefresh
|
|
242
|
+
});
|
|
240
243
|
|
|
241
244
|
res.json({
|
|
242
245
|
success: true,
|
|
@@ -775,6 +778,53 @@ router.post('/install', async (req, res) => {
|
|
|
775
778
|
}
|
|
776
779
|
});
|
|
777
780
|
|
|
781
|
+
/**
|
|
782
|
+
* 安装本地托管代理
|
|
783
|
+
* POST /api/agents/install-local
|
|
784
|
+
* Body: { fileName }
|
|
785
|
+
*/
|
|
786
|
+
router.post('/install-local', (req, res) => {
|
|
787
|
+
try {
|
|
788
|
+
const { platform, service } = getAgentsService(req);
|
|
789
|
+
if (isCodexRepoOperationUnsupported(platform)) {
|
|
790
|
+
return res.status(400).json({
|
|
791
|
+
success: false,
|
|
792
|
+
message: 'Codex 平台暂不支持本地托管代理安装'
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const { fileName } = req.body;
|
|
797
|
+
if (!fileName) {
|
|
798
|
+
return res.status(400).json({
|
|
799
|
+
success: false,
|
|
800
|
+
message: 'Missing fileName'
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const fileNameError = validateAgentFileName(fileName);
|
|
805
|
+
if (fileNameError) {
|
|
806
|
+
return res.status(400).json({
|
|
807
|
+
success: false,
|
|
808
|
+
message: fileNameError
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const result = service.installLocalAgent(fileName);
|
|
813
|
+
|
|
814
|
+
res.json({
|
|
815
|
+
success: true,
|
|
816
|
+
platform,
|
|
817
|
+
...result
|
|
818
|
+
});
|
|
819
|
+
} catch (err) {
|
|
820
|
+
console.error('[Agents API] Install local agent error:', err);
|
|
821
|
+
res.status(500).json({
|
|
822
|
+
success: false,
|
|
823
|
+
message: err.message
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
|
|
778
828
|
/**
|
|
779
829
|
* 卸载代理
|
|
780
830
|
* POST /api/agents/uninstall
|
|
@@ -35,8 +35,11 @@ function getCommandsService(req) {
|
|
|
35
35
|
router.get('/', (req, res) => {
|
|
36
36
|
try {
|
|
37
37
|
const { platform, service } = getCommandsService(req);
|
|
38
|
-
const { projectPath } = req.query;
|
|
39
|
-
const
|
|
38
|
+
const { projectPath, refresh } = req.query;
|
|
39
|
+
const forceRefresh = refresh === '1';
|
|
40
|
+
const result = service.listCommands(projectPath || null, {
|
|
41
|
+
syncManagedLocalCommands: forceRefresh
|
|
42
|
+
});
|
|
40
43
|
|
|
41
44
|
res.json({
|
|
42
45
|
success: true,
|
|
@@ -446,6 +449,39 @@ router.post('/install', async (req, res) => {
|
|
|
446
449
|
}
|
|
447
450
|
});
|
|
448
451
|
|
|
452
|
+
/**
|
|
453
|
+
* 安装本地托管命令
|
|
454
|
+
* POST /api/commands/install-local
|
|
455
|
+
* Body: { path }
|
|
456
|
+
*/
|
|
457
|
+
router.post('/install-local', (req, res) => {
|
|
458
|
+
try {
|
|
459
|
+
const { platform, service } = getCommandsService(req);
|
|
460
|
+
const { path } = req.body;
|
|
461
|
+
|
|
462
|
+
if (!path) {
|
|
463
|
+
return res.status(400).json({
|
|
464
|
+
success: false,
|
|
465
|
+
message: 'Missing path'
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const result = service.installLocalCommand(path);
|
|
470
|
+
|
|
471
|
+
res.json({
|
|
472
|
+
success: true,
|
|
473
|
+
platform,
|
|
474
|
+
...result
|
|
475
|
+
});
|
|
476
|
+
} catch (err) {
|
|
477
|
+
console.error('[Commands API] Install local command error:', err);
|
|
478
|
+
res.status(500).json({
|
|
479
|
+
success: false,
|
|
480
|
+
message: err.message
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
449
485
|
/**
|
|
450
486
|
* 卸载命令
|
|
451
487
|
* POST /api/commands/uninstall
|
|
@@ -88,7 +88,10 @@ function sanitizeRepos(service, repos = []) {
|
|
|
88
88
|
router.get('/', (req, res) => {
|
|
89
89
|
try {
|
|
90
90
|
const { platform, service } = getPluginsService(req);
|
|
91
|
-
const
|
|
91
|
+
const forceRefresh = req.query.refresh === '1';
|
|
92
|
+
const result = service.listPlugins({
|
|
93
|
+
syncManagedLocalPlugins: forceRefresh
|
|
94
|
+
});
|
|
92
95
|
|
|
93
96
|
res.json({
|
|
94
97
|
success: true,
|
|
@@ -189,6 +192,46 @@ router.post('/install', async (req, res) => {
|
|
|
189
192
|
}
|
|
190
193
|
});
|
|
191
194
|
|
|
195
|
+
/**
|
|
196
|
+
* 安装本地托管插件
|
|
197
|
+
* POST /api/plugins/install-local
|
|
198
|
+
* Body: { name }
|
|
199
|
+
*/
|
|
200
|
+
router.post('/install-local', (req, res) => {
|
|
201
|
+
try {
|
|
202
|
+
const { platform, service } = getPluginsService(req);
|
|
203
|
+
const { name } = req.body;
|
|
204
|
+
|
|
205
|
+
if (!name) {
|
|
206
|
+
return res.status(400).json({
|
|
207
|
+
success: false,
|
|
208
|
+
message: 'name is required'
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const result = service.installLocalPlugin(name);
|
|
213
|
+
if (!result.success) {
|
|
214
|
+
return res.status(400).json({
|
|
215
|
+
success: false,
|
|
216
|
+
message: result.error || result.message || 'Install failed'
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
res.json({
|
|
221
|
+
success: true,
|
|
222
|
+
platform,
|
|
223
|
+
plugin: result.plugin,
|
|
224
|
+
message: result.message || `Plugin "${result.plugin?.name || name}" installed successfully`
|
|
225
|
+
});
|
|
226
|
+
} catch (err) {
|
|
227
|
+
console.error('[Plugins API] Install local plugin error:', err);
|
|
228
|
+
res.status(500).json({
|
|
229
|
+
success: false,
|
|
230
|
+
message: err.message
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
192
235
|
// ==================== 仓库管理 API ====================
|
|
193
236
|
|
|
194
237
|
/**
|
|
@@ -440,6 +483,66 @@ router.post('/sync', async (req, res) => {
|
|
|
440
483
|
}
|
|
441
484
|
});
|
|
442
485
|
|
|
486
|
+
/**
|
|
487
|
+
* 获取插件文件列表
|
|
488
|
+
* GET /api/plugins/:name/files
|
|
489
|
+
*/
|
|
490
|
+
router.get('/:name/files', async (req, res) => {
|
|
491
|
+
try {
|
|
492
|
+
const { platform, service } = getPluginsService(req);
|
|
493
|
+
const { name } = req.params;
|
|
494
|
+
const pluginInfo = extractPluginQueryInfo(name, req.query);
|
|
495
|
+
const result = await service.getPluginFiles(pluginInfo);
|
|
496
|
+
|
|
497
|
+
res.json({
|
|
498
|
+
success: true,
|
|
499
|
+
platform,
|
|
500
|
+
...result
|
|
501
|
+
});
|
|
502
|
+
} catch (err) {
|
|
503
|
+
console.error('[Plugins API] Get plugin files error:', err);
|
|
504
|
+
res.status(500).json({
|
|
505
|
+
success: false,
|
|
506
|
+
message: err.message,
|
|
507
|
+
files: []
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* 获取插件单个文件内容
|
|
514
|
+
* GET /api/plugins/:name/file/*
|
|
515
|
+
*/
|
|
516
|
+
router.get('/:name/file/*', async (req, res) => {
|
|
517
|
+
try {
|
|
518
|
+
const { platform, service } = getPluginsService(req);
|
|
519
|
+
const { name } = req.params;
|
|
520
|
+
const filePath = req.params[0];
|
|
521
|
+
|
|
522
|
+
if (!filePath) {
|
|
523
|
+
return res.status(400).json({
|
|
524
|
+
success: false,
|
|
525
|
+
message: '请指定文件路径'
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const pluginInfo = extractPluginQueryInfo(name, req.query);
|
|
530
|
+
const result = await service.getPluginFileContent(pluginInfo, filePath);
|
|
531
|
+
|
|
532
|
+
res.json({
|
|
533
|
+
success: true,
|
|
534
|
+
platform,
|
|
535
|
+
...result
|
|
536
|
+
});
|
|
537
|
+
} catch (err) {
|
|
538
|
+
console.error('[Plugins API] Get plugin file content error:', err);
|
|
539
|
+
res.status(500).json({
|
|
540
|
+
success: false,
|
|
541
|
+
message: err.message
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
|
|
443
546
|
/**
|
|
444
547
|
* 获取插件 README
|
|
445
548
|
* GET /api/plugins/:name/readme
|
|
@@ -7,7 +7,7 @@ const { getSessionsForProject, deleteSession, forkSession, saveSessionOrder, par
|
|
|
7
7
|
const { loadAliases } = require('../services/alias');
|
|
8
8
|
const { broadcastLog } = require('../websocket-server');
|
|
9
9
|
const { buildLaunchCommand } = require('../services/session-launch-command');
|
|
10
|
-
const { NATIVE_PATHS } = require('../../config/paths');
|
|
10
|
+
const { NATIVE_PATHS, joinNativeBasePath } = require('../../config/paths');
|
|
11
11
|
const CLAUDE_PROJECTS_DIR = NATIVE_PATHS.claude.projects;
|
|
12
12
|
|
|
13
13
|
module.exports = (config) => {
|
|
@@ -171,13 +171,13 @@ module.exports = (config) => {
|
|
|
171
171
|
const year = now.getFullYear();
|
|
172
172
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
173
173
|
const day = String(now.getDate()).padStart(2, '0');
|
|
174
|
-
sessionDir =
|
|
175
|
-
sessionFile =
|
|
174
|
+
sessionDir = joinNativeBasePath(NATIVE_PATHS.codex.sessions, String(year), month, day);
|
|
175
|
+
sessionFile = joinNativeBasePath(sessionDir, `${newSessionId}.jsonl`);
|
|
176
176
|
} else if (toolType === 'gemini') {
|
|
177
177
|
// Gemini: ~/.gemini/tmp/{hash}/chats/{sessionId}.json
|
|
178
178
|
const pathHash = crypto.createHash('sha256').update(fullPath).digest('hex');
|
|
179
|
-
sessionDir =
|
|
180
|
-
sessionFile =
|
|
179
|
+
sessionDir = joinNativeBasePath(NATIVE_PATHS.gemini.tmp, pathHash, 'chats');
|
|
180
|
+
sessionFile = joinNativeBasePath(sessionDir, `${newSessionId}.json`);
|
|
181
181
|
} else {
|
|
182
182
|
return res.status(400).json({ error: 'Invalid toolType. Must be claude, codex, or gemini' });
|
|
183
183
|
}
|
package/src/server/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
+
const https = require('https');
|
|
2
3
|
const path = require('path');
|
|
3
4
|
const chalk = require('chalk');
|
|
4
5
|
const { loadConfig } = require('../config/loader');
|
|
@@ -23,6 +24,8 @@ const { startGeminiProxyServer } = require('./gemini-proxy-server');
|
|
|
23
24
|
const { startOpenCodeProxyServer, collectProxyModelList } = require('./opencode-proxy-server');
|
|
24
25
|
const { createRemoteMutationGuard, isRemoteMutationAllowed } = require('./services/network-access');
|
|
25
26
|
const { createApiRequestLogger } = require('./services/request-logger');
|
|
27
|
+
const { getLocalHttpsCredentials } = require('./services/https-cert');
|
|
28
|
+
const { getWebUiProtocol, isHttpsEnabled, getWebUiBaseUrl, getWebSocketBaseUrl } = require('./services/web-ui-runtime');
|
|
26
29
|
|
|
27
30
|
function getInquirer() {
|
|
28
31
|
return require('inquirer');
|
|
@@ -59,6 +62,8 @@ function printPortToolIssue(issue = getPortToolIssue()) {
|
|
|
59
62
|
async function startServer(port, host = '127.0.0.1', options = {}) {
|
|
60
63
|
ensureStorageDirMigrated();
|
|
61
64
|
const config = loadConfig();
|
|
65
|
+
const webUiProtocol = getWebUiProtocol({ https: options.https });
|
|
66
|
+
const httpsEnabled = isHttpsEnabled({ protocol: webUiProtocol });
|
|
62
67
|
// 使用配置的端口,如果没有传入参数
|
|
63
68
|
if (!port) {
|
|
64
69
|
port = config.ports?.webUI || 19999;
|
|
@@ -247,7 +252,18 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
|
|
|
247
252
|
}
|
|
248
253
|
|
|
249
254
|
// Start server(确保监听成功后才返回,避免命令误报“已启动”)
|
|
250
|
-
|
|
255
|
+
let httpsCertificate = null;
|
|
256
|
+
const server = httpsEnabled
|
|
257
|
+
? (() => {
|
|
258
|
+
httpsCertificate = getLocalHttpsCredentials();
|
|
259
|
+
const httpsServer = https.createServer({
|
|
260
|
+
key: httpsCertificate.key,
|
|
261
|
+
cert: httpsCertificate.cert
|
|
262
|
+
}, app);
|
|
263
|
+
httpsServer.listen(port, host);
|
|
264
|
+
return httpsServer;
|
|
265
|
+
})()
|
|
266
|
+
: app.listen(port, host);
|
|
251
267
|
await new Promise((resolve) => {
|
|
252
268
|
const onListening = () => {
|
|
253
269
|
server.off('error', onError);
|
|
@@ -274,15 +290,19 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
|
|
|
274
290
|
console.log(`\n[START] Coding-Tool Web UI running at:`);
|
|
275
291
|
if (host === '0.0.0.0') {
|
|
276
292
|
console.log(chalk.yellow(` [WARN] 警告: 服务正在监听所有网络接口 (LAN 可访问)`));
|
|
277
|
-
console.log(`
|
|
278
|
-
console.log(chalk.gray(`
|
|
293
|
+
console.log(` ${getWebUiBaseUrl(port, { protocol: webUiProtocol })}`);
|
|
294
|
+
console.log(chalk.gray(` ${webUiProtocol}://<your-ip>:${port} (LAN 访问)`));
|
|
279
295
|
} else {
|
|
280
|
-
console.log(`
|
|
296
|
+
console.log(` ${getWebUiBaseUrl(port, { protocol: webUiProtocol })}`);
|
|
281
297
|
}
|
|
282
298
|
|
|
283
299
|
// 附加 WebSocket 服务器到同一个端口
|
|
284
300
|
attachWebSocketServer(server, { host });
|
|
285
|
-
console.log(`
|
|
301
|
+
console.log(` ${getWebSocketBaseUrl(port, { protocol: webUiProtocol })}/ws\n`);
|
|
302
|
+
|
|
303
|
+
if (httpsEnabled && httpsCertificate?.generated) {
|
|
304
|
+
console.log(chalk.gray(` [LOCK] 已生成本地 HTTPS 证书: ${httpsCertificate.certPath}`));
|
|
305
|
+
}
|
|
286
306
|
|
|
287
307
|
if (host === '0.0.0.0' && !allowRemoteMutation) {
|
|
288
308
|
console.log(chalk.yellow(' [LOCK] 已禁用 LAN 远程写操作 (CC_TOOL_ALLOW_REMOTE_WRITE=false)'));
|