@adversity/coding-tool-x 3.0.6 → 3.1.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.
Files changed (133) hide show
  1. package/CHANGELOG.md +38 -18
  2. package/README.md +8 -8
  3. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  4. package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
  5. package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
  6. package/dist/web/assets/Home-Di2qsylF.css +1 -0
  7. package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
  8. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  9. package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
  10. package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
  11. package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
  12. package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
  13. package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
  14. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  15. package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
  16. package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
  17. package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
  18. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  19. package/dist/web/assets/icons-kcfLIMBB.js +1 -0
  20. package/dist/web/assets/index-Ufv5rCa5.css +1 -0
  21. package/dist/web/assets/index-lAkrRC3h.js +2 -0
  22. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  23. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  24. package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
  25. package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
  26. package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
  27. package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
  28. package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
  29. package/dist/web/index.html +8 -6
  30. package/package.json +4 -2
  31. package/src/commands/channels.js +48 -1
  32. package/src/commands/cli-type.js +4 -2
  33. package/src/commands/daemon.js +92 -13
  34. package/src/commands/doctor.js +10 -9
  35. package/src/commands/list.js +1 -1
  36. package/src/commands/logs.js +6 -4
  37. package/src/commands/port-config.js +24 -4
  38. package/src/commands/proxy-control.js +12 -6
  39. package/src/commands/search.js +1 -1
  40. package/src/commands/security.js +3 -2
  41. package/src/commands/stats.js +226 -52
  42. package/src/commands/switch.js +1 -1
  43. package/src/commands/toggle-proxy.js +31 -6
  44. package/src/commands/ui.js +8 -1
  45. package/src/commands/update.js +97 -0
  46. package/src/commands/workspace.js +1 -1
  47. package/src/config/default.js +39 -2
  48. package/src/config/loader.js +74 -8
  49. package/src/config/paths.js +105 -33
  50. package/src/index.js +67 -4
  51. package/src/plugins/constants.js +3 -2
  52. package/src/plugins/plugin-api.js +1 -1
  53. package/src/reset-config.js +4 -2
  54. package/src/server/api/agents.js +57 -14
  55. package/src/server/api/channels.js +112 -33
  56. package/src/server/api/codex-channels.js +111 -18
  57. package/src/server/api/codex-proxy.js +14 -8
  58. package/src/server/api/commands.js +71 -18
  59. package/src/server/api/config-export.js +0 -6
  60. package/src/server/api/config-registry.js +11 -3
  61. package/src/server/api/config.js +376 -5
  62. package/src/server/api/convert.js +133 -0
  63. package/src/server/api/dashboard.js +22 -6
  64. package/src/server/api/gemini-channels.js +107 -18
  65. package/src/server/api/gemini-proxy.js +14 -8
  66. package/src/server/api/gemini-sessions.js +1 -1
  67. package/src/server/api/health-check.js +4 -3
  68. package/src/server/api/mcp.js +3 -3
  69. package/src/server/api/opencode-channels.js +419 -0
  70. package/src/server/api/opencode-projects.js +99 -0
  71. package/src/server/api/opencode-proxy.js +198 -0
  72. package/src/server/api/opencode-sessions.js +403 -0
  73. package/src/server/api/opencode-statistics.js +57 -0
  74. package/src/server/api/plugins.js +66 -19
  75. package/src/server/api/prompts.js +2 -2
  76. package/src/server/api/proxy.js +7 -4
  77. package/src/server/api/sessions.js +3 -0
  78. package/src/server/api/skills.js +69 -18
  79. package/src/server/api/workspaces.js +78 -6
  80. package/src/server/codex-proxy-server.js +32 -19
  81. package/src/server/dev-server.js +1 -1
  82. package/src/server/gemini-proxy-server.js +17 -3
  83. package/src/server/index.js +164 -48
  84. package/src/server/opencode-proxy-server.js +4375 -0
  85. package/src/server/proxy-server.js +30 -19
  86. package/src/server/services/agents-service.js +61 -24
  87. package/src/server/services/channel-scheduler.js +9 -5
  88. package/src/server/services/channels.js +70 -12
  89. package/src/server/services/codex-channels.js +61 -23
  90. package/src/server/services/codex-settings-manager.js +271 -49
  91. package/src/server/services/codex-statistics-service.js +2 -2
  92. package/src/server/services/commands-service.js +84 -25
  93. package/src/server/services/config-export-service.js +7 -45
  94. package/src/server/services/config-registry-service.js +63 -17
  95. package/src/server/services/config-sync-manager.js +160 -7
  96. package/src/server/services/config-templates-service.js +204 -51
  97. package/src/server/services/env-checker.js +26 -12
  98. package/src/server/services/env-manager.js +126 -18
  99. package/src/server/services/favorites.js +5 -3
  100. package/src/server/services/gemini-channels.js +37 -15
  101. package/src/server/services/gemini-statistics-service.js +2 -2
  102. package/src/server/services/mcp-service.js +350 -9
  103. package/src/server/services/model-detector.js +707 -221
  104. package/src/server/services/network-access.js +80 -0
  105. package/src/server/services/opencode-channels.js +206 -0
  106. package/src/server/services/opencode-gateway-converter.js +639 -0
  107. package/src/server/services/opencode-sessions.js +663 -0
  108. package/src/server/services/opencode-settings-manager.js +342 -0
  109. package/src/server/services/opencode-statistics-service.js +255 -0
  110. package/src/server/services/plugins-service.js +479 -22
  111. package/src/server/services/prompts-service.js +53 -11
  112. package/src/server/services/proxy-runtime.js +1 -1
  113. package/src/server/services/repo-scanner-base.js +1 -1
  114. package/src/server/services/security-config.js +1 -1
  115. package/src/server/services/session-cache.js +1 -1
  116. package/src/server/services/skill-service.js +300 -46
  117. package/src/server/services/speed-test.js +464 -186
  118. package/src/server/services/statistics-service.js +2 -2
  119. package/src/server/services/terminal-commands.js +10 -3
  120. package/src/server/services/terminal-config.js +1 -1
  121. package/src/server/services/ui-config.js +1 -1
  122. package/src/server/services/workspace-service.js +57 -100
  123. package/src/server/websocket-server.js +132 -3
  124. package/src/ui/menu.js +49 -40
  125. package/src/utils/port-helper.js +22 -8
  126. package/src/utils/session.js +5 -4
  127. package/dist/web/assets/icons-BxudHPiX.js +0 -1
  128. package/dist/web/assets/index-D2VfwJBa.js +0 -14
  129. package/dist/web/assets/index-oXBzu0bd.css +0 -41
  130. package/dist/web/assets/naive-ui-DT-Uur8K.js +0 -1
  131. package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
  132. package/src/server/api/permissions.js +0 -385
  133. package/src/server/services/permission-templates-service.js +0 -308
@@ -5,12 +5,14 @@
5
5
  <link rel="icon" href="/favicon.ico">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>CC-TOOL - ClaudeCode增强工作助手</title>
8
- <script type="module" crossorigin src="/assets/index-D2VfwJBa.js"></script>
9
- <link rel="modulepreload" crossorigin href="/assets/vue-vendor-6JaYHOiI.js">
10
- <link rel="modulepreload" crossorigin href="/assets/vendors-D2HHw_aW.js">
11
- <link rel="modulepreload" crossorigin href="/assets/icons-BxudHPiX.js">
12
- <link rel="modulepreload" crossorigin href="/assets/naive-ui-DT-Uur8K.js">
13
- <link rel="stylesheet" crossorigin href="/assets/index-oXBzu0bd.css">
8
+ <script type="module" crossorigin src="/assets/index-lAkrRC3h.js"></script>
9
+ <link rel="modulepreload" crossorigin href="/assets/markdown-C9MYpaSi.js">
10
+ <link rel="modulepreload" crossorigin href="/assets/vue-vendor-DqyWIXEb.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/vendors-CO3Upi1d.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/naive-ui-CSrLusZZ.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/icons-kcfLIMBB.js">
14
+ <link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
15
+ <link rel="stylesheet" crossorigin href="/assets/index-Ufv5rCa5.css">
14
16
  </head>
15
17
  <body>
16
18
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adversity/coding-tool-x",
3
- "version": "3.0.6",
3
+ "version": "3.1.1",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -8,7 +8,9 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node bin/ctx.js",
11
- "test": "echo \"Error: no test specified\" && exit 1",
11
+ "test": "npm run test:basic && npm run test:api",
12
+ "test:basic": "node scripts/test-basic.js",
13
+ "test:api": "node scripts/test-api-consistency.js",
12
14
  "build:web": "cd src/web && npm run build",
13
15
  "dev:web": "cd src/web && npm run dev",
14
16
  "dev:server": "nodemon"
@@ -47,6 +47,22 @@ function getChannelServices(cliType) {
47
47
  updateChannel,
48
48
  getProxyStatus: getGeminiProxyStatus
49
49
  };
50
+ } else if (cliType === 'opencode') {
51
+ const {
52
+ getChannels,
53
+ createChannel,
54
+ updateChannel
55
+ } = require('../server/services/opencode-channels');
56
+ const { getOpenCodeProxyStatus } = require('../server/opencode-proxy-server');
57
+ return {
58
+ getAllChannels: () => {
59
+ const result = getChannels();
60
+ return Array.isArray(result?.channels) ? result.channels : [];
61
+ },
62
+ createChannel,
63
+ updateChannel,
64
+ getProxyStatus: getOpenCodeProxyStatus
65
+ };
50
66
  }
51
67
  }
52
68
 
@@ -62,6 +78,11 @@ async function handleChannelManagement() {
62
78
  const config = loadConfig();
63
79
  const cliType = config.currentCliType || 'claude';
64
80
  const services = getChannelServices(cliType);
81
+ if (!services || typeof services.createChannel !== 'function') {
82
+ console.log(chalk.red(`当前 CLI 类型 (${cliType}) 暂不支持添加渠道`));
83
+ await inquirer.prompt([{ type: 'input', name: 'continue', message: '按回车返回...' }]);
84
+ return;
85
+ }
65
86
  if (!services || typeof services.getAllChannels !== 'function') {
66
87
  console.log(chalk.red(`当前 CLI 类型 (${cliType}) 暂不支持渠道管理`));
67
88
  await inquirer.prompt([{ type: 'input', name: 'continue', message: '按回车返回...' }]);
@@ -200,6 +221,31 @@ async function handleAddChannel() {
200
221
  modelAnswer.model.trim(),
201
222
  { websiteUrl: answers.websiteUrl.trim() || undefined }
202
223
  );
224
+ } else if (cliType === 'opencode') {
225
+ const extraAnswers = await inquirer.prompt([
226
+ {
227
+ type: 'input',
228
+ name: 'wireApi',
229
+ message: 'Wire API (默认: openai):',
230
+ default: 'openai'
231
+ },
232
+ {
233
+ type: 'input',
234
+ name: 'model',
235
+ message: '默认模型(可选,直接回车跳过):'
236
+ }
237
+ ]);
238
+
239
+ channel = services.createChannel(
240
+ answers.name.trim(),
241
+ answers.baseUrl.trim(),
242
+ answers.apiKey.trim(),
243
+ {
244
+ wireApi: extraAnswers.wireApi.trim() || 'openai',
245
+ model: extraAnswers.model.trim() || null,
246
+ websiteUrl: answers.websiteUrl.trim() || undefined
247
+ }
248
+ );
203
249
  }
204
250
 
205
251
  console.log(chalk.green(`\n✅ 渠道添加成功: ${channel.name}\n`));
@@ -237,7 +283,8 @@ async function handleChannelStatus() {
237
283
  const sources = [
238
284
  { key: 'claude', label: 'Claude (Claude Code)' },
239
285
  { key: 'codex', label: 'Codex (OpenAI)' },
240
- { key: 'gemini', label: 'Gemini' }
286
+ { key: 'gemini', label: 'Gemini' },
287
+ { key: 'opencode', label: 'OpenCode' }
241
288
  ];
242
289
 
243
290
  sources.forEach((source) => {
@@ -6,7 +6,8 @@ const { loadConfig, saveConfig } = require('../config/loader');
6
6
  const CLI_TYPES = {
7
7
  claude: { name: 'Claude Code', color: 'cyan' },
8
8
  codex: { name: 'Codex', color: 'green' },
9
- gemini: { name: 'Gemini', color: 'magenta' }
9
+ gemini: { name: 'Gemini', color: 'magenta' },
10
+ opencode: { name: 'OpenCode', color: 'yellow' }
10
11
  };
11
12
 
12
13
  /**
@@ -20,8 +21,9 @@ async function handleSwitchCliType() {
20
21
 
21
22
  const config = loadConfig();
22
23
  const currentType = config.currentCliType || 'claude';
24
+ const currentTypeInfo = CLI_TYPES[currentType] || CLI_TYPES.claude;
23
25
 
24
- console.log(chalk.gray(`当前类型: ${CLI_TYPES[currentType].name}\n`));
26
+ console.log(chalk.gray(`当前类型: ${currentTypeInfo.name}\n`));
25
27
 
26
28
  // 构建类型选项
27
29
  const choices = Object.entries(CLI_TYPES).map(([type, info]) => {
@@ -2,6 +2,8 @@ const pm2 = require('pm2');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
4
  const { loadConfig } = require('../config/loader');
5
+ const { PATHS, ensureStorageDirMigrated } = require('../config/paths');
6
+ const { findProcessByPort } = require('../utils/port-helper');
5
7
 
6
8
  const PM2_APP_NAME = 'cc-tool';
7
9
 
@@ -50,6 +52,47 @@ async function getCCToolProcess() {
50
52
  return list.find(proc => proc.name === PM2_APP_NAME);
51
53
  }
52
54
 
55
+ function sleep(ms) {
56
+ return new Promise(resolve => setTimeout(resolve, ms));
57
+ }
58
+
59
+ function isPortOwnedByPid(port, pid) {
60
+ if (!pid || pid <= 0) {
61
+ return false;
62
+ }
63
+ const pids = findProcessByPort(port);
64
+ return pids.includes(String(pid));
65
+ }
66
+
67
+ async function waitForServiceReady(port, timeoutMs = 6000, intervalMs = 300) {
68
+ const startAt = Date.now();
69
+ let lastProcess = null;
70
+ let stablePassCount = 0;
71
+
72
+ while (Date.now() - startAt < timeoutMs) {
73
+ lastProcess = await getCCToolProcess();
74
+ if (lastProcess && lastProcess.pm2_env.status === 'online') {
75
+ const ownsPort = isPortOwnedByPid(port, lastProcess.pid);
76
+ if (ownsPort) {
77
+ // 连续多次检查通过,避免“瞬时 online 但马上崩溃”的误报
78
+ stablePassCount += 1;
79
+ } else {
80
+ stablePassCount = 0;
81
+ }
82
+
83
+ if (stablePassCount >= 3) {
84
+ return { ready: true, process: lastProcess };
85
+ }
86
+ } else {
87
+ stablePassCount = 0;
88
+ }
89
+ await sleep(intervalMs);
90
+ }
91
+
92
+ lastProcess = await getCCToolProcess();
93
+ return { ready: false, process: lastProcess };
94
+ }
95
+
53
96
  /**
54
97
  * 启动服务(后台)
55
98
  */
@@ -70,13 +113,20 @@ async function handleStart() {
70
113
  }
71
114
 
72
115
  const config = loadConfig();
73
- const port = config.ports?.webUI || 10099;
116
+ const port = config.ports?.webUI || 19999;
117
+
118
+ // 检查是否启用 LAN 访问 (--host 标志)
119
+ const enableHost = process.argv.includes('--host');
120
+ const pmArgs = ['ui', '--daemon'];
121
+ if (enableHost) {
122
+ pmArgs.push('--host');
123
+ }
74
124
 
75
125
  // 启动 PM2 进程
76
126
  pm2.start({
77
127
  name: PM2_APP_NAME,
78
128
  script: path.join(__dirname, '../index.js'),
79
- args: ['ui', '--daemon'],
129
+ args: pmArgs,
80
130
  interpreter: 'node',
81
131
  autorestart: true,
82
132
  max_memory_restart: '500M',
@@ -84,19 +134,44 @@ async function handleStart() {
84
134
  NODE_ENV: 'production',
85
135
  CC_TOOL_PORT: port
86
136
  },
87
- output: path.join(require('os').homedir(), '.claude/logs/cc-tool-out.log'),
88
- error: path.join(require('os').homedir(), '.claude/logs/cc-tool-error.log'),
137
+ output: path.join(require('os').homedir(), '.cc-tool/logs/cc-tool-out.log'),
138
+ error: path.join(require('os').homedir(), '.cc-tool/logs/cc-tool-error.log'),
89
139
  merge_logs: true,
90
140
  log_date_format: 'YYYY-MM-DD HH:mm:ss'
91
- }, (err) => {
141
+ }, async (err) => {
92
142
  if (err) {
93
143
  console.error(chalk.red('\n❌ 启动服务失败:'), err.message);
94
144
  disconnectPM2();
95
145
  process.exit(1);
96
146
  }
97
147
 
148
+ try {
149
+ const readyState = await waitForServiceReady(port);
150
+ if (!readyState.ready) {
151
+ const statusText = readyState.process?.pm2_env?.status || 'unknown';
152
+ console.error(chalk.red('\n❌ Coding-Tool 服务启动失败,进程未就绪\n'));
153
+ console.error(chalk.gray(`PM2 状态: ${statusText}`));
154
+ console.error(chalk.yellow('💡 请使用 ctx logs ui 查看详细日志\n'));
155
+
156
+ pm2.delete(PM2_APP_NAME, () => {
157
+ pm2.dump(() => {
158
+ disconnectPM2();
159
+ process.exit(1);
160
+ });
161
+ });
162
+ return;
163
+ }
164
+ } catch (checkError) {
165
+ console.error(chalk.red('\n❌ 启动后健康检查失败:'), checkError.message);
166
+ disconnectPM2();
167
+ process.exit(1);
168
+ }
169
+
98
170
  console.log(chalk.green('\n✅ Coding-Tool 服务已启动(后台运行)\n'));
99
171
  console.log(chalk.gray(`Web UI: http://localhost:${port}`));
172
+ if (enableHost) {
173
+ console.log(chalk.yellow(`⚠️ LAN 访问已启用 (http://<your-ip>:${port})`));
174
+ }
100
175
  console.log(chalk.gray('\n可以安全关闭此终端窗口'));
101
176
  console.log(chalk.gray('\n常用命令:'));
102
177
  console.log(chalk.gray(' ') + chalk.cyan('ctx status') + chalk.gray(' - 查看服务状态'));
@@ -208,7 +283,7 @@ async function handleStatus() {
208
283
  console.log(chalk.bold('📱 Web UI 服务:'));
209
284
  if (existing && existing.pm2_env.status === 'online') {
210
285
  console.log(chalk.green(' ✅ 状态: 运行中'));
211
- console.log(chalk.gray(` 🌐 地址: http://localhost:${config.ports?.webUI || 10099}`));
286
+ console.log(chalk.gray(` 🌐 地址: http://localhost:${config.ports?.webUI || 19999}`));
212
287
  console.log(chalk.gray(` 🔑 进程 ID: ${existing.pid}`));
213
288
  console.log(chalk.gray(` ⏱️ 运行时长: ${formatUptime(existing.pm2_env.pm_uptime)}`));
214
289
  console.log(chalk.gray(` 💾 内存使用: ${formatMemory(existing.monit?.memory)}`));
@@ -219,21 +294,25 @@ async function handleStatus() {
219
294
 
220
295
  // 代理服务状态(从运行时文件检测)
221
296
  const fs = require('fs');
222
- const os = require('os');
223
- const claudeActive = fs.existsSync(path.join(os.homedir(), '.claude/.proxy-active-claude'));
224
- const codexActive = fs.existsSync(path.join(os.homedir(), '.claude/.proxy-active-codex'));
225
- const geminiActive = fs.existsSync(path.join(os.homedir(), '.claude/.proxy-active-gemini'));
297
+ ensureStorageDirMigrated();
298
+ const claudeActive = fs.existsSync(PATHS.activeChannel.claude);
299
+ const codexActive = fs.existsSync(PATHS.activeChannel.codex);
300
+ const geminiActive = fs.existsSync(PATHS.activeChannel.gemini);
301
+ const opencodeActive = fs.existsSync(PATHS.activeChannel.opencode);
226
302
 
227
303
  console.log(chalk.bold('\n🔌 代理服务:'));
228
304
 
229
305
  console.log(chalk.gray(' Claude: ') + (claudeActive ? chalk.green('✅ 运行中') : chalk.gray('⏹️ 未启动')) +
230
- chalk.gray(` (http://localhost:${config.ports?.proxy || 10088})`));
306
+ chalk.gray(` (http://localhost:${config.ports?.proxy || 20088})`));
231
307
 
232
308
  console.log(chalk.gray(' Codex: ') + (codexActive ? chalk.green('✅ 运行中') : chalk.gray('⏹️ 未启动')) +
233
- chalk.gray(` (http://localhost:${config.ports?.codexProxy || 10089})`));
309
+ chalk.gray(` (http://localhost:${config.ports?.codexProxy || 20089})`));
234
310
 
235
311
  console.log(chalk.gray(' Gemini: ') + (geminiActive ? chalk.green('✅ 运行中') : chalk.gray('⏹️ 未启动')) +
236
- chalk.gray(` (http://localhost:${config.ports?.geminiProxy || 10090})`));
312
+ chalk.gray(` (http://localhost:${config.ports?.geminiProxy || 20090})`));
313
+
314
+ console.log(chalk.gray(' OpenCode:') + (opencodeActive ? chalk.green('✅ 运行中') : chalk.gray('⏹️ 未启动')) +
315
+ chalk.gray(` (http://localhost:${config.ports?.opencodeProxy || 20091})`));
237
316
 
238
317
  console.log(chalk.bold('\n💡 提示:'));
239
318
  console.log(chalk.gray(' • 代理服务通过 Web UI 界面控制'));
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const os = require('os');
5
5
  const { exec } = require('child_process');
6
6
  const { promisify } = require('util');
7
- const { loadConfig } = require('../config/loader');
7
+ const { loadConfig, getConfigFilePath } = require('../config/loader');
8
8
  const { isPortInUse } = require('../utils/port-helper');
9
9
 
10
10
  const execAsync = promisify(exec);
@@ -114,7 +114,7 @@ async function checkNodeVersion() {
114
114
  * 检查配置文件
115
115
  */
116
116
  async function checkConfigFiles() {
117
- const configPath = path.join(os.homedir(), '.claude/config.json');
117
+ const configPath = getConfigFilePath();
118
118
  const exists = fs.existsSync(configPath);
119
119
 
120
120
  if (exists) {
@@ -130,7 +130,7 @@ async function checkConfigFiles() {
130
130
  name: '配置文件',
131
131
  status: 'fail',
132
132
  message: '配置文件存在但解析失败',
133
- suggestion: '使用 ctx config reset 重置配置'
133
+ suggestion: '使用 ctx reset 重置配置'
134
134
  };
135
135
  }
136
136
  } else {
@@ -149,10 +149,11 @@ async function checkConfigFiles() {
149
149
  async function checkPorts() {
150
150
  const config = loadConfig();
151
151
  const ports = {
152
- 'Web UI': config.ports?.webUI || 10099,
153
- 'Claude Proxy': config.ports?.proxy || 10088,
154
- 'Codex Proxy': config.ports?.codexProxy || 10089,
155
- 'Gemini Proxy': config.ports?.geminiProxy || 10090
152
+ 'Web UI': config.ports?.webUI || 19999,
153
+ 'Claude Proxy': config.ports?.proxy || 20088,
154
+ 'Codex Proxy': config.ports?.codexProxy || 20089,
155
+ 'Gemini Proxy': config.ports?.geminiProxy || 20090,
156
+ 'OpenCode Proxy': config.ports?.opencodeProxy || 20091
156
157
  };
157
158
 
158
159
  const conflicts = [];
@@ -175,7 +176,7 @@ async function checkPorts() {
175
176
  name: '端口检查',
176
177
  status: 'warning',
177
178
  message: `以下端口被占用: ${conflicts.join(', ')}`,
178
- suggestion: '如果服务正在运行这是正常的;否则使用 ctx config port 修改端口'
179
+ suggestion: '如果服务正在运行这是正常的;否则运行 ctx port 修改端口'
179
180
  };
180
181
  }
181
182
  }
@@ -207,7 +208,7 @@ async function checkClaudeConfig() {
207
208
  * 检查日志目录
208
209
  */
209
210
  async function checkLogsDirectory() {
210
- const logsDir = path.join(os.homedir(), '.claude/logs');
211
+ const logsDir = path.join(os.homedir(), '.cc-tool', 'logs');
211
212
  const exists = fs.existsSync(logsDir);
212
213
 
213
214
  if (exists) {
@@ -89,7 +89,7 @@ async function listRecentSessionsAcrossProjects(config, limit = null) {
89
89
  const maxSessions = limit || 15; // 默认显示15条最新对话
90
90
  const spinner = ora('加载最新对话...').start();
91
91
 
92
- const sessions = getRecentSessions(config, maxSessions);
92
+ const sessions = await getRecentSessions(config, maxSessions);
93
93
 
94
94
  if (sessions.length === 0) {
95
95
  spinner.fail('暂无会话记录');
@@ -4,13 +4,14 @@ const path = require('path');
4
4
  const os = require('os');
5
5
  const { spawn } = require('child_process');
6
6
 
7
- const LOGS_DIR = path.join(os.homedir(), '.claude/logs');
7
+ const LOGS_DIR = path.join(os.homedir(), '.cc-tool', 'logs');
8
8
 
9
9
  const LOG_FILES = {
10
10
  ui: 'cc-tool-out.log',
11
11
  claude: 'claude-proxy.log',
12
12
  codex: 'codex-proxy.log',
13
- gemini: 'gemini-proxy.log'
13
+ gemini: 'gemini-proxy.log',
14
+ opencode: 'opencode-proxy.log'
14
15
  };
15
16
 
16
17
  /**
@@ -46,7 +47,7 @@ async function handleLogs(type = null, options = {}) {
46
47
  const logFile = LOG_FILES[type];
47
48
  if (!logFile) {
48
49
  console.error(chalk.red(`\n❌ 无效的日志类型: ${type}\n`));
49
- console.log(chalk.gray('支持的类型: ui, claude, codex, gemini\n'));
50
+ console.log(chalk.gray('支持的类型: ui, claude, codex, gemini, opencode\n'));
50
51
  process.exit(1);
51
52
  }
52
53
 
@@ -249,7 +250,8 @@ function getTypeColor(type) {
249
250
  ui: chalk.blue,
250
251
  claude: chalk.green,
251
252
  codex: chalk.cyan,
252
- gemini: chalk.magenta
253
+ gemini: chalk.magenta,
254
+ opencode: chalk.yellow
253
255
  };
254
256
  return colors[type] || chalk.gray;
255
257
  }
@@ -8,6 +8,11 @@ const { loadConfig, saveConfig } = require('../config/loader');
8
8
  * 配置端口
9
9
  */
10
10
  async function handlePortConfig() {
11
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
12
+ console.log(chalk.yellow('\n当前环境不支持交互式端口配置,请在本地终端中运行 `ctx port`。\n'));
13
+ return;
14
+ }
15
+
11
16
  console.clear();
12
17
  console.log(chalk.bold.cyan('\n╔═══════════════════════════════════════╗'));
13
18
  console.log(chalk.bold.cyan('║ 端口配置 ║'));
@@ -18,8 +23,9 @@ async function handlePortConfig() {
18
23
  console.log(chalk.cyan('当前端口配置:'));
19
24
  console.log(chalk.gray(`• Web UI 页面端口: ${config.ports.webUI} (同时用于 WebSocket)`));
20
25
  console.log(chalk.gray(`• Claude 代理端口: ${config.ports.proxy}`));
21
- console.log(chalk.gray(`• Codex 代理端口: ${config.ports.codexProxy || 10089}`));
22
- console.log(chalk.gray(`• Gemini 代理端口: ${config.ports.geminiProxy || 10090}\n`));
26
+ console.log(chalk.gray(`• Codex 代理端口: ${config.ports.codexProxy || 20089}`));
27
+ console.log(chalk.gray(`• Gemini 代理端口: ${config.ports.geminiProxy || 20090}`));
28
+ console.log(chalk.gray(`• OpenCode 代理端口: ${config.ports.opencodeProxy || 20091}\n`));
23
29
 
24
30
  console.log(chalk.yellow('说明:'));
25
31
  console.log(chalk.gray('• 端口范围: 1024-65535'));
@@ -57,7 +63,7 @@ async function handlePortConfig() {
57
63
  type: 'input',
58
64
  name: 'codexProxy',
59
65
  message: 'Codex 代理服务端口:',
60
- default: config.ports.codexProxy || 10089,
66
+ default: config.ports.codexProxy || 20089,
61
67
  validate: (input) => {
62
68
  const port = parseInt(input);
63
69
  if (isNaN(port) || port < 1024 || port > 65535) {
@@ -70,7 +76,20 @@ async function handlePortConfig() {
70
76
  type: 'input',
71
77
  name: 'geminiProxy',
72
78
  message: 'Gemini 代理服务端口:',
73
- default: config.ports.geminiProxy || 10090,
79
+ default: config.ports.geminiProxy || 20090,
80
+ validate: (input) => {
81
+ const port = parseInt(input);
82
+ if (isNaN(port) || port < 1024 || port > 65535) {
83
+ return '端口必须是 1024-65535 之间的数字';
84
+ }
85
+ return true;
86
+ },
87
+ },
88
+ {
89
+ type: 'input',
90
+ name: 'opencodeProxy',
91
+ message: 'OpenCode 代理服务端口:',
92
+ default: config.ports.opencodeProxy || 20091,
74
93
  validate: (input) => {
75
94
  const port = parseInt(input);
76
95
  if (isNaN(port) || port < 1024 || port > 65535) {
@@ -87,6 +106,7 @@ async function handlePortConfig() {
87
106
  proxy: parseInt(answers.proxy),
88
107
  codexProxy: parseInt(answers.codexProxy),
89
108
  geminiProxy: parseInt(answers.geminiProxy),
109
+ opencodeProxy: parseInt(answers.opencodeProxy),
90
110
  };
91
111
 
92
112
  // 保存配置(保留其余字段)
@@ -11,12 +11,17 @@ const CHANNEL_CONFIG = {
11
11
  codex: {
12
12
  name: 'Codex',
13
13
  icon: '🔵',
14
- apiPath: '/api/codex-proxy'
14
+ apiPath: '/api/codex/proxy'
15
15
  },
16
16
  gemini: {
17
17
  name: 'Gemini',
18
18
  icon: '🟣',
19
- apiPath: '/api/gemini-proxy'
19
+ apiPath: '/api/gemini/proxy'
20
+ },
21
+ opencode: {
22
+ name: 'OpenCode',
23
+ icon: '🟠',
24
+ apiPath: '/api/opencode/proxy'
20
25
  }
21
26
  };
22
27
 
@@ -25,7 +30,7 @@ const CHANNEL_CONFIG = {
25
30
  */
26
31
  function httpRequest(method, path, data = null) {
27
32
  const config = loadConfig();
28
- const port = config.ports?.webUI || 10099;
33
+ const port = config.ports?.webUI || 19999;
29
34
 
30
35
  return new Promise((resolve, reject) => {
31
36
  const postData = data ? JSON.stringify(data) : null;
@@ -80,7 +85,7 @@ function httpRequest(method, path, data = null) {
80
85
  */
81
86
  async function checkUIService() {
82
87
  try {
83
- await httpRequest('GET', '/api/ping');
88
+ await httpRequest('GET', '/api/proxy/status');
84
89
  return true;
85
90
  } catch (err) {
86
91
  return false;
@@ -94,7 +99,7 @@ async function handleProxyStart(channel) {
94
99
  const channelInfo = CHANNEL_CONFIG[channel];
95
100
  if (!channelInfo) {
96
101
  console.error(chalk.red(`\n❌ 无效的渠道类型: ${channel}\n`));
97
- console.log(chalk.gray('支持的渠道: claude, codex, gemini\n'));
102
+ console.log(chalk.gray('支持的渠道: claude, codex, gemini, opencode\n'));
98
103
  process.exit(1);
99
104
  }
100
105
 
@@ -203,7 +208,8 @@ async function handleProxyStatus(channel) {
203
208
 
204
209
  try {
205
210
  const response = await httpRequest('GET', `${channelInfo.apiPath}/status`);
206
- const status = response.data;
211
+ const payload = response.data || {};
212
+ const status = payload.proxy || payload;
207
213
 
208
214
  console.log(chalk.bold.cyan(`\n╔══════════════════════════════════════╗`));
209
215
  console.log(chalk.bold.cyan(`║ ${channelInfo.name} 代理服务状态 ║`));
@@ -13,7 +13,7 @@ const { loadAliases } = require('../server/services/alias');
13
13
  async function searchSessionsAcrossProjects(config, keyword) {
14
14
  const spinner = ora(`🔍 正在搜索 "${keyword}"...`).start();
15
15
 
16
- const projects = getProjects(config);
16
+ const projects = await getProjects(config);
17
17
  const aliases = loadAliases();
18
18
  const allResults = [];
19
19
 
@@ -1,9 +1,9 @@
1
1
  const chalk = require('chalk');
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
- const os = require('os');
4
+ const { PATHS, ensureStorageDirMigrated } = require('../config/paths');
5
5
 
6
- const SECURITY_FILE = path.join(os.homedir(), '.claude', 'cc-tool', 'security.json');
6
+ const SECURITY_FILE = path.join(PATHS.base, 'security.json');
7
7
 
8
8
  function showSecurityHelp() {
9
9
  console.log(chalk.yellow('\n🔐 安全设置命令:'));
@@ -13,6 +13,7 @@ function showSecurityHelp() {
13
13
 
14
14
  async function handleSecurityReset() {
15
15
  console.log(chalk.cyan('\n🔐 安全设置 - 关闭访问密码\n'));
16
+ ensureStorageDirMigrated();
16
17
 
17
18
  if (!fs.existsSync(SECURITY_FILE)) {
18
19
  console.log(chalk.yellow('⚠️ 未检测到安全配置文件'));