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
@@ -1,6 +1,7 @@
1
1
  const chalk = require('chalk');
2
2
  const http = require('http');
3
3
  const { loadConfig } = require('../config/loader');
4
+ const { normalizePlatformKey } = require('../shared/platforms');
4
5
 
5
6
  const CHANNEL_CONFIG = {
6
7
  claude: {
@@ -22,9 +23,34 @@ const CHANNEL_CONFIG = {
22
23
  name: 'OpenCode',
23
24
  icon: '[*]',
24
25
  apiPath: '/api/opencode/proxy'
26
+ },
27
+ pi: {
28
+ name: 'OMP',
29
+ icon: '[*]',
30
+ apiPath: '/api/pi/proxy',
31
+ serviceLabel: 'OMP 受管模型配置',
32
+ proxyLabel: '受管模型配置',
33
+ startAction: '启用',
34
+ stopAction: '停用',
35
+ restartAction: '重新同步',
36
+ startedMessage: 'OMP 受管模型配置已启用',
37
+ stoppedMessage: 'OMP 受管模型配置已停用',
38
+ runningText: '已启用',
39
+ stoppedText: '未启用',
40
+ portLabel: '状态端口',
41
+ addressLabel: '状态地址',
42
+ startTip: '启用受管模型配置'
25
43
  }
26
44
  };
27
45
 
46
+ function getServiceLabel(channelInfo) {
47
+ return channelInfo.serviceLabel || `${channelInfo.name} 代理服务`;
48
+ }
49
+
50
+ function getProxyLabel(channelInfo) {
51
+ return channelInfo.proxyLabel || '代理';
52
+ }
53
+
28
54
  /**
29
55
  * HTTP 请求辅助函数
30
56
  */
@@ -96,14 +122,15 @@ async function checkUIService() {
96
122
  * 启动代理
97
123
  */
98
124
  async function handleProxyStart(channel) {
99
- const channelInfo = CHANNEL_CONFIG[channel];
125
+ const normalizedChannel = normalizePlatformKey(channel);
126
+ const channelInfo = CHANNEL_CONFIG[normalizedChannel];
100
127
  if (!channelInfo) {
101
128
  console.error(chalk.red(`\n[ERROR] 无效的渠道类型: ${channel}\n`));
102
- console.log(chalk.gray('支持的渠道: claude, codex, gemini, opencode\n'));
129
+ console.log(chalk.gray('支持的渠道: claude, codex, gemini, opencode, pi, omp\n'));
103
130
  process.exit(1);
104
131
  }
105
132
 
106
- console.log(chalk.cyan(`\n[START] 启动 ${channelInfo.name} 代理服务...\n`));
133
+ console.log(chalk.cyan(`\n[START] ${channelInfo.startAction || '启动'} ${getServiceLabel(channelInfo)}...\n`));
107
134
 
108
135
  // 检查 UI 服务
109
136
  const uiRunning = await checkUIService();
@@ -118,9 +145,9 @@ async function handleProxyStart(channel) {
118
145
  const response = await httpRequest('POST', `${channelInfo.apiPath}/start`);
119
146
 
120
147
  if (response.data.success) {
121
- console.log(chalk.green(`[OK] ${channelInfo.name} 代理已启动\n`));
122
- console.log(chalk.gray(`${channelInfo.icon} 代理端口: ${response.data.port}`));
123
- console.log(chalk.gray(`[NET] 代理地址: http://localhost:${response.data.port}\n`));
148
+ console.log(chalk.green(`[OK] ${channelInfo.startedMessage || `${channelInfo.name} ${getProxyLabel(channelInfo)}已启动`}\n`));
149
+ console.log(chalk.gray(`${channelInfo.icon} ${channelInfo.portLabel || '代理端口'}: ${response.data.port}`));
150
+ console.log(chalk.gray(`[NET] ${channelInfo.addressLabel || '代理地址'}: http://localhost:${response.data.port}\n`));
124
151
  } else {
125
152
  console.error(chalk.red(`[ERROR] 启动失败: ${response.data.message}\n`));
126
153
  process.exit(1);
@@ -140,13 +167,15 @@ async function handleProxyStart(channel) {
140
167
  * 停止代理
141
168
  */
142
169
  async function handleProxyStop(channel) {
143
- const channelInfo = CHANNEL_CONFIG[channel];
170
+ const normalizedChannel = normalizePlatformKey(channel);
171
+ const channelInfo = CHANNEL_CONFIG[normalizedChannel];
144
172
  if (!channelInfo) {
145
173
  console.error(chalk.red(`\n[ERROR] 无效的渠道类型: ${channel}\n`));
174
+ console.log(chalk.gray('支持的渠道: claude, codex, gemini, opencode, pi, omp\n'));
146
175
  process.exit(1);
147
176
  }
148
177
 
149
- console.log(chalk.cyan(`\n[STOP] 停止 ${channelInfo.name} 代理服务...\n`));
178
+ console.log(chalk.cyan(`\n[STOP] ${channelInfo.stopAction || '停止'} ${getServiceLabel(channelInfo)}...\n`));
150
179
 
151
180
  const uiRunning = await checkUIService();
152
181
  if (!uiRunning) {
@@ -158,7 +187,7 @@ async function handleProxyStop(channel) {
158
187
  const response = await httpRequest('POST', `${channelInfo.apiPath}/stop`);
159
188
 
160
189
  if (response.data.success) {
161
- console.log(chalk.green(`[OK] ${channelInfo.name} 代理已停止\n`));
190
+ console.log(chalk.green(`[OK] ${channelInfo.stoppedMessage || `${channelInfo.name} ${getProxyLabel(channelInfo)}已停止`}\n`));
162
191
  } else {
163
192
  console.error(chalk.red(`[ERROR] 停止失败: ${response.data.message}\n`));
164
193
  process.exit(1);
@@ -173,13 +202,15 @@ async function handleProxyStop(channel) {
173
202
  * 重启代理
174
203
  */
175
204
  async function handleProxyRestart(channel) {
176
- const channelInfo = CHANNEL_CONFIG[channel];
205
+ const normalizedChannel = normalizePlatformKey(channel);
206
+ const channelInfo = CHANNEL_CONFIG[normalizedChannel];
177
207
  if (!channelInfo) {
178
208
  console.error(chalk.red(`\n[ERROR] 无效的渠道类型: ${channel}\n`));
209
+ console.log(chalk.gray('支持的渠道: claude, codex, gemini, opencode, pi, omp\n'));
179
210
  process.exit(1);
180
211
  }
181
212
 
182
- console.log(chalk.cyan(`\n[SYNC] 重启 ${channelInfo.name} 代理服务...\n`));
213
+ console.log(chalk.cyan(`\n[SYNC] ${channelInfo.restartAction || '重启'} ${getServiceLabel(channelInfo)}...\n`));
183
214
 
184
215
  await handleProxyStop(channel);
185
216
  await new Promise(resolve => setTimeout(resolve, 1000));
@@ -190,16 +221,18 @@ async function handleProxyRestart(channel) {
190
221
  * 查看代理状态
191
222
  */
192
223
  async function handleProxyStatus(channel) {
193
- const channelInfo = CHANNEL_CONFIG[channel];
224
+ const normalizedChannel = normalizePlatformKey(channel);
225
+ const channelInfo = CHANNEL_CONFIG[normalizedChannel];
194
226
  if (!channelInfo) {
195
227
  console.error(chalk.red(`\n[ERROR] 无效的渠道类型: ${channel}\n`));
228
+ console.log(chalk.gray('支持的渠道: claude, codex, gemini, opencode, pi, omp\n'));
196
229
  process.exit(1);
197
230
  }
198
231
 
199
232
  const uiRunning = await checkUIService();
200
233
  if (!uiRunning) {
201
234
  console.log(chalk.bold.cyan(`\n╔======================================╗`));
202
- console.log(chalk.bold.cyan(`║ ${channelInfo.name} 代理服务状态 ║`));
235
+ console.log(chalk.bold.cyan(`║ ${getServiceLabel(channelInfo)}状态 ║`));
203
236
  console.log(chalk.bold.cyan(`╚======================================╝\n`));
204
237
  console.log(chalk.gray(' [ERROR] UI 服务未运行\n'));
205
238
  console.log(chalk.yellow('[TIP] 请先启动 UI 服务: ') + chalk.cyan('ctx start\n'));
@@ -212,22 +245,22 @@ async function handleProxyStatus(channel) {
212
245
  const status = payload.proxy || payload;
213
246
 
214
247
  console.log(chalk.bold.cyan(`\n╔======================================╗`));
215
- console.log(chalk.bold.cyan(`║ ${channelInfo.name} 代理服务状态 ║`));
248
+ console.log(chalk.bold.cyan(`║ ${getServiceLabel(channelInfo)}状态 ║`));
216
249
  console.log(chalk.bold.cyan(`╚======================================╝\n`));
217
250
 
218
251
  if (status.running) {
219
- console.log(chalk.green(' [OK] 状态: 运行中'));
220
- console.log(chalk.gray(` ${channelInfo.icon} 端口: ${status.port}`));
221
- console.log(chalk.gray(` [NET] 地址: http://localhost:${status.port}`));
252
+ console.log(chalk.green(` [OK] 状态: ${channelInfo.runningText || '运行中'}`));
253
+ console.log(chalk.gray(` ${channelInfo.icon} ${channelInfo.portLabel || '端口'}: ${status.port}`));
254
+ console.log(chalk.gray(` [NET] ${channelInfo.addressLabel || '地址'}: http://localhost:${status.port}`));
222
255
  if (status.runtime) {
223
256
  console.log(chalk.gray(` [TIMER] 运行时长: ${formatRuntime(status.runtime)}`));
224
257
  }
225
258
  } else {
226
- console.log(chalk.gray(' [ERROR] 状态: 未运行'));
259
+ console.log(chalk.gray(` [ERROR] 状态: ${channelInfo.stoppedText || '未运行'}`));
227
260
  }
228
261
 
229
262
  console.log(chalk.bold('\n[TIP] 提示:'));
230
- console.log(chalk.gray(` • 使用 `) + chalk.cyan(`ctx ${channel} start`) + chalk.gray(` 启动代理`));
263
+ console.log(chalk.gray(` • 使用 `) + chalk.cyan(`ctx ${channel} start`) + chalk.gray(` ${channelInfo.startTip || '启动代理'}`));
231
264
  console.log(chalk.gray(` • 使用 `) + chalk.cyan(`ctx logs ${channel}`) + chalk.gray(` 查看日志`));
232
265
  console.log(chalk.gray(` • 使用 `) + chalk.cyan(`ctx stats ${channel}`) + chalk.gray(` 查看统计\n`));
233
266
  } catch (error) {
@@ -1,13 +1,15 @@
1
1
  const chalk = require('chalk');
2
2
  const http = require('http');
3
3
  const { loadConfig } = require('../config/loader');
4
+ const { normalizePlatformKey } = require('../shared/platforms');
4
5
 
5
- const TOOL_TYPES = ['claude', 'codex', 'gemini', 'opencode'];
6
+ const TOOL_TYPES = ['claude', 'codex', 'gemini', 'opencode', 'pi'];
6
7
  const TOOL_ENDPOINTS = {
7
8
  claude: '/api/claude/statistics',
8
9
  codex: '/api/codex/statistics',
9
10
  gemini: '/api/gemini/statistics',
10
- opencode: '/api/opencode/statistics'
11
+ opencode: '/api/opencode/statistics',
12
+ pi: '/api/pi/statistics'
11
13
  };
12
14
 
13
15
  /**
@@ -79,7 +81,7 @@ async function checkUIService() {
79
81
 
80
82
  function validateToolType(type) {
81
83
  if (!type) return true;
82
- return TOOL_TYPES.includes(type);
84
+ return TOOL_TYPES.includes(normalizePlatformKey(type));
83
85
  }
84
86
 
85
87
  function getDateString(offsetDays = 0) {
@@ -174,7 +176,8 @@ function getRangeDays(timeRange) {
174
176
  }
175
177
 
176
178
  async function fetchToolStats(toolType, timeRange) {
177
- const endpointBase = TOOL_ENDPOINTS[toolType];
179
+ const normalizedToolType = normalizePlatformKey(toolType);
180
+ const endpointBase = TOOL_ENDPOINTS[normalizedToolType];
178
181
  if (!endpointBase) {
179
182
  throw new Error(`不支持的渠道类型: ${toolType}`);
180
183
  }
@@ -235,6 +238,7 @@ function buildDisplayPayload(type, timeRange, data) {
235
238
  * 查看统计信息
236
239
  */
237
240
  async function handleStats(type = null, options = {}) {
241
+ const normalizedType = type ? normalizePlatformKey(type) : null;
238
242
  // 检查 UI 服务
239
243
  const uiRunning = await checkUIService();
240
244
  if (!uiRunning) {
@@ -246,16 +250,16 @@ async function handleStats(type = null, options = {}) {
246
250
  const timeRange = options.today ? 'today' : options.week ? 'week' : options.month ? 'month' : 'all';
247
251
 
248
252
  try {
249
- if (!validateToolType(type)) {
253
+ if (!validateToolType(normalizedType)) {
250
254
  console.error(chalk.red(`\n[ERROR] 无效的渠道类型: ${type}\n`));
251
- console.log(chalk.gray('支持的类型: claude, codex, gemini, opencode\n'));
255
+ console.log(chalk.gray('支持的类型: claude, codex, gemini, opencode, pi, omp\n'));
252
256
  process.exit(1);
253
257
  }
254
258
 
255
259
  let payload;
256
- if (type) {
257
- const summary = await fetchToolStats(type, timeRange);
258
- payload = buildDisplayPayload(type, timeRange, summary);
260
+ if (normalizedType) {
261
+ const summary = await fetchToolStats(normalizedType, timeRange);
262
+ payload = buildDisplayPayload(normalizedType, timeRange, summary);
259
263
  } else {
260
264
  const overall = await fetchOverallStats(timeRange);
261
265
  payload = buildDisplayPayload(null, timeRange, overall);
@@ -274,7 +278,7 @@ async function handleStats(type = null, options = {}) {
274
278
  function displayStats(stats) {
275
279
  const type = stats.type;
276
280
  const timeRange = stats.timeRange;
277
- const title = type ? `${type.toUpperCase()} 统计信息` : '总体统计信息';
281
+ const title = type ? `${getToolDisplayName(type)} 统计信息` : '总体统计信息';
278
282
  const rangeText = {
279
283
  today: '今日',
280
284
  week: '本周',
@@ -316,11 +320,11 @@ function displayStats(stats) {
316
320
  }
317
321
 
318
322
  if (!type && stats.byToolType) {
319
- const iconMap = { claude: '[*]', codex: '[*]', gemini: '[*]', opencode: '[*]' };
323
+ const iconMap = { claude: '[*]', codex: '[*]', gemini: '[*]', opencode: '[*]', pi: '[*]' };
320
324
  console.log(chalk.bold('\n[CH] 分渠道汇总:'));
321
325
  TOOL_TYPES.forEach((toolType) => {
322
326
  const item = stats.byToolType[toolType] || emptySummary();
323
- console.log(chalk.gray(` ${iconMap[toolType]} ${toolType.toUpperCase()}:`));
327
+ console.log(chalk.gray(` ${iconMap[toolType]} ${getToolDisplayName(toolType)}:`));
324
328
  console.log(
325
329
  chalk.gray(
326
330
  ` 请求: ${formatNumber(item.requests)} | Tokens: ${formatNumber(item.tokens)} | 成本: $${normalizeNumber(item.cost).toFixed(4)}`
@@ -332,10 +336,17 @@ function displayStats(stats) {
332
336
  console.log(chalk.gray('\n[TIP] 提示:'));
333
337
  console.log(chalk.gray(' • 使用 ') + chalk.cyan('ctx stats --today') + chalk.gray(' 查看今日统计'));
334
338
  console.log(chalk.gray(' • 使用 ') + chalk.cyan('ctx stats claude') + chalk.gray(' 查看特定渠道'));
339
+ console.log(chalk.gray(' • 使用 ') + chalk.cyan('ctx stats omp') + chalk.gray(' 查看 OMP 统计'));
335
340
  console.log(chalk.gray(' • 使用 ') + chalk.cyan('ctx stats opencode') + chalk.gray(' 查看 OpenCode 统计'));
336
341
  console.log(chalk.gray(' • 使用 ') + chalk.cyan('ctx stats export') + chalk.gray(' 导出统计数据\n'));
337
342
  }
338
343
 
344
+ function getToolDisplayName(type) {
345
+ const normalizedType = normalizePlatformKey(type);
346
+ if (normalizedType === 'pi') return 'OMP';
347
+ return String(normalizedType || '').toUpperCase();
348
+ }
349
+
339
350
  /**
340
351
  * 格式化数字
341
352
  */
@@ -349,6 +360,7 @@ function formatNumber(num) {
349
360
  */
350
361
  async function handleStatsExport(type = null, format = 'json') {
351
362
  console.log(chalk.cyan('\n[EXPORT] 导出统计数据...\n'));
363
+ const normalizedType = type ? normalizePlatformKey(type) : null;
352
364
 
353
365
  const uiRunning = await checkUIService();
354
366
  if (!uiRunning) {
@@ -357,9 +369,9 @@ async function handleStatsExport(type = null, format = 'json') {
357
369
  }
358
370
 
359
371
  try {
360
- if (!validateToolType(type)) {
372
+ if (!validateToolType(normalizedType)) {
361
373
  console.error(chalk.red(`\n[ERROR] 无效的渠道类型: ${type}\n`));
362
- console.log(chalk.gray('支持的类型: claude, codex, gemini, opencode\n'));
374
+ console.log(chalk.gray('支持的类型: claude, codex, gemini, opencode, pi, omp\n'));
363
375
  process.exit(1);
364
376
  }
365
377
 
@@ -369,9 +381,9 @@ async function handleStatsExport(type = null, format = 'json') {
369
381
  }
370
382
 
371
383
  let payload;
372
- if (type) {
373
- const summary = await fetchToolStats(type, 'all');
374
- payload = buildDisplayPayload(type, 'all', summary);
384
+ if (normalizedType) {
385
+ const summary = await fetchToolStats(normalizedType, 'all');
386
+ payload = buildDisplayPayload(normalizedType, 'all', summary);
375
387
  } else {
376
388
  const overall = await fetchOverallStats('all');
377
389
  payload = buildDisplayPayload(null, 'all', overall);
@@ -379,7 +391,7 @@ async function handleStatsExport(type = null, format = 'json') {
379
391
 
380
392
  const fs = require('fs');
381
393
  const path = require('path');
382
- const filename = `cc-tool-stats-${type || 'all'}-${Date.now()}.json`;
394
+ const filename = `cc-tool-stats-${normalizedType || 'all'}-${Date.now()}.json`;
383
395
  const filepath = path.join(process.cwd(), filename);
384
396
 
385
397
  fs.writeFileSync(filepath, JSON.stringify(payload, null, 2));
@@ -394,5 +406,11 @@ async function handleStatsExport(type = null, format = 'json') {
394
406
 
395
407
  module.exports = {
396
408
  handleStats,
397
- handleStatsExport
409
+ handleStatsExport,
410
+ _test: {
411
+ buildDisplayPayload,
412
+ fetchToolStats,
413
+ getToolDisplayName,
414
+ validateToolType
415
+ }
398
416
  };
@@ -5,6 +5,7 @@ const inquirer = require('inquirer');
5
5
  const { loadConfig } = require('../config/loader');
6
6
  const { PATHS } = require('../config/paths');
7
7
  const { clearNativeOAuth } = require('../server/services/native-oauth-adapters');
8
+ const { normalizePlatformKey } = require('../shared/platforms');
8
9
  const SETTINGS_MANAGERS = {
9
10
  claude: () => require('../server/services/settings-manager'),
10
11
  codex: () => require('../server/services/codex-settings-manager'),
@@ -16,10 +17,11 @@ const SETTINGS_MANAGERS = {
16
17
  * 获取当前类型的代理服务
17
18
  */
18
19
  function getProxyServices(cliType) {
19
- if (cliType === 'claude') {
20
+ const normalizedCliType = normalizePlatformKey(cliType || 'claude');
21
+ if (normalizedCliType === 'claude') {
20
22
  const { getProxyStatus, startProxyServer, stopProxyServer } = require('../server/proxy-server');
21
23
  return { getProxyStatus, startProxyServer, stopProxyServer, defaultPort: 20088 };
22
- } else if (cliType === 'codex') {
24
+ } else if (normalizedCliType === 'codex') {
23
25
  const { getCodexProxyStatus, startCodexProxyServer, stopCodexProxyServer } = require('../server/codex-proxy-server');
24
26
  return {
25
27
  getProxyStatus: getCodexProxyStatus,
@@ -27,7 +29,7 @@ function getProxyServices(cliType) {
27
29
  stopProxyServer: stopCodexProxyServer,
28
30
  defaultPort: 20089
29
31
  };
30
- } else if (cliType === 'gemini') {
32
+ } else if (normalizedCliType === 'gemini') {
31
33
  const { getGeminiProxyStatus, startGeminiProxyServer, stopGeminiProxyServer } = require('../server/gemini-proxy-server');
32
34
  return {
33
35
  getProxyStatus: getGeminiProxyStatus,
@@ -35,7 +37,7 @@ function getProxyServices(cliType) {
35
37
  stopProxyServer: stopGeminiProxyServer,
36
38
  defaultPort: 20090
37
39
  };
38
- } else if (cliType === 'opencode') {
40
+ } else if (normalizedCliType === 'opencode') {
39
41
  const { getOpenCodeProxyStatus, startOpenCodeProxyServer, stopOpenCodeProxyServer } = require('../server/opencode-proxy-server');
40
42
  return {
41
43
  getProxyStatus: getOpenCodeProxyStatus,
@@ -43,11 +45,24 @@ function getProxyServices(cliType) {
43
45
  stopProxyServer: stopOpenCodeProxyServer,
44
46
  defaultPort: 20091
45
47
  };
48
+ } else if (normalizedCliType === 'pi') {
49
+ const { getPiProxyStatus, startPiProxyServer, stopPiProxyServer } = require('../server/pi-proxy-server');
50
+ const config = loadConfig();
51
+ return {
52
+ getProxyStatus: getPiProxyStatus,
53
+ startProxyServer: startPiProxyServer,
54
+ stopProxyServer: stopPiProxyServer,
55
+ defaultPort: config.ports?.piProxy || 20092,
56
+ managedProviderConfig: true
57
+ };
46
58
  }
59
+
60
+ return null;
47
61
  }
48
62
 
49
63
  function getSettingsManager(cliType) {
50
- const loader = SETTINGS_MANAGERS[cliType] || SETTINGS_MANAGERS.claude;
64
+ const normalizedCliType = normalizePlatformKey(cliType || 'claude');
65
+ const loader = SETTINGS_MANAGERS[normalizedCliType] || SETTINGS_MANAGERS.claude;
51
66
  const manager = loader();
52
67
  return {
53
68
  setProxyConfig: manager.setProxyConfig,
@@ -58,7 +73,8 @@ function getSettingsManager(cliType) {
58
73
  }
59
74
 
60
75
  function removeActiveChannelMarker(cliType) {
61
- const markerPath = PATHS.activeChannel?.[cliType];
76
+ const normalizedCliType = normalizePlatformKey(cliType || 'claude');
77
+ const markerPath = PATHS.activeChannel?.[normalizedCliType];
62
78
  if (!markerPath) {
63
79
  return;
64
80
  }
@@ -73,7 +89,8 @@ function removeActiveChannelMarker(cliType) {
73
89
  }
74
90
 
75
91
  function loadActiveChannelId(cliType) {
76
- const markerPath = PATHS.activeChannel?.[cliType];
92
+ const normalizedCliType = normalizePlatformKey(cliType || 'claude');
93
+ const markerPath = PATHS.activeChannel?.[normalizedCliType];
77
94
  if (!markerPath) {
78
95
  return null;
79
96
  }
@@ -116,27 +133,28 @@ function pickRestoredChannel(cliType, channels = []) {
116
133
  }
117
134
 
118
135
  function restoreSingleChannelMode(cliType) {
119
- if (cliType === 'claude') {
136
+ const normalizedCliType = normalizePlatformKey(cliType || 'claude');
137
+ if (normalizedCliType === 'claude') {
120
138
  const { getAllChannels, applyChannelToSettings } = require('../server/services/channels');
121
- const target = pickRestoredChannel(cliType, getAllChannels());
139
+ const target = pickRestoredChannel(normalizedCliType, getAllChannels());
122
140
  return target ? applyChannelToSettings(target.id) : null;
123
141
  }
124
142
 
125
- if (cliType === 'codex') {
143
+ if (normalizedCliType === 'codex') {
126
144
  const { getChannels, applyChannelToSettings } = require('../server/services/codex-channels');
127
- const target = pickRestoredChannel(cliType, getChannels().channels || []);
145
+ const target = pickRestoredChannel(normalizedCliType, getChannels().channels || []);
128
146
  return target ? applyChannelToSettings(target.id) : null;
129
147
  }
130
148
 
131
- if (cliType === 'gemini') {
149
+ if (normalizedCliType === 'gemini') {
132
150
  const { getChannels, applyChannelToSettings } = require('../server/services/gemini-channels');
133
- const target = pickRestoredChannel(cliType, getChannels().channels || []);
151
+ const target = pickRestoredChannel(normalizedCliType, getChannels().channels || []);
134
152
  return target ? applyChannelToSettings(target.id) : null;
135
153
  }
136
154
 
137
- if (cliType === 'opencode') {
155
+ if (normalizedCliType === 'opencode') {
138
156
  const { getChannels, applyChannelToSettings } = require('../server/services/opencode-channels');
139
- const target = pickRestoredChannel(cliType, getChannels().channels || []);
157
+ const target = pickRestoredChannel(normalizedCliType, getChannels().channels || []);
140
158
  return target ? applyChannelToSettings(target.id) : null;
141
159
  }
142
160
 
@@ -148,7 +166,7 @@ function restoreSingleChannelMode(cliType) {
148
166
  */
149
167
  async function handleToggleProxy() {
150
168
  const config = loadConfig();
151
- const cliType = config.currentCliType || 'claude';
169
+ const cliType = normalizePlatformKey(config.currentCliType || 'claude');
152
170
  const services = getProxyServices(cliType);
153
171
  if (!services) {
154
172
  console.log(chalk.red(`\n[ERROR] 当前 CLI 类型 (${cliType}) 暂不支持动态切换\n`));
@@ -179,21 +197,33 @@ async function handleStartProxy(cliType, services) {
179
197
  claude: 'Claude Code',
180
198
  codex: 'Codex',
181
199
  gemini: 'Gemini',
182
- opencode: 'OpenCode'
200
+ opencode: 'OpenCode',
201
+ pi: 'OMP'
183
202
  };
184
203
  const toolName = toolNameMap[cliType] || 'Claude Code';
185
204
  const defaultPort = services.defaultPort;
186
205
 
187
206
  console.log(chalk.cyan('动态切换功能说明:'));
188
- console.log(chalk.gray('• 开启后会在本地启动一个代理服务'));
189
- console.log(chalk.gray(`• 可以在不重启 ${toolName} 的情况下动态管理渠道`));
190
- console.log(chalk.gray('• 通过 Web UI 或"渠道管理"功能快速调整启用的线路'));
191
- console.log(chalk.gray(`• 代理服务地址: http://127.0.0.1:${defaultPort}\n`));
207
+ if (services.managedProviderConfig) {
208
+ console.log(chalk.gray('• 开启后会把已启用的 OMP 渠道写入受管 models.yml provider 配置'));
209
+ console.log(chalk.gray(`• 可以通过 Web UI 或"渠道管理"功能调整启用的 ${toolName} provider`));
210
+ console.log(chalk.gray(`• 状态端口: http://127.0.0.1:${defaultPort}\n`));
211
+ } else {
212
+ console.log(chalk.gray('• 开启后会在本地启动一个代理服务'));
213
+ console.log(chalk.gray(`• 可以在不重启 ${toolName} 的情况下动态管理渠道`));
214
+ console.log(chalk.gray('• 通过 Web UI 或"渠道管理"功能快速调整启用的线路'));
215
+ console.log(chalk.gray(`• 代理服务地址: http://127.0.0.1:${defaultPort}\n`));
216
+ }
192
217
 
193
218
  console.log(chalk.yellow('[WARN] 重要提示:'));
194
- console.log(chalk.yellow('• 开启期间请勿关闭 CLI 终端窗口'));
195
- console.log(chalk.yellow('• 如果异常关闭导致代理失效,请运行: ctx reset'));
196
- console.log(chalk.yellow('• 或使用主菜单的"恢复默认配置"功能\n'));
219
+ if (services.managedProviderConfig) {
220
+ console.log(chalk.yellow('• 启用后会写入 Coding-Tool 管理的 OMP models.yml provider 配置'));
221
+ console.log(chalk.yellow('• 关闭会移除受管 provider,不会清理 OMP 原生 auth.json\n'));
222
+ } else {
223
+ console.log(chalk.yellow('• 开启期间请勿关闭 CLI 终端窗口'));
224
+ console.log(chalk.yellow('• 如果异常关闭导致代理失效,请运行: ctx reset'));
225
+ console.log(chalk.yellow('• 或使用主菜单的"恢复默认配置"功能\n'));
226
+ }
197
227
 
198
228
  const { confirm } = await inquirer.prompt([
199
229
  {
@@ -210,29 +240,47 @@ async function handleStartProxy(cliType, services) {
210
240
  }
211
241
 
212
242
  try {
213
- console.log(chalk.cyan('\n[START] 正在启动代理服务...\n'));
243
+ console.log(chalk.cyan(services.managedProviderConfig
244
+ ? '\n[START] 正在启用 OMP models.yml 受管 provider...\n'
245
+ : '\n[START] 正在启动代理服务...\n'));
214
246
 
215
247
  // 启动代理服务器
216
248
  const proxyResult = await services.startProxyServer();
217
249
 
218
250
  if (!proxyResult.success) {
219
- throw new Error('代理服务器启动失败');
251
+ throw new Error(services.managedProviderConfig ? 'OMP 受管 provider 启用失败' : '代理服务器启动失败');
220
252
  }
221
253
 
222
- console.log(chalk.green(`[OK] 代理服务已启动: http://127.0.0.1:${proxyResult.port}`));
254
+ if (services.managedProviderConfig) {
255
+ console.log(chalk.green('[OK] OMP models.yml 受管 provider 已启用'));
256
+ if (proxyResult.port) {
257
+ console.log(chalk.gray(`状态端口: http://127.0.0.1:${proxyResult.port}`));
258
+ }
259
+ (proxyResult.warnings || []).forEach((warning) => {
260
+ console.log(chalk.yellow(`[WARN] ${warning}`));
261
+ });
262
+ } else {
263
+ console.log(chalk.green(`[OK] 代理服务已启动: http://127.0.0.1:${proxyResult.port}`));
264
+ }
223
265
 
224
266
  // 修改配置文件
225
- const settingsManager = getSettingsManager(cliType);
226
- clearNativeOAuth(cliType);
227
- settingsManager.setProxyConfig(proxyResult.port);
228
- console.log(chalk.green('[OK] 配置文件已更新'));
229
-
230
- if (settingsManager.hasBackup()) {
231
- console.log(chalk.green('[OK] 原配置已备份'));
267
+ if (!services.managedProviderConfig) {
268
+ const settingsManager = getSettingsManager(cliType);
269
+ clearNativeOAuth(cliType);
270
+ settingsManager.setProxyConfig(proxyResult.port);
271
+ console.log(chalk.green('[OK] 配置文件已更新'));
272
+
273
+ if (settingsManager.hasBackup()) {
274
+ console.log(chalk.green('[OK] 原配置已备份'));
275
+ }
232
276
  }
233
277
 
234
- console.log(chalk.cyan('\n[TIP] 动态切换已启用!'));
235
- console.log(chalk.gray(` 现在可以通过"渠道管理"功能快速调整,无需重启 ${toolName}\n`));
278
+ console.log(chalk.cyan(services.managedProviderConfig ? '\n[TIP] OMP 受管 provider 已启用!' : '\n[TIP] 动态切换已启用!'));
279
+ if (services.managedProviderConfig) {
280
+ console.log(chalk.gray(' 现在可以通过"渠道管理"功能调整已启用的 OMP provider,并重新同步 models.yml\n'));
281
+ } else {
282
+ console.log(chalk.gray(` 现在可以通过"渠道管理"功能快速调整,无需重启 ${toolName}\n`));
283
+ }
236
284
 
237
285
  await inquirer.prompt([
238
286
  {
@@ -267,20 +315,32 @@ async function handleStopProxy(cliType, services) {
267
315
  claude: 'Claude Code',
268
316
  codex: 'Codex',
269
317
  gemini: 'Gemini',
270
- opencode: 'OpenCode'
318
+ opencode: 'OpenCode',
319
+ pi: 'OMP'
271
320
  };
272
321
  const toolName = toolNameMap[cliType] || 'Claude Code';
273
322
  const proxyStatus = services.getProxyStatus();
274
323
 
275
324
  console.log(chalk.cyan('当前状态:'));
276
- console.log(chalk.gray(`• 代理服务: ${chalk.green('运行中')}`));
277
- console.log(chalk.gray(`• 代理端口: ${proxyStatus.port}`));
278
- console.log(chalk.gray(`• 代理地址: http://127.0.0.1:${proxyStatus.port}\n`));
325
+ if (services.managedProviderConfig) {
326
+ console.log(chalk.gray(`• 受管配置: ${chalk.green('已启用')}`));
327
+ console.log(chalk.gray(`• 配置模式: ${proxyStatus.mode || 'models-yml-provider-config'}`));
328
+ console.log(chalk.gray(`• 状态端口: http://127.0.0.1:${proxyStatus.port || proxyStatus.defaultPort}\n`));
329
+ } else {
330
+ console.log(chalk.gray(`• 代理服务: ${chalk.green('运行中')}`));
331
+ console.log(chalk.gray(`• 代理端口: ${proxyStatus.port}`));
332
+ console.log(chalk.gray(`• 代理地址: http://127.0.0.1:${proxyStatus.port}\n`));
333
+ }
279
334
 
280
335
  console.log(chalk.yellow('关闭后:'));
281
- console.log(chalk.gray('• 代理服务将被停止'));
282
- console.log(chalk.gray('• 配置将恢复为当前激活渠道的单渠道模式'));
283
- console.log(chalk.gray(`• 之后管理渠道将需要重启 ${toolName}\n`));
336
+ if (services.managedProviderConfig) {
337
+ console.log(chalk.gray('• OMP 受管 models.yml provider 配置将被移除'));
338
+ console.log(chalk.gray(`• 之后调整 ${toolName} provider 需要重新启用受管配置\n`));
339
+ } else {
340
+ console.log(chalk.gray('• 代理服务将被停止'));
341
+ console.log(chalk.gray('• 配置将恢复为当前激活渠道的单渠道模式'));
342
+ console.log(chalk.gray(`• 之后管理渠道将需要重启 ${toolName}\n`));
343
+ }
284
344
 
285
345
  const { confirm } = await inquirer.prompt([
286
346
  {
@@ -297,25 +357,38 @@ async function handleStopProxy(cliType, services) {
297
357
  }
298
358
 
299
359
  try {
300
- console.log(chalk.cyan('\n[STOP] 正在停止代理服务...\n'));
360
+ console.log(chalk.cyan(services.managedProviderConfig
361
+ ? '\n[STOP] 正在移除 OMP models.yml 受管 provider...\n'
362
+ : '\n[STOP] 正在停止代理服务...\n'));
301
363
 
302
364
  // 停止代理服务器
303
365
  await services.stopProxyServer();
304
- console.log(chalk.green('[OK] 代理服务已停止'));
366
+ console.log(chalk.green(services.managedProviderConfig
367
+ ? '[OK] OMP models.yml 受管 provider 已移除'
368
+ : '[OK] 代理服务已停止'));
305
369
 
306
370
  // 恢复配置文件
307
- const settingsManager = getSettingsManager(cliType);
308
- settingsManager.deleteBackup?.();
309
- const restoredChannel = restoreSingleChannelMode(cliType);
310
- removeActiveChannelMarker(cliType);
311
- if (restoredChannel?.name) {
312
- console.log(chalk.green(`[OK] 已恢复到渠道: ${restoredChannel.name}`));
371
+ if (!services.managedProviderConfig) {
372
+ const settingsManager = getSettingsManager(cliType);
373
+ settingsManager.deleteBackup?.();
374
+ const restoredChannel = restoreSingleChannelMode(cliType);
375
+ removeActiveChannelMarker(cliType);
376
+ if (restoredChannel?.name) {
377
+ console.log(chalk.green(`[OK] 已恢复到渠道: ${restoredChannel.name}`));
378
+ } else {
379
+ console.log(chalk.green('[OK] 已清理代理接管状态'));
380
+ }
313
381
  } else {
382
+ removeActiveChannelMarker(cliType);
314
383
  console.log(chalk.green('[OK] 已清理代理接管状态'));
315
384
  }
316
385
 
317
- console.log(chalk.cyan('\n[TIP] 动态切换已关闭'));
318
- console.log(chalk.gray(` 现在调整渠道需要重启 ${toolName} 才能生效\n`));
386
+ console.log(chalk.cyan(services.managedProviderConfig ? '\n[TIP] OMP 受管 provider 已关闭' : '\n[TIP] 动态切换已关闭'));
387
+ if (services.managedProviderConfig) {
388
+ console.log(chalk.gray(' 如需恢复受管 provider 配置,请再次运行 ctx omp start\n'));
389
+ } else {
390
+ console.log(chalk.gray(` 现在调整渠道需要重启 ${toolName} 才能生效\n`));
391
+ }
319
392
 
320
393
  await inquirer.prompt([
321
394
  {
@@ -339,4 +412,9 @@ async function handleStopProxy(cliType, services) {
339
412
 
340
413
  module.exports = {
341
414
  handleToggleProxy,
415
+ _test: {
416
+ getProxyServices,
417
+ pickRestoredChannel,
418
+ restoreSingleChannelMode
419
+ }
342
420
  };