coding-tool-x 3.2.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 (185) hide show
  1. package/CHANGELOG.md +599 -0
  2. package/LICENSE +21 -0
  3. package/README.md +439 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
  6. package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
  7. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  8. package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
  9. package/dist/web/assets/Home-38JTUlYt.js +1 -0
  10. package/dist/web/assets/Home-CjupSEWE.css +1 -0
  11. package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
  12. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  13. package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
  14. package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
  15. package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
  16. package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
  17. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  18. package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
  19. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  20. package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
  21. package/dist/web/assets/icons-DRrXwWZi.js +1 -0
  22. package/dist/web/assets/index-CetESrXw.css +1 -0
  23. package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
  24. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  25. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  26. package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
  27. package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
  28. package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
  29. package/dist/web/favicon.ico +0 -0
  30. package/dist/web/index.html +20 -0
  31. package/dist/web/logo.png +0 -0
  32. package/docs/bannel.png +0 -0
  33. package/docs/home.png +0 -0
  34. package/docs/logo.png +0 -0
  35. package/docs/model-redirection.md +251 -0
  36. package/docs/multi-channel-load-balancing.md +249 -0
  37. package/package.json +80 -0
  38. package/src/commands/channels.js +551 -0
  39. package/src/commands/cli-type.js +101 -0
  40. package/src/commands/daemon.js +365 -0
  41. package/src/commands/doctor.js +333 -0
  42. package/src/commands/export-config.js +205 -0
  43. package/src/commands/list.js +222 -0
  44. package/src/commands/logs.js +261 -0
  45. package/src/commands/plugin.js +585 -0
  46. package/src/commands/port-config.js +135 -0
  47. package/src/commands/proxy-control.js +264 -0
  48. package/src/commands/proxy.js +152 -0
  49. package/src/commands/resume.js +137 -0
  50. package/src/commands/search.js +190 -0
  51. package/src/commands/security.js +37 -0
  52. package/src/commands/stats.js +398 -0
  53. package/src/commands/switch.js +48 -0
  54. package/src/commands/toggle-proxy.js +247 -0
  55. package/src/commands/ui.js +99 -0
  56. package/src/commands/update.js +97 -0
  57. package/src/commands/workspace.js +454 -0
  58. package/src/config/default.js +69 -0
  59. package/src/config/loader.js +149 -0
  60. package/src/config/model-metadata.js +167 -0
  61. package/src/config/model-metadata.json +125 -0
  62. package/src/config/model-pricing.js +35 -0
  63. package/src/config/paths.js +190 -0
  64. package/src/index.js +680 -0
  65. package/src/plugins/constants.js +15 -0
  66. package/src/plugins/event-bus.js +54 -0
  67. package/src/plugins/manifest-validator.js +129 -0
  68. package/src/plugins/plugin-api.js +128 -0
  69. package/src/plugins/plugin-installer.js +601 -0
  70. package/src/plugins/plugin-loader.js +229 -0
  71. package/src/plugins/plugin-manager.js +170 -0
  72. package/src/plugins/registry.js +152 -0
  73. package/src/plugins/schema/plugin-manifest.json +115 -0
  74. package/src/reset-config.js +94 -0
  75. package/src/server/api/agents.js +826 -0
  76. package/src/server/api/aliases.js +36 -0
  77. package/src/server/api/channels.js +368 -0
  78. package/src/server/api/claude-hooks.js +480 -0
  79. package/src/server/api/codex-channels.js +417 -0
  80. package/src/server/api/codex-projects.js +104 -0
  81. package/src/server/api/codex-proxy.js +195 -0
  82. package/src/server/api/codex-sessions.js +483 -0
  83. package/src/server/api/codex-statistics.js +57 -0
  84. package/src/server/api/commands.js +482 -0
  85. package/src/server/api/config-export.js +212 -0
  86. package/src/server/api/config-registry.js +357 -0
  87. package/src/server/api/config-sync.js +155 -0
  88. package/src/server/api/config-templates.js +248 -0
  89. package/src/server/api/config.js +521 -0
  90. package/src/server/api/convert.js +260 -0
  91. package/src/server/api/dashboard.js +142 -0
  92. package/src/server/api/env.js +144 -0
  93. package/src/server/api/favorites.js +77 -0
  94. package/src/server/api/gemini-channels.js +366 -0
  95. package/src/server/api/gemini-projects.js +91 -0
  96. package/src/server/api/gemini-proxy.js +173 -0
  97. package/src/server/api/gemini-sessions.js +376 -0
  98. package/src/server/api/gemini-statistics.js +57 -0
  99. package/src/server/api/health-check.js +31 -0
  100. package/src/server/api/mcp.js +399 -0
  101. package/src/server/api/opencode-channels.js +419 -0
  102. package/src/server/api/opencode-projects.js +99 -0
  103. package/src/server/api/opencode-proxy.js +207 -0
  104. package/src/server/api/opencode-sessions.js +327 -0
  105. package/src/server/api/opencode-statistics.js +57 -0
  106. package/src/server/api/plugins.js +463 -0
  107. package/src/server/api/pm2-autostart.js +269 -0
  108. package/src/server/api/projects.js +124 -0
  109. package/src/server/api/prompts.js +279 -0
  110. package/src/server/api/proxy.js +306 -0
  111. package/src/server/api/security.js +53 -0
  112. package/src/server/api/sessions.js +514 -0
  113. package/src/server/api/settings.js +142 -0
  114. package/src/server/api/skills.js +570 -0
  115. package/src/server/api/statistics.js +238 -0
  116. package/src/server/api/ui-config.js +64 -0
  117. package/src/server/api/workspaces.js +456 -0
  118. package/src/server/codex-proxy-server.js +681 -0
  119. package/src/server/dev-server.js +26 -0
  120. package/src/server/gemini-proxy-server.js +610 -0
  121. package/src/server/index.js +422 -0
  122. package/src/server/opencode-proxy-server.js +4771 -0
  123. package/src/server/proxy-server.js +669 -0
  124. package/src/server/services/agents-service.js +1137 -0
  125. package/src/server/services/alias.js +71 -0
  126. package/src/server/services/channel-health.js +234 -0
  127. package/src/server/services/channel-scheduler.js +240 -0
  128. package/src/server/services/channels.js +447 -0
  129. package/src/server/services/codex-channels.js +705 -0
  130. package/src/server/services/codex-config.js +90 -0
  131. package/src/server/services/codex-parser.js +322 -0
  132. package/src/server/services/codex-sessions.js +936 -0
  133. package/src/server/services/codex-settings-manager.js +619 -0
  134. package/src/server/services/codex-speed-test-template.json +24 -0
  135. package/src/server/services/codex-statistics-service.js +161 -0
  136. package/src/server/services/commands-service.js +574 -0
  137. package/src/server/services/config-export-service.js +1165 -0
  138. package/src/server/services/config-registry-service.js +828 -0
  139. package/src/server/services/config-sync-manager.js +941 -0
  140. package/src/server/services/config-sync-service.js +504 -0
  141. package/src/server/services/config-templates-service.js +913 -0
  142. package/src/server/services/enhanced-cache.js +196 -0
  143. package/src/server/services/env-checker.js +409 -0
  144. package/src/server/services/env-manager.js +436 -0
  145. package/src/server/services/favorites.js +165 -0
  146. package/src/server/services/format-converter.js +620 -0
  147. package/src/server/services/gemini-channels.js +459 -0
  148. package/src/server/services/gemini-config.js +73 -0
  149. package/src/server/services/gemini-sessions.js +689 -0
  150. package/src/server/services/gemini-settings-manager.js +263 -0
  151. package/src/server/services/gemini-statistics-service.js +157 -0
  152. package/src/server/services/health-check.js +85 -0
  153. package/src/server/services/mcp-client.js +790 -0
  154. package/src/server/services/mcp-service.js +1732 -0
  155. package/src/server/services/model-detector.js +1245 -0
  156. package/src/server/services/network-access.js +80 -0
  157. package/src/server/services/opencode-channels.js +366 -0
  158. package/src/server/services/opencode-gateway-adapters.js +1168 -0
  159. package/src/server/services/opencode-gateway-converter.js +639 -0
  160. package/src/server/services/opencode-sessions.js +931 -0
  161. package/src/server/services/opencode-settings-manager.js +478 -0
  162. package/src/server/services/opencode-statistics-service.js +161 -0
  163. package/src/server/services/plugins-service.js +1268 -0
  164. package/src/server/services/prompts-service.js +534 -0
  165. package/src/server/services/proxy-runtime.js +79 -0
  166. package/src/server/services/repo-scanner-base.js +708 -0
  167. package/src/server/services/request-logger.js +130 -0
  168. package/src/server/services/response-decoder.js +21 -0
  169. package/src/server/services/security-config.js +131 -0
  170. package/src/server/services/session-cache.js +127 -0
  171. package/src/server/services/session-converter.js +577 -0
  172. package/src/server/services/sessions.js +900 -0
  173. package/src/server/services/settings-manager.js +163 -0
  174. package/src/server/services/skill-service.js +1482 -0
  175. package/src/server/services/speed-test.js +1146 -0
  176. package/src/server/services/statistics-service.js +1043 -0
  177. package/src/server/services/ui-config.js +132 -0
  178. package/src/server/services/workspace-service.js +830 -0
  179. package/src/server/utils/pricing.js +73 -0
  180. package/src/server/websocket-server.js +513 -0
  181. package/src/ui/menu.js +139 -0
  182. package/src/ui/prompts.js +100 -0
  183. package/src/utils/format.js +43 -0
  184. package/src/utils/port-helper.js +108 -0
  185. package/src/utils/session.js +240 -0
@@ -0,0 +1,454 @@
1
+ // 工作区管理命令
2
+ const chalk = require('chalk');
3
+ const inquirer = require('inquirer');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const workspaceService = require('../server/services/workspace-service');
7
+ const { getProjectsWithStats } = require('../server/services/sessions');
8
+ const { loadConfig } = require('../config/loader');
9
+
10
+ /**
11
+ * 列出所有工作区
12
+ */
13
+ async function listWorkspaces() {
14
+ try {
15
+ const workspaces = workspaceService.listWorkspaces();
16
+
17
+ if (workspaces.length === 0) {
18
+ console.log(chalk.yellow('\n暂无工作区\n'));
19
+ return;
20
+ }
21
+
22
+ console.log(chalk.bold.cyan('\n工作区列表:\n'));
23
+
24
+ workspaces.forEach((ws, index) => {
25
+ const status = ws.exists ? chalk.green('✓') : chalk.red('✗');
26
+ console.log(`${index + 1}. ${status} ${chalk.bold(ws.name)}`);
27
+
28
+ if (ws.description) {
29
+ console.log(chalk.gray(` 描述: ${ws.description}`));
30
+ }
31
+
32
+ console.log(chalk.gray(` 路径: ${ws.path}`));
33
+ console.log(chalk.gray(` 项目数: ${ws.projectCount}`));
34
+ console.log(chalk.gray(` 最后使用: ${new Date(ws.lastUsed).toLocaleString('zh-CN')}`));
35
+ console.log('');
36
+ });
37
+ } catch (error) {
38
+ console.error(chalk.red(`\n❌ ${error.message}\n`));
39
+ }
40
+ }
41
+
42
+ /**
43
+ * 创建工作区
44
+ */
45
+ async function createWorkspace() {
46
+ try {
47
+ const config = loadConfig();
48
+
49
+ // 1. 输入工作区名称
50
+ const { name } = await inquirer.prompt([
51
+ {
52
+ type: 'input',
53
+ name: 'name',
54
+ message: '工作区名称:',
55
+ validate: input => {
56
+ if (!input || !input.trim()) {
57
+ return '名称不能为空';
58
+ }
59
+ // 检查名称是否包含非法字符
60
+ if (!/^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/.test(input)) {
61
+ return '名称只能包含字母、数字、下划线、中划线和中文';
62
+ }
63
+ return true;
64
+ }
65
+ }
66
+ ]);
67
+
68
+ // 2. 输入描述(可选)
69
+ const { description } = await inquirer.prompt([
70
+ {
71
+ type: 'input',
72
+ name: 'description',
73
+ message: '工作区描述(可选):',
74
+ default: ''
75
+ }
76
+ ]);
77
+
78
+ // 3. 选择基础目录
79
+ const { baseDirOption } = await inquirer.prompt([
80
+ {
81
+ type: 'list',
82
+ name: 'baseDirOption',
83
+ message: '选择工作区存放位置:',
84
+ choices: [
85
+ { name: '自动(第一个项目的父目录)', value: 'auto' },
86
+ { name: '自定义路径', value: 'custom' }
87
+ ]
88
+ }
89
+ ]);
90
+
91
+ let baseDir = '';
92
+ if (baseDirOption === 'custom') {
93
+ const { customPath } = await inquirer.prompt([
94
+ {
95
+ type: 'input',
96
+ name: 'customPath',
97
+ message: '输入基础目录路径:',
98
+ validate: input => {
99
+ if (!input || !input.trim()) {
100
+ return '路径不能为空';
101
+ }
102
+ const expanded = input.replace(/^~/, require('os').homedir());
103
+ if (!fs.existsSync(expanded)) {
104
+ return `路径不存在: ${expanded}`;
105
+ }
106
+ if (!fs.statSync(expanded).isDirectory()) {
107
+ return '必须是目录路径';
108
+ }
109
+ return true;
110
+ }
111
+ }
112
+ ]);
113
+ baseDir = customPath.replace(/^~/, require('os').homedir());
114
+ }
115
+
116
+ // 4. 选择项目
117
+ const projects = [];
118
+ let continueAdding = true;
119
+
120
+ while (continueAdding) {
121
+ const availableProjects = await getProjectsWithStats(config);
122
+
123
+ if (availableProjects.length === 0) {
124
+ console.log(chalk.yellow('\n没有可用的项目\n'));
125
+ break;
126
+ }
127
+
128
+ const projectChoices = availableProjects.map(proj => ({
129
+ name: `${proj.displayName} (${proj.sessionCount} 会话)`,
130
+ value: proj
131
+ }));
132
+
133
+ projectChoices.push(
134
+ new inquirer.Separator(chalk.gray('─'.repeat(14))),
135
+ { name: chalk.gray('✓ 完成选择'), value: null }
136
+ );
137
+
138
+ const { selectedProject } = await inquirer.prompt([
139
+ {
140
+ type: 'list',
141
+ name: 'selectedProject',
142
+ message: `选择项目 (${projects.length} 个已选):`,
143
+ pageSize: 15,
144
+ choices: projectChoices
145
+ }
146
+ ]);
147
+
148
+ if (!selectedProject) {
149
+ if (projects.length === 0) {
150
+ console.log(chalk.yellow('\n至少需要选择一个项目\n'));
151
+ continue;
152
+ }
153
+ continueAdding = false;
154
+ break;
155
+ }
156
+
157
+ // 检查是否已添加
158
+ if (projects.find(p => p.sourcePath === selectedProject.fullPath)) {
159
+ console.log(chalk.yellow('\n该项目已添加\n'));
160
+ continue;
161
+ }
162
+
163
+ // 获取项目路径
164
+ const sourcePath = selectedProject.fullPath;
165
+
166
+ // 输入软链接名称
167
+ const defaultLinkName = path.basename(sourcePath);
168
+ const { linkName } = await inquirer.prompt([
169
+ {
170
+ type: 'input',
171
+ name: 'linkName',
172
+ message: '软链接名称:',
173
+ default: defaultLinkName,
174
+ validate: input => {
175
+ if (!input || !input.trim()) {
176
+ return '名称不能为空';
177
+ }
178
+ if (projects.find(p => p.name === input)) {
179
+ return '该名称已被使用';
180
+ }
181
+ return true;
182
+ }
183
+ }
184
+ ]);
185
+
186
+ // 检查是否是 git 仓库
187
+ const isGit = workspaceService.isGitRepo(sourcePath);
188
+
189
+ let createWorktree = false;
190
+ let branch = '';
191
+
192
+ if (isGit) {
193
+ const { shouldCreateWorktree } = await inquirer.prompt([
194
+ {
195
+ type: 'confirm',
196
+ name: 'shouldCreateWorktree',
197
+ message: '是否为此项目创建 git worktree?',
198
+ default: false
199
+ }
200
+ ]);
201
+
202
+ if (shouldCreateWorktree) {
203
+ const { branchName } = await inquirer.prompt([
204
+ {
205
+ type: 'input',
206
+ name: 'branchName',
207
+ message: '输入分支名:',
208
+ validate: input => {
209
+ if (!input || !input.trim()) {
210
+ return '分支名不能为空';
211
+ }
212
+ return true;
213
+ }
214
+ }
215
+ ]);
216
+
217
+ createWorktree = true;
218
+ branch = branchName;
219
+ }
220
+ }
221
+
222
+ projects.push({
223
+ sourcePath,
224
+ name: linkName,
225
+ createWorktree,
226
+ branch
227
+ });
228
+
229
+ console.log(chalk.green(`\n✓ 已添加: ${linkName}\n`));
230
+ }
231
+
232
+ if (projects.length === 0) {
233
+ console.log(chalk.yellow('\n取消创建\n'));
234
+ return;
235
+ }
236
+
237
+ // 5. 确认创建
238
+ console.log(chalk.bold.cyan('\n工作区配置预览:\n'));
239
+ console.log(chalk.gray(`名称: ${name}`));
240
+ if (description) {
241
+ console.log(chalk.gray(`描述: ${description}`));
242
+ }
243
+ console.log(chalk.gray(`项目数: ${projects.length}`));
244
+ console.log(chalk.gray('\n包含项目:'));
245
+ projects.forEach((proj, index) => {
246
+ console.log(chalk.gray(` ${index + 1}. ${proj.name} → ${proj.sourcePath}`));
247
+ if (proj.createWorktree) {
248
+ console.log(chalk.gray(` (创建 worktree: ${proj.branch})`));
249
+ }
250
+ });
251
+ console.log('');
252
+
253
+ const { confirm } = await inquirer.prompt([
254
+ {
255
+ type: 'confirm',
256
+ name: 'confirm',
257
+ message: '确认创建工作区?',
258
+ default: true
259
+ }
260
+ ]);
261
+
262
+ if (!confirm) {
263
+ console.log(chalk.yellow('\n取消创建\n'));
264
+ return;
265
+ }
266
+
267
+ // 创建工作区
268
+ console.log(chalk.cyan('\n正在创建工作区...\n'));
269
+
270
+ const workspace = workspaceService.createWorkspace({
271
+ name,
272
+ description,
273
+ baseDir,
274
+ projects
275
+ });
276
+
277
+ console.log(chalk.green(`\n✅ 工作区创建成功!\n`));
278
+ console.log(chalk.gray(`工作区路径: ${workspace.path}\n`));
279
+ console.log(chalk.gray(`提示: 可以在此路径下启动 Claude Code 以访问所有项目\n`));
280
+
281
+ } catch (error) {
282
+ console.error(chalk.red(`\n❌ ${error.message}\n`));
283
+ }
284
+ }
285
+
286
+ /**
287
+ * 查看工作区详情
288
+ */
289
+ async function viewWorkspace() {
290
+ try {
291
+ const workspaces = workspaceService.listWorkspaces();
292
+
293
+ if (workspaces.length === 0) {
294
+ console.log(chalk.yellow('\n暂无工作区\n'));
295
+ return;
296
+ }
297
+
298
+ const { workspace } = await inquirer.prompt([
299
+ {
300
+ type: 'list',
301
+ name: 'workspace',
302
+ message: '选择工作区:',
303
+ pageSize: 15,
304
+ choices: workspaces.map(ws => ({
305
+ name: `${ws.name} (${ws.projectCount} 个项目)`,
306
+ value: ws
307
+ }))
308
+ }
309
+ ]);
310
+
311
+ const detail = workspaceService.getWorkspace(workspace.id);
312
+
313
+ console.log(chalk.bold.cyan(`\n工作区: ${detail.name}\n`));
314
+
315
+ if (detail.description) {
316
+ console.log(chalk.gray(`描述: ${detail.description}`));
317
+ }
318
+
319
+ console.log(chalk.gray(`路径: ${detail.path}`));
320
+ console.log(chalk.gray(`状态: ${detail.exists ? chalk.green('存在') : chalk.red('不存在')}`));
321
+ console.log(chalk.gray(`创建时间: ${new Date(detail.createdAt).toLocaleString('zh-CN')}`));
322
+ console.log(chalk.gray(`最后使用: ${new Date(detail.lastUsed).toLocaleString('zh-CN')}`));
323
+
324
+ console.log(chalk.bold.cyan(`\n包含项目 (${detail.projects.length}):\n`));
325
+
326
+ detail.projects.forEach((proj, index) => {
327
+ const linkStatus = proj.linkExists ? chalk.green('✓') : chalk.red('✗');
328
+ const sourceStatus = proj.sourceExists ? chalk.green('✓') : chalk.red('✗');
329
+
330
+ console.log(`${index + 1}. ${linkStatus} ${chalk.bold(proj.name)}`);
331
+ console.log(chalk.gray(` 源路径: ${sourceStatus} ${proj.sourcePath}`));
332
+
333
+ if (proj.worktrees && proj.worktrees.length > 0) {
334
+ console.log(chalk.gray(` Worktrees (${proj.worktrees.length}):`));
335
+ proj.worktrees.forEach(wt => {
336
+ console.log(chalk.gray(` - ${wt.branch || 'detached'}: ${wt.path}`));
337
+ });
338
+ }
339
+
340
+ console.log('');
341
+ });
342
+
343
+ } catch (error) {
344
+ console.error(chalk.red(`\n❌ ${error.message}\n`));
345
+ }
346
+ }
347
+
348
+ /**
349
+ * 删除工作区
350
+ */
351
+ async function deleteWorkspace() {
352
+ try {
353
+ const workspaces = workspaceService.listWorkspaces();
354
+
355
+ if (workspaces.length === 0) {
356
+ console.log(chalk.yellow('\n暂无工作区\n'));
357
+ return;
358
+ }
359
+
360
+ const { workspace } = await inquirer.prompt([
361
+ {
362
+ type: 'list',
363
+ name: 'workspace',
364
+ message: '选择要删除的工作区:',
365
+ pageSize: 15,
366
+ choices: workspaces.map(ws => ({
367
+ name: `${ws.name} (${ws.projectCount} 个项目)`,
368
+ value: ws
369
+ }))
370
+ }
371
+ ]);
372
+
373
+ const { removeFiles } = await inquirer.prompt([
374
+ {
375
+ type: 'confirm',
376
+ name: 'removeFiles',
377
+ message: '是否同时删除工作区目录和 worktrees?',
378
+ default: false
379
+ }
380
+ ]);
381
+
382
+ const { confirm } = await inquirer.prompt([
383
+ {
384
+ type: 'confirm',
385
+ name: 'confirm',
386
+ message: chalk.yellow(`确认删除工作区 "${workspace.name}"?`),
387
+ default: false
388
+ }
389
+ ]);
390
+
391
+ if (!confirm) {
392
+ console.log(chalk.yellow('\n取消删除\n'));
393
+ return;
394
+ }
395
+
396
+ workspaceService.deleteWorkspace(workspace.id, removeFiles);
397
+ console.log(chalk.green('\n✅ 工作区删除成功\n'));
398
+
399
+ } catch (error) {
400
+ console.error(chalk.red(`\n❌ ${error.message}\n`));
401
+ }
402
+ }
403
+
404
+ /**
405
+ * 工作区管理主菜单
406
+ */
407
+ async function workspaceMenu() {
408
+ let continueMenu = true;
409
+
410
+ while (continueMenu) {
411
+ const { action } = await inquirer.prompt([
412
+ {
413
+ type: 'list',
414
+ name: 'action',
415
+ message: '工作区管理:',
416
+ pageSize: 10,
417
+ choices: [
418
+ { name: chalk.cyan('创建工作区'), value: 'create' },
419
+ { name: chalk.green('查看工作区'), value: 'view' },
420
+ { name: chalk.blue('列出所有工作区'), value: 'list' },
421
+ { name: chalk.red('删除工作区'), value: 'delete' },
422
+ new inquirer.Separator(chalk.gray('─'.repeat(14))),
423
+ { name: chalk.gray('返回主菜单'), value: 'back' }
424
+ ]
425
+ }
426
+ ]);
427
+
428
+ switch (action) {
429
+ case 'create':
430
+ await createWorkspace();
431
+ break;
432
+ case 'view':
433
+ await viewWorkspace();
434
+ break;
435
+ case 'list':
436
+ await listWorkspaces();
437
+ break;
438
+ case 'delete':
439
+ await deleteWorkspace();
440
+ break;
441
+ case 'back':
442
+ continueMenu = false;
443
+ break;
444
+ }
445
+ }
446
+ }
447
+
448
+ module.exports = {
449
+ workspaceMenu,
450
+ listWorkspaces,
451
+ createWorkspace,
452
+ viewWorkspace,
453
+ deleteWorkspace
454
+ };
@@ -0,0 +1,69 @@
1
+ // 默认配置
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const modelMetadataConfig = require('./model-metadata.json');
5
+
6
+ const DEFAULT_CONFIG = {
7
+ projectsDir: path.join(os.homedir(), '.claude', 'projects'),
8
+ defaultProject: null,
9
+ maxDisplaySessions: 100,
10
+ pageSize: 15,
11
+ currentCliType: 'claude', // 当前CLI工具类型: claude, codex, gemini, opencode
12
+ ports: {
13
+ webUI: 19999, // Web UI 页面端口 (同时用于 WebSocket)
14
+ proxy: 20088, // Claude 代理服务端口
15
+ codexProxy: 20089, // Codex 代理服务端口
16
+ geminiProxy: 20090, // Gemini 代理服务端口
17
+ opencodeProxy: 20091 // OpenCode 代理服务端口
18
+ },
19
+ maxLogs: 100,
20
+ statsInterval: 30,
21
+ modelDiscovery: {
22
+ useV1ModelsEndpoint: false
23
+ },
24
+ defaultModels: modelMetadataConfig.defaultModels || { claude: [], codex: [], gemini: [] },
25
+ defaultSpeedTestModels: modelMetadataConfig.defaultSpeedTestModels || {
26
+ claude: 'claude-haiku-4-5',
27
+ codex: 'gpt-5.2',
28
+ gemini: 'gemini-2.5-pro'
29
+ },
30
+ pricing: {
31
+ claude: {
32
+ mode: 'auto',
33
+ input: 3,
34
+ output: 15,
35
+ cacheCreation: 3.75,
36
+ cacheRead: 0.30,
37
+ models: {
38
+ // All models use centralized pricing from src/config/model-pricing.js
39
+ // Add custom entries here only if you need to override official pricing
40
+ }
41
+ },
42
+ codex: {
43
+ mode: 'auto',
44
+ input: 2.5,
45
+ output: 10,
46
+ models: {
47
+ 'gpt-5-codex': { mode: 'auto' },
48
+ 'gpt-4o-mini': { mode: 'auto' }
49
+ }
50
+ },
51
+ gemini: {
52
+ mode: 'auto',
53
+ input: 1.25,
54
+ output: 5,
55
+ models: {
56
+ 'gemini-2.5-pro': { mode: 'auto' },
57
+ 'gemini-2.5-flash': { mode: 'auto' }
58
+ }
59
+ },
60
+ opencode: {
61
+ mode: 'auto',
62
+ input: 2.5,
63
+ output: 10,
64
+ models: {}
65
+ }
66
+ }
67
+ };
68
+
69
+ module.exports = DEFAULT_CONFIG;
@@ -0,0 +1,149 @@
1
+ // 配置加载和保存
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const DEFAULT_CONFIG = require('./default');
6
+ const { PATHS, ensureStorageDirMigrated } = require('./paths');
7
+ const eventBus = require('../plugins/event-bus');
8
+
9
+ const LEGACY_CONFIG_FILES = [
10
+ path.join(__dirname, '../../config.json'),
11
+ path.join(os.homedir(), '.claude', 'config.json')
12
+ ];
13
+
14
+ function getConfigFilePath() {
15
+ ensureStorageDirMigrated();
16
+ return PATHS.configFile;
17
+ }
18
+
19
+ /**
20
+ * 展开 ~ 为用户主目录
21
+ */
22
+ function expandHome(filepath) {
23
+ if (filepath.startsWith('~')) {
24
+ return path.join(os.homedir(), filepath.slice(1));
25
+ }
26
+ return filepath;
27
+ }
28
+
29
+ function mergePricing(defaultPricing, overrides = {}) {
30
+ const merged = {};
31
+ Object.keys(defaultPricing).forEach((key) => {
32
+ merged[key] = {
33
+ ...defaultPricing[key],
34
+ ...(overrides && overrides[key] ? overrides[key] : {})
35
+ };
36
+ if (!merged[key].mode) {
37
+ merged[key].mode = 'auto';
38
+ }
39
+ });
40
+ return merged;
41
+ }
42
+
43
+ function mergeDefaultModels(defaultModels, overrides = {}) {
44
+ const merged = {};
45
+ Object.keys(defaultModels).forEach((key) => {
46
+ // If user config has this tool type, use it; otherwise use default
47
+ merged[key] = (overrides && overrides[key]) ? overrides[key] : defaultModels[key];
48
+ });
49
+ return merged;
50
+ }
51
+
52
+ function mergeDefaultSpeedTestModels(defaultModels, overrides = {}) {
53
+ return {
54
+ ...defaultModels,
55
+ ...(overrides || {})
56
+ };
57
+ }
58
+
59
+ function readJsonFile(filePath) {
60
+ const content = fs.readFileSync(filePath, 'utf8');
61
+ return JSON.parse(content);
62
+ }
63
+
64
+ function migrateLegacyConfigIfNeeded(configFilePath) {
65
+ if (fs.existsSync(configFilePath)) return;
66
+
67
+ for (const legacyPath of LEGACY_CONFIG_FILES) {
68
+ try {
69
+ if (!legacyPath || legacyPath === configFilePath) continue;
70
+ if (!fs.existsSync(legacyPath)) continue;
71
+ const legacyConfig = readJsonFile(legacyPath);
72
+ const dir = path.dirname(configFilePath);
73
+ if (!fs.existsSync(dir)) {
74
+ fs.mkdirSync(dir, { recursive: true });
75
+ }
76
+ fs.writeFileSync(configFilePath, JSON.stringify(legacyConfig, null, 2), 'utf8');
77
+ console.log(`[Config] 已迁移历史配置: ${legacyPath} -> ${configFilePath}`);
78
+ return;
79
+ } catch (error) {
80
+ console.warn(`[Config] 迁移历史配置失败: ${legacyPath}`, error.message);
81
+ }
82
+ }
83
+ }
84
+
85
+ /**
86
+ * 加载配置
87
+ */
88
+ function loadConfig() {
89
+ const configFilePath = getConfigFilePath();
90
+ migrateLegacyConfigIfNeeded(configFilePath);
91
+
92
+ try {
93
+ if (fs.existsSync(configFilePath)) {
94
+ const userConfig = readJsonFile(configFilePath);
95
+ const config = { ...DEFAULT_CONFIG, ...userConfig };
96
+ config.projectsDir = expandHome(config.projectsDir || DEFAULT_CONFIG.projectsDir);
97
+
98
+ // 合并 ports 配置
99
+ config.ports = { ...DEFAULT_CONFIG.ports, ...userConfig.ports };
100
+ config.pricing = mergePricing(DEFAULT_CONFIG.pricing, userConfig.pricing);
101
+ config.defaultModels = mergeDefaultModels(DEFAULT_CONFIG.defaultModels, userConfig.defaultModels);
102
+ config.defaultSpeedTestModels = mergeDefaultSpeedTestModels(
103
+ DEFAULT_CONFIG.defaultSpeedTestModels,
104
+ userConfig.defaultSpeedTestModels
105
+ );
106
+
107
+ // 确保有 currentProject,使用 defaultProject 作为 currentProject
108
+ if (!config.currentProject && config.defaultProject) {
109
+ config.currentProject = config.defaultProject;
110
+ }
111
+
112
+ eventBus.emitSync('config:loaded', { config });
113
+ return config;
114
+ }
115
+ } catch (error) {
116
+ console.error(`加载配置文件失败,使用默认配置: ${configFilePath}`);
117
+ }
118
+ const defaultConfig = {
119
+ ...DEFAULT_CONFIG,
120
+ projectsDir: expandHome(DEFAULT_CONFIG.projectsDir),
121
+ currentProject: DEFAULT_CONFIG.defaultProject
122
+ };
123
+ eventBus.emitSync('config:loaded', { config: defaultConfig });
124
+ return defaultConfig;
125
+ }
126
+
127
+ /**
128
+ * 保存配置
129
+ */
130
+ function saveConfig(config) {
131
+ const configFilePath = getConfigFilePath();
132
+ try {
133
+ const dir = path.dirname(configFilePath);
134
+ if (!fs.existsSync(dir)) {
135
+ fs.mkdirSync(dir, { recursive: true });
136
+ }
137
+ fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2), 'utf8');
138
+ eventBus.emitSync('config:saved', { config });
139
+ } catch (error) {
140
+ console.error(`保存配置失败 (${configFilePath}):`, error.message);
141
+ }
142
+ }
143
+
144
+ module.exports = {
145
+ loadConfig,
146
+ saveConfig,
147
+ expandHome,
148
+ getConfigFilePath
149
+ };