coding-tool-x 3.3.5 → 3.3.7

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 (27) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/web/assets/{Analytics-B6CWdkhx.js → Analytics-IW6eAy9u.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-BW6LEgd8.js → ConfigTemplates-BPtkTMSc.js} +1 -1
  4. package/dist/web/assets/{Home-B2B2gS2-.js → Home-obifg_9E.js} +1 -1
  5. package/dist/web/assets/{PluginManager-Bqc7ldY-.js → PluginManager-BGx9MSDV.js} +1 -1
  6. package/dist/web/assets/{ProjectList-BFdZZm_8.js → ProjectList-BCn-mrCx.js} +1 -1
  7. package/dist/web/assets/{SessionList-B_Tp37kM.js → SessionList-CzLfebJQ.js} +1 -1
  8. package/dist/web/assets/{SkillManager-ul2rcS3o.js → SkillManager-CXz2vBQx.js} +1 -1
  9. package/dist/web/assets/{WorkspaceManager-Dp5Jvdtu.js → WorkspaceManager-CHtgMfKc.js} +1 -1
  10. package/dist/web/assets/index-C7LPdVsN.js +2 -0
  11. package/dist/web/assets/{index-DxRneGyu.css → index-eEmjZKWP.css} +1 -1
  12. package/dist/web/index.html +2 -2
  13. package/package.json +1 -1
  14. package/src/commands/daemon.js +44 -6
  15. package/src/commands/update.js +21 -6
  16. package/src/config/default.js +1 -1
  17. package/src/config/model-metadata.js +2 -2
  18. package/src/config/model-metadata.json +7 -2
  19. package/src/server/api/config-export.js +21 -2
  20. package/src/server/api/mcp.js +26 -4
  21. package/src/server/index.js +25 -2
  22. package/src/server/services/config-export-service.js +639 -138
  23. package/src/server/services/mcp-client.js +162 -18
  24. package/src/server/services/mcp-service.js +130 -15
  25. package/src/server/services/model-detector.js +1 -0
  26. package/src/utils/port-helper.js +87 -2
  27. package/dist/web/assets/index-CSBDZxYn.js +0 -2
@@ -5,14 +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-CSBDZxYn.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-C7LPdVsN.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/markdown-C9MYpaSi.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vue-vendor-DET08QYg.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendors-DMjSfzlv.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/naive-ui-CxpuzdjU.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/icons-B29onFfZ.js">
14
14
  <link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
15
- <link rel="stylesheet" crossorigin href="/assets/index-DxRneGyu.css">
15
+ <link rel="stylesheet" crossorigin href="/assets/index-eEmjZKWP.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.3.5",
3
+ "version": "3.3.7",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const chalk = require('chalk');
4
4
  const { loadConfig } = require('../config/loader');
5
5
  const { PATHS, ensureStorageDirMigrated } = require('../config/paths');
6
- const { findProcessByPort } = require('../utils/port-helper');
6
+ const { findProcessByPort, getPortToolIssue, formatPortToolIssue } = require('../utils/port-helper');
7
7
 
8
8
  const PM2_APP_NAME = 'cc-tool';
9
9
 
@@ -61,27 +61,55 @@ function isPortOwnedByPid(port, pid) {
61
61
  return false;
62
62
  }
63
63
  const pids = findProcessByPort(port);
64
+ if (getPortToolIssue()) {
65
+ return null;
66
+ }
64
67
  return pids.includes(String(pid));
65
68
  }
66
69
 
70
+ function printPortToolIssue(issue = getPortToolIssue()) {
71
+ const lines = formatPortToolIssue(issue);
72
+ if (lines.length === 0) {
73
+ return;
74
+ }
75
+
76
+ console.log(chalk.yellow(`\n⚠️ ${lines[0]}`));
77
+ lines.slice(1).forEach((line) => {
78
+ console.log(chalk.gray(` ${line}`));
79
+ });
80
+ }
81
+
82
+ function shouldTreatPortOwnershipAsReady(ownsPort) {
83
+ return ownsPort === true || ownsPort === null;
84
+ }
85
+
67
86
  async function waitForServiceReady(port, timeoutMs = 6000, intervalMs = 300) {
68
87
  const startAt = Date.now();
69
88
  let lastProcess = null;
70
89
  let stablePassCount = 0;
90
+ let degradedPortCheckIssue = null;
71
91
 
72
92
  while (Date.now() - startAt < timeoutMs) {
73
93
  lastProcess = await getCCToolProcess();
74
94
  if (lastProcess && lastProcess.pm2_env.status === 'online') {
75
95
  const ownsPort = isPortOwnedByPid(port, lastProcess.pid);
76
- if (ownsPort) {
96
+ if (shouldTreatPortOwnershipAsReady(ownsPort)) {
77
97
  // 连续多次检查通过,避免“瞬时 online 但马上崩溃”的误报
78
98
  stablePassCount += 1;
99
+ if (ownsPort === null) {
100
+ degradedPortCheckIssue = getPortToolIssue();
101
+ }
79
102
  } else {
103
+ degradedPortCheckIssue = getPortToolIssue();
80
104
  stablePassCount = 0;
81
105
  }
82
106
 
83
107
  if (stablePassCount >= 3) {
84
- return { ready: true, process: lastProcess };
108
+ return {
109
+ ready: true,
110
+ process: lastProcess,
111
+ degradedPortCheckIssue
112
+ };
85
113
  }
86
114
  } else {
87
115
  stablePassCount = 0;
@@ -90,7 +118,11 @@ async function waitForServiceReady(port, timeoutMs = 6000, intervalMs = 300) {
90
118
  }
91
119
 
92
120
  lastProcess = await getCCToolProcess();
93
- return { ready: false, process: lastProcess };
121
+ return {
122
+ ready: false,
123
+ process: lastProcess,
124
+ degradedPortCheckIssue: degradedPortCheckIssue || getPortToolIssue()
125
+ };
94
126
  }
95
127
 
96
128
  /**
@@ -145,12 +177,14 @@ async function handleStart() {
145
177
  process.exit(1);
146
178
  }
147
179
 
180
+ let readyState = null;
148
181
  try {
149
- const readyState = await waitForServiceReady(port);
182
+ readyState = await waitForServiceReady(port);
150
183
  if (!readyState.ready) {
151
184
  const statusText = readyState.process?.pm2_env?.status || 'unknown';
152
185
  console.error(chalk.red('\n❌ Coding-Tool 服务启动失败,进程未就绪\n'));
153
186
  console.error(chalk.gray(`PM2 状态: ${statusText}`));
187
+ printPortToolIssue(readyState.degradedPortCheckIssue);
154
188
  console.error(chalk.yellow('💡 请使用 ctx logs ui 查看详细日志\n'));
155
189
 
156
190
  pm2.delete(PM2_APP_NAME, () => {
@@ -169,6 +203,7 @@ async function handleStart() {
169
203
 
170
204
  console.log(chalk.green('\n✅ Coding-Tool 服务已启动(后台运行)\n'));
171
205
  console.log(chalk.gray(`Web UI: http://localhost:${port}`));
206
+ printPortToolIssue(readyState.degradedPortCheckIssue);
172
207
  if (enableHost) {
173
208
  console.log(chalk.yellow(`⚠️ LAN 访问已启用 (http://<your-ip>:${port})`));
174
209
  }
@@ -361,5 +396,8 @@ module.exports = {
361
396
  handleStart,
362
397
  handleStop,
363
398
  handleRestart,
364
- handleStatus
399
+ handleStatus,
400
+ _test: {
401
+ shouldTreatPortOwnershipAsReady
402
+ }
365
403
  };
@@ -1,14 +1,22 @@
1
- const { spawn } = require('child_process');
1
+ const { spawn, execFile } = require('child_process');
2
2
  const { promisify } = require('util');
3
- const { exec } = require('child_process');
4
3
  const semver = require('semver');
5
4
  const chalk = require('chalk');
6
5
  const packageInfo = require('../../package.json');
7
6
 
8
- const execAsync = promisify(exec);
7
+ const execFileAsync = promisify(execFile);
8
+
9
+ function resolveNpmCommand() {
10
+ return process.platform === 'win32' ? 'npm.cmd' : 'npm';
11
+ }
9
12
 
10
13
  async function getLatestVersion(packageName) {
11
- const { stdout } = await execAsync(`npm view ${packageName} version --json`, { timeout: 15000 });
14
+ const npmCommand = resolveNpmCommand();
15
+ const { stdout } = await execFileAsync(
16
+ npmCommand,
17
+ ['view', packageName, 'version', '--json'],
18
+ { timeout: 15000 }
19
+ );
12
20
  const parsed = JSON.parse(stdout.trim());
13
21
  if (typeof parsed === 'string') return parsed;
14
22
  throw new Error('无法解析 npm 返回的版本号');
@@ -16,11 +24,18 @@ async function getLatestVersion(packageName) {
16
24
 
17
25
  function runNpmInstall(packageName, version) {
18
26
  return new Promise((resolve, reject) => {
19
- const child = spawn('npm', ['install', '-g', `${packageName}@${version}`], {
27
+ const npmCommand = resolveNpmCommand();
28
+ const child = spawn(npmCommand, ['install', '-g', `${packageName}@${version}`], {
20
29
  stdio: 'inherit'
21
30
  });
22
31
 
23
- child.on('error', reject);
32
+ child.on('error', (err) => {
33
+ if (err && err.code === 'ENOENT') {
34
+ reject(new Error(`命令 "${npmCommand}" 未找到,请确认 Node.js/npm 已安装并在 PATH 中`));
35
+ return;
36
+ }
37
+ reject(err);
38
+ });
24
39
  child.on('exit', (code) => {
25
40
  if (code === 0) {
26
41
  resolve();
@@ -27,7 +27,7 @@ const DEFAULT_CONFIG = {
27
27
  defaultModels: modelMetadataConfig.defaultModels || { claude: [], codex: [], gemini: [] },
28
28
  defaultSpeedTestModels: modelMetadataConfig.defaultSpeedTestModels || {
29
29
  claude: 'claude-haiku-4-5',
30
- codex: 'gpt-5.2',
30
+ codex: 'gpt-5.4',
31
31
  gemini: 'gemini-2.5-pro'
32
32
  },
33
33
  pricing: {
@@ -13,7 +13,7 @@ const MODEL_ALIASES = metadataConfig.aliases || {};
13
13
  const DEFAULT_MODELS = metadataConfig.defaultModels || { claude: [], codex: [], gemini: [] };
14
14
  const DEFAULT_SPEED_TEST_MODELS = metadataConfig.defaultSpeedTestModels || {
15
15
  claude: 'claude-haiku-4-5',
16
- codex: 'gpt-5.2',
16
+ codex: 'gpt-5.4',
17
17
  gemini: 'gemini-2.5-pro'
18
18
  };
19
19
 
@@ -163,5 +163,5 @@ module.exports = {
163
163
  getDefaultSpeedTestModels,
164
164
  getDefaultSpeedTestModelByToolType,
165
165
  saveDefaultSpeedTestModels,
166
- METADATA_LAST_UPDATED: metadataConfig.lastUpdated || '2026-02-27'
166
+ METADATA_LAST_UPDATED: metadataConfig.lastUpdated || '2026-03-06'
167
167
  };
@@ -1,5 +1,5 @@
1
1
  {
2
- "lastUpdated": "2026-02-27",
2
+ "lastUpdated": "2026-03-06",
3
3
  "defaultModels": {
4
4
  "claude": [
5
5
  "claude-opus-4-6",
@@ -9,6 +9,7 @@
9
9
  "claude-haiku-4-5-20251001"
10
10
  ],
11
11
  "codex": [
12
+ "gpt-5.4",
12
13
  "gpt-5.3-codex",
13
14
  "gpt-5.2-codex",
14
15
  "gpt-5.1-codex-max",
@@ -30,7 +31,7 @@
30
31
  },
31
32
  "defaultSpeedTestModels": {
32
33
  "claude": "claude-haiku-4-5",
33
- "codex": "gpt-5.2",
34
+ "codex": "gpt-5.4",
34
35
  "gemini": "gemini-2.5-pro"
35
36
  },
36
37
  "aliases": {
@@ -73,6 +74,10 @@
73
74
  "limit": { "context": 1000000, "output": 32768 },
74
75
  "pricing": { "input": 2, "output": 8 }
75
76
  },
77
+ "gpt-5.4": {
78
+ "limit": { "context": 1000000, "output": 32768 },
79
+ "pricing": { "input": 2, "output": 8 }
80
+ },
76
81
  "gpt-5-codex": {
77
82
  "limit": { "context": 1000000, "output": 32768 },
78
83
  "pricing": { "input": 2, "output": 8 }
@@ -18,13 +18,32 @@ function parseConfigZip(buffer) {
18
18
  return JSON.parse(content);
19
19
  }
20
20
 
21
+ function resolveChannelsByType(exportData) {
22
+ const raw = exportData?.data || {};
23
+ const typed = raw.channelsByType && typeof raw.channelsByType === 'object' ? raw.channelsByType : {};
24
+ return {
25
+ claude: Array.isArray(typed.claude) ? typed.claude : (Array.isArray(raw.channels) ? raw.channels : []),
26
+ codex: Array.isArray(typed.codex) ? typed.codex : [],
27
+ gemini: Array.isArray(typed.gemini) ? typed.gemini : [],
28
+ opencode: Array.isArray(typed.opencode) ? typed.opencode : []
29
+ };
30
+ }
31
+
21
32
  function buildPreviewSummary(data) {
33
+ const channelsByType = resolveChannelsByType(data);
34
+ const allChannels = [
35
+ ...channelsByType.claude.map(c => ({ ...c, type: c.type || 'claude' })),
36
+ ...channelsByType.codex.map(c => ({ ...c, type: c.type || 'codex' })),
37
+ ...channelsByType.gemini.map(c => ({ ...c, type: c.type || 'gemini' })),
38
+ ...channelsByType.opencode.map(c => ({ ...c, type: c.type || 'opencode' }))
39
+ ];
40
+
22
41
  return {
23
42
  version: data.version,
24
43
  exportedAt: data.exportedAt,
25
44
  counts: {
26
45
  configTemplates: (data.data.configTemplates || []).length,
27
- channels: (data.data.channels || []).length,
46
+ channels: allChannels.length,
28
47
  plugins: (data.data.plugins || []).length
29
48
  },
30
49
  items: {
@@ -33,7 +52,7 @@ function buildPreviewSummary(data) {
33
52
  name: t.name,
34
53
  description: t.description
35
54
  })),
36
- channels: (data.data.channels || []).map(c => ({
55
+ channels: allChannels.map(c => ({
37
56
  id: c.id,
38
57
  name: c.name,
39
58
  type: c.type
@@ -241,7 +241,9 @@ router.post('/servers/:id/test', async (req, res) => {
241
241
  console.error('[MCP API] Test server failed:', error);
242
242
  res.status(500).json({
243
243
  success: false,
244
- error: error.message
244
+ error: error.message,
245
+ message: error.message,
246
+ hint: error?.data?.hint || null
245
247
  });
246
248
  }
247
249
  });
@@ -341,9 +343,24 @@ router.get('/servers/:id/tools', async (req, res) => {
341
343
  try {
342
344
  const { id } = req.params;
343
345
  const result = await mcpService.getServerTools(id);
346
+ if (result.status === 'error') {
347
+ return res.status(502).json({
348
+ success: false,
349
+ error: result.error || '获取工具列表失败',
350
+ message: result.error || '获取工具列表失败',
351
+ hint: result.hint || null,
352
+ duration: result.duration,
353
+ tools: []
354
+ });
355
+ }
344
356
  res.json({ success: true, ...result });
345
357
  } catch (err) {
346
- res.status(404).json({ success: false, error: err.message });
358
+ res.status(404).json({
359
+ success: false,
360
+ error: err.message,
361
+ message: err.message,
362
+ hint: err?.data?.hint || null
363
+ });
347
364
  }
348
365
  });
349
366
 
@@ -357,13 +374,18 @@ router.post('/servers/:id/tools/test', async (req, res) => {
357
374
  const { toolName, arguments: args } = req.body;
358
375
 
359
376
  if (!toolName) {
360
- return res.status(400).json({ success: false, error: '缺少 toolName 参数' });
377
+ return res.status(400).json({ success: false, error: '缺少 toolName 参数', message: '缺少 toolName 参数' });
361
378
  }
362
379
 
363
380
  const result = await mcpService.callServerTool(id, toolName, args || {});
364
381
  res.json({ success: true, ...result });
365
382
  } catch (err) {
366
- res.status(500).json({ success: false, error: err.message });
383
+ res.status(500).json({
384
+ success: false,
385
+ error: err.message,
386
+ message: err.message,
387
+ hint: err?.data?.hint || null
388
+ });
367
389
  }
368
390
  });
369
391
 
@@ -5,7 +5,13 @@ const inquirer = require('inquirer');
5
5
  const { loadConfig } = require('../config/loader');
6
6
  const { PATHS, ensureStorageDirMigrated } = require('../config/paths');
7
7
  const { startWebSocketServer: attachWebSocketServer } = require('./websocket-server');
8
- const { isPortInUse, killProcessByPort, waitForPortRelease } = require('../utils/port-helper');
8
+ const {
9
+ isPortInUse,
10
+ killProcessByPort,
11
+ waitForPortRelease,
12
+ getPortToolIssue,
13
+ formatPortToolIssue
14
+ } = require('../utils/port-helper');
9
15
  const { isProxyConfig } = require('./services/settings-manager');
10
16
  const {
11
17
  isProxyConfig: isCodexProxyConfig,
@@ -35,6 +41,18 @@ function printPortConflictHelp(port) {
35
41
  console.log(chalk.gray(` 2. 或手动关闭占用端口 ${port} 的程序\n`));
36
42
  }
37
43
 
44
+ function printPortToolIssue(issue = getPortToolIssue()) {
45
+ const lines = formatPortToolIssue(issue);
46
+ if (lines.length === 0) {
47
+ return;
48
+ }
49
+
50
+ console.error(chalk.yellow(`\n💡 ${lines[0]}`));
51
+ lines.slice(1).forEach((line) => {
52
+ console.error(chalk.gray(` ${line}`));
53
+ });
54
+ }
55
+
38
56
  async function startServer(port, host = '127.0.0.1', options = {}) {
39
57
  ensureStorageDirMigrated();
40
58
  const config = loadConfig();
@@ -85,7 +103,12 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
85
103
  const killed = killProcessByPort(port);
86
104
 
87
105
  if (!killed) {
88
- console.error(chalk.red('\n❌ 无法关闭占用端口的进程'));
106
+ const toolIssue = getPortToolIssue();
107
+ if (toolIssue) {
108
+ printPortToolIssue(toolIssue);
109
+ } else {
110
+ console.error(chalk.red('\n❌ 无法关闭占用端口的进程'));
111
+ }
89
112
  console.error(chalk.yellow('\n💡 请手动关闭占用端口的程序,或使用其他端口\n'));
90
113
  process.exit(1);
91
114
  }