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.
Files changed (69) hide show
  1. package/README.md +17 -0
  2. package/bin/ctx.js +6 -1
  3. package/dist/web/assets/{Analytics-CRNCHeui.js → Analytics-BzoNzfbi.js} +2 -2
  4. package/dist/web/assets/Analytics-vQS5IWvs.css +1 -0
  5. package/dist/web/assets/{ConfigTemplates-C0erJdo2.js → ConfigTemplates-O4ikBt1o.js} +1 -1
  6. package/dist/web/assets/{Home-CL5z6Q4d.js → Home-BQjsnblU.js} +1 -1
  7. package/dist/web/assets/Home-qzk118Of.css +1 -0
  8. package/dist/web/assets/{PluginManager-hDx0XMO_.js → PluginManager-DS_DJnVc.js} +1 -1
  9. package/dist/web/assets/ProjectList-CqYDtsHx.js +1 -0
  10. package/dist/web/assets/ProjectList-GCC2QOmq.css +1 -0
  11. package/dist/web/assets/SessionList-CfPtcq6Y.css +1 -0
  12. package/dist/web/assets/SessionList-DMlLtMCz.js +1 -0
  13. package/dist/web/assets/{SkillManager-D6Vwpajh.js → SkillManager-DpNE02r0.js} +1 -1
  14. package/dist/web/assets/{WorkspaceManager-C3TjeOPy.js → WorkspaceManager-DMY7_SHh.js} +1 -1
  15. package/dist/web/assets/icons-CEq2hYB-.js +1 -0
  16. package/dist/web/assets/index-Clf0l3wc.js +2 -0
  17. package/dist/web/assets/index-Dih_bOsv.css +1 -0
  18. package/dist/web/assets/{naive-ui-BaTCPPL5.js → naive-ui-Cg4_ZeoT.js} +1 -1
  19. package/dist/web/assets/{vendors-Fza9uSYn.js → vendors-Bsp-dq2d.js} +1 -1
  20. package/dist/web/assets/vue-vendor-BxIT0uQq.js +45 -0
  21. package/dist/web/index.html +7 -7
  22. package/docs/Caddyfile.example +19 -0
  23. package/docs/reverse-proxy-https.md +57 -0
  24. package/package.json +2 -1
  25. package/src/commands/daemon.js +33 -5
  26. package/src/commands/export-config.js +6 -6
  27. package/src/commands/ui.js +12 -3
  28. package/src/config/default.js +2 -6
  29. package/src/config/loader.js +2 -2
  30. package/src/config/paths.js +166 -33
  31. package/src/index.js +124 -34
  32. package/src/server/api/agents.js +52 -2
  33. package/src/server/api/commands.js +38 -2
  34. package/src/server/api/plugins.js +104 -1
  35. package/src/server/api/sessions.js +5 -5
  36. package/src/server/index.js +25 -5
  37. package/src/server/services/agents-service.js +269 -62
  38. package/src/server/services/commands-service.js +281 -81
  39. package/src/server/services/config-export-service.js +7 -7
  40. package/src/server/services/config-registry-service.js +4 -5
  41. package/src/server/services/config-sync-manager.js +61 -41
  42. package/src/server/services/config-sync-service.js +3 -3
  43. package/src/server/services/gemini-channels.js +5 -5
  44. package/src/server/services/gemini-config.js +3 -4
  45. package/src/server/services/gemini-sessions.js +23 -20
  46. package/src/server/services/gemini-settings-manager.js +2 -3
  47. package/src/server/services/https-cert.js +171 -0
  48. package/src/server/services/mcp-service.js +9 -14
  49. package/src/server/services/native-oauth-adapters.js +3 -3
  50. package/src/server/services/network-access.js +47 -2
  51. package/src/server/services/notification-hooks.js +11 -5
  52. package/src/server/services/opencode-sessions.js +4 -4
  53. package/src/server/services/opencode-settings-manager.js +3 -3
  54. package/src/server/services/plugins-service.js +499 -23
  55. package/src/server/services/prompts-service.js +5 -9
  56. package/src/server/services/sessions.js +2 -2
  57. package/src/server/services/skill-service.js +155 -18
  58. package/src/server/services/web-ui-runtime.js +54 -0
  59. package/src/server/websocket-server.js +11 -4
  60. package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
  61. package/dist/web/assets/Home-BQxQ1LhR.css +0 -1
  62. package/dist/web/assets/ProjectList-BNsz96av.js +0 -1
  63. package/dist/web/assets/ProjectList-DL4JK6ci.css +0 -1
  64. package/dist/web/assets/SessionList-B8dXVXfi.css +0 -1
  65. package/dist/web/assets/SessionList-CG1UhFo3.js +0 -1
  66. package/dist/web/assets/icons-CQuif85v.js +0 -1
  67. package/dist/web/assets/index-GuER-BmS.js +0 -2
  68. package/dist/web/assets/index-VGAxnLqi.css +0 -1
  69. 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('[PROXY] 代理管理:'));
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(chalk.gray(' (codex/gemini/opencode 命令与 claude 类似)\n'));
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('[PROXY] 插件管理:'));
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
- process.on('uncaughtException', (err) => {
117
- // 忽略终端相关的错误(通常在 Ctrl+C 时发生)
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
- // 处理 SIGINT 信号(Ctrl+C)
125
- process.on('SIGINT', async () => {
126
- eventBus.emitSync('cli:shutdown', {});
127
- PluginManager.shutdownPlugins();
128
- process.exit(0);
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
- console.log(chalk.red(`\n[ERROR] 未知 daemon 子命令: ${subCommand}\n`));
189
- console.log(chalk.gray('支持的命令: start, stop, restart, status, logs\n'));
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 === 'start') {
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
- const { handleUI } = require('./commands/ui');
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
- // 默认执行 start
361
- await handleProxyStart();
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
- if (pluginResult.loaded > 0) {
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 (args[0] && PluginManager.isPluginCommand(args[0])) {
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
- main().catch((error) => {
690
- console.error('程序出错:', error);
691
- process.exit(1);
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
+ };
@@ -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 result = service.listAgents(normalizedProjectPath.projectPath);
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 result = service.listCommands(projectPath || null);
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 result = service.listPlugins();
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 = path.join(NATIVE_PATHS.codex.sessions, String(year), month, day);
175
- sessionFile = path.join(sessionDir, `${newSessionId}.jsonl`);
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 = path.join(NATIVE_PATHS.gemini.tmp, pathHash, 'chats');
180
- sessionFile = path.join(sessionDir, `${newSessionId}.json`);
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
  }
@@ -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
- const server = app.listen(port, host);
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(` http://localhost:${port}`);
278
- console.log(chalk.gray(` http://<your-ip>:${port} (LAN 访问)`));
293
+ console.log(` ${getWebUiBaseUrl(port, { protocol: webUiProtocol })}`);
294
+ console.log(chalk.gray(` ${webUiProtocol}://<your-ip>:${port} (LAN 访问)`));
279
295
  } else {
280
- console.log(` http://localhost:${port}`);
296
+ console.log(` ${getWebUiBaseUrl(port, { protocol: webUiProtocol })}`);
281
297
  }
282
298
 
283
299
  // 附加 WebSocket 服务器到同一个端口
284
300
  attachWebSocketServer(server, { host });
285
- console.log(` ws://localhost:${port}/ws\n`);
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)'));