coding-tool-x 3.6.13 → 3.7.1
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 +45 -33
- package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dpje9L84.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-Cz6kasek.js} +1 -1
- package/dist/web/assets/Home-BwTaYu3q.js +1 -0
- package/dist/web/assets/Home-Jom_ZcdY.css +1 -0
- package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-DsN-xNXx.js} +1 -1
- package/dist/web/assets/ProjectList-4BCObimU.js +1 -0
- package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-D3a0oRum.css} +1 -1
- package/dist/web/assets/SessionList-D4c8FRzq.js +1 -0
- package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-C-KYbXB5.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-B-BlPJHX.js} +1 -1
- package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
- package/dist/web/assets/index-Btfvh0GG.js +2 -0
- package/dist/web/assets/index-CLNa6DhH.css +1 -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 +3 -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 +46 -6
- package/src/commands/port-config.js +83 -75
- package/src/commands/proxy-control.js +59 -26
- package/src/commands/stats.js +37 -19
- package/src/commands/switch.js +5 -1
- package/src/commands/toggle-proxy.js +131 -53
- package/src/commands/ui.js +3 -4
- package/src/commands/workspace.js +3 -35
- package/src/config/default.js +19 -2
- package/src/config/loader.js +5 -76
- package/src/config/paths.js +96 -11
- package/src/index.js +10 -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 +5 -1
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/mcp.js +5 -3
- package/src/server/api/omp-channels.js +349 -0
- package/src/server/api/omp-config.js +164 -0
- package/src/server/api/omp-projects.js +76 -0
- package/src/server/api/omp-proxy.js +154 -0
- package/src/server/api/omp-sessions.js +334 -0
- package/src/server/api/omp-statistics.js +40 -0
- package/src/server/api/plugins.js +1 -1
- package/src/server/api/prompts.js +3 -2
- package/src/server/api/settings.js +2 -0
- package/src/server/api/skills.js +1 -1
- package/src/server/api/workspaces.js +3 -41
- package/src/server/index.js +41 -0
- package/src/server/omp-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 +296 -39
- 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/notification-hooks.js +138 -6
- package/src/server/services/omp-auth-providers.js +294 -0
- package/src/server/services/omp-channels.js +94 -0
- package/src/server/services/omp-config.js +269 -0
- package/src/server/services/omp-sessions.js +490 -0
- package/src/server/services/omp-settings-manager.js +975 -0
- package/src/server/services/omp-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/sessions.js +19 -35
- package/src/server/services/skill-service.js +13 -3
- package/src/server/services/speed-test.js +34 -28
- package/src/server/services/ui-config.js +50 -12
- package/src/server/services/workspace-service.js +117 -225
- package/src/server/websocket-server.js +7 -1
- package/src/shared/platforms.js +201 -0
- package/src/utils/session.js +4 -10
- package/dist/web/assets/Home-BiaKZMah.js +0 -1
- package/dist/web/assets/Home-CIua-b-o.css +0 -1
- package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
- package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
- package/dist/web/assets/index-BpBFF3b2.js +0 -2
- package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
- package/src/utils/cli-flags.js +0 -7
|
@@ -1,30 +1,56 @@
|
|
|
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: {
|
|
7
8
|
name: 'Claude',
|
|
8
9
|
icon: '[*]',
|
|
9
|
-
|
|
10
|
+
aompPath: '/api/proxy'
|
|
10
11
|
},
|
|
11
12
|
codex: {
|
|
12
13
|
name: 'Codex',
|
|
13
14
|
icon: '[*]',
|
|
14
|
-
|
|
15
|
+
aompPath: '/api/codex/proxy'
|
|
15
16
|
},
|
|
16
17
|
gemini: {
|
|
17
18
|
name: 'Gemini',
|
|
18
19
|
icon: '[*]',
|
|
19
|
-
|
|
20
|
+
aompPath: '/api/gemini/proxy'
|
|
20
21
|
},
|
|
21
22
|
opencode: {
|
|
22
23
|
name: 'OpenCode',
|
|
23
24
|
icon: '[*]',
|
|
24
|
-
|
|
25
|
+
aompPath: '/api/opencode/proxy'
|
|
26
|
+
},
|
|
27
|
+
omp: {
|
|
28
|
+
name: 'OMP',
|
|
29
|
+
icon: '[*]',
|
|
30
|
+
aompPath: '/api/omp/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, 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();
|
|
@@ -115,12 +142,12 @@ async function handleProxyStart(channel) {
|
|
|
115
142
|
}
|
|
116
143
|
|
|
117
144
|
try {
|
|
118
|
-
const response = await httpRequest('POST', `${channelInfo.
|
|
145
|
+
const response = await httpRequest('POST', `${channelInfo.aompPath}/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, 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) {
|
|
@@ -155,10 +184,10 @@ async function handleProxyStop(channel) {
|
|
|
155
184
|
}
|
|
156
185
|
|
|
157
186
|
try {
|
|
158
|
-
const response = await httpRequest('POST', `${channelInfo.
|
|
187
|
+
const response = await httpRequest('POST', `${channelInfo.aompPath}/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, 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, 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'));
|
|
@@ -207,27 +240,27 @@ async function handleProxyStatus(channel) {
|
|
|
207
240
|
}
|
|
208
241
|
|
|
209
242
|
try {
|
|
210
|
-
const response = await httpRequest('GET', `${channelInfo.
|
|
243
|
+
const response = await httpRequest('GET', `${channelInfo.aompPath}/status`);
|
|
211
244
|
const payload = response.data || {};
|
|
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', 'omp'];
|
|
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
|
+
omp: '/api/omp/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, 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: '[*]', omp: '[*]' };
|
|
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 === 'omp') 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, 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
|
};
|
package/src/commands/switch.js
CHANGED
|
@@ -3,6 +3,7 @@ const chalk = require('chalk');
|
|
|
3
3
|
const { getAvailableProjects } = require('../utils/session');
|
|
4
4
|
const { promptSelectProject } = require('../ui/prompts');
|
|
5
5
|
const { saveConfig } = require('../config/loader');
|
|
6
|
+
const { HOME_DIR } = require('../config/paths');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 切换项目
|
|
@@ -28,7 +29,10 @@ async function switchProject(config) {
|
|
|
28
29
|
config.defaultProject = selectedProject;
|
|
29
30
|
|
|
30
31
|
// 保存到配置文件(保留其余字段)
|
|
31
|
-
saveConfig({
|
|
32
|
+
saveConfig({
|
|
33
|
+
...config,
|
|
34
|
+
projectsDir: config.projectsDir.replace(HOME_DIR, '~')
|
|
35
|
+
});
|
|
32
36
|
|
|
33
37
|
// 使用解析后的名称显示
|
|
34
38
|
const { parseRealProjectPath } = require('../server/services/sessions');
|