coding-tool-x 3.3.3 → 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 (69) hide show
  1. package/dist/web/assets/{Analytics-DtR00OYP.js → Analytics-B6CWdkhx.js} +1 -1
  2. package/dist/web/assets/{ConfigTemplates-DWiSFOp5.js → ConfigTemplates-BW6LEgd8.js} +1 -1
  3. package/dist/web/assets/{Home-DUu2mGb6.js → Home-B2B2gS2-.js} +1 -1
  4. package/dist/web/assets/{PluginManager-DsJ1KtNr.js → PluginManager-Bqc7ldY-.js} +1 -1
  5. package/dist/web/assets/{ProjectList-CzTJaBJb.js → ProjectList-BFdZZm_8.js} +1 -1
  6. package/dist/web/assets/{SessionList-D1ovPZ0I.js → SessionList-B_Tp37kM.js} +1 -1
  7. package/dist/web/assets/{SkillManager-DqpDTc2c.js → SkillManager-ul2rcS3o.js} +1 -1
  8. package/dist/web/assets/{WorkspaceManager-Dj28-3G5.js → WorkspaceManager-Dp5Jvdtu.js} +1 -1
  9. package/dist/web/assets/index-CSBDZxYn.js +2 -0
  10. package/dist/web/assets/index-DxRneGyu.css +1 -0
  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/config/default.js +4 -1
  19. package/src/config/loader.js +5 -2
  20. package/src/config/paths.js +25 -21
  21. package/src/reset-config.js +3 -5
  22. package/src/server/api/agents.js +2 -3
  23. package/src/server/api/claude-hooks.js +92 -12
  24. package/src/server/api/codex-sessions.js +6 -5
  25. package/src/server/api/opencode-sessions.js +2 -2
  26. package/src/server/api/pm2-autostart.js +20 -8
  27. package/src/server/api/proxy.js +2 -3
  28. package/src/server/api/sessions.js +42 -12
  29. package/src/server/index.js +5 -9
  30. package/src/server/opencode-proxy-server.js +11 -1
  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-parser.js +31 -4
  36. package/src/server/services/codex-settings-manager.js +18 -9
  37. package/src/server/services/commands-service.js +2 -2
  38. package/src/server/services/config-export-service.js +7 -6
  39. package/src/server/services/config-registry-service.js +7 -6
  40. package/src/server/services/config-sync-manager.js +2 -2
  41. package/src/server/services/config-sync-service.js +2 -2
  42. package/src/server/services/env-checker.js +2 -2
  43. package/src/server/services/favorites.js +3 -4
  44. package/src/server/services/gemini-channels.js +4 -4
  45. package/src/server/services/gemini-config.js +2 -2
  46. package/src/server/services/gemini-sessions.js +3 -3
  47. package/src/server/services/gemini-settings-manager.js +5 -5
  48. package/src/server/services/mcp-service.js +7 -4
  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/opencode-sessions.js +28 -3
  52. package/src/server/services/plugins-service.js +3 -4
  53. package/src/server/services/prompts-service.js +7 -4
  54. package/src/server/services/proxy-runtime.js +2 -2
  55. package/src/server/services/repo-scanner-base.js +2 -2
  56. package/src/server/services/request-logger.js +2 -2
  57. package/src/server/services/security-config.js +2 -2
  58. package/src/server/services/session-cache.js +2 -2
  59. package/src/server/services/session-converter.js +9 -4
  60. package/src/server/services/sessions.js +8 -5
  61. package/src/server/services/settings-manager.js +3 -4
  62. package/src/server/services/skill-service.js +5 -5
  63. package/src/server/services/statistics-service.js +2 -2
  64. package/src/server/services/ui-config.js +3 -4
  65. package/src/server/websocket-server.js +2 -2
  66. package/src/utils/home-dir.js +82 -0
  67. package/src/utils/port-helper.js +34 -12
  68. package/dist/web/assets/index-CaKktouI.js +0 -2
  69. package/dist/web/assets/index-DZjDFGqR.css +0 -1
@@ -2125,6 +2125,16 @@ function sendChatCompletionsSse(res, responseObject) {
2125
2125
  const message = responseObject?.choices?.[0]?.message || {};
2126
2126
  const text = message?.content || '';
2127
2127
  const toolCalls = Array.isArray(message?.tool_calls) ? message.tool_calls : [];
2128
+ const streamedToolCalls = toolCalls.map((toolCall, index) => {
2129
+ const numericIndex = Number(toolCall?.index);
2130
+ const normalizedIndex = Number.isFinite(numericIndex) ? numericIndex : index;
2131
+
2132
+ if (toolCall && typeof toolCall === 'object' && !Array.isArray(toolCall)) {
2133
+ return { ...toolCall, index: normalizedIndex };
2134
+ }
2135
+
2136
+ return { index: normalizedIndex };
2137
+ });
2128
2138
  const finishReason = responseObject?.choices?.[0]?.finish_reason || 'stop';
2129
2139
 
2130
2140
  setSseHeaders(res);
@@ -2140,7 +2150,7 @@ function sendChatCompletionsSse(res, responseObject) {
2140
2150
  delta: {
2141
2151
  role: 'assistant',
2142
2152
  ...(text ? { content: text } : {}),
2143
- ...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {})
2153
+ ...(streamedToolCalls.length > 0 ? { tool_calls: streamedToolCalls } : {})
2144
2154
  },
2145
2155
  finish_reason: null
2146
2156
  }
@@ -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
  /**
@@ -63,11 +63,22 @@ function extractSessionMeta(lines) {
63
63
  */
64
64
  function extractMessages(lines) {
65
65
  const messages = [];
66
+ let currentTurnModel = null;
67
+ let lastAssistantModel = null;
66
68
 
67
69
  lines.forEach(line => {
70
+ if (line.type === 'turn_context') {
71
+ const model = line.payload?.model;
72
+ if (typeof model === 'string' && model.trim()) {
73
+ currentTurnModel = model.trim();
74
+ }
75
+ return;
76
+ }
77
+
68
78
  if (line.type !== 'response_item') return;
69
79
 
70
80
  const payload = line.payload;
81
+ const resolvedModel = payload?.model || currentTurnModel || lastAssistantModel || null;
71
82
 
72
83
  // 用户/助手消息
73
84
  if (payload.type === 'message') {
@@ -81,8 +92,12 @@ function extractMessages(lines) {
81
92
  messages.push({
82
93
  role: payload.role,
83
94
  content: text,
84
- timestamp: line.timestamp
95
+ timestamp: line.timestamp,
96
+ model: payload.role === 'assistant' ? resolvedModel : null
85
97
  });
98
+ if (payload.role === 'assistant' && resolvedModel) {
99
+ lastAssistantModel = resolvedModel;
100
+ }
86
101
  }
87
102
  }
88
103
 
@@ -101,8 +116,12 @@ function extractMessages(lines) {
101
116
  name: payload.name,
102
117
  arguments: parsedArguments,
103
118
  callId: payload.call_id,
104
- timestamp: line.timestamp
119
+ timestamp: line.timestamp,
120
+ model: resolvedModel
105
121
  });
122
+ if (resolvedModel) {
123
+ lastAssistantModel = resolvedModel;
124
+ }
106
125
  }
107
126
 
108
127
  // 工具输出
@@ -119,8 +138,12 @@ function extractMessages(lines) {
119
138
  role: 'tool_output',
120
139
  callId: payload.call_id,
121
140
  output: parsedOutput,
122
- timestamp: line.timestamp
141
+ timestamp: line.timestamp,
142
+ model: resolvedModel
123
143
  });
144
+ if (resolvedModel) {
145
+ lastAssistantModel = resolvedModel;
146
+ }
124
147
  }
125
148
 
126
149
  // 推理内容
@@ -135,8 +158,12 @@ function extractMessages(lines) {
135
158
  messages.push({
136
159
  role: 'reasoning',
137
160
  content: text,
138
- timestamp: line.timestamp
161
+ timestamp: line.timestamp,
162
+ model: resolvedModel
139
163
  });
164
+ if (resolvedModel) {
165
+ lastAssistantModel = resolvedModel;
166
+ }
140
167
  }
141
168
  }
142
169
  });
@@ -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
  // 读取所有渠道