coding-tool-x 3.6.12 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +46 -33
  2. package/dist/web/assets/{Analytics-BuBlsbcI.js → Analytics-Dma_j2Rq.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-DQczxuOX.js → ConfigTemplates-C49Y1PLH.js} +1 -1
  4. package/dist/web/assets/Home-DGGC8H4g.js +1 -0
  5. package/dist/web/assets/Home-ewgeIhF7.css +1 -0
  6. package/dist/web/assets/{PluginManager-C8gAlaps.js → PluginManager-B1ECTHyY.js} +1 -1
  7. package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-B_L55-l9.css} +1 -1
  8. package/dist/web/assets/ProjectList-DyiGyT-H.js +1 -0
  9. package/dist/web/assets/SessionList-CH5GwwiJ.js +1 -0
  10. package/dist/web/assets/{SkillManager-BDbNbNkK.js → SkillManager-CRyVhjYQ.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-DGGaGW2c.js → WorkspaceManager-CO9tclVt.js} +1 -1
  12. package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
  13. package/dist/web/assets/index-CM26UrnJ.css +1 -0
  14. package/dist/web/assets/index-EQq56pRa.js +2 -0
  15. package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
  16. package/dist/web/index.html +4 -4
  17. package/package.json +2 -1
  18. package/src/commands/channels.js +85 -24
  19. package/src/commands/daemon.js +7 -4
  20. package/src/commands/doctor.js +17 -8
  21. package/src/commands/logs.js +48 -6
  22. package/src/commands/port-config.js +81 -75
  23. package/src/commands/proxy-control.js +52 -19
  24. package/src/commands/stats.js +37 -19
  25. package/src/commands/toggle-proxy.js +131 -53
  26. package/src/commands/ui.js +3 -4
  27. package/src/config/default.js +14 -3
  28. package/src/config/paths.js +99 -11
  29. package/src/index.js +11 -16
  30. package/src/server/api/commands.js +1 -1
  31. package/src/server/api/config-export.js +4 -2
  32. package/src/server/api/config-registry.js +40 -34
  33. package/src/server/api/config.js +2 -1
  34. package/src/server/api/dashboard.js +22 -6
  35. package/src/server/api/mcp.js +5 -3
  36. package/src/server/api/pi-channels.js +349 -0
  37. package/src/server/api/pi-config.js +164 -0
  38. package/src/server/api/pi-projects.js +76 -0
  39. package/src/server/api/pi-proxy.js +154 -0
  40. package/src/server/api/pi-sessions.js +334 -0
  41. package/src/server/api/pi-statistics.js +40 -0
  42. package/src/server/api/plugins.js +1 -1
  43. package/src/server/api/prompts.js +3 -2
  44. package/src/server/api/skills.js +1 -1
  45. package/src/server/api/workspaces.js +2 -2
  46. package/src/server/index.js +41 -0
  47. package/src/server/pi-proxy-server.js +66 -0
  48. package/src/server/services/agents-service.js +2 -1
  49. package/src/server/services/base/proxy-utils.js +1 -0
  50. package/src/server/services/channel-balance.js +151 -1
  51. package/src/server/services/channel-scheduler.js +7 -1
  52. package/src/server/services/commands-service.js +11 -2
  53. package/src/server/services/config-export-service.js +275 -18
  54. package/src/server/services/config-registry-service.js +20 -18
  55. package/src/server/services/config-sync-manager.js +115 -6
  56. package/src/server/services/config-sync-service.js +4 -2
  57. package/src/server/services/config-templates-service.js +183 -45
  58. package/src/server/services/mcp-service.js +217 -33
  59. package/src/server/services/pi-auth-providers.js +294 -0
  60. package/src/server/services/pi-channels.js +94 -0
  61. package/src/server/services/pi-config.js +287 -0
  62. package/src/server/services/pi-sessions.js +490 -0
  63. package/src/server/services/pi-settings-manager.js +571 -0
  64. package/src/server/services/pi-statistics-service.js +317 -0
  65. package/src/server/services/plugins-service.js +673 -11
  66. package/src/server/services/prompts-service.js +112 -7
  67. package/src/server/services/proxy-log-helper.js +1 -0
  68. package/src/server/services/proxy-runtime.js +5 -1
  69. package/src/server/services/skill-service.js +13 -3
  70. package/src/server/services/speed-test.js +9 -3
  71. package/src/server/services/ui-config.js +50 -12
  72. package/src/server/services/workspace-service.js +19 -5
  73. package/src/server/websocket-server.js +7 -1
  74. package/dist/web/assets/Home-BvAzzWnB.js +0 -1
  75. package/dist/web/assets/Home-CIua-b-o.css +0 -1
  76. package/dist/web/assets/ProjectList-nqgi6_cR.js +0 -1
  77. package/dist/web/assets/SessionList-D8AxMYy6.js +0 -1
  78. package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
  79. package/dist/web/assets/index-y-eEayxR.js +0 -2
  80. package/src/utils/cli-flags.js +0 -7
@@ -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>coding-tool-x · AI 编程工作台</title>
8
- <script type="module" crossorigin src="/assets/index-y-eEayxR.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-EQq56pRa.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/markdown-BNJfEgCA.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vue-vendor-B5RqjEjQ.js">
11
- <link rel="modulepreload" crossorigin href="/assets/naive-ui-kcWDeJwl.js">
12
- <link rel="modulepreload" crossorigin href="/assets/icons-QFXzEgqM.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/naive-ui-CVyvdmg1.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/icons-Dop5j7Bf.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/vendors-DQA4Hw9v.js">
14
14
  <link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
15
- <link rel="stylesheet" crossorigin href="/assets/index-Dsh4Q5Cg.css">
15
+ <link rel="stylesheet" crossorigin href="/assets/index-CM26UrnJ.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.6.12",
3
+ "version": "3.7.0",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -64,6 +64,7 @@
64
64
  "http-proxy": "^1.18.1",
65
65
  "https-proxy-agent": "^7.0.6",
66
66
  "inquirer": "^8.2.7",
67
+ "js-yaml": "^4.1.1",
67
68
  "open": "^8.4.2",
68
69
  "ora": "^5.4.1",
69
70
  "pm2": "^6.0.14",
@@ -2,12 +2,14 @@
2
2
  const chalk = require('chalk');
3
3
  const inquirer = require('inquirer');
4
4
  const { loadConfig } = require('../config/loader');
5
+ const { normalizePlatformKey } = require('../shared/platforms');
5
6
 
6
7
  /**
7
8
  * 获取当前类型的渠道服务
8
9
  */
9
10
  function getChannelServices(cliType) {
10
- if (cliType === 'claude') {
11
+ const normalizedCliType = normalizePlatformKey(cliType || 'claude');
12
+ if (normalizedCliType === 'claude') {
11
13
  const {
12
14
  getAllChannels,
13
15
  createChannel,
@@ -15,7 +17,7 @@ function getChannelServices(cliType) {
15
17
  } = require('../server/services/channels');
16
18
  const { getProxyStatus } = require('../server/proxy-server');
17
19
  return { getAllChannels, createChannel, updateChannel, getProxyStatus };
18
- } else if (cliType === 'codex') {
20
+ } else if (normalizedCliType === 'codex') {
19
21
  const {
20
22
  getChannels,
21
23
  createChannel,
@@ -31,7 +33,7 @@ function getChannelServices(cliType) {
31
33
  updateChannel,
32
34
  getProxyStatus: getCodexProxyStatus
33
35
  };
34
- } else if (cliType === 'gemini') {
36
+ } else if (normalizedCliType === 'gemini') {
35
37
  const {
36
38
  getChannels,
37
39
  createChannel,
@@ -47,7 +49,7 @@ function getChannelServices(cliType) {
47
49
  updateChannel,
48
50
  getProxyStatus: getGeminiProxyStatus
49
51
  };
50
- } else if (cliType === 'opencode') {
52
+ } else if (normalizedCliType === 'opencode') {
51
53
  const {
52
54
  getChannels,
53
55
  createChannel,
@@ -63,7 +65,27 @@ function getChannelServices(cliType) {
63
65
  updateChannel,
64
66
  getProxyStatus: getOpenCodeProxyStatus
65
67
  };
68
+ } else if (normalizedCliType === 'pi') {
69
+ const {
70
+ getChannels,
71
+ createChannel,
72
+ updateChannel
73
+ } = require('../server/services/pi-channels');
74
+ const { getPiProxyStatus } = require('../server/pi-proxy-server');
75
+ return {
76
+ getAllChannels: () => {
77
+ const result = getChannels();
78
+ return Array.isArray(result?.channels) ? result.channels : [];
79
+ },
80
+ createChannel,
81
+ updateChannel,
82
+ getProxyStatus: getPiProxyStatus,
83
+ supportsCliCreate: false,
84
+ managedProviderConfig: true
85
+ };
66
86
  }
87
+
88
+ return null;
67
89
  }
68
90
 
69
91
  /**
@@ -76,13 +98,8 @@ async function handleChannelManagement() {
76
98
  console.log(chalk.bold.cyan('╚=======================================╝\n'));
77
99
 
78
100
  const config = loadConfig();
79
- const cliType = config.currentCliType || 'claude';
101
+ const cliType = normalizePlatformKey(config.currentCliType || 'claude');
80
102
  const services = getChannelServices(cliType);
81
- if (!services || typeof services.createChannel !== 'function') {
82
- console.log(chalk.red(`当前 CLI 类型 (${cliType}) 暂不支持添加渠道`));
83
- await inquirer.prompt([{ type: 'input', name: 'continue', message: '按回车返回...' }]);
84
- return;
85
- }
86
103
  if (!services || typeof services.getAllChannels !== 'function') {
87
104
  console.log(chalk.red(`当前 CLI 类型 (${cliType}) 暂不支持渠道管理`));
88
105
  await inquirer.prompt([{ type: 'input', name: 'continue', message: '按回车返回...' }]);
@@ -93,7 +110,13 @@ async function handleChannelManagement() {
93
110
 
94
111
  if (channels.length === 0) {
95
112
  console.log(chalk.yellow('还没有添加任何渠道'));
96
- console.log(chalk.gray('提示: 使用主菜单的"添加渠道"功能来添加新渠道\n'));
113
+ if (cliType === 'pi') {
114
+ console.log(chalk.gray('提示: OMP 渠道请通过 Web UI 或 API 添加后,再在这里启停调度。\n'));
115
+ } else if (services.supportsCliCreate === false || typeof services.createChannel !== 'function') {
116
+ console.log(chalk.gray('提示: 当前平台不支持在 CLI 中添加渠道,请通过 Web UI 添加。\n'));
117
+ } else {
118
+ console.log(chalk.gray('提示: 使用主菜单的"添加渠道"功能来添加新渠道\n'));
119
+ }
97
120
 
98
121
  const { action } = await inquirer.prompt([
99
122
  {
@@ -129,9 +152,19 @@ async function handleAddChannel() {
129
152
  console.log(chalk.bold.cyan('╚=======================================╝\n'));
130
153
 
131
154
  const config = loadConfig();
132
- const cliType = config.currentCliType || 'claude';
155
+ const cliType = normalizePlatformKey(config.currentCliType || 'claude');
133
156
  const services = getChannelServices(cliType);
134
157
 
158
+ if (!services || services.supportsCliCreate === false || typeof services.createChannel !== 'function') {
159
+ if (cliType === 'pi') {
160
+ console.log(chalk.yellow('OMP 渠道创建/编辑需要使用 Web UI 或 API,以便配置 providerKey、providerApi、模型白名单等 OMP 字段。\n'));
161
+ } else {
162
+ console.log(chalk.red(`当前 CLI 类型 (${cliType}) 暂不支持添加渠道\n`));
163
+ }
164
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: '按回车返回...' }]);
165
+ return;
166
+ }
167
+
135
168
  const answers = await inquirer.prompt([
136
169
  {
137
170
  type: 'input',
@@ -280,12 +313,7 @@ async function handleChannelStatus() {
280
313
 
281
314
  try {
282
315
  const { getSchedulerState } = require('../server/services/channel-scheduler');
283
- const sources = [
284
- { key: 'claude', label: 'Claude (Claude Code)' },
285
- { key: 'codex', label: 'Codex (OpenAI)' },
286
- { key: 'gemini', label: 'Gemini' },
287
- { key: 'opencode', label: 'OpenCode' }
288
- ];
316
+ const sources = buildSchedulerSources();
289
317
 
290
318
  sources.forEach((source) => {
291
319
  const state = getSchedulerState(source.key);
@@ -317,13 +345,18 @@ async function handleChannelStatus() {
317
345
  module.exports = {
318
346
  handleChannelManagement,
319
347
  handleAddChannel,
320
- handleChannelStatus
348
+ handleChannelStatus,
349
+ _test: {
350
+ buildSchedulerSources,
351
+ getChannelServices
352
+ }
321
353
  };
322
354
 
323
355
  /**
324
356
  * 统一的多渠道选择处理
325
357
  */
326
358
  async function handleChannelToggle(channels, services, cliType) {
359
+ const normalizedCliType = normalizePlatformKey(cliType || 'claude');
327
360
  const choices = channels.map(channel => {
328
361
  const enabled = channel.enabled !== false;
329
362
  const detailParts = [];
@@ -333,14 +366,31 @@ async function handleChannelToggle(channels, services, cliType) {
333
366
  detailParts.push(cleaned);
334
367
  }
335
368
 
336
- if (cliType === 'codex' && channel.providerKey) {
369
+ if (normalizedCliType === 'codex' && channel.providerKey) {
337
370
  detailParts.push(`provider ${channel.providerKey}`);
338
371
  }
339
372
 
340
- if (cliType === 'gemini' && channel.model) {
373
+ if (normalizedCliType === 'gemini' && channel.model) {
341
374
  detailParts.push(`model ${channel.model}`);
342
375
  }
343
376
 
377
+ if (normalizedCliType === 'pi') {
378
+ if (channel.providerKey) {
379
+ detailParts.push(`provider ${channel.providerKey}`);
380
+ }
381
+ if (channel.model) {
382
+ detailParts.push(`model ${channel.model}`);
383
+ }
384
+ const allowedModels = Array.isArray(channel.allowedModels) && channel.allowedModels.length > 0
385
+ ? channel.allowedModels
386
+ : channel.models;
387
+ if (Array.isArray(allowedModels) && allowedModels.length > 0) {
388
+ const preview = allowedModels.slice(0, 3).join(', ');
389
+ const suffix = allowedModels.length > 3 ? ` +${allowedModels.length - 3}` : '';
390
+ detailParts.push(`models ${preview}${suffix}`);
391
+ }
392
+ }
393
+
344
394
  if (channel.maxConcurrency) {
345
395
  detailParts.push(`并发 ${channel.maxConcurrency}`);
346
396
  }
@@ -389,10 +439,10 @@ async function handleChannelToggle(channels, services, cliType) {
389
439
  }
390
440
 
391
441
  if (hasChanged) {
392
- broadcastSchedulerSnapshot(cliType);
442
+ broadcastSchedulerSnapshot(normalizedCliType);
393
443
  }
394
444
 
395
- await handleAdvancedConfig(services, cliType);
445
+ await handleAdvancedConfig(services, normalizedCliType);
396
446
 
397
447
  console.log(
398
448
  chalk.cyan(
@@ -503,15 +553,26 @@ async function handleAdvancedConfig(services, cliType) {
503
553
  }
504
554
 
505
555
  function broadcastSchedulerSnapshot(source = 'claude') {
556
+ const normalizedSource = normalizePlatformKey(source || 'claude');
506
557
  try {
507
558
  const { getSchedulerState } = require('../server/services/channel-scheduler');
508
559
  const { broadcastSchedulerState } = require('../server/websocket-server');
509
- broadcastSchedulerState(source, getSchedulerState(source));
560
+ broadcastSchedulerState(normalizedSource, getSchedulerState(normalizedSource));
510
561
  } catch (err) {
511
562
  // ignore when scheduler not available
512
563
  }
513
564
  }
514
565
 
566
+ function buildSchedulerSources() {
567
+ return [
568
+ { key: 'claude', label: 'Claude (Claude Code)' },
569
+ { key: 'codex', label: 'Codex (OpenAI)' },
570
+ { key: 'gemini', label: 'Gemini' },
571
+ { key: 'opencode', label: 'OpenCode' },
572
+ { key: 'pi', label: 'OMP' }
573
+ ];
574
+ }
575
+
515
576
  async function handleLegacySwitch(channels, services) {
516
577
  const choices = channels.map(channel => {
517
578
  let name = channel.enabled !== false ? chalk.green('● ') : ' ';
@@ -11,7 +11,6 @@ const {
11
11
  getPortToolIssue,
12
12
  formatPortToolIssue
13
13
  } = require('../utils/port-helper');
14
- const { hasHostFlag } = require('../utils/cli-flags');
15
14
 
16
15
  const PM2_APP_NAME = 'cc-tool';
17
16
  const STARTUP_LOG_FILE = 'cc-tool-out.log';
@@ -260,7 +259,8 @@ function getManagedPorts(config = loadConfig()) {
260
259
  config.ports?.proxy || 20088,
261
260
  config.ports?.codexProxy || 20089,
262
261
  config.ports?.geminiProxy || 20090,
263
- config.ports?.opencodeProxy || 20091
262
+ config.ports?.opencodeProxy || 20091,
263
+ config.ports?.piProxy || 20092
264
264
  ].filter((port, index, list) => Number.isInteger(port) && port > 0 && list.indexOf(port) === index);
265
265
  }
266
266
 
@@ -500,7 +500,7 @@ async function handleStart() {
500
500
  try {
501
501
  const config = loadConfig();
502
502
  const port = config.ports?.webUI || 19999;
503
- const enableHost = hasHostFlag();
503
+ const enableHost = process.argv.includes('--host');
504
504
  const enableHttps = process.argv.includes('--https');
505
505
  fs.mkdirSync(PATHS.logs, { recursive: true });
506
506
 
@@ -668,6 +668,7 @@ async function handleStatus() {
668
668
  const codexActive = fs.existsSync(PATHS.activeChannel.codex);
669
669
  const geminiActive = fs.existsSync(PATHS.activeChannel.gemini);
670
670
  const opencodeActive = fs.existsSync(PATHS.activeChannel.opencode);
671
+ const piActive = fs.existsSync(PATHS.activeChannel.pi);
671
672
 
672
673
  console.log(chalk.bold('\n[PROXY] 代理服务:'));
673
674
 
@@ -683,6 +684,9 @@ async function handleStatus() {
683
684
  console.log(chalk.gray(' OpenCode:') + (opencodeActive ? chalk.green('[OK] 运行中') : chalk.gray('[STOP] 未启动')) +
684
685
  chalk.gray(` (http://localhost:${config.ports?.opencodeProxy || 20091})`));
685
686
 
687
+ console.log(chalk.gray(' OMP: ') + (piActive ? chalk.green('[OK] 运行中') : chalk.gray('[STOP] 未启动')) +
688
+ chalk.gray(` (http://localhost:${config.ports?.piProxy || 20092})`));
689
+
686
690
  console.log(chalk.bold('\n[TIP] 提示:'));
687
691
  console.log(chalk.gray(' • 代理服务通过 Web UI 界面控制'));
688
692
  console.log(chalk.gray(' • 使用 ') + chalk.cyan('ctx logs [type]') + chalk.gray(' 查看日志'));
@@ -734,7 +738,6 @@ module.exports = {
734
738
  _test: {
735
739
  detectStalePm2RuntimeIssue,
736
740
  extractMissingPm2ForkScriptPath,
737
- buildStartOptions,
738
741
  shouldTreatPortOwnershipAsReady,
739
742
  getManagedPorts,
740
743
  shouldStopPM2Process
@@ -148,13 +148,7 @@ async function checkConfigFiles() {
148
148
  */
149
149
  async function checkPorts() {
150
150
  const config = loadConfig();
151
- const ports = {
152
- 'Web UI': config.ports?.webUI || 19999,
153
- 'Claude Proxy': config.ports?.proxy || 20088,
154
- 'Codex Proxy': config.ports?.codexProxy || 20089,
155
- 'Gemini Proxy': config.ports?.geminiProxy || 20090,
156
- 'OpenCode Proxy': config.ports?.opencodeProxy || 20091
157
- };
151
+ const ports = buildPortCheckMap(config);
158
152
 
159
153
  const conflicts = [];
160
154
 
@@ -181,6 +175,17 @@ async function checkPorts() {
181
175
  }
182
176
  }
183
177
 
178
+ function buildPortCheckMap(config = {}) {
179
+ return {
180
+ 'Web UI': config.ports?.webUI || 19999,
181
+ 'Claude Proxy': config.ports?.proxy || 20088,
182
+ 'Codex Proxy': config.ports?.codexProxy || 20089,
183
+ 'Gemini Proxy': config.ports?.geminiProxy || 20090,
184
+ 'OpenCode Proxy': config.ports?.opencodeProxy || 20091,
185
+ 'OMP Proxy': config.ports?.piProxy || 20092
186
+ };
187
+ }
188
+
184
189
  /**
185
190
  * 检查 Claude Code 配置
186
191
  */
@@ -329,5 +334,9 @@ async function checkDiskSpace() {
329
334
  }
330
335
 
331
336
  module.exports = {
332
- handleDoctor
337
+ handleDoctor,
338
+ _test: {
339
+ buildPortCheckMap,
340
+ checkPorts
341
+ }
333
342
  };
@@ -3,6 +3,7 @@ const fs = require('fs');
3
3
  const path = require('path');
4
4
  const { spawn } = require('child_process');
5
5
  const { PATHS } = require('../config/paths');
6
+ const { normalizePlatformKey } = require('../shared/platforms');
6
7
 
7
8
  const LOGS_DIR = PATHS.logs;
8
9
 
@@ -13,6 +14,34 @@ const LOG_FILES = {
13
14
  gemini: 'gemini-proxy.log',
14
15
  opencode: 'opencode-proxy.log'
15
16
  };
17
+ const LOG_ALIASES = {
18
+ pi: 'ui',
19
+ omp: 'ui'
20
+ };
21
+ const LOG_ALIAS_NOTES = {
22
+ pi: 'OMP 受管 provider 配置没有独立请求代理日志,相关活动记录在 UI/server 日志中。',
23
+ omp: 'OMP 受管 provider 配置没有独立请求代理日志,相关活动记录在 UI/server 日志中。'
24
+ };
25
+
26
+ function getSupportedLogTypes() {
27
+ return [...Object.keys(LOG_FILES), 'pi', 'omp'];
28
+ }
29
+
30
+ function resolveLogType(type) {
31
+ const normalized = normalizePlatformKey(type);
32
+ if (!normalized) {
33
+ return { requestedType: type, type: null, file: null, note: null };
34
+ }
35
+ const aliasTarget = LOG_ALIASES[normalized];
36
+ const resolvedType = aliasTarget || normalized;
37
+ return {
38
+ requestedType: type,
39
+ normalizedType: normalized,
40
+ type: resolvedType,
41
+ file: LOG_FILES[resolvedType] || null,
42
+ note: LOG_ALIAS_NOTES[normalized] || null
43
+ };
44
+ }
16
45
 
17
46
  /**
18
47
  * 确保日志目录存在
@@ -44,12 +73,16 @@ async function handleLogs(type = null, options = {}) {
44
73
  }
45
74
 
46
75
  // 显示特定类型的日志
47
- const logFile = LOG_FILES[type];
76
+ const resolved = resolveLogType(type);
77
+ const logFile = resolved.file;
48
78
  if (!logFile) {
49
79
  console.error(chalk.red(`\n[ERROR] 无效的日志类型: ${type}\n`));
50
- console.log(chalk.gray('支持的类型: ui, claude, codex, gemini, opencode\n'));
80
+ console.log(chalk.gray(`支持的类型: ${getSupportedLogTypes().join(', ')}\n`));
51
81
  process.exit(1);
52
82
  }
83
+ if (resolved.note) {
84
+ console.log(chalk.yellow(`\n[INFO] ${resolved.note}\n`));
85
+ }
53
86
 
54
87
  const logPath = path.join(LOGS_DIR, logFile);
55
88
 
@@ -60,7 +93,7 @@ async function handleLogs(type = null, options = {}) {
60
93
  return;
61
94
  }
62
95
 
63
- console.log(chalk.cyan(`\n[LOG] ${type.toUpperCase()} 日志 ${follow ? '(实时)' : `(最近 ${lines} 行)`}\n`));
96
+ console.log(chalk.cyan(`\n[LOG] ${String(type).toUpperCase()} 日志 ${follow ? '(实时)' : `(最近 ${lines} 行)`}\n`));
64
97
  console.log(chalk.gray(`=`.repeat(60)) + '\n');
65
98
 
66
99
  if (follow) {
@@ -116,7 +149,7 @@ function showAllLogs(lines, follow) {
116
149
  });
117
150
 
118
151
  console.log(chalk.gray(`\n=`.repeat(60)));
119
- console.log(chalk.gray(`\n[TIP] 使用 `) + chalk.cyan(`ctx logs ${Object.keys(LOG_FILES).join('|')}`) + chalk.gray(` 查看特定类型日志\n`));
152
+ console.log(chalk.gray(`\n[TIP] 使用 `) + chalk.cyan(`ctx logs ${getSupportedLogTypes().join('|')}`) + chalk.gray(` 查看特定类型日志\n`));
120
153
  }
121
154
 
122
155
  /**
@@ -226,11 +259,18 @@ function clearLogs(type) {
226
259
  console.log(chalk.green(`\n[OK] 共清空 ${cleared} 个日志文件\n`));
227
260
  } else {
228
261
  // 清空特定类型日志
229
- const logFile = LOG_FILES[type];
262
+ const resolved = resolveLogType(type);
263
+ const logFile = resolved.file;
230
264
  if (!logFile) {
231
265
  console.error(chalk.red(`\n[ERROR] 无效的日志类型: ${type}\n`));
266
+ console.log(chalk.gray(`支持的类型: ${getSupportedLogTypes().join(', ')}\n`));
232
267
  process.exit(1);
233
268
  }
269
+ if (resolved.note) {
270
+ console.log(chalk.yellow(`\n[INFO] ${resolved.note}`));
271
+ console.log(chalk.gray('如需清空该日志,请使用 ctx logs ui --clear。\n'));
272
+ return;
273
+ }
234
274
 
235
275
  const logPath = path.join(LOGS_DIR, logFile);
236
276
  if (fs.existsSync(logPath)) {
@@ -289,6 +329,8 @@ function getTypeColor(type) {
289
329
  module.exports = {
290
330
  handleLogs,
291
331
  _test: {
292
- buildFollowProcessSpec
332
+ buildFollowProcessSpec,
333
+ getSupportedLogTypes,
334
+ resolveLogType
293
335
  }
294
336
  };
@@ -4,110 +4,111 @@ const inquirer = require('inquirer');
4
4
  const { loadConfig, saveConfig } = require('../config/loader');
5
5
  const { HOME_DIR } = require('../config/paths');
6
6
 
7
- /**
8
- * 配置端口
9
- */
10
- async function handlePortConfig() {
11
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
12
- console.log(chalk.yellow('\n当前环境不支持交互式端口配置,请在本地终端中运行 `ctx port`。\n'));
13
- return;
14
- }
15
-
16
- console.clear();
17
- console.log(chalk.bold.cyan('\n╔=======================================╗'));
18
- console.log(chalk.bold.cyan('║ 端口配置 ║'));
19
- console.log(chalk.bold.cyan('╚=======================================╝\n'));
20
-
21
- const config = loadConfig();
22
-
23
- console.log(chalk.cyan('当前端口配置:'));
24
- console.log(chalk.gray(`• Web UI 页面端口: ${config.ports.webUI} (同时用于 WebSocket)`));
25
- console.log(chalk.gray(`• Claude 代理端口: ${config.ports.proxy}`));
26
- console.log(chalk.gray(`• Codex 代理端口: ${config.ports.codexProxy || 20089}`));
27
- console.log(chalk.gray(`• Gemini 代理端口: ${config.ports.geminiProxy || 20090}`));
28
- console.log(chalk.gray(`• OpenCode 代理端口: ${config.ports.opencodeProxy || 20091}\n`));
7
+ function normalizePort(value) {
8
+ return parseInt(value, 10);
9
+ }
29
10
 
30
- console.log(chalk.yellow('说明:'));
31
- console.log(chalk.gray('• 端口范围: 1024-65535'));
32
- console.log(chalk.gray('• 修改后需要重启相关服务才能生效'));
33
- console.log(chalk.gray('• 如果端口被占用,请修改为其他端口\n'));
11
+ function validatePort(input) {
12
+ const port = normalizePort(input);
13
+ if (isNaN(port) || port < 1024 || port > 65535) {
14
+ return '端口必须是 1024-65535 之间的数字';
15
+ }
16
+ return true;
17
+ }
34
18
 
35
- const answers = await inquirer.prompt([
19
+ function buildPortQuestions(config = {}) {
20
+ const ports = config.ports || {};
21
+ return [
36
22
  {
37
23
  type: 'input',
38
24
  name: 'webUI',
39
25
  message: 'Web UI 页面端口 (同时用于 WebSocket):',
40
- default: config.ports.webUI,
41
- validate: (input) => {
42
- const port = parseInt(input);
43
- if (isNaN(port) || port < 1024 || port > 65535) {
44
- return '端口必须是 1024-65535 之间的数字';
45
- }
46
- return true;
47
- },
26
+ default: ports.webUI || 19999,
27
+ validate: validatePort,
48
28
  },
49
29
  {
50
30
  type: 'input',
51
31
  name: 'proxy',
52
32
  message: 'Claude 代理服务端口:',
53
- default: config.ports.proxy,
54
- validate: (input) => {
55
- const port = parseInt(input);
56
- if (isNaN(port) || port < 1024 || port > 65535) {
57
- return '端口必须是 1024-65535 之间的数字';
58
- }
59
- return true;
60
- },
33
+ default: ports.proxy || 20088,
34
+ validate: validatePort,
61
35
  },
62
36
  {
63
37
  type: 'input',
64
38
  name: 'codexProxy',
65
39
  message: 'Codex 代理服务端口:',
66
- default: config.ports.codexProxy || 20089,
67
- validate: (input) => {
68
- const port = parseInt(input);
69
- if (isNaN(port) || port < 1024 || port > 65535) {
70
- return '端口必须是 1024-65535 之间的数字';
71
- }
72
- return true;
73
- },
40
+ default: ports.codexProxy || 20089,
41
+ validate: validatePort,
74
42
  },
75
43
  {
76
44
  type: 'input',
77
45
  name: 'geminiProxy',
78
46
  message: 'Gemini 代理服务端口:',
79
- default: config.ports.geminiProxy || 20090,
80
- validate: (input) => {
81
- const port = parseInt(input);
82
- if (isNaN(port) || port < 1024 || port > 65535) {
83
- return '端口必须是 1024-65535 之间的数字';
84
- }
85
- return true;
86
- },
47
+ default: ports.geminiProxy || 20090,
48
+ validate: validatePort,
87
49
  },
88
50
  {
89
51
  type: 'input',
90
52
  name: 'opencodeProxy',
91
53
  message: 'OpenCode 代理服务端口:',
92
- default: config.ports.opencodeProxy || 20091,
93
- validate: (input) => {
94
- const port = parseInt(input);
95
- if (isNaN(port) || port < 1024 || port > 65535) {
96
- return '端口必须是 1024-65535 之间的数字';
97
- }
98
- return true;
99
- },
54
+ default: ports.opencodeProxy || 20091,
55
+ validate: validatePort,
100
56
  },
101
- ]);
57
+ {
58
+ type: 'input',
59
+ name: 'piProxy',
60
+ message: 'OMP 代理服务端口:',
61
+ default: ports.piProxy || 20092,
62
+ validate: validatePort,
63
+ },
64
+ ];
65
+ }
102
66
 
103
- // 更新配置
104
- config.ports = {
105
- webUI: parseInt(answers.webUI),
106
- proxy: parseInt(answers.proxy),
107
- codexProxy: parseInt(answers.codexProxy),
108
- geminiProxy: parseInt(answers.geminiProxy),
109
- opencodeProxy: parseInt(answers.opencodeProxy),
67
+ function buildPortsConfig(answers = {}) {
68
+ return {
69
+ webUI: normalizePort(answers.webUI),
70
+ proxy: normalizePort(answers.proxy),
71
+ codexProxy: normalizePort(answers.codexProxy),
72
+ geminiProxy: normalizePort(answers.geminiProxy),
73
+ opencodeProxy: normalizePort(answers.opencodeProxy),
74
+ piProxy: normalizePort(answers.piProxy),
110
75
  };
76
+ }
77
+
78
+ /**
79
+ * 配置端口
80
+ */
81
+ async function handlePortConfig() {
82
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
83
+ console.log(chalk.yellow('\n当前环境不支持交互式端口配置,请在本地终端中运行 `ctx port`。\n'));
84
+ return;
85
+ }
86
+
87
+ console.clear();
88
+ console.log(chalk.bold.cyan('\n╔=======================================╗'));
89
+ console.log(chalk.bold.cyan('║ 端口配置 ║'));
90
+ console.log(chalk.bold.cyan('╚=======================================╝\n'));
91
+
92
+ const config = loadConfig();
93
+ const ports = config.ports || {};
94
+
95
+ console.log(chalk.cyan('当前端口配置:'));
96
+ console.log(chalk.gray(`• Web UI 页面端口: ${ports.webUI || 19999} (同时用于 WebSocket)`));
97
+ console.log(chalk.gray(`• Claude 代理端口: ${ports.proxy || 20088}`));
98
+ console.log(chalk.gray(`• Codex 代理端口: ${ports.codexProxy || 20089}`));
99
+ console.log(chalk.gray(`• Gemini 代理端口: ${ports.geminiProxy || 20090}`));
100
+ console.log(chalk.gray(`• OpenCode 代理端口: ${ports.opencodeProxy || 20091}`));
101
+ console.log(chalk.gray(`• OMP 代理端口: ${ports.piProxy || 20092}\n`));
102
+
103
+ console.log(chalk.yellow('说明:'));
104
+ console.log(chalk.gray('• 端口范围: 1024-65535'));
105
+ console.log(chalk.gray('• 修改后需要重启相关服务才能生效'));
106
+ console.log(chalk.gray('• 如果端口被占用,请修改为其他端口\n'));
107
+
108
+ const answers = await inquirer.prompt(buildPortQuestions(config));
109
+
110
+ // 更新配置
111
+ config.ports = buildPortsConfig(answers);
111
112
 
112
113
  // 保存配置(保留其余字段)
113
114
  saveConfig({
@@ -132,4 +133,9 @@ async function handlePortConfig() {
132
133
 
133
134
  module.exports = {
134
135
  handlePortConfig,
136
+ _test: {
137
+ buildPortQuestions,
138
+ buildPortsConfig,
139
+ validatePort
140
+ }
135
141
  };