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.
Files changed (66) hide show
  1. package/dist/web/assets/{Analytics-B6CWdkhx.js → Analytics-TtaduRqL.js} +1 -1
  2. package/dist/web/assets/{ConfigTemplates-BW6LEgd8.js → ConfigTemplates-BP2lLBMN.js} +1 -1
  3. package/dist/web/assets/{Home-B2B2gS2-.js → Home-CbbyopS-.js} +1 -1
  4. package/dist/web/assets/{PluginManager-Bqc7ldY-.js → PluginManager-HmISlyMK.js} +1 -1
  5. package/dist/web/assets/{ProjectList-BFdZZm_8.js → ProjectList-DoN8Hjbu.js} +1 -1
  6. package/dist/web/assets/{SessionList-B_Tp37kM.js → SessionList-Da8BYzNi.js} +1 -1
  7. package/dist/web/assets/{SkillManager-ul2rcS3o.js → SkillManager-DqLAXh9o.js} +1 -1
  8. package/dist/web/assets/{WorkspaceManager-Dp5Jvdtu.js → WorkspaceManager-B_TxOgPW.js} +1 -1
  9. package/dist/web/assets/{index-CSBDZxYn.js → index-By3mDEvx.js} +2 -2
  10. package/dist/web/assets/{index-DxRneGyu.css → index-CsWInMQV.css} +1 -1
  11. package/dist/web/index.html +2 -2
  12. package/package.json +2 -1
  13. package/src/commands/doctor.js +3 -3
  14. package/src/commands/export-config.js +2 -2
  15. package/src/commands/logs.js +42 -9
  16. package/src/commands/port-config.js +2 -2
  17. package/src/commands/switch.js +2 -2
  18. package/src/commands/update.js +21 -6
  19. package/src/config/default.js +4 -1
  20. package/src/config/loader.js +5 -2
  21. package/src/config/paths.js +25 -21
  22. package/src/reset-config.js +3 -5
  23. package/src/server/api/agents.js +2 -3
  24. package/src/server/api/claude-hooks.js +67 -10
  25. package/src/server/api/config-export.js +21 -2
  26. package/src/server/api/opencode-sessions.js +2 -2
  27. package/src/server/api/pm2-autostart.js +20 -8
  28. package/src/server/api/proxy.js +2 -3
  29. package/src/server/api/sessions.js +6 -6
  30. package/src/server/index.js +5 -9
  31. package/src/server/services/agents-service.js +6 -3
  32. package/src/server/services/channels.js +6 -7
  33. package/src/server/services/codex-channels.js +4 -1
  34. package/src/server/services/codex-config.js +4 -1
  35. package/src/server/services/codex-settings-manager.js +18 -9
  36. package/src/server/services/commands-service.js +2 -2
  37. package/src/server/services/config-export-service.js +96 -28
  38. package/src/server/services/config-registry-service.js +7 -6
  39. package/src/server/services/config-sync-manager.js +2 -2
  40. package/src/server/services/config-sync-service.js +2 -2
  41. package/src/server/services/env-checker.js +2 -2
  42. package/src/server/services/favorites.js +3 -4
  43. package/src/server/services/gemini-channels.js +4 -4
  44. package/src/server/services/gemini-config.js +2 -2
  45. package/src/server/services/gemini-sessions.js +3 -3
  46. package/src/server/services/gemini-settings-manager.js +5 -5
  47. package/src/server/services/mcp-client.js +101 -14
  48. package/src/server/services/mcp-service.js +98 -8
  49. package/src/server/services/model-detector.js +2 -2
  50. package/src/server/services/opencode-channels.js +5 -5
  51. package/src/server/services/plugins-service.js +3 -4
  52. package/src/server/services/prompts-service.js +7 -4
  53. package/src/server/services/proxy-runtime.js +2 -2
  54. package/src/server/services/repo-scanner-base.js +2 -2
  55. package/src/server/services/request-logger.js +2 -2
  56. package/src/server/services/security-config.js +2 -2
  57. package/src/server/services/session-cache.js +2 -2
  58. package/src/server/services/session-converter.js +9 -4
  59. package/src/server/services/sessions.js +8 -5
  60. package/src/server/services/settings-manager.js +3 -4
  61. package/src/server/services/skill-service.js +5 -5
  62. package/src/server/services/statistics-service.js +2 -2
  63. package/src/server/services/ui-config.js +3 -4
  64. package/src/server/websocket-server.js +2 -2
  65. package/src/utils/home-dir.js +82 -0
  66. 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 os = require('os');
18
+ const { HOME_DIR } = require('../../config/paths');
19
19
 
20
20
  // 全局配置目录
21
- const GLOBAL_CONFIG_DIR = path.join(os.homedir(), '.claude');
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 = os.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 path = require('path');
3
- const os = require('os');
2
+ const { PATHS } = require('../../config/paths');
4
3
 
5
- const FAVORITES_DIR = path.join(os.homedir(), '.cc-tool');
6
- const FAVORITES_FILE = path.join(FAVORITES_DIR, 'favorites.json');
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.join(os.homedir(), '.gemini');
29
+ return path.dirname(NATIVE_PATHS.gemini.env);
30
30
  }
31
31
 
32
32
  // 获取渠道存储文件路径
33
33
  function getChannelsFilePath() {
34
- const ccToolDir = path.join(os.homedir(), '.cc-tool');
34
+ const ccToolDir = PATHS.base;
35
35
  if (!fs.existsSync(ccToolDir)) {
36
36
  fs.mkdirSync(ccToolDir, { recursive: true });
37
37
  }
38
- return path.join(ccToolDir, 'gemini-channels.json');
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 os = require('os');
3
+ const { NATIVE_PATHS } = require('../../config/paths');
4
4
 
5
5
  /**
6
6
  * 获取 Gemini 配置目录
7
7
  */
8
8
  function getGeminiDir() {
9
- return path.join(os.homedir(), '.gemini');
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 os = require('os');
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 = os.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 === os.homedir()) {
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 os = require('os');
3
+ const { NATIVE_PATHS } = require('../../config/paths');
4
4
 
5
5
  // Gemini 配置文件路径
6
6
  function getEnvPath() {
7
- return path.join(os.homedir(), '.gemini', '.env');
7
+ return NATIVE_PATHS.gemini.env;
8
8
  }
9
9
 
10
10
  function getSettingsPath() {
11
- return path.join(os.homedir(), '.gemini', 'settings.json');
11
+ return path.join(path.dirname(NATIVE_PATHS.gemini.env), 'settings.json');
12
12
  }
13
13
 
14
14
  // 备份文件路径
15
15
  function getEnvBackupPath() {
16
- return path.join(os.homedir(), '.gemini', '.env.cc-tool-backup');
16
+ return NATIVE_PATHS.gemini.envBackup;
17
17
  }
18
18
 
19
19
  function getSettingsBackupPath() {
20
- return path.join(os.homedir(), '.gemini', 'settings.json.cc-tool-backup');
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
- try {
241
- // 确保 PATH 不被覆盖,优先使用用户提供的 env,但保留 PATH
242
- const mergedEnv = { ...process.env, ...env };
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
- this._child = spawn(command, args, {
329
+ try {
330
+ this._child = spawn(resolvedCommand, args, {
249
331
  env: mergedEnv,
250
332
  stdio: ['pipe', 'pipe', 'pipe'],
251
- cwd: cwd || process.cwd()
333
+ cwd: finalCwd
252
334
  });
253
335
  } catch (err) {
254
336
  clearTimeout(timer);
255
- throw new McpClientError(`Failed to spawn "${command}": ${err.message}`);
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 pathHint = mergedEnv.PATH
278
- ? `\n Current PATH: ${mergedEnv.PATH.split(':').slice(0, 5).join(':')}\n (showing first 5 entries)`
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 "${command}" not found. Please check:\n` +
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(os.homedir(), '.cc-tool');
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(os.homedir(), '.claude.json');
23
- const CODEX_CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml');
24
- const GEMINI_CONFIG_PATH = path.join(os.homedir(), '.gemini', 'settings.json');
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(command, args, {
1261
- env: { ...process.env, ...spec.env },
1349
+ child = spawn(resolvedCommand, args, {
1350
+ env: mergedEnv,
1262
1351
  stdio: ['pipe', 'pipe', 'pipe'],
1263
- cwd: spec.cwd || process.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: `命令 "${command}" 未找到,请确保已安装`,
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(os.homedir(), '.cc-tool');
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 = path.join(os.homedir(), '.cc-tool');
44
+ const ccToolDir = PATHS.base;
45
45
  if (!fs.existsSync(ccToolDir)) {
46
46
  fs.mkdirSync(ccToolDir, { recursive: true });
47
47
  }
48
- return path.join(ccToolDir, 'opencode-channels.json');
48
+ return PATHS.channels.opencode;
49
49
  }
50
50
 
51
51
  function getCodexChannelsFilePath() {
52
- const ccToolDir = path.join(os.homedir(), '.cc-tool');
52
+ const ccToolDir = PATHS.base;
53
53
  if (!fs.existsSync(ccToolDir)) {
54
54
  fs.mkdirSync(ccToolDir, { recursive: true });
55
55
  }
56
- return path.join(ccToolDir, 'codex-channels.json');
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(os.homedir(), '.claude', 'plugins');
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(os.homedir(), '.cc-tool');
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(os.homedir(), '.cc-tool');
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(os.homedir(), '.claude', 'CLAUDE.md');
18
- const CODEX_PROMPT_PATH = path.join(os.homedir(), '.codex', 'AGENTS.md');
19
- const GEMINI_PROMPT_PATH = path.join(os.homedir(), '.gemini', 'GEMINI.md');
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 os = require('os');
3
+ const { PATHS } = require('../../config/paths');
4
4
 
5
5
  function getRuntimeFilePath(proxyType) {
6
- const ccToolDir = path.join(os.homedir(), '.cc-tool');
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(os.homedir(), '.cc-tool');
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 os = require('os');
19
+ const { PATHS } = require('../../config/paths');
20
20
 
21
- const CC_TOOL_DIR = path.join(os.homedir(), '.cc-tool');
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 = path.join(os.homedir(), '.cc-tool');
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 os = require('os');
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 path.join(os.homedir(), '.cc-tool');
14
+ return PATHS.base;
15
15
  }
16
16
 
17
17
  function ensureDirExists(dir) {