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
@@ -6,6 +6,11 @@ const { getSessionById: getClaudeSessionById } = require('./sessions');
6
6
  const { getSessionById: getCodexSessionById } = require('./codex-sessions');
7
7
  const { getSessionById: getGeminiSessionById, getProjectPath } = require('./gemini-sessions');
8
8
  const { readJSONL, parseSession: parseCodexFull } = require('./codex-parser');
9
+ const { NATIVE_PATHS, HOME_DIR } = require('../../config/paths');
10
+
11
+ const CLAUDE_PROJECTS_DIR = NATIVE_PATHS.claude.projects;
12
+ const CODEX_SESSIONS_DIR = NATIVE_PATHS.codex.sessions;
13
+ const GEMINI_TMP_DIR = NATIVE_PATHS.gemini.tmp;
9
14
 
10
15
  /**
11
16
  * 统一中间格式
@@ -192,7 +197,7 @@ function parseGeminiToUnified(filePath) {
192
197
 
193
198
  return {
194
199
  sessionId: session.sessionId || crypto.randomUUID(),
195
- cwd: projectPath || os.homedir(),
200
+ cwd: projectPath || HOME_DIR,
196
201
  gitBranch: null, // Gemini 不记录 git branch
197
202
  startTime: session.startTime || new Date().toISOString(),
198
203
  messages,
@@ -380,7 +385,7 @@ function generateTargetPath(targetType, unified, options = {}) {
380
385
 
381
386
  if (targetType === 'claude') {
382
387
  // Claude: ~/.claude/projects/{encoded-path}/{uuid}.jsonl
383
- const projectsDir = path.join(os.homedir(), '.claude', 'projects');
388
+ const projectsDir = CLAUDE_PROJECTS_DIR;
384
389
  const encodedPath = options.targetProject
385
390
  ? encodePath(options.targetProject)
386
391
  : encodePath(unified.cwd);
@@ -388,7 +393,7 @@ function generateTargetPath(targetType, unified, options = {}) {
388
393
  return path.join(projectsDir, encodedPath, `${newSessionId}.jsonl`);
389
394
  } else if (targetType === 'codex') {
390
395
  // Codex: ~/.codex/sessions/{YYYY}/{MM}/{DD}/rollout-{timestamp}-{uuid}.jsonl
391
- const sessionsDir = path.join(os.homedir(), '.codex', 'sessions');
396
+ const sessionsDir = CODEX_SESSIONS_DIR;
392
397
  const now = new Date();
393
398
  const year = now.getFullYear();
394
399
  const month = String(now.getMonth() + 1).padStart(2, '0');
@@ -401,7 +406,7 @@ function generateTargetPath(targetType, unified, options = {}) {
401
406
  `rollout-${timestamp}-${newSessionId}.jsonl`);
402
407
  } else if (targetType === 'gemini') {
403
408
  // Gemini: ~/.gemini/tmp/{project_hash}/chats/session-{timestamp}-{shortId}.json
404
- const geminiDir = path.join(os.homedir(), '.gemini', 'tmp');
409
+ const geminiDir = GEMINI_TMP_DIR;
405
410
  const projectHash = unified.metadata.projectHash ||
406
411
  crypto.createHash('sha256').update(unified.cwd).digest('hex');
407
412
  const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
@@ -1,6 +1,5 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const os = require('os');
4
3
  const crypto = require('crypto');
5
4
  const { getAllSessions, parseSessionInfoFast } = require('../../utils/session');
6
5
  const { loadAliases } = require('./alias');
@@ -12,7 +11,11 @@ const {
12
11
  rememberHasMessages
13
12
  } = require('./session-cache');
14
13
  const { globalCache, CacheKeys } = require('./enhanced-cache');
15
- const { PATHS } = require('../../config/paths');
14
+ const { PATHS, NATIVE_PATHS } = require('../../config/paths');
15
+
16
+ const CLAUDE_PROJECTS_DIR = NATIVE_PATHS.claude.projects;
17
+ const CODEX_PROJECTS_DIR = path.join(path.dirname(NATIVE_PATHS.codex.config), 'projects');
18
+ const GEMINI_PROJECTS_DIR = path.join(path.dirname(NATIVE_PATHS.gemini.env), 'projects');
16
19
 
17
20
  // Base directory for cc-tool data
18
21
  function getCcToolDir() {
@@ -238,7 +241,7 @@ function validateProjectPath(candidatePath) {
238
241
 
239
242
  function tryResolvePathFromSessions(encodedName) {
240
243
  try {
241
- const projectDir = path.join(os.homedir(), '.claude', 'projects', encodedName);
244
+ const projectDir = path.join(CLAUDE_PROJECTS_DIR, encodedName);
242
245
  if (!fs.existsSync(projectDir)) {
243
246
  return null;
244
247
  }
@@ -824,7 +827,7 @@ async function searchSessionsAcrossProjects(config, keyword, contextLength = 35)
824
827
 
825
828
  try {
826
829
  // Search in Codex projects
827
- const codexProjectsDir = path.join(os.homedir(), '.codex', 'projects');
830
+ const codexProjectsDir = CODEX_PROJECTS_DIR;
828
831
  if (fs.existsSync(codexProjectsDir)) {
829
832
  const codexConfig = { ...config, projectsDir: codexProjectsDir };
830
833
  const codexProjects = await getProjects(codexConfig);
@@ -849,7 +852,7 @@ async function searchSessionsAcrossProjects(config, keyword, contextLength = 35)
849
852
 
850
853
  try {
851
854
  // Search in Gemini projects
852
- const geminiProjectsDir = path.join(os.homedir(), '.gemini', 'projects');
855
+ const geminiProjectsDir = GEMINI_PROJECTS_DIR;
853
856
  if (fs.existsSync(geminiProjectsDir)) {
854
857
  const geminiConfig = { ...config, projectsDir: geminiProjectsDir };
855
858
  const geminiProjects = await getProjects(geminiConfig);
@@ -1,15 +1,14 @@
1
1
  const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
2
+ const { NATIVE_PATHS } = require('../../config/paths');
4
3
 
5
4
  // Claude Code 配置文件路径
6
5
  function getSettingsPath() {
7
- return path.join(os.homedir(), '.claude', 'settings.json');
6
+ return NATIVE_PATHS.claude.settings;
8
7
  }
9
8
 
10
9
  // 备份文件路径
11
10
  function getBackupPath() {
12
- return path.join(os.homedir(), '.claude', 'settings.json.cc-tool-backup');
11
+ return NATIVE_PATHS.claude.settingsBackup;
13
12
  }
14
13
 
15
14
  // 检查配置文件是否存在
@@ -16,7 +16,7 @@ const AdmZip = require('adm-zip');
16
16
  const {
17
17
  parseSkillContent,
18
18
  } = require('./format-converter');
19
- const { NATIVE_PATHS } = require('../../config/paths');
19
+ const { NATIVE_PATHS, HOME_DIR } = require('../../config/paths');
20
20
 
21
21
  const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
22
22
  const OPENCODE_SKILL_NAME_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
@@ -46,17 +46,17 @@ const DEFAULT_REPOS_BY_PLATFORM = {
46
46
 
47
47
  const PLATFORM_CONFIG = {
48
48
  claude: {
49
- installDir: path.join(os.homedir(), '.claude', 'skills'),
49
+ installDir: path.join(HOME_DIR, '.claude', 'skills'),
50
50
  reposFile: 'skill-repos.json',
51
51
  cacheFile: 'skills-cache.json'
52
52
  },
53
53
  codex: {
54
- installDir: path.join(os.homedir(), '.codex', 'skills'),
54
+ installDir: path.join(HOME_DIR, '.codex', 'skills'),
55
55
  reposFile: 'codex-skill-repos.json',
56
56
  cacheFile: 'codex-skills-cache.json'
57
57
  },
58
58
  gemini: {
59
- installDir: path.join(os.homedir(), '.gemini', 'skills'),
59
+ installDir: path.join(HOME_DIR, '.gemini', 'skills'),
60
60
  reposFile: 'gemini-skill-repos.json',
61
61
  cacheFile: 'gemini-skills-cache.json'
62
62
  },
@@ -73,7 +73,7 @@ const CACHE_TTL = 5 * 60 * 1000;
73
73
  class SkillService {
74
74
  constructor(platform = 'claude') {
75
75
  this.platform = normalizePlatform(platform);
76
- this.configDir = path.join(os.homedir(), '.cc-tool');
76
+ this.configDir = path.join(HOME_DIR, '.cc-tool');
77
77
 
78
78
  const platformConfig = PLATFORM_CONFIG[this.platform];
79
79
  this.installDir = platformConfig.installDir;
@@ -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
  // 北京时间辅助(UTC+8),统一所有时间计算
6
6
  const CST_OFFSET_MS = 8 * 60 * 60 * 1000;
@@ -39,7 +39,7 @@ function getCSTHour(ts) {
39
39
 
40
40
  // 获取基础目录
41
41
  function getBaseDir() {
42
- const dir = path.join(os.homedir(), '.cc-tool');
42
+ const dir = PATHS.base;
43
43
  if (!fs.existsSync(dir)) {
44
44
  fs.mkdirSync(dir, { recursive: true });
45
45
  }
@@ -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 UI_CONFIG_DIR = path.join(os.homedir(), '.cc-tool');
6
- const UI_CONFIG_FILE = path.join(UI_CONFIG_DIR, 'ui-config.json');
4
+ const UI_CONFIG_DIR = PATHS.base;
5
+ const UI_CONFIG_FILE = PATHS.uiConfig;
7
6
 
8
7
  // Default UI config
9
8
  const DEFAULT_UI_CONFIG = {
@@ -2,8 +2,8 @@ const WebSocket = require('ws');
2
2
  const chalk = require('chalk');
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const os = require('os');
6
5
  const { loadConfig } = require('../config/loader');
6
+ const { PATHS } = require('../config/paths');
7
7
  const {
8
8
  normalizeAddress,
9
9
  isLoopbackAddress,
@@ -159,7 +159,7 @@ function installOriginGuard(server) {
159
159
 
160
160
  // 日志持久化文件路径
161
161
  function getLogsFilePath() {
162
- const ccToolDir = path.join(os.homedir(), '.cc-tool');
162
+ const ccToolDir = PATHS.base;
163
163
  if (!fs.existsSync(ccToolDir)) {
164
164
  fs.mkdirSync(ccToolDir, { recursive: true });
165
165
  }
@@ -0,0 +1,82 @@
1
+ const path = require('path');
2
+
3
+ function isWindowsLikePlatform(platform = process.platform, env = process.env) {
4
+ if (platform === 'win32' || platform === 'cygwin' || platform === 'msys') {
5
+ return true;
6
+ }
7
+
8
+ const hasWindowsEnv = Boolean(env.SYSTEMROOT || env.WINDIR || env.COMSPEC || env.ComSpec);
9
+ const hasWindowsHome = /^[a-zA-Z]:[\\/]/.test(env.USERPROFILE || '') ||
10
+ (Boolean(env.HOMEDRIVE) && Boolean(env.HOMEPATH));
11
+ return hasWindowsEnv && hasWindowsHome;
12
+ }
13
+
14
+ function normalizeWindowsHomePath(rawPath, env = process.env) {
15
+ if (!rawPath || typeof rawPath !== 'string') {
16
+ return '';
17
+ }
18
+
19
+ let value = rawPath.trim();
20
+ if (!value) {
21
+ return '';
22
+ }
23
+
24
+ const msysDriveMatch = value.match(/^\/([a-zA-Z])\/(.+)$/);
25
+ if (msysDriveMatch) {
26
+ const drive = msysDriveMatch[1].toUpperCase();
27
+ const rest = msysDriveMatch[2].replace(/\//g, '\\');
28
+ value = `${drive}:\\${rest}`;
29
+ } else if (/^\/Users\//i.test(value)) {
30
+ const drive = (env.SYSTEMDRIVE || env.HOMEDRIVE || 'C:').replace(/\\+$/, '');
31
+ const rest = value.replace(/^\/+/, '').replace(/\//g, '\\');
32
+ value = `${drive}\\${rest}`;
33
+ } else {
34
+ value = value.replace(/\//g, '\\');
35
+ }
36
+
37
+ if (/^[a-zA-Z]:[^\\]/.test(value)) {
38
+ value = `${value.slice(0, 2)}\\${value.slice(2)}`;
39
+ }
40
+
41
+ if (!path.win32.isAbsolute(value)) {
42
+ return '';
43
+ }
44
+
45
+ return path.win32.normalize(value);
46
+ }
47
+
48
+ function isGitInstallHomePath(homePath) {
49
+ return /[\\/]Program Files[\\/]Git[\\/]Users[\\/]/i.test(homePath || '');
50
+ }
51
+
52
+ function resolvePreferredHomeDir(platform = process.platform, env = process.env, fallbackHome = '') {
53
+ if (!isWindowsLikePlatform(platform, env)) {
54
+ return fallbackHome;
55
+ }
56
+
57
+ const candidates = [];
58
+ if (env.USERPROFILE) {
59
+ candidates.push(env.USERPROFILE);
60
+ }
61
+ if (env.HOMEDRIVE && env.HOMEPATH) {
62
+ candidates.push(`${env.HOMEDRIVE}${env.HOMEPATH}`);
63
+ }
64
+ if (env.HOME) {
65
+ candidates.push(env.HOME);
66
+ }
67
+ candidates.push(fallbackHome);
68
+
69
+ const normalizedCandidates = candidates
70
+ .map(candidate => normalizeWindowsHomePath(candidate, env))
71
+ .filter(Boolean);
72
+
73
+ const preferredHome = normalizedCandidates.find(candidate => !isGitInstallHomePath(candidate));
74
+ return preferredHome || normalizedCandidates[0] || fallbackHome;
75
+ }
76
+
77
+ module.exports = {
78
+ isWindowsLikePlatform,
79
+ normalizeWindowsHomePath,
80
+ resolvePreferredHomeDir,
81
+ isGitInstallHomePath
82
+ };
@@ -1,5 +1,31 @@
1
1
  const { execSync } = require('child_process');
2
2
  const net = require('net');
3
+ const { isWindowsLikePlatform } = require('./home-dir');
4
+
5
+ function isWindowsLikeRuntime(platform = process.platform, env = process.env) {
6
+ return isWindowsLikePlatform(platform, env);
7
+ }
8
+
9
+ function parsePidsFromNetstatOutput(output, port) {
10
+ const target = `:${port}`;
11
+ const pids = new Set();
12
+
13
+ String(output || '').split(/\r?\n/).forEach((line) => {
14
+ const row = line.trim();
15
+ if (!row || !row.includes(target)) {
16
+ return;
17
+ }
18
+ if (row.startsWith('UDP') && !row.includes(`${target} `) && !row.endsWith(target)) {
19
+ return;
20
+ }
21
+ const match = row.match(/\s+(\d+)\s*$/);
22
+ if (match && match[1] !== '0') {
23
+ pids.add(match[1]);
24
+ }
25
+ });
26
+
27
+ return Array.from(pids);
28
+ }
3
29
 
4
30
  /**
5
31
  * 检查端口是否被占用
@@ -43,18 +69,12 @@ function isPortInUse(port, host = '127.0.0.1') {
43
69
  * 查找占用端口的进程PID(跨平台)
44
70
  */
45
71
  function findProcessByPort(port) {
46
- const isWindows = process.platform === 'win32';
72
+ const isWindows = isWindowsLikeRuntime();
47
73
  if (isWindows) {
48
74
  try {
49
- // Windows: netstat -ano 列出所有连接,findstr 过滤端口
50
- const result = execSync(`netstat -ano | findstr ":${port} "`, { encoding: 'utf-8' });
51
- const pids = new Set();
52
- result.split('\n').forEach(line => {
53
- // 格式: " TCP 0.0.0.0:9999 0.0.0.0:0 LISTENING 1234"
54
- const match = line.trim().match(/\s+(\d+)\s*$/);
55
- if (match) pids.add(match[1]);
56
- });
57
- return Array.from(pids).filter(pid => pid && pid !== '0');
75
+ // Windows: 直接解析 netstat 输出,避免依赖 findstr/lsof
76
+ const result = execSync('netstat -ano', { encoding: 'utf-8' });
77
+ return parsePidsFromNetstatOutput(result, port);
58
78
  } catch (e) {
59
79
  return [];
60
80
  }
@@ -85,7 +105,7 @@ function killProcessByPort(port) {
85
105
  return false;
86
106
  }
87
107
 
88
- const isWindows = process.platform === 'win32';
108
+ const isWindows = isWindowsLikeRuntime();
89
109
  pids.forEach(pid => {
90
110
  try {
91
111
  if (isWindows) {
@@ -125,5 +145,7 @@ module.exports = {
125
145
  isPortInUse,
126
146
  findProcessByPort,
127
147
  killProcessByPort,
128
- waitForPortRelease
148
+ waitForPortRelease,
149
+ isWindowsLikeRuntime,
150
+ parsePidsFromNetstatOutput
129
151
  };