coding-tool-x 3.3.4 → 3.3.5

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 (52) hide show
  1. package/package.json +2 -1
  2. package/src/commands/doctor.js +3 -3
  3. package/src/commands/export-config.js +2 -2
  4. package/src/commands/logs.js +42 -9
  5. package/src/commands/port-config.js +2 -2
  6. package/src/commands/switch.js +2 -2
  7. package/src/config/default.js +4 -1
  8. package/src/config/loader.js +5 -2
  9. package/src/config/paths.js +25 -21
  10. package/src/reset-config.js +3 -5
  11. package/src/server/api/agents.js +2 -3
  12. package/src/server/api/claude-hooks.js +67 -10
  13. package/src/server/api/opencode-sessions.js +2 -2
  14. package/src/server/api/pm2-autostart.js +20 -8
  15. package/src/server/api/proxy.js +2 -3
  16. package/src/server/api/sessions.js +6 -6
  17. package/src/server/index.js +5 -9
  18. package/src/server/services/agents-service.js +6 -3
  19. package/src/server/services/channels.js +6 -7
  20. package/src/server/services/codex-channels.js +4 -1
  21. package/src/server/services/codex-config.js +4 -1
  22. package/src/server/services/codex-settings-manager.js +18 -9
  23. package/src/server/services/commands-service.js +2 -2
  24. package/src/server/services/config-export-service.js +7 -6
  25. package/src/server/services/config-registry-service.js +7 -6
  26. package/src/server/services/config-sync-manager.js +2 -2
  27. package/src/server/services/config-sync-service.js +2 -2
  28. package/src/server/services/env-checker.js +2 -2
  29. package/src/server/services/favorites.js +3 -4
  30. package/src/server/services/gemini-channels.js +4 -4
  31. package/src/server/services/gemini-config.js +2 -2
  32. package/src/server/services/gemini-sessions.js +3 -3
  33. package/src/server/services/gemini-settings-manager.js +5 -5
  34. package/src/server/services/mcp-service.js +7 -4
  35. package/src/server/services/model-detector.js +2 -2
  36. package/src/server/services/opencode-channels.js +5 -5
  37. package/src/server/services/plugins-service.js +3 -4
  38. package/src/server/services/prompts-service.js +7 -4
  39. package/src/server/services/proxy-runtime.js +2 -2
  40. package/src/server/services/repo-scanner-base.js +2 -2
  41. package/src/server/services/request-logger.js +2 -2
  42. package/src/server/services/security-config.js +2 -2
  43. package/src/server/services/session-cache.js +2 -2
  44. package/src/server/services/session-converter.js +9 -4
  45. package/src/server/services/sessions.js +8 -5
  46. package/src/server/services/settings-manager.js +3 -4
  47. package/src/server/services/skill-service.js +5 -5
  48. package/src/server/services/statistics-service.js +2 -2
  49. package/src/server/services/ui-config.js +3 -4
  50. package/src/server/websocket-server.js +2 -2
  51. package/src/utils/home-dir.js +82 -0
  52. package/src/utils/port-helper.js +34 -12
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const chalk = require('chalk');
4
4
  const inquirer = require('inquirer');
5
5
  const { loadConfig } = require('../config/loader');
6
- const { ensureStorageDirMigrated } = require('../config/paths');
6
+ const { PATHS, ensureStorageDirMigrated } = require('../config/paths');
7
7
  const { startWebSocketServer: attachWebSocketServer } = require('./websocket-server');
8
8
  const { isPortInUse, killProcessByPort, waitForPortRelease } = require('../utils/port-helper');
9
9
  const { isProxyConfig } = require('./services/settings-manager');
@@ -266,14 +266,10 @@ async function startServer(port, host = '127.0.0.1', options = {}) {
266
266
  // 自动恢复代理状态
267
267
  function autoRestoreProxies() {
268
268
  const config = loadConfig();
269
- const os = require('os');
270
269
  const fs = require('fs');
271
- const path = require('path');
272
-
273
- const ccToolDir = path.join(os.homedir(), '.cc-tool');
274
270
 
275
271
  // 检查 Claude 代理状态文件
276
- const claudeActiveFile = path.join(ccToolDir, 'active-channel.json');
272
+ const claudeActiveFile = PATHS.activeChannel.claude;
277
273
  if (fs.existsSync(claudeActiveFile)) {
278
274
  console.log(chalk.cyan('\n🔄 检测到 Claude 代理状态文件,正在自动启动...'));
279
275
  const proxyPort = config.ports?.proxy || 20088;
@@ -287,7 +283,7 @@ function autoRestoreProxies() {
287
283
  }
288
284
 
289
285
  // 检查 Codex 代理状态文件
290
- const codexActiveFile = path.join(ccToolDir, 'codex-active-channel.json');
286
+ const codexActiveFile = PATHS.activeChannel.codex;
291
287
  if (fs.existsSync(codexActiveFile)) {
292
288
  console.log(chalk.cyan('\n🔄 检测到 Codex 代理状态文件,正在自动启动...'));
293
289
  const codexProxyPort = config.ports?.codexProxy || 20089;
@@ -312,7 +308,7 @@ function autoRestoreProxies() {
312
308
  }
313
309
 
314
310
  // 检查 Gemini 代理状态文件
315
- const geminiActiveFile = path.join(ccToolDir, 'gemini-active-channel.json');
311
+ const geminiActiveFile = PATHS.activeChannel.gemini;
316
312
  if (fs.existsSync(geminiActiveFile)) {
317
313
  console.log(chalk.cyan('\n🔄 检测到 Gemini 代理状态文件,正在自动启动...'));
318
314
  const geminiProxyPort = config.ports?.geminiProxy || 20090;
@@ -332,7 +328,7 @@ function autoRestoreProxies() {
332
328
  }
333
329
 
334
330
  // 检查 OpenCode 代理状态文件
335
- const opencodeActiveFile = path.join(ccToolDir, 'opencode-active-channel.json');
331
+ const opencodeActiveFile = PATHS.activeChannel.opencode;
336
332
  if (fs.existsSync(opencodeActiveFile)) {
337
333
  console.log(chalk.cyan('\n🔄 检测到 OpenCode 代理状态文件,正在自动启动...'));
338
334
  const opencodeProxyPort = config.ports?.opencodeProxy || 20091;
@@ -12,18 +12,21 @@ const toml = require('toml');
12
12
  const tomlStringify = require('@iarna/toml').stringify;
13
13
  const { RepoScannerBase } = require('./repo-scanner-base');
14
14
  const { NATIVE_PATHS } = require('../../config/paths');
15
+ const { resolvePreferredHomeDir } = require('../../utils/home-dir');
15
16
 
16
17
  // 默认仓库源
17
18
  const DEFAULT_REPOS = [];
18
19
  const SUPPORTED_PLATFORMS = ['claude', 'codex', 'opencode'];
19
20
  const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
20
21
  const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
21
- const CODEX_AGENTS_DIR = path.join(os.homedir(), '.codex', 'agents');
22
+ const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
23
+ const CODEX_AGENTS_DIR = path.join(path.dirname(CODEX_CONFIG_PATH), 'agents');
24
+ const CLAUDE_AGENTS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'agents');
22
25
  const CODEX_CONFIG_MODES = new Set(['none', 'managed', 'custom']);
23
26
 
24
27
  const PLATFORM_CONFIG = {
25
28
  claude: {
26
- userAgentsDir: path.join(os.homedir(), '.claude', 'agents'),
29
+ userAgentsDir: CLAUDE_AGENTS_DIR,
27
30
  projectAgentsDir: (projectPath) => path.join(projectPath, '.claude', 'agents'),
28
31
  repoType: 'agents'
29
32
  },
@@ -206,7 +209,7 @@ function resolveCodexConfigPath(configPath) {
206
209
  if (!normalized) return '';
207
210
 
208
211
  if (normalized.startsWith('~/')) {
209
- return path.join(os.homedir(), normalized.slice(2));
212
+ return path.join(HOME_DIR, normalized.slice(2));
210
213
  }
211
214
 
212
215
  if (path.isAbsolute(normalized)) {
@@ -1,26 +1,25 @@
1
1
  const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
2
  const { isProxyConfig } = require('./settings-manager');
3
+ const { PATHS, NATIVE_PATHS } = require('../../config/paths');
5
4
 
6
5
  function getChannelsFilePath() {
7
- const dir = path.join(os.homedir(), '.cc-tool');
6
+ const dir = PATHS.base;
8
7
  if (!fs.existsSync(dir)) {
9
8
  fs.mkdirSync(dir, { recursive: true });
10
9
  }
11
- return path.join(dir, 'channels.json');
10
+ return PATHS.channels.claude;
12
11
  }
13
12
 
14
13
  function getActiveChannelIdPath() {
15
- const dir = path.join(os.homedir(), '.cc-tool');
14
+ const dir = PATHS.base;
16
15
  if (!fs.existsSync(dir)) {
17
16
  fs.mkdirSync(dir, { recursive: true });
18
17
  }
19
- return path.join(dir, 'active-channel.json');
18
+ return PATHS.activeChannel.claude;
20
19
  }
21
20
 
22
21
  function getClaudeSettingsPath() {
23
- return path.join(os.homedir(), '.claude', 'settings.json');
22
+ return NATIVE_PATHS.claude.settings;
24
23
  }
25
24
 
26
25
  function saveActiveChannelId(channelId) {
@@ -4,9 +4,12 @@ const os = require('os');
4
4
  const crypto = require('crypto');
5
5
  const toml = require('toml');
6
6
  const tomlStringify = require('@iarna/toml').stringify;
7
+ const { resolvePreferredHomeDir } = require('../../utils/home-dir');
7
8
  const { getCodexDir } = require('./codex-config');
8
9
  const { injectEnvToShell, removeEnvFromShell, isProxyConfig } = require('./codex-settings-manager');
9
10
 
11
+ const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
12
+
10
13
  /**
11
14
  * Codex 渠道管理服务(多渠道架构)
12
15
  *
@@ -30,7 +33,7 @@ function normalizeGatewaySourceType(value, fallback = 'codex') {
30
33
 
31
34
  // 获取渠道存储文件路径
32
35
  function getChannelsFilePath() {
33
- const ccToolDir = path.join(os.homedir(), '.cc-tool');
36
+ const ccToolDir = path.join(HOME_DIR, '.cc-tool');
34
37
  if (!fs.existsSync(ccToolDir)) {
35
38
  fs.mkdirSync(ccToolDir, { recursive: true });
36
39
  }
@@ -2,12 +2,15 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
4
  const toml = require('toml');
5
+ const { resolvePreferredHomeDir } = require('../../utils/home-dir');
6
+
7
+ const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
5
8
 
6
9
  /**
7
10
  * 获取 Codex 配置目录
8
11
  */
9
12
  function getCodexDir() {
10
- return path.join(os.homedir(), '.codex');
13
+ return path.join(HOME_DIR, '.codex');
11
14
  }
12
15
 
13
16
  /**
@@ -3,23 +3,26 @@ const path = require('path');
3
3
  const os = require('os');
4
4
  const toml = require('toml');
5
5
  const tomlStringify = require('@iarna/toml').stringify;
6
+ const { resolvePreferredHomeDir, isWindowsLikePlatform } = require('../../utils/home-dir');
7
+
8
+ const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
6
9
 
7
10
  // Codex 配置文件路径
8
11
  function getConfigPath() {
9
- return path.join(os.homedir(), '.codex', 'config.toml');
12
+ return path.join(HOME_DIR, '.codex', 'config.toml');
10
13
  }
11
14
 
12
15
  function getAuthPath() {
13
- return path.join(os.homedir(), '.codex', 'auth.json');
16
+ return path.join(HOME_DIR, '.codex', 'auth.json');
14
17
  }
15
18
 
16
19
  // 备份文件路径
17
20
  function getConfigBackupPath() {
18
- return path.join(os.homedir(), '.codex', 'config.toml.cc-tool-backup');
21
+ return path.join(HOME_DIR, '.codex', 'config.toml.cc-tool-backup');
19
22
  }
20
23
 
21
24
  function getAuthBackupPath() {
22
- return path.join(os.homedir(), '.codex', 'auth.json.cc-tool-backup');
25
+ return path.join(HOME_DIR, '.codex', 'auth.json.cc-tool-backup');
23
26
  }
24
27
 
25
28
  // 检查配置文件是否存在
@@ -74,7 +77,7 @@ function writeFileAtomic(filePath, content) {
74
77
 
75
78
  function normalizeHomePath(filePath) {
76
79
  const normalizedPath = String(filePath || '').replace(/\\/g, '/');
77
- const normalizedHome = os.homedir().replace(/\\/g, '/');
80
+ const normalizedHome = HOME_DIR.replace(/\\/g, '/');
78
81
  if (normalizedPath.startsWith(normalizedHome)) {
79
82
  return `~${normalizedPath.slice(normalizedHome.length)}`;
80
83
  }
@@ -111,11 +114,13 @@ function isPowerShellProfile(filePath) {
111
114
  }
112
115
 
113
116
  function getShellConfigCandidates() {
114
- const homeDir = os.homedir();
117
+ const homeDir = HOME_DIR;
115
118
  const shell = String(process.env.SHELL || '').toLowerCase();
116
119
  const candidates = [];
117
120
 
118
- if (process.platform === 'win32') {
121
+ if (isWindowsLikePlatform(process.platform, process.env)) {
122
+ const oneDriveDir = process.env.OneDrive || process.env.ONEDRIVE || '';
123
+
119
124
  if (shell.includes('zsh')) {
120
125
  candidates.push(path.join(homeDir, '.zshrc'));
121
126
  }
@@ -127,6 +132,10 @@ function getShellConfigCandidates() {
127
132
 
128
133
  candidates.push(path.join(homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'));
129
134
  candidates.push(path.join(homeDir, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'));
135
+ if (oneDriveDir) {
136
+ candidates.push(path.join(oneDriveDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'));
137
+ candidates.push(path.join(oneDriveDir, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'));
138
+ }
130
139
  candidates.push(path.join(homeDir, '.bashrc'));
131
140
  candidates.push(path.join(homeDir, '.profile'));
132
141
  } else if (shell.includes('zsh')) {
@@ -154,7 +163,7 @@ function getShellConfigCandidates() {
154
163
 
155
164
  function getShellReloadCommand(configPath) {
156
165
  if (!configPath) {
157
- return process.platform === 'win32' ? '重启终端' : 'source ~/.zshrc';
166
+ return isWindowsLikePlatform(process.platform, process.env) ? '重启终端' : 'source ~/.zshrc';
158
167
  }
159
168
 
160
169
  const displayPath = normalizeHomePath(configPath);
@@ -176,7 +185,7 @@ function getShellReloadCommand(configPath) {
176
185
  return 'source ~/.profile';
177
186
  }
178
187
 
179
- if (process.platform === 'win32') {
188
+ if (isWindowsLikePlatform(process.platform, process.env)) {
180
189
  return '. $PROFILE';
181
190
  }
182
191
 
@@ -7,7 +7,6 @@
7
7
 
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
- const os = require('os');
11
10
  const { RepoScannerBase } = require('./repo-scanner-base');
12
11
  const { NATIVE_PATHS } = require('../../config/paths');
13
12
  const {
@@ -19,10 +18,11 @@ const {
19
18
  const DEFAULT_REPOS = [];
20
19
  const SUPPORTED_PLATFORMS = ['claude', 'opencode'];
21
20
  const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
21
+ const CLAUDE_COMMANDS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'commands');
22
22
 
23
23
  const PLATFORM_CONFIG = {
24
24
  claude: {
25
- userCommandsDir: path.join(os.homedir(), '.claude', 'commands'),
25
+ userCommandsDir: CLAUDE_COMMANDS_DIR,
26
26
  projectCommandsDir: (projectPath) => path.join(projectPath, '.claude', 'commands'),
27
27
  repoType: 'commands'
28
28
  },
@@ -5,24 +5,25 @@
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
- const os = require('os');
9
8
  const AdmZip = require('adm-zip');
10
9
  const configTemplatesService = require('./config-templates-service');
11
10
  const channelsService = require('./channels');
12
11
  const { AgentsService } = require('./agents-service');
13
12
  const { CommandsService } = require('./commands-service');
14
13
  const { SkillService } = require('./skill-service');
14
+ const { PATHS, NATIVE_PATHS } = require('../../config/paths');
15
15
 
16
16
  const CONFIG_VERSION = '1.2.0';
17
17
  const SKILL_FILE_ENCODING = 'base64';
18
18
  const SKILL_IGNORE_DIRS = new Set(['.git']);
19
19
  const SKILL_IGNORE_FILES = new Set(['.DS_Store']);
20
- const CC_TOOL_DIR = path.join(os.homedir(), '.cc-tool');
21
- const LEGACY_CC_TOOL_DIR = path.join(os.homedir(), '.cc-tool');
22
- const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
20
+ const CC_TOOL_DIR = PATHS.base;
21
+ const LEGACY_CC_TOOL_DIR = PATHS.base;
22
+ const CLAUDE_SETTINGS_PATH = NATIVE_PATHS.claude.settings;
23
23
  const LEGACY_PLUGINS_DIR = path.join(LEGACY_CC_TOOL_DIR, 'plugins', 'installed');
24
24
  const LEGACY_PLUGINS_REGISTRY = path.join(LEGACY_CC_TOOL_DIR, 'plugins', 'registry.json');
25
- const NATIVE_PLUGINS_REGISTRY = path.join(os.homedir(), '.claude', 'plugins', 'installed_plugins.json');
25
+ const CLAUDE_PLUGINS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'plugins');
26
+ const NATIVE_PLUGINS_REGISTRY = path.join(CLAUDE_PLUGINS_DIR, 'installed_plugins.json');
26
27
  const PLUGIN_IGNORE_DIRS = new Set(['.git', 'node_modules', '.DS_Store']);
27
28
  const PLUGIN_IGNORE_FILES = new Set(['.DS_Store']);
28
29
  const PLUGIN_SENSITIVE_PATTERNS = [
@@ -799,7 +800,7 @@ async function importConfigs(importData, options = {}) {
799
800
  results.plugins.failed++;
800
801
  continue;
801
802
  }
802
- targetDir = path.join(os.homedir(), '.claude', 'plugins', pluginId);
803
+ targetDir = path.join(CLAUDE_PLUGINS_DIR, pluginId);
803
804
  registryPath = NATIVE_PLUGINS_REGISTRY;
804
805
  } else {
805
806
  console.warn(`[ConfigImport] Unknown plugin type: ${pluginType}`);
@@ -9,19 +9,20 @@
9
9
 
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
- const os = require('os');
12
+ const { PATHS, NATIVE_PATHS } = require('../../config/paths');
13
13
 
14
14
  // Configuration paths
15
- const CC_TOOL_DIR = path.join(os.homedir(), '.cc-tool');
15
+ const CC_TOOL_DIR = PATHS.base;
16
16
  const REGISTRY_FILE = path.join(CC_TOOL_DIR, 'config-registry.json');
17
17
  const CONFIGS_DIR = path.join(CC_TOOL_DIR, 'configs');
18
18
 
19
19
  // Claude Code native directories
20
+ const CLAUDE_HOME_DIR = path.dirname(NATIVE_PATHS.claude.settings);
20
21
  const CLAUDE_DIRS = {
21
- skills: path.join(os.homedir(), '.claude', 'skills'),
22
- commands: path.join(os.homedir(), '.claude', 'commands'),
23
- agents: path.join(os.homedir(), '.claude', 'agents'),
24
- plugins: path.join(os.homedir(), '.claude', 'plugins')
22
+ skills: path.join(CLAUDE_HOME_DIR, 'skills'),
23
+ commands: path.join(CLAUDE_HOME_DIR, 'commands'),
24
+ agents: path.join(CLAUDE_HOME_DIR, 'agents'),
25
+ plugins: path.join(CLAUDE_HOME_DIR, 'plugins')
25
26
  };
26
27
 
27
28
  // Valid config types
@@ -20,10 +20,10 @@ const os = require('os');
20
20
  const toml = require('toml');
21
21
  const tomlStringify = require('@iarna/toml').stringify;
22
22
  const { convertSkillToCodex, convertCommandToCodex } = require('./format-converter');
23
- const { PATHS, NATIVE_PATHS, ensureStorageDirMigrated } = require('../../config/paths');
23
+ const { PATHS, NATIVE_PATHS, HOME_DIR, ensureStorageDirMigrated } = require('../../config/paths');
24
24
 
25
25
  // Paths
26
- const HOME = os.homedir();
26
+ const HOME = HOME_DIR || os.homedir();
27
27
  const CC_TOOL_CONFIGS = path.join(PATHS.base, 'configs');
28
28
  const CLAUDE_CODE_DIR = path.join(HOME, '.claude');
29
29
  const CODEX_DIR = path.join(HOME, '.codex');
@@ -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
  // 检查配置文件是否存在
@@ -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'),
@@ -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
  }