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.
- package/CHANGELOG.md +9 -0
- package/dist/web/assets/{Analytics-B6CWdkhx.js → Analytics-IW6eAy9u.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-BW6LEgd8.js → ConfigTemplates-BPtkTMSc.js} +1 -1
- package/dist/web/assets/{Home-B2B2gS2-.js → Home-obifg_9E.js} +1 -1
- package/dist/web/assets/{PluginManager-Bqc7ldY-.js → PluginManager-BGx9MSDV.js} +1 -1
- package/dist/web/assets/{ProjectList-BFdZZm_8.js → ProjectList-BCn-mrCx.js} +1 -1
- package/dist/web/assets/{SessionList-B_Tp37kM.js → SessionList-CzLfebJQ.js} +1 -1
- package/dist/web/assets/{SkillManager-ul2rcS3o.js → SkillManager-CXz2vBQx.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Dp5Jvdtu.js → WorkspaceManager-CHtgMfKc.js} +1 -1
- package/dist/web/assets/index-C7LPdVsN.js +2 -0
- package/dist/web/assets/{index-DxRneGyu.css → index-eEmjZKWP.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/commands/daemon.js +44 -6
- package/src/commands/update.js +21 -6
- package/src/config/default.js +1 -1
- package/src/config/model-metadata.js +2 -2
- package/src/config/model-metadata.json +7 -2
- package/src/server/api/config-export.js +21 -2
- package/src/server/api/mcp.js +26 -4
- package/src/server/index.js +25 -2
- package/src/server/services/config-export-service.js +639 -138
- package/src/server/services/mcp-client.js +162 -18
- package/src/server/services/mcp-service.js +130 -15
- package/src/server/services/model-detector.js +1 -0
- package/src/utils/port-helper.js +87 -2
- package/dist/web/assets/index-CSBDZxYn.js +0 -2
package/dist/web/index.html
CHANGED
|
@@ -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-
|
|
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-
|
|
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
package/src/commands/daemon.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
};
|
package/src/commands/update.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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',
|
|
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();
|
package/src/config/default.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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-
|
|
166
|
+
METADATA_LAST_UPDATED: metadataConfig.lastUpdated || '2026-03-06'
|
|
167
167
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"lastUpdated": "2026-
|
|
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.
|
|
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:
|
|
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:
|
|
55
|
+
channels: allChannels.map(c => ({
|
|
37
56
|
id: c.id,
|
|
38
57
|
name: c.name,
|
|
39
58
|
type: c.type
|
package/src/server/api/mcp.js
CHANGED
|
@@ -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({
|
|
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({
|
|
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
|
|
package/src/server/index.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
}
|