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
@@ -3,11 +3,18 @@ const { exec } = require('child_process');
3
3
  const { promisify } = require('util');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
- const os = require('os');
7
6
  const pm2 = require('pm2');
7
+ const { HOME_DIR } = require('../../config/paths');
8
8
 
9
9
  const execAsync = promisify(exec);
10
10
 
11
+ function getExecOptions(timeout = 30000, runtimePlatform = process.platform) {
12
+ if (runtimePlatform === 'win32') {
13
+ return { timeout };
14
+ }
15
+ return { shell: '/bin/bash', timeout };
16
+ }
17
+
11
18
  /**
12
19
  * Check if PM2 autostart is enabled
13
20
  * by looking for PM2 startup script in system
@@ -18,7 +25,7 @@ async function checkAutoStartStatus() {
18
25
 
19
26
  if (platform === 'darwin') {
20
27
  // macOS - check for LaunchDaemon
21
- const launchDaemonsPath = path.join(os.homedir(), 'Library/LaunchDaemons');
28
+ const launchDaemonsPath = path.join(HOME_DIR, 'Library/LaunchDaemons');
22
29
  const pm2Files = fs.existsSync(launchDaemonsPath)
23
30
  ? fs.readdirSync(launchDaemonsPath).filter(f => f.includes('pm2'))
24
31
  : [];
@@ -27,11 +34,11 @@ async function checkAutoStartStatus() {
27
34
  } else if (platform === 'linux') {
28
35
  // Linux - check for systemd service
29
36
  const systemdPath = '/etc/systemd/system/pm2-root.service';
30
- const userSystemdPath = path.join(os.homedir(), '.config/systemd/user/pm2-*.service');
37
+ const userSystemdPath = path.join(HOME_DIR, '.config/systemd/user/pm2-*.service');
31
38
 
32
39
  const rootExists = fs.existsSync(systemdPath);
33
- const userExists = fs.existsSync(path.join(os.homedir(), '.config/systemd/user')) &&
34
- fs.readdirSync(path.join(os.homedir(), '.config/systemd/user')).some(f => f.includes('pm2'));
40
+ const userExists = fs.existsSync(path.join(HOME_DIR, '.config/systemd/user')) &&
41
+ fs.readdirSync(path.join(HOME_DIR, '.config/systemd/user')).some(f => f.includes('pm2'));
35
42
 
36
43
  return { enabled: rootExists || userExists, platform: 'linux' };
37
44
  } else if (platform === 'win32') {
@@ -104,7 +111,7 @@ async function enableAutoStart() {
104
111
 
105
112
  console.log(`Running startup command: ${command}`);
106
113
 
107
- exec(command, { shell: '/bin/bash', timeout: 30000 }, (execErr, stdout, stderr) => {
114
+ exec(command, getExecOptions(30000), (execErr, stdout, stderr) => {
108
115
  pm2.disconnect();
109
116
 
110
117
  if (execErr) {
@@ -163,7 +170,7 @@ async function disableAutoStart() {
163
170
 
164
171
  console.log(`Running unstartup command: ${command}`);
165
172
 
166
- exec(command, { shell: '/bin/bash', timeout: 30000 }, (execErr, stdout, stderr) => {
173
+ exec(command, getExecOptions(30000), (execErr, stdout, stderr) => {
167
174
  pm2.disconnect();
168
175
 
169
176
  if (execErr) {
@@ -195,7 +202,7 @@ async function disableAutoStart() {
195
202
  });
196
203
  }
197
204
 
198
- module.exports = () => {
205
+ function createPm2AutostartRouter() {
199
206
  const router = express.Router();
200
207
 
201
208
  /**
@@ -266,4 +273,9 @@ module.exports = () => {
266
273
  });
267
274
 
268
275
  return router;
276
+ }
277
+
278
+ module.exports = createPm2AutostartRouter;
279
+ module.exports._test = {
280
+ getExecOptions
269
281
  };
@@ -12,10 +12,9 @@ const {
12
12
  } = require('../services/settings-manager');
13
13
  const { getAllChannels } = require('../services/channels');
14
14
  const { clearAllLogs } = require('../websocket-server');
15
- const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
15
+ const { PATHS, NATIVE_PATHS, ensureStorageDirMigrated } = require('../../config/paths');
16
16
  const fs = require('fs');
17
17
  const path = require('path');
18
- const os = require('os');
19
18
 
20
19
  function sanitizeChannelForResponse(channel) {
21
20
  if (!channel) return null;
@@ -285,7 +284,7 @@ router.post('/stop', async (req, res) => {
285
284
 
286
285
  // 3. 删除备份文件和active-channel.json
287
286
  if (hasBackup()) {
288
- const backupPath = path.join(os.homedir(), '.claude', 'settings.json.cc-tool-backup');
287
+ const backupPath = NATIVE_PATHS.claude.settingsBackup;
289
288
  if (fs.existsSync(backupPath)) {
290
289
  fs.unlinkSync(backupPath);
291
290
  console.log('✅ Removed backup file');
@@ -2,11 +2,12 @@ const express = require('express');
2
2
  const router = express.Router();
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
- const os = require('os');
6
5
  const readline = require('readline');
7
6
  const { getSessionsForProject, deleteSession, forkSession, saveSessionOrder, parseRealProjectPath, searchSessions, getRecentSessions, searchSessionsAcrossProjects, hasActualMessages } = require('../services/sessions');
8
7
  const { loadAliases } = require('../services/alias');
9
8
  const { broadcastLog } = require('../websocket-server');
9
+ const { NATIVE_PATHS } = require('../../config/paths');
10
+ const CLAUDE_PROJECTS_DIR = NATIVE_PATHS.claude.projects;
10
11
 
11
12
  module.exports = (config) => {
12
13
  // GET /api/sessions/search/global - Search sessions across all projects
@@ -120,12 +121,12 @@ module.exports = (config) => {
120
121
  const year = now.getFullYear();
121
122
  const month = String(now.getMonth() + 1).padStart(2, '0');
122
123
  const day = String(now.getDate()).padStart(2, '0');
123
- sessionDir = path.join(os.homedir(), '.codex', 'sessions', String(year), month, day);
124
+ sessionDir = path.join(NATIVE_PATHS.codex.sessions, String(year), month, day);
124
125
  sessionFile = path.join(sessionDir, `${newSessionId}.jsonl`);
125
126
  } else if (toolType === 'gemini') {
126
127
  // Gemini: ~/.gemini/tmp/{hash}/chats/{sessionId}.json
127
128
  const pathHash = crypto.createHash('sha256').update(fullPath).digest('hex');
128
- sessionDir = path.join(os.homedir(), '.gemini', 'tmp', pathHash, 'chats');
129
+ sessionDir = path.join(NATIVE_PATHS.gemini.tmp, pathHash, 'chats');
129
130
  sessionFile = path.join(sessionDir, `${newSessionId}.json`);
130
131
  } else {
131
132
  return res.status(400).json({ error: 'Invalid toolType. Must be claude, codex, or gemini' });
@@ -243,7 +244,7 @@ module.exports = (config) => {
243
244
  let sessionFile = null;
244
245
  const possiblePaths = [
245
246
  path.join(fullPath, '.claude', 'sessions', sessionId + '.jsonl'),
246
- path.join(os.homedir(), '.claude', 'projects', projectName, sessionId + '.jsonl')
247
+ path.join(CLAUDE_PROJECTS_DIR, projectName, sessionId + '.jsonl')
247
248
  ];
248
249
 
249
250
  console.log(`[Messages API] Trying paths:`, possiblePaths);
@@ -423,7 +424,6 @@ module.exports = (config) => {
423
424
  const { projectName, sessionId } = req.params;
424
425
  const path = require('path');
425
426
  const fs = require('fs');
426
- const os = require('os');
427
427
 
428
428
  // Parse real project path (important for cross-project sessions)
429
429
  const { fullPath } = parseRealProjectPath(projectName);
@@ -436,7 +436,7 @@ module.exports = (config) => {
436
436
  const possiblePaths = [
437
437
  projectSessionFile,
438
438
  // Location 2: User's .claude/projects directory (ClaudeCode default)
439
- path.join(os.homedir(), '.claude', 'projects', projectName, sessionId + '.jsonl')
439
+ path.join(CLAUDE_PROJECTS_DIR, projectName, sessionId + '.jsonl')
440
440
  ];
441
441
 
442
442
  for (const testPath of possiblePaths) {
@@ -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,28 @@
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');
11
+ const codexChannelsService = require('./codex-channels');
12
+ const geminiChannelsService = require('./gemini-channels');
13
+ const opencodeChannelsService = require('./opencode-channels');
12
14
  const { AgentsService } = require('./agents-service');
13
15
  const { CommandsService } = require('./commands-service');
14
16
  const { SkillService } = require('./skill-service');
17
+ const { PATHS, NATIVE_PATHS } = require('../../config/paths');
15
18
 
16
19
  const CONFIG_VERSION = '1.2.0';
17
20
  const SKILL_FILE_ENCODING = 'base64';
18
21
  const SKILL_IGNORE_DIRS = new Set(['.git']);
19
22
  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');
23
+ const CC_TOOL_DIR = PATHS.base;
24
+ const LEGACY_CC_TOOL_DIR = PATHS.base;
25
+ const CLAUDE_SETTINGS_PATH = NATIVE_PATHS.claude.settings;
23
26
  const LEGACY_PLUGINS_DIR = path.join(LEGACY_CC_TOOL_DIR, 'plugins', 'installed');
24
27
  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');
28
+ const CLAUDE_PLUGINS_DIR = path.join(path.dirname(NATIVE_PATHS.claude.settings), 'plugins');
29
+ const NATIVE_PLUGINS_REGISTRY = path.join(CLAUDE_PLUGINS_DIR, 'installed_plugins.json');
26
30
  const PLUGIN_IGNORE_DIRS = new Set(['.git', 'node_modules', '.DS_Store']);
27
31
  const PLUGIN_IGNORE_FILES = new Set(['.DS_Store']);
28
32
  const PLUGIN_SENSITIVE_PATTERNS = [
@@ -413,6 +417,14 @@ function writeTextFile(baseDir, relativePath, content, overwrite) {
413
417
  return 'success';
414
418
  }
415
419
 
420
+ function getAllChannelsByType() {
421
+ const claude = channelsService.getAllChannels() || [];
422
+ const codex = codexChannelsService.getChannels()?.channels || [];
423
+ const gemini = geminiChannelsService.getChannels()?.channels || [];
424
+ const opencode = opencodeChannelsService.getChannels()?.channels || [];
425
+ return { claude, codex, gemini, opencode };
426
+ }
427
+
416
428
  /**
417
429
  * 导出所有配置为JSON
418
430
  * @returns {Object} 配置导出对象
@@ -423,8 +435,9 @@ function exportAllConfigs() {
423
435
  const allConfigTemplates = configTemplatesService.getAllTemplates();
424
436
  const customConfigTemplates = allConfigTemplates.filter(t => !t.isBuiltin);
425
437
 
426
- // 获取所有频道配置
427
- const channels = channelsService.getAllChannels() || [];
438
+ // 获取所有频道配置(向后兼容:channels 仍保留 Claude 渠道)
439
+ const channelsByType = getAllChannelsByType();
440
+ const channels = channelsByType.claude || [];
428
441
 
429
442
  // 获取工作区配置
430
443
  const workspaceService = require('./workspace-service');
@@ -513,6 +526,7 @@ function exportAllConfigs() {
513
526
  data: {
514
527
  configTemplates: customConfigTemplates,
515
528
  channels: channels || [],
529
+ channelsByType,
516
530
  workspaces: workspaces || { workspaces: [] },
517
531
  favorites: favorites || { favorites: [] },
518
532
  agents: agents || [],
@@ -601,6 +615,7 @@ async function importConfigs(importData, options = {}) {
601
615
  const {
602
616
  configTemplates = [],
603
617
  channels = [],
618
+ channelsByType = null,
604
619
  workspaces = null,
605
620
  favorites = null,
606
621
  agents = [],
@@ -615,6 +630,16 @@ async function importConfigs(importData, options = {}) {
615
630
  claudeHooks = null
616
631
  } = importData.data;
617
632
 
633
+ const hasTypedChannels = channelsByType && typeof channelsByType === 'object';
634
+ const importChannelsByType = {
635
+ claude: hasTypedChannels && Array.isArray(channelsByType.claude)
636
+ ? channelsByType.claude
637
+ : (Array.isArray(channels) ? channels : []),
638
+ codex: hasTypedChannels && Array.isArray(channelsByType.codex) ? channelsByType.codex : [],
639
+ gemini: hasTypedChannels && Array.isArray(channelsByType.gemini) ? channelsByType.gemini : [],
640
+ opencode: hasTypedChannels && Array.isArray(channelsByType.opencode) ? channelsByType.opencode : []
641
+ };
642
+
618
643
  // 导入配置模板
619
644
  for (const template of configTemplates) {
620
645
  try {
@@ -642,29 +667,72 @@ async function importConfigs(importData, options = {}) {
642
667
  }
643
668
  }
644
669
 
645
- // 导入频道配置
646
- for (const channel of channels) {
647
- try {
648
- const existingChannels = channelsService.getAllChannels() || [];
649
- const existing = existingChannels.find(c => c.id === channel.id);
650
-
651
- if (existing && !overwrite) {
652
- results.channels.skipped++;
653
- continue;
654
- }
670
+ // 导入频道配置(兼容旧结构 channels 和新结构 channelsByType)
671
+ const importTypedChannels = (type, service, createChannel, findExisting = null) => {
672
+ const sourceChannels = importChannelsByType[type];
673
+ for (const channel of sourceChannels) {
674
+ try {
675
+ const existingChannels = service.getChannels
676
+ ? (service.getChannels()?.channels || [])
677
+ : (service.getAllChannels?.() || []);
678
+ const existing = typeof findExisting === 'function'
679
+ ? findExisting(existingChannels, channel)
680
+ : existingChannels.find(c => c.id === channel.id);
681
+
682
+ if (existing && !overwrite) {
683
+ results.channels.skipped++;
684
+ continue;
685
+ }
655
686
 
656
- if (existing && overwrite) {
657
- channelsService.updateChannel(channel.id, channel);
658
- } else {
659
- const { name, baseUrl, apiKey, websiteUrl, ...extraConfig } = channel;
660
- channelsService.createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig);
687
+ if (existing && overwrite) {
688
+ service.updateChannel(existing.id, { ...channel, id: existing.id });
689
+ } else {
690
+ createChannel(channel);
691
+ }
692
+ results.channels.success++;
693
+ } catch (err) {
694
+ console.error(`[ConfigImport] 导入${type}频道失败: ${channel.name}`, err);
695
+ results.channels.failed++;
661
696
  }
662
- results.channels.success++;
663
- } catch (err) {
664
- console.error(`[ConfigImport] 导入频道失败: ${channel.name}`, err);
665
- results.channels.failed++;
666
697
  }
667
- }
698
+ };
699
+
700
+ importTypedChannels('claude', channelsService, channel => {
701
+ const { name, baseUrl, apiKey, websiteUrl, ...extraConfig } = channel;
702
+ channelsService.createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig);
703
+ }, (existingChannels, channel) => existingChannels.find(c => c.id === channel.id));
704
+
705
+ importTypedChannels('codex', codexChannelsService, channel => {
706
+ const {
707
+ name,
708
+ providerKey,
709
+ baseUrl,
710
+ apiKey,
711
+ wireApi,
712
+ ...extraConfig
713
+ } = channel;
714
+ codexChannelsService.createChannel(name, providerKey, baseUrl, apiKey, wireApi, extraConfig);
715
+ }, (existingChannels, channel) => existingChannels.find(c =>
716
+ (channel.id && c.id === channel.id) ||
717
+ (channel.providerKey && c.providerKey === channel.providerKey)
718
+ ));
719
+
720
+ importTypedChannels('gemini', geminiChannelsService, channel => {
721
+ const { name, baseUrl, apiKey, model, ...extraConfig } = channel;
722
+ geminiChannelsService.createChannel(name, baseUrl, apiKey, model, extraConfig);
723
+ }, (existingChannels, channel) => existingChannels.find(c =>
724
+ (channel.id && c.id === channel.id) ||
725
+ (channel.name && c.name === channel.name)
726
+ ));
727
+
728
+ importTypedChannels('opencode', opencodeChannelsService, channel => {
729
+ const { name, baseUrl, apiKey, ...extraConfig } = channel;
730
+ opencodeChannelsService.createChannel(name, baseUrl, apiKey, extraConfig);
731
+ }, (existingChannels, channel) => existingChannels.find(c =>
732
+ (channel.id && c.id === channel.id) ||
733
+ (channel.providerKey && c.providerKey === channel.providerKey) ||
734
+ (channel.name && channel.baseUrl && c.name === channel.name && c.baseUrl === channel.baseUrl)
735
+ ));
668
736
 
669
737
  // 导入工作区配置
670
738
  if (workspaces && overwrite) {
@@ -799,7 +867,7 @@ async function importConfigs(importData, options = {}) {
799
867
  results.plugins.failed++;
800
868
  continue;
801
869
  }
802
- targetDir = path.join(os.homedir(), '.claude', 'plugins', pluginId);
870
+ targetDir = path.join(CLAUDE_PLUGINS_DIR, pluginId);
803
871
  registryPath = NATIVE_PLUGINS_REGISTRY;
804
872
  } else {
805
873
  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');