coding-tool-x 3.3.4 → 3.3.6
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/dist/web/assets/{Analytics-B6CWdkhx.js → Analytics-TtaduRqL.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-BW6LEgd8.js → ConfigTemplates-BP2lLBMN.js} +1 -1
- package/dist/web/assets/{Home-B2B2gS2-.js → Home-CbbyopS-.js} +1 -1
- package/dist/web/assets/{PluginManager-Bqc7ldY-.js → PluginManager-HmISlyMK.js} +1 -1
- package/dist/web/assets/{ProjectList-BFdZZm_8.js → ProjectList-DoN8Hjbu.js} +1 -1
- package/dist/web/assets/{SessionList-B_Tp37kM.js → SessionList-Da8BYzNi.js} +1 -1
- package/dist/web/assets/{SkillManager-ul2rcS3o.js → SkillManager-DqLAXh9o.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Dp5Jvdtu.js → WorkspaceManager-B_TxOgPW.js} +1 -1
- package/dist/web/assets/{index-CSBDZxYn.js → index-By3mDEvx.js} +2 -2
- package/dist/web/assets/{index-DxRneGyu.css → index-CsWInMQV.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +2 -1
- package/src/commands/doctor.js +3 -3
- package/src/commands/export-config.js +2 -2
- package/src/commands/logs.js +42 -9
- package/src/commands/port-config.js +2 -2
- package/src/commands/switch.js +2 -2
- package/src/commands/update.js +21 -6
- package/src/config/default.js +4 -1
- package/src/config/loader.js +5 -2
- package/src/config/paths.js +25 -21
- package/src/reset-config.js +3 -5
- package/src/server/api/agents.js +2 -3
- package/src/server/api/claude-hooks.js +67 -10
- package/src/server/api/config-export.js +21 -2
- package/src/server/api/opencode-sessions.js +2 -2
- package/src/server/api/pm2-autostart.js +20 -8
- package/src/server/api/proxy.js +2 -3
- package/src/server/api/sessions.js +6 -6
- package/src/server/index.js +5 -9
- package/src/server/services/agents-service.js +6 -3
- package/src/server/services/channels.js +6 -7
- package/src/server/services/codex-channels.js +4 -1
- package/src/server/services/codex-config.js +4 -1
- package/src/server/services/codex-settings-manager.js +18 -9
- package/src/server/services/commands-service.js +2 -2
- package/src/server/services/config-export-service.js +96 -28
- package/src/server/services/config-registry-service.js +7 -6
- package/src/server/services/config-sync-manager.js +2 -2
- package/src/server/services/config-sync-service.js +2 -2
- package/src/server/services/env-checker.js +2 -2
- package/src/server/services/favorites.js +3 -4
- package/src/server/services/gemini-channels.js +4 -4
- package/src/server/services/gemini-config.js +2 -2
- package/src/server/services/gemini-sessions.js +3 -3
- package/src/server/services/gemini-settings-manager.js +5 -5
- package/src/server/services/mcp-client.js +101 -14
- package/src/server/services/mcp-service.js +98 -8
- package/src/server/services/model-detector.js +2 -2
- package/src/server/services/opencode-channels.js +5 -5
- package/src/server/services/plugins-service.js +3 -4
- package/src/server/services/prompts-service.js +7 -4
- package/src/server/services/proxy-runtime.js +2 -2
- package/src/server/services/repo-scanner-base.js +2 -2
- package/src/server/services/request-logger.js +2 -2
- package/src/server/services/security-config.js +2 -2
- package/src/server/services/session-cache.js +2 -2
- package/src/server/services/session-converter.js +9 -4
- package/src/server/services/sessions.js +8 -5
- package/src/server/services/settings-manager.js +3 -4
- package/src/server/services/skill-service.js +5 -5
- package/src/server/services/statistics-service.js +2 -2
- package/src/server/services/ui-config.js +3 -4
- package/src/server/websocket-server.js +2 -2
- package/src/utils/home-dir.js +82 -0
- package/src/utils/port-helper.js +34 -12
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
|
-
const
|
|
18
|
+
const { HOME_DIR } = require('../../config/paths');
|
|
19
19
|
|
|
20
20
|
// 全局配置目录
|
|
21
|
-
const GLOBAL_CONFIG_DIR = path.join(
|
|
21
|
+
const GLOBAL_CONFIG_DIR = path.join(HOME_DIR, '.claude');
|
|
22
22
|
|
|
23
23
|
// 配置类型定义
|
|
24
24
|
const CONFIG_TYPES = {
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
-
const os = require('os');
|
|
11
10
|
const crypto = require('crypto');
|
|
11
|
+
const { HOME_DIR } = require('../../config/paths');
|
|
12
12
|
|
|
13
13
|
// 各平台需要检测的环境变量关键词
|
|
14
14
|
const PLATFORM_KEYWORDS = {
|
|
@@ -141,7 +141,7 @@ function checkProcessEnv(keywords) {
|
|
|
141
141
|
*/
|
|
142
142
|
function checkShellConfigs(keywords) {
|
|
143
143
|
const conflicts = [];
|
|
144
|
-
const homeDir =
|
|
144
|
+
const homeDir = HOME_DIR;
|
|
145
145
|
|
|
146
146
|
for (const fileName of SHELL_CONFIG_FILES) {
|
|
147
147
|
const filePath = path.isAbsolute(fileName) ? fileName : path.join(homeDir, fileName);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
|
-
const
|
|
3
|
-
const os = require('os');
|
|
2
|
+
const { PATHS } = require('../../config/paths');
|
|
4
3
|
|
|
5
|
-
const FAVORITES_DIR =
|
|
6
|
-
const FAVORITES_FILE =
|
|
4
|
+
const FAVORITES_DIR = PATHS.base;
|
|
5
|
+
const FAVORITES_FILE = PATHS.favorites;
|
|
7
6
|
|
|
8
7
|
// 内存缓存
|
|
9
8
|
let favoritesCache = null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const crypto = require('crypto');
|
|
4
|
+
const { PATHS, NATIVE_PATHS } = require('../../config/paths');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Gemini 渠道管理服务(多渠道架构)
|
|
@@ -26,16 +26,16 @@ function normalizeGatewaySourceType(value, fallback = 'gemini') {
|
|
|
26
26
|
|
|
27
27
|
// 获取 Gemini 配置目录
|
|
28
28
|
function getGeminiDir() {
|
|
29
|
-
return path.
|
|
29
|
+
return path.dirname(NATIVE_PATHS.gemini.env);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// 获取渠道存储文件路径
|
|
33
33
|
function getChannelsFilePath() {
|
|
34
|
-
const ccToolDir =
|
|
34
|
+
const ccToolDir = PATHS.base;
|
|
35
35
|
if (!fs.existsSync(ccToolDir)) {
|
|
36
36
|
fs.mkdirSync(ccToolDir, { recursive: true });
|
|
37
37
|
}
|
|
38
|
-
return
|
|
38
|
+
return PATHS.channels.gemini;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// 检查是否在代理模式
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const
|
|
3
|
+
const { NATIVE_PATHS } = require('../../config/paths');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* 获取 Gemini 配置目录
|
|
7
7
|
*/
|
|
8
8
|
function getGeminiDir() {
|
|
9
|
-
return path.
|
|
9
|
+
return path.dirname(NATIVE_PATHS.gemini.env);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const crypto = require('crypto');
|
|
4
|
-
const
|
|
4
|
+
const { HOME_DIR } = require('../../config/paths');
|
|
5
5
|
const { getGeminiDir } = require('./gemini-config');
|
|
6
6
|
|
|
7
7
|
// 路径映射缓存
|
|
@@ -102,7 +102,7 @@ function buildPathMapping() {
|
|
|
102
102
|
|
|
103
103
|
const targetHashes = new Set(projectHashes);
|
|
104
104
|
const results = new Map();
|
|
105
|
-
const homeDir =
|
|
105
|
+
const homeDir = HOME_DIR;
|
|
106
106
|
|
|
107
107
|
// 定义要扫描的目录及其最大深度
|
|
108
108
|
// 深度说明:depth=3 表示可以扫描到 Desktop/a/b/c 这样的 4 层目录
|
|
@@ -362,7 +362,7 @@ function getProjects() {
|
|
|
362
362
|
if (projectPath) {
|
|
363
363
|
displayName = path.basename(projectPath);
|
|
364
364
|
// 如果是 home 目录,显示 ~
|
|
365
|
-
if (projectPath ===
|
|
365
|
+
if (projectPath === HOME_DIR) {
|
|
366
366
|
displayName = '~';
|
|
367
367
|
}
|
|
368
368
|
} else {
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const
|
|
3
|
+
const { NATIVE_PATHS } = require('../../config/paths');
|
|
4
4
|
|
|
5
5
|
// Gemini 配置文件路径
|
|
6
6
|
function getEnvPath() {
|
|
7
|
-
return
|
|
7
|
+
return NATIVE_PATHS.gemini.env;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
function getSettingsPath() {
|
|
11
|
-
return path.join(
|
|
11
|
+
return path.join(path.dirname(NATIVE_PATHS.gemini.env), 'settings.json');
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// 备份文件路径
|
|
15
15
|
function getEnvBackupPath() {
|
|
16
|
-
return
|
|
16
|
+
return NATIVE_PATHS.gemini.envBackup;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function getSettingsBackupPath() {
|
|
20
|
-
return path.join(
|
|
20
|
+
return path.join(path.dirname(NATIVE_PATHS.gemini.env), 'settings.json.cc-tool-backup');
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// 检查配置文件是否存在
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const { spawn } = require('child_process');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
17
19
|
const http = require('http');
|
|
18
20
|
const https = require('https');
|
|
19
21
|
const { EventEmitter } = require('events');
|
|
@@ -22,6 +24,89 @@ const DEFAULT_TIMEOUT = 10000; // 10 seconds
|
|
|
22
24
|
const JSONRPC_VERSION = '2.0';
|
|
23
25
|
const MCP_PROTOCOL_VERSION = '2024-11-05';
|
|
24
26
|
|
|
27
|
+
function getPathEnvKey(envObj = {}) {
|
|
28
|
+
return Object.keys(envObj).find(key => key.toLowerCase() === 'path') || 'PATH';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function stripWrappingQuotes(value) {
|
|
32
|
+
const text = String(value || '').trim();
|
|
33
|
+
if (!text) return '';
|
|
34
|
+
if (
|
|
35
|
+
(text.startsWith('"') && text.endsWith('"')) ||
|
|
36
|
+
(text.startsWith("'") && text.endsWith("'"))
|
|
37
|
+
) {
|
|
38
|
+
return text.slice(1, -1);
|
|
39
|
+
}
|
|
40
|
+
return text;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function mergeSpawnEnv(extraEnv = {}) {
|
|
44
|
+
const mergedEnv = { ...process.env, ...extraEnv };
|
|
45
|
+
const processPathKey = getPathEnvKey(process.env);
|
|
46
|
+
const extraPathKey = getPathEnvKey(extraEnv);
|
|
47
|
+
const mergedPathKey = getPathEnvKey(mergedEnv);
|
|
48
|
+
|
|
49
|
+
const extraPath = extraEnv && typeof extraEnv[extraPathKey] === 'string'
|
|
50
|
+
? extraEnv[extraPathKey]
|
|
51
|
+
: '';
|
|
52
|
+
const processPath = process.env && typeof process.env[processPathKey] === 'string'
|
|
53
|
+
? process.env[processPathKey]
|
|
54
|
+
: '';
|
|
55
|
+
|
|
56
|
+
if (extraPath && processPath) {
|
|
57
|
+
mergedEnv[mergedPathKey] = `${extraPath}${path.delimiter}${processPath}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return mergedEnv;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveWindowsSpawnCommand(command, env, cwd) {
|
|
64
|
+
if (process.platform !== 'win32') {
|
|
65
|
+
return stripWrappingQuotes(command);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const normalizedCommand = stripWrappingQuotes(command);
|
|
69
|
+
if (!normalizedCommand) {
|
|
70
|
+
return normalizedCommand;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const hasPathSegment = /[\\/]/.test(normalizedCommand) || /^[a-zA-Z]:/.test(normalizedCommand);
|
|
74
|
+
const hasExtension = path.extname(normalizedCommand).length > 0;
|
|
75
|
+
const extensions = hasExtension ? [''] : ['.cmd', '.exe', '.bat', '.com'];
|
|
76
|
+
const resolveCandidate = (basePath) => {
|
|
77
|
+
for (const ext of extensions) {
|
|
78
|
+
const candidate = ext ? `${basePath}${ext}` : basePath;
|
|
79
|
+
if (fs.existsSync(candidate)) {
|
|
80
|
+
return candidate;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (hasPathSegment) {
|
|
87
|
+
const absoluteBasePath = path.isAbsolute(normalizedCommand)
|
|
88
|
+
? normalizedCommand
|
|
89
|
+
: path.resolve(cwd || process.cwd(), normalizedCommand);
|
|
90
|
+
return resolveCandidate(absoluteBasePath) || normalizedCommand;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const pathKey = getPathEnvKey(env || process.env);
|
|
94
|
+
const pathValue = env && typeof env[pathKey] === 'string' ? env[pathKey] : '';
|
|
95
|
+
if (!pathValue) {
|
|
96
|
+
return normalizedCommand;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const searchPaths = pathValue.split(path.delimiter).filter(Boolean);
|
|
100
|
+
for (const searchPath of searchPaths) {
|
|
101
|
+
const found = resolveCandidate(path.join(searchPath.trim(), normalizedCommand));
|
|
102
|
+
if (found) {
|
|
103
|
+
return found;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return normalizedCommand;
|
|
108
|
+
}
|
|
109
|
+
|
|
25
110
|
// ============================================================================
|
|
26
111
|
// McpClient
|
|
27
112
|
// ============================================================================
|
|
@@ -237,22 +322,19 @@ class McpClient extends EventEmitter {
|
|
|
237
322
|
reject(new McpClientError(`Connection timeout after ${this._timeout}ms`));
|
|
238
323
|
}, this._timeout);
|
|
239
324
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// 如果用户提供的 env 中有 PATH,将其追加到系统 PATH 前面
|
|
244
|
-
if (env && env.PATH && process.env.PATH) {
|
|
245
|
-
mergedEnv.PATH = `${env.PATH}:${process.env.PATH}`;
|
|
246
|
-
}
|
|
325
|
+
const finalCwd = cwd || process.cwd();
|
|
326
|
+
const mergedEnv = mergeSpawnEnv(env || {});
|
|
327
|
+
const resolvedCommand = resolveWindowsSpawnCommand(command, mergedEnv, finalCwd);
|
|
247
328
|
|
|
248
|
-
|
|
329
|
+
try {
|
|
330
|
+
this._child = spawn(resolvedCommand, args, {
|
|
249
331
|
env: mergedEnv,
|
|
250
332
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
251
|
-
cwd:
|
|
333
|
+
cwd: finalCwd
|
|
252
334
|
});
|
|
253
335
|
} catch (err) {
|
|
254
336
|
clearTimeout(timer);
|
|
255
|
-
throw new McpClientError(`Failed to spawn "${
|
|
337
|
+
throw new McpClientError(`Failed to spawn "${resolvedCommand}": ${err.message}`);
|
|
256
338
|
}
|
|
257
339
|
|
|
258
340
|
// Once we get the spawn event (or first stdout), consider connected
|
|
@@ -274,13 +356,18 @@ class McpClient extends EventEmitter {
|
|
|
274
356
|
|
|
275
357
|
this._child.on('error', (err) => {
|
|
276
358
|
if (err.code === 'ENOENT') {
|
|
277
|
-
const
|
|
278
|
-
|
|
359
|
+
const pathKey = getPathEnvKey(mergedEnv);
|
|
360
|
+
const pathValue = typeof mergedEnv[pathKey] === 'string' ? mergedEnv[pathKey] : '';
|
|
361
|
+
const pathHint = pathValue
|
|
362
|
+
? `\n Current PATH: ${pathValue.split(path.delimiter).slice(0, 5).join(path.delimiter)}\n (showing first 5 entries)`
|
|
279
363
|
: '\n PATH is not set!';
|
|
364
|
+
const commandHint = resolvedCommand === command
|
|
365
|
+
? command
|
|
366
|
+
: `${command} (resolved: ${resolvedCommand})`;
|
|
280
367
|
settle(new McpClientError(
|
|
281
|
-
`Command "${
|
|
368
|
+
`Command "${commandHint}" not found. Please check:\n` +
|
|
282
369
|
` 1. Is "${command}" installed?\n` +
|
|
283
|
-
` 2. Try using absolute path (e.g., /usr/bin/node or $(which ${command}))\n` +
|
|
370
|
+
` 2. Try using absolute path${process.platform === 'win32' ? ' (e.g., C:\\\\Program Files\\\\nodejs\\\\npx.cmd)' : ` (e.g., /usr/bin/node or $(which ${command}))`}\n` +
|
|
284
371
|
` 3. Check your PATH environment variable${pathHint}`
|
|
285
372
|
));
|
|
286
373
|
} else {
|
|
@@ -13,15 +13,18 @@ const http = require('http');
|
|
|
13
13
|
const https = require('https');
|
|
14
14
|
const { McpClient } = require('./mcp-client');
|
|
15
15
|
const { NATIVE_PATHS } = require('../../config/paths');
|
|
16
|
+
const { resolvePreferredHomeDir } = require('../../utils/home-dir');
|
|
17
|
+
|
|
18
|
+
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
16
19
|
|
|
17
20
|
// MCP 配置文件路径
|
|
18
|
-
const CC_TOOL_DIR = path.join(
|
|
21
|
+
const CC_TOOL_DIR = path.join(HOME_DIR, '.cc-tool');
|
|
19
22
|
const MCP_SERVERS_FILE = path.join(CC_TOOL_DIR, 'mcp-servers.json');
|
|
20
23
|
|
|
21
24
|
// 各平台配置文件路径
|
|
22
|
-
const CLAUDE_CONFIG_PATH = path.join(
|
|
23
|
-
const CODEX_CONFIG_PATH =
|
|
24
|
-
const GEMINI_CONFIG_PATH = path.join(
|
|
25
|
+
const CLAUDE_CONFIG_PATH = path.join(HOME_DIR, '.claude.json');
|
|
26
|
+
const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
|
|
27
|
+
const GEMINI_CONFIG_PATH = path.join(path.dirname(NATIVE_PATHS.gemini.env), 'settings.json');
|
|
25
28
|
const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
|
|
26
29
|
const OPENCODE_CONFIG_PATHS = {
|
|
27
30
|
jsonc: path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc'),
|
|
@@ -415,6 +418,89 @@ function writeOpenCodeConfig(filePath, data) {
|
|
|
415
418
|
fs.renameSync(tempPath, filePath);
|
|
416
419
|
}
|
|
417
420
|
|
|
421
|
+
function getPathEnvKey(envObj = {}) {
|
|
422
|
+
return Object.keys(envObj).find(key => key.toLowerCase() === 'path') || 'PATH';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function stripWrappingQuotes(value) {
|
|
426
|
+
const text = String(value || '').trim();
|
|
427
|
+
if (!text) return '';
|
|
428
|
+
if (
|
|
429
|
+
(text.startsWith('"') && text.endsWith('"')) ||
|
|
430
|
+
(text.startsWith("'") && text.endsWith("'"))
|
|
431
|
+
) {
|
|
432
|
+
return text.slice(1, -1);
|
|
433
|
+
}
|
|
434
|
+
return text;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function mergeSpawnEnv(extraEnv = {}) {
|
|
438
|
+
const mergedEnv = { ...process.env, ...extraEnv };
|
|
439
|
+
const processPathKey = getPathEnvKey(process.env);
|
|
440
|
+
const extraPathKey = getPathEnvKey(extraEnv);
|
|
441
|
+
const mergedPathKey = getPathEnvKey(mergedEnv);
|
|
442
|
+
|
|
443
|
+
const extraPath = extraEnv && typeof extraEnv[extraPathKey] === 'string'
|
|
444
|
+
? extraEnv[extraPathKey]
|
|
445
|
+
: '';
|
|
446
|
+
const processPath = process.env && typeof process.env[processPathKey] === 'string'
|
|
447
|
+
? process.env[processPathKey]
|
|
448
|
+
: '';
|
|
449
|
+
|
|
450
|
+
if (extraPath && processPath) {
|
|
451
|
+
mergedEnv[mergedPathKey] = `${extraPath}${path.delimiter}${processPath}`;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return mergedEnv;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function resolveWindowsSpawnCommand(command, env, cwd) {
|
|
458
|
+
if (process.platform !== 'win32') {
|
|
459
|
+
return stripWrappingQuotes(command);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const normalizedCommand = stripWrappingQuotes(command);
|
|
463
|
+
if (!normalizedCommand) {
|
|
464
|
+
return normalizedCommand;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const hasPathSegment = /[\\/]/.test(normalizedCommand) || /^[a-zA-Z]:/.test(normalizedCommand);
|
|
468
|
+
const hasExtension = path.extname(normalizedCommand).length > 0;
|
|
469
|
+
const extensions = hasExtension ? [''] : ['.cmd', '.exe', '.bat', '.com'];
|
|
470
|
+
const resolveCandidate = (basePath) => {
|
|
471
|
+
for (const ext of extensions) {
|
|
472
|
+
const candidate = ext ? `${basePath}${ext}` : basePath;
|
|
473
|
+
if (fs.existsSync(candidate)) {
|
|
474
|
+
return candidate;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return null;
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
if (hasPathSegment) {
|
|
481
|
+
const absoluteBasePath = path.isAbsolute(normalizedCommand)
|
|
482
|
+
? normalizedCommand
|
|
483
|
+
: path.resolve(cwd || process.cwd(), normalizedCommand);
|
|
484
|
+
return resolveCandidate(absoluteBasePath) || normalizedCommand;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const pathKey = getPathEnvKey(env || process.env);
|
|
488
|
+
const pathValue = env && typeof env[pathKey] === 'string' ? env[pathKey] : '';
|
|
489
|
+
if (!pathValue) {
|
|
490
|
+
return normalizedCommand;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const searchPaths = pathValue.split(path.delimiter).filter(Boolean);
|
|
494
|
+
for (const searchPath of searchPaths) {
|
|
495
|
+
const found = resolveCandidate(path.join(searchPath.trim(), normalizedCommand));
|
|
496
|
+
if (found) {
|
|
497
|
+
return found;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return normalizedCommand;
|
|
502
|
+
}
|
|
503
|
+
|
|
418
504
|
// ============================================================================
|
|
419
505
|
// MCP 数据管理
|
|
420
506
|
// ============================================================================
|
|
@@ -1234,6 +1320,9 @@ async function testStdioServer(spec) {
|
|
|
1234
1320
|
// 检查命令是否存在
|
|
1235
1321
|
const command = spec.command;
|
|
1236
1322
|
const args = spec.args || [];
|
|
1323
|
+
const cwd = spec.cwd || process.cwd();
|
|
1324
|
+
const mergedEnv = mergeSpawnEnv(spec.env || {});
|
|
1325
|
+
const resolvedCommand = resolveWindowsSpawnCommand(command, mergedEnv, cwd);
|
|
1237
1326
|
|
|
1238
1327
|
let child;
|
|
1239
1328
|
let resolved = false;
|
|
@@ -1257,10 +1346,10 @@ async function testStdioServer(spec) {
|
|
|
1257
1346
|
};
|
|
1258
1347
|
|
|
1259
1348
|
try {
|
|
1260
|
-
child = spawn(
|
|
1261
|
-
env:
|
|
1349
|
+
child = spawn(resolvedCommand, args, {
|
|
1350
|
+
env: mergedEnv,
|
|
1262
1351
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1263
|
-
cwd
|
|
1352
|
+
cwd
|
|
1264
1353
|
});
|
|
1265
1354
|
|
|
1266
1355
|
child.stdout.on('data', (data) => {
|
|
@@ -1281,9 +1370,10 @@ async function testStdioServer(spec) {
|
|
|
1281
1370
|
|
|
1282
1371
|
child.on('error', (err) => {
|
|
1283
1372
|
if (err.code === 'ENOENT') {
|
|
1373
|
+
const commandLabel = resolvedCommand === command ? command : `${command} (resolved: ${resolvedCommand})`;
|
|
1284
1374
|
done({
|
|
1285
1375
|
success: false,
|
|
1286
|
-
message: `命令 "${
|
|
1376
|
+
message: `命令 "${commandLabel}" 未找到,请确保已安装(Windows 可尝试 npx.cmd 或绝对路径)`,
|
|
1287
1377
|
duration: Date.now() - startTime
|
|
1288
1378
|
});
|
|
1289
1379
|
} else {
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const os = require('os');
|
|
9
8
|
const https = require('https');
|
|
10
9
|
const http = require('http');
|
|
11
10
|
const { URL } = require('url');
|
|
12
11
|
const crypto = require('crypto');
|
|
13
12
|
const zlib = require('zlib');
|
|
14
13
|
const { loadConfig } = require('../../config/loader');
|
|
14
|
+
const { HOME_DIR } = require('../../config/paths');
|
|
15
15
|
|
|
16
16
|
// 内置模型优先级(当配置缺失时兜底)
|
|
17
17
|
const MODEL_PRIORITY = {
|
|
@@ -668,7 +668,7 @@ function collectResponseBody(res) {
|
|
|
668
668
|
* Get cache file path
|
|
669
669
|
*/
|
|
670
670
|
function getCacheFilePath() {
|
|
671
|
-
const dir = path.join(
|
|
671
|
+
const dir = path.join(HOME_DIR, '.cc-tool');
|
|
672
672
|
if (!fs.existsSync(dir)) {
|
|
673
673
|
fs.mkdirSync(dir, { recursive: true });
|
|
674
674
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const crypto = require('crypto');
|
|
4
|
+
const { PATHS } = require('../../config/paths');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* OpenCode 渠道管理服务
|
|
@@ -41,19 +41,19 @@ function normalizeChannelName(value) {
|
|
|
41
41
|
|
|
42
42
|
// 获取渠道存储文件路径
|
|
43
43
|
function getChannelsFilePath() {
|
|
44
|
-
const ccToolDir =
|
|
44
|
+
const ccToolDir = PATHS.base;
|
|
45
45
|
if (!fs.existsSync(ccToolDir)) {
|
|
46
46
|
fs.mkdirSync(ccToolDir, { recursive: true });
|
|
47
47
|
}
|
|
48
|
-
return
|
|
48
|
+
return PATHS.channels.opencode;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function getCodexChannelsFilePath() {
|
|
52
|
-
const ccToolDir =
|
|
52
|
+
const ccToolDir = PATHS.base;
|
|
53
53
|
if (!fs.existsSync(ccToolDir)) {
|
|
54
54
|
fs.mkdirSync(ccToolDir, { recursive: true });
|
|
55
55
|
}
|
|
56
|
-
return
|
|
56
|
+
return PATHS.channels.codex;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// 读取所有渠道
|
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const os = require('os');
|
|
10
9
|
const { listPlugins, getPlugin, updatePlugin: updatePluginRegistry } = require('../../plugins/registry');
|
|
11
10
|
const { installPlugin: installPluginCore, uninstallPlugin: uninstallPluginCore } = require('../../plugins/plugin-installer');
|
|
12
11
|
const { initializePlugins, shutdownPlugins } = require('../../plugins/plugin-manager');
|
|
13
12
|
const { INSTALLED_DIR, CONFIG_DIR } = require('../../plugins/constants');
|
|
14
|
-
const { NATIVE_PATHS } = require('../../config/paths');
|
|
13
|
+
const { NATIVE_PATHS, HOME_DIR } = require('../../config/paths');
|
|
15
14
|
|
|
16
|
-
const CLAUDE_PLUGINS_DIR = path.join(
|
|
15
|
+
const CLAUDE_PLUGINS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'plugins');
|
|
17
16
|
const CLAUDE_INSTALLED_FILE = path.join(CLAUDE_PLUGINS_DIR, 'installed_plugins.json');
|
|
18
17
|
const CLAUDE_MARKETPLACES_FILE = path.join(CLAUDE_PLUGINS_DIR, 'known_marketplaces.json');
|
|
19
18
|
const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
|
|
@@ -107,7 +106,7 @@ function stripJsonComments(input = '') {
|
|
|
107
106
|
class PluginsService {
|
|
108
107
|
constructor(platform = 'claude') {
|
|
109
108
|
this.platform = ['claude', 'opencode'].includes(platform) ? platform : 'claude';
|
|
110
|
-
this.ccToolConfigDir = path.join(
|
|
109
|
+
this.ccToolConfigDir = path.join(HOME_DIR, '.cc-tool');
|
|
111
110
|
this.opencodePluginsDir = path.join(OPENCODE_CONFIG_DIR, 'plugins');
|
|
112
111
|
this.opencodeLegacyPluginsDir = path.join(OPENCODE_CONFIG_DIR, 'plugin');
|
|
113
112
|
}
|
|
@@ -8,15 +8,18 @@ const fs = require('fs');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const os = require('os');
|
|
10
10
|
const { NATIVE_PATHS } = require('../../config/paths');
|
|
11
|
+
const { resolvePreferredHomeDir } = require('../../utils/home-dir');
|
|
12
|
+
|
|
13
|
+
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
11
14
|
|
|
12
15
|
// Prompts 配置文件路径
|
|
13
|
-
const CC_TOOL_DIR = path.join(
|
|
16
|
+
const CC_TOOL_DIR = path.join(HOME_DIR, '.cc-tool');
|
|
14
17
|
const PROMPTS_FILE = path.join(CC_TOOL_DIR, 'prompts.json');
|
|
15
18
|
|
|
16
19
|
// 各平台提示词文件路径
|
|
17
|
-
const CLAUDE_PROMPT_PATH = path.join(
|
|
18
|
-
const CODEX_PROMPT_PATH = path.join(
|
|
19
|
-
const GEMINI_PROMPT_PATH = path.join(
|
|
20
|
+
const CLAUDE_PROMPT_PATH = path.join(HOME_DIR, '.claude', 'CLAUDE.md');
|
|
21
|
+
const CODEX_PROMPT_PATH = path.join(HOME_DIR, '.codex', 'AGENTS.md');
|
|
22
|
+
const GEMINI_PROMPT_PATH = path.join(HOME_DIR, '.gemini', 'GEMINI.md');
|
|
20
23
|
const OPENCODE_PROMPT_PATH = path.join(NATIVE_PATHS.opencode.config, 'AGENTS.md');
|
|
21
24
|
|
|
22
25
|
function normalizeApps(apps = {}, defaults = { claude: true, codex: true, gemini: true, opencode: false }) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const
|
|
3
|
+
const { PATHS } = require('../../config/paths');
|
|
4
4
|
|
|
5
5
|
function getRuntimeFilePath(proxyType) {
|
|
6
|
-
const ccToolDir =
|
|
6
|
+
const ccToolDir = PATHS.base;
|
|
7
7
|
if (!fs.existsSync(ccToolDir)) {
|
|
8
8
|
fs.mkdirSync(ccToolDir, { recursive: true });
|
|
9
9
|
}
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
-
const os = require('os');
|
|
11
10
|
const https = require('https');
|
|
12
11
|
const http = require('http');
|
|
13
12
|
const { createWriteStream } = require('fs');
|
|
14
13
|
const AdmZip = require('adm-zip');
|
|
14
|
+
const { HOME_DIR } = require('../../config/paths');
|
|
15
15
|
|
|
16
16
|
// 缓存有效期(5分钟)
|
|
17
17
|
const CACHE_TTL = 5 * 60 * 1000;
|
|
@@ -70,7 +70,7 @@ class RepoScannerBase {
|
|
|
70
70
|
this.fileExtension = options.fileExtension || '.md';
|
|
71
71
|
this.defaultRepos = options.defaultRepos || [];
|
|
72
72
|
|
|
73
|
-
this.configDir = path.join(
|
|
73
|
+
this.configDir = path.join(HOME_DIR, '.cc-tool');
|
|
74
74
|
this.reposConfigPath = path.join(this.configDir, `${this.type}-repos.json`);
|
|
75
75
|
this.cachePath = path.join(this.configDir, `${this.type}-cache.json`);
|
|
76
76
|
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const path = require('path');
|
|
19
|
-
const
|
|
19
|
+
const { PATHS } = require('../../config/paths');
|
|
20
20
|
|
|
21
|
-
const CC_TOOL_DIR =
|
|
21
|
+
const CC_TOOL_DIR = PATHS.base;
|
|
22
22
|
|
|
23
23
|
function ensureDir(dir) {
|
|
24
24
|
if (!fs.existsSync(dir)) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const crypto = require('crypto');
|
|
4
|
+
const { PATHS } = require('../../config/paths');
|
|
5
5
|
|
|
6
|
-
const SECURITY_DIR =
|
|
6
|
+
const SECURITY_DIR = PATHS.base;
|
|
7
7
|
const SECURITY_FILE = path.join(SECURITY_DIR, 'security.json');
|
|
8
8
|
|
|
9
9
|
const DEFAULT_SECURITY_CONFIG = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const
|
|
3
|
+
const { PATHS } = require('../../config/paths');
|
|
4
4
|
|
|
5
5
|
const PROJECTS_CACHE_TTL = 30 * 1000; // 30s
|
|
6
6
|
const projectsCache = new Map();
|
|
@@ -11,7 +11,7 @@ let hasMessagesPersisted = {};
|
|
|
11
11
|
let hasMessagesPersistTimer = null;
|
|
12
12
|
|
|
13
13
|
function getCcToolDir() {
|
|
14
|
-
return
|
|
14
|
+
return PATHS.base;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function ensureDirExists(dir) {
|