coding-tool-x 3.6.13 → 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 (86) hide show
  1. package/README.md +46 -33
  2. package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dma_j2Rq.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-D8XEscQY.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-BzKZ8TYu.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-fxOvFTYD.js → SkillManager-CRyVhjYQ.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.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 +83 -75
  23. package/src/commands/proxy-control.js +52 -19
  24. package/src/commands/stats.js +37 -19
  25. package/src/commands/switch.js +5 -1
  26. package/src/commands/toggle-proxy.js +131 -53
  27. package/src/commands/ui.js +3 -4
  28. package/src/commands/workspace.js +3 -35
  29. package/src/config/default.js +19 -2
  30. package/src/config/loader.js +5 -76
  31. package/src/config/paths.js +99 -11
  32. package/src/index.js +11 -16
  33. package/src/server/api/commands.js +1 -1
  34. package/src/server/api/config-export.js +4 -2
  35. package/src/server/api/config-registry.js +40 -34
  36. package/src/server/api/config.js +5 -1
  37. package/src/server/api/dashboard.js +22 -6
  38. package/src/server/api/mcp.js +5 -3
  39. package/src/server/api/pi-channels.js +349 -0
  40. package/src/server/api/pi-config.js +164 -0
  41. package/src/server/api/pi-projects.js +76 -0
  42. package/src/server/api/pi-proxy.js +154 -0
  43. package/src/server/api/pi-sessions.js +334 -0
  44. package/src/server/api/pi-statistics.js +40 -0
  45. package/src/server/api/plugins.js +1 -1
  46. package/src/server/api/prompts.js +3 -2
  47. package/src/server/api/settings.js +2 -0
  48. package/src/server/api/skills.js +1 -1
  49. package/src/server/api/workspaces.js +3 -41
  50. package/src/server/index.js +41 -0
  51. package/src/server/pi-proxy-server.js +66 -0
  52. package/src/server/services/agents-service.js +2 -1
  53. package/src/server/services/base/proxy-utils.js +1 -0
  54. package/src/server/services/channel-balance.js +151 -1
  55. package/src/server/services/channel-scheduler.js +7 -1
  56. package/src/server/services/commands-service.js +11 -2
  57. package/src/server/services/config-export-service.js +275 -18
  58. package/src/server/services/config-registry-service.js +20 -18
  59. package/src/server/services/config-sync-manager.js +115 -6
  60. package/src/server/services/config-sync-service.js +4 -2
  61. package/src/server/services/config-templates-service.js +183 -45
  62. package/src/server/services/mcp-service.js +217 -33
  63. package/src/server/services/pi-auth-providers.js +294 -0
  64. package/src/server/services/pi-channels.js +94 -0
  65. package/src/server/services/pi-config.js +287 -0
  66. package/src/server/services/pi-sessions.js +490 -0
  67. package/src/server/services/pi-settings-manager.js +571 -0
  68. package/src/server/services/pi-statistics-service.js +317 -0
  69. package/src/server/services/plugins-service.js +673 -11
  70. package/src/server/services/prompts-service.js +112 -7
  71. package/src/server/services/proxy-log-helper.js +1 -0
  72. package/src/server/services/proxy-runtime.js +5 -1
  73. package/src/server/services/sessions.js +19 -35
  74. package/src/server/services/skill-service.js +13 -3
  75. package/src/server/services/speed-test.js +9 -3
  76. package/src/server/services/ui-config.js +50 -12
  77. package/src/server/services/workspace-service.js +117 -225
  78. package/src/server/websocket-server.js +7 -1
  79. package/src/utils/session.js +4 -10
  80. package/dist/web/assets/Home-BiaKZMah.js +0 -1
  81. package/dist/web/assets/Home-CIua-b-o.css +0 -1
  82. package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
  83. package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
  84. package/dist/web/assets/index-BpBFF3b2.js +0 -2
  85. package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
  86. 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
  };
@@ -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({ ...config });
32
+ saveConfig({
33
+ ...config,
34
+ projectsDir: config.projectsDir.replace(HOME_DIR, '~')
35
+ });
32
36
 
33
37
  // 使用解析后的名称显示
34
38
  const { parseRealProjectPath } = require('../server/services/sessions');