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.
- package/README.md +46 -33
- package/dist/web/assets/{Analytics-BuBlsbcI.js → Analytics-Dma_j2Rq.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-DQczxuOX.js → ConfigTemplates-C49Y1PLH.js} +1 -1
- package/dist/web/assets/Home-DGGC8H4g.js +1 -0
- package/dist/web/assets/Home-ewgeIhF7.css +1 -0
- package/dist/web/assets/{PluginManager-C8gAlaps.js → PluginManager-B1ECTHyY.js} +1 -1
- package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-B_L55-l9.css} +1 -1
- package/dist/web/assets/ProjectList-DyiGyT-H.js +1 -0
- package/dist/web/assets/SessionList-CH5GwwiJ.js +1 -0
- package/dist/web/assets/{SkillManager-BDbNbNkK.js → SkillManager-CRyVhjYQ.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-DGGaGW2c.js → WorkspaceManager-CO9tclVt.js} +1 -1
- package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
- package/dist/web/assets/index-CM26UrnJ.css +1 -0
- package/dist/web/assets/index-EQq56pRa.js +2 -0
- package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
- package/dist/web/index.html +4 -4
- package/package.json +2 -1
- package/src/commands/channels.js +85 -24
- package/src/commands/daemon.js +7 -4
- package/src/commands/doctor.js +17 -8
- package/src/commands/logs.js +48 -6
- package/src/commands/port-config.js +81 -75
- package/src/commands/proxy-control.js +52 -19
- package/src/commands/stats.js +37 -19
- package/src/commands/toggle-proxy.js +131 -53
- package/src/commands/ui.js +3 -4
- package/src/config/default.js +14 -3
- package/src/config/paths.js +99 -11
- package/src/index.js +11 -16
- package/src/server/api/commands.js +1 -1
- package/src/server/api/config-export.js +4 -2
- package/src/server/api/config-registry.js +40 -34
- package/src/server/api/config.js +2 -1
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/mcp.js +5 -3
- package/src/server/api/pi-channels.js +349 -0
- package/src/server/api/pi-config.js +164 -0
- package/src/server/api/pi-projects.js +76 -0
- package/src/server/api/pi-proxy.js +154 -0
- package/src/server/api/pi-sessions.js +334 -0
- package/src/server/api/pi-statistics.js +40 -0
- package/src/server/api/plugins.js +1 -1
- package/src/server/api/prompts.js +3 -2
- package/src/server/api/skills.js +1 -1
- package/src/server/api/workspaces.js +2 -2
- package/src/server/index.js +41 -0
- package/src/server/pi-proxy-server.js +66 -0
- package/src/server/services/agents-service.js +2 -1
- package/src/server/services/base/proxy-utils.js +1 -0
- package/src/server/services/channel-balance.js +151 -1
- package/src/server/services/channel-scheduler.js +7 -1
- package/src/server/services/commands-service.js +11 -2
- package/src/server/services/config-export-service.js +275 -18
- package/src/server/services/config-registry-service.js +20 -18
- package/src/server/services/config-sync-manager.js +115 -6
- package/src/server/services/config-sync-service.js +4 -2
- package/src/server/services/config-templates-service.js +183 -45
- package/src/server/services/mcp-service.js +217 -33
- package/src/server/services/pi-auth-providers.js +294 -0
- package/src/server/services/pi-channels.js +94 -0
- package/src/server/services/pi-config.js +287 -0
- package/src/server/services/pi-sessions.js +490 -0
- package/src/server/services/pi-settings-manager.js +571 -0
- package/src/server/services/pi-statistics-service.js +317 -0
- package/src/server/services/plugins-service.js +673 -11
- package/src/server/services/prompts-service.js +112 -7
- package/src/server/services/proxy-log-helper.js +1 -0
- package/src/server/services/proxy-runtime.js +5 -1
- package/src/server/services/skill-service.js +13 -3
- package/src/server/services/speed-test.js +9 -3
- package/src/server/services/ui-config.js +50 -12
- package/src/server/services/workspace-service.js +19 -5
- package/src/server/websocket-server.js +7 -1
- package/dist/web/assets/Home-BvAzzWnB.js +0 -1
- package/dist/web/assets/Home-CIua-b-o.css +0 -1
- package/dist/web/assets/ProjectList-nqgi6_cR.js +0 -1
- package/dist/web/assets/SessionList-D8AxMYy6.js +0 -1
- package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
- package/dist/web/assets/index-y-eEayxR.js +0 -2
- 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
|
|
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
|
|
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}
|
|
122
|
-
console.log(chalk.gray(`${channelInfo.icon}
|
|
123
|
-
console.log(chalk.gray(`[NET]
|
|
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
|
|
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
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
220
|
-
console.log(chalk.gray(` ${channelInfo.icon}
|
|
221
|
-
console.log(chalk.gray(` [NET]
|
|
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(
|
|
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) {
|
package/src/commands/stats.js
CHANGED
|
@@ -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
|
|
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(
|
|
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 (
|
|
257
|
-
const summary = await fetchToolStats(
|
|
258
|
-
payload = buildDisplayPayload(
|
|
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
|
|
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
|
|
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(
|
|
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 (
|
|
373
|
-
const summary = await fetchToolStats(
|
|
374
|
-
payload = buildDisplayPayload(
|
|
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-${
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
136
|
+
const normalizedCliType = normalizePlatformKey(cliType || 'claude');
|
|
137
|
+
if (normalizedCliType === 'claude') {
|
|
120
138
|
const { getAllChannels, applyChannelToSettings } = require('../server/services/channels');
|
|
121
|
-
const target = pickRestoredChannel(
|
|
139
|
+
const target = pickRestoredChannel(normalizedCliType, getAllChannels());
|
|
122
140
|
return target ? applyChannelToSettings(target.id) : null;
|
|
123
141
|
}
|
|
124
142
|
|
|
125
|
-
if (
|
|
143
|
+
if (normalizedCliType === 'codex') {
|
|
126
144
|
const { getChannels, applyChannelToSettings } = require('../server/services/codex-channels');
|
|
127
|
-
const target = pickRestoredChannel(
|
|
145
|
+
const target = pickRestoredChannel(normalizedCliType, getChannels().channels || []);
|
|
128
146
|
return target ? applyChannelToSettings(target.id) : null;
|
|
129
147
|
}
|
|
130
148
|
|
|
131
|
-
if (
|
|
149
|
+
if (normalizedCliType === 'gemini') {
|
|
132
150
|
const { getChannels, applyChannelToSettings } = require('../server/services/gemini-channels');
|
|
133
|
-
const target = pickRestoredChannel(
|
|
151
|
+
const target = pickRestoredChannel(normalizedCliType, getChannels().channels || []);
|
|
134
152
|
return target ? applyChannelToSettings(target.id) : null;
|
|
135
153
|
}
|
|
136
154
|
|
|
137
|
-
if (
|
|
155
|
+
if (normalizedCliType === 'opencode') {
|
|
138
156
|
const { getChannels, applyChannelToSettings } = require('../server/services/opencode-channels');
|
|
139
|
-
const target = pickRestoredChannel(
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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(
|
|
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(
|
|
366
|
+
console.log(chalk.green(services.managedProviderConfig
|
|
367
|
+
? '[OK] OMP models.yml 受管 provider 已移除'
|
|
368
|
+
: '[OK] 代理服务已停止'));
|
|
305
369
|
|
|
306
370
|
// 恢复配置文件
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
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
|
};
|