coding-tool-x 3.5.7 → 3.5.9

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 (60) hide show
  1. package/dist/web/assets/{Analytics-C6DEmD3D.js → Analytics-C5W3axXs.js} +2 -2
  2. package/dist/web/assets/Analytics-vQS5IWvs.css +1 -0
  3. package/dist/web/assets/{ConfigTemplates-Cf_iTpC4.js → ConfigTemplates-DzyVFDx9.js} +1 -1
  4. package/dist/web/assets/{Home-BtBmYLJ1.js → Home-C9TQNB6f.js} +1 -1
  5. package/dist/web/assets/Home-qzk118Of.css +1 -0
  6. package/dist/web/assets/{PluginManager-DEk8vSw5.js → PluginManager-9B_brLWT.js} +1 -1
  7. package/dist/web/assets/ProjectList-Bjt6mrsV.js +1 -0
  8. package/dist/web/assets/ProjectList-GCC2QOmq.css +1 -0
  9. package/dist/web/assets/SessionList-BsHPgmUR.css +1 -0
  10. package/dist/web/assets/SessionList-DcBH13uA.js +1 -0
  11. package/dist/web/assets/{SkillManager-DcZOiiSf.js → SkillManager-vST8DRRg.js} +1 -1
  12. package/dist/web/assets/{WorkspaceManager-BHqI8aGV.js → WorkspaceManager-ov1KgRXR.js} +1 -1
  13. package/dist/web/assets/icons-CEq2hYB-.js +1 -0
  14. package/dist/web/assets/index-Dih_bOsv.css +1 -0
  15. package/dist/web/assets/index-Duc7QP4e.js +2 -0
  16. package/dist/web/assets/{naive-ui-BaTCPPL5.js → naive-ui-Cg4_ZeoT.js} +1 -1
  17. package/dist/web/assets/{vendors-Fza9uSYn.js → vendors-Bsp-dq2d.js} +1 -1
  18. package/dist/web/assets/vue-vendor-BxIT0uQq.js +45 -0
  19. package/dist/web/index.html +7 -7
  20. package/package.json +1 -1
  21. package/src/commands/export-config.js +6 -6
  22. package/src/config/default.js +2 -6
  23. package/src/config/loader.js +2 -2
  24. package/src/config/paths.js +160 -33
  25. package/src/server/api/agents.js +52 -2
  26. package/src/server/api/codex-sessions.js +4 -2
  27. package/src/server/api/commands.js +38 -2
  28. package/src/server/api/opencode-sessions.js +4 -2
  29. package/src/server/api/plugins.js +104 -1
  30. package/src/server/api/sessions.js +9 -7
  31. package/src/server/services/agents-service.js +269 -62
  32. package/src/server/services/commands-service.js +281 -81
  33. package/src/server/services/config-export-service.js +7 -7
  34. package/src/server/services/config-registry-service.js +4 -5
  35. package/src/server/services/config-sync-manager.js +61 -41
  36. package/src/server/services/config-sync-service.js +3 -3
  37. package/src/server/services/gemini-channels.js +5 -5
  38. package/src/server/services/gemini-config.js +3 -4
  39. package/src/server/services/gemini-sessions.js +23 -20
  40. package/src/server/services/gemini-settings-manager.js +2 -3
  41. package/src/server/services/mcp-service.js +9 -14
  42. package/src/server/services/native-oauth-adapters.js +3 -3
  43. package/src/server/services/notification-hooks.js +3 -3
  44. package/src/server/services/opencode-sessions.js +16 -6
  45. package/src/server/services/opencode-settings-manager.js +3 -3
  46. package/src/server/services/plugins-service.js +499 -23
  47. package/src/server/services/prompts-service.js +5 -9
  48. package/src/server/services/session-launch-command.js +1 -24
  49. package/src/server/services/sessions.js +91 -40
  50. package/src/server/services/skill-service.js +155 -18
  51. package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
  52. package/dist/web/assets/Home-BQxQ1LhR.css +0 -1
  53. package/dist/web/assets/ProjectList-BMVhA_Kh.js +0 -1
  54. package/dist/web/assets/ProjectList-DL4JK6ci.css +0 -1
  55. package/dist/web/assets/SessionList-B5ioAXxg.js +0 -1
  56. package/dist/web/assets/SessionList-B8dXVXfi.css +0 -1
  57. package/dist/web/assets/icons-CQuif85v.js +0 -1
  58. package/dist/web/assets/index-CtByKdkA.js +0 -2
  59. package/dist/web/assets/index-VGAxnLqi.css +0 -1
  60. package/dist/web/assets/vue-vendor-aWwwFAao.js +0 -45
@@ -16,21 +16,43 @@
16
16
 
17
17
  const fs = require('fs');
18
18
  const path = require('path');
19
- const os = require('os');
20
19
  const toml = require('toml');
21
20
  const tomlStringify = require('@iarna/toml').stringify;
22
21
  const { convertSkillToCodex, convertCommandToCodex } = require('./format-converter');
23
- const { PATHS, NATIVE_PATHS, HOME_DIR, ensureStorageDirMigrated } = require('../../config/paths');
22
+ const {
23
+ PATHS,
24
+ NATIVE_PATHS,
25
+ HOME_DIR,
26
+ ensureStorageDirMigrated,
27
+ getNativePlatformDir,
28
+ isWindowsAbsolutePath,
29
+ isNativeAbsolutePath,
30
+ joinNativeBasePath,
31
+ resolveNativeBasePath,
32
+ getNativePathDir
33
+ } = require('../../config/paths');
24
34
 
25
- // Paths
26
- const HOME = HOME_DIR || os.homedir();
27
35
  const CC_TOOL_CONFIGS = PATHS.configs;
28
- const CLAUDE_CODE_DIR = path.join(HOME, '.claude');
29
- const CODEX_DIR = path.join(HOME, '.codex');
30
- const GEMINI_DIR = path.join(HOME, '.gemini');
36
+ const CLAUDE_CODE_DIR = getNativePlatformDir('claude');
37
+ const CODEX_DIR = getNativePlatformDir('codex');
38
+ const GEMINI_DIR = getNativePlatformDir('gemini');
31
39
  const OPENCODE_DIR = NATIVE_PATHS.opencode.config;
32
40
  const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
33
41
 
42
+ function isPathInsideNativeDir(targetPath, baseDir) {
43
+ const shouldFoldCase = isWindowsAbsolutePath(baseDir);
44
+ const normalize = (value) => {
45
+ const normalized = String(value || '')
46
+ .replace(/[\\/]+/g, '/')
47
+ .replace(/\/+$/, '');
48
+ return shouldFoldCase ? normalized.toLowerCase() : normalized;
49
+ };
50
+
51
+ const normalizedTarget = normalize(targetPath);
52
+ const normalizedBase = normalize(baseDir);
53
+ return normalizedTarget === normalizedBase || normalizedTarget.startsWith(`${normalizedBase}/`);
54
+ }
55
+
34
56
  // Config type definitions
35
57
  const CONFIG_TYPES = {
36
58
  skills: {
@@ -108,7 +130,7 @@ class ConfigSyncManager {
108
130
  return { success: false, error: 'Invalid config item name' };
109
131
  }
110
132
  const sourcePath = path.join(this.ccToolConfigs, type, safeName);
111
- const targetPath = path.join(this.claudeDir, config.claudeTarget, safeName);
133
+ const targetPath = joinNativeBasePath(this.claudeDir, config.claudeTarget, safeName);
112
134
 
113
135
  // Check if source exists
114
136
  if (!fs.existsSync(sourcePath)) {
@@ -119,12 +141,12 @@ class ConfigSyncManager {
119
141
  try {
120
142
  if (config.isDirectory) {
121
143
  // Copy entire directory recursively
122
- this._ensureDir(path.dirname(targetPath));
144
+ this._ensureDir(getNativePathDir(targetPath));
123
145
  this._copyDirRecursive(sourcePath, targetPath);
124
146
  console.log(`[ConfigSyncManager] Synced ${type}/${name} to Claude Code (directory)`);
125
147
  } else {
126
148
  // Copy single file, preserving subdirectory structure
127
- this._ensureDir(path.dirname(targetPath));
149
+ this._ensureDir(getNativePathDir(targetPath));
128
150
  this._copyFile(sourcePath, targetPath);
129
151
  console.log(`[ConfigSyncManager] Synced ${type}/${name} to Claude Code (file)`);
130
152
  }
@@ -152,7 +174,7 @@ class ConfigSyncManager {
152
174
  if (!safeName) {
153
175
  return { success: false, error: 'Invalid config item name' };
154
176
  }
155
- const targetPath = path.join(this.claudeDir, config.claudeTarget, safeName);
177
+ const targetPath = joinNativeBasePath(this.claudeDir, config.claudeTarget, safeName);
156
178
 
157
179
  if (!fs.existsSync(targetPath)) {
158
180
  console.log(`[ConfigSyncManager] Target not found (already removed): ${targetPath}`);
@@ -170,7 +192,7 @@ class ConfigSyncManager {
170
192
  console.log(`[ConfigSyncManager] Removed ${type}/${name} from Claude Code (file)`);
171
193
 
172
194
  // Clean up empty parent directories for file-based configs
173
- this._cleanupEmptyParents(path.dirname(targetPath), path.join(this.claudeDir, config.claudeTarget));
195
+ this._cleanupEmptyParents(getNativePathDir(targetPath), joinNativeBasePath(this.claudeDir, config.claudeTarget));
174
196
  }
175
197
 
176
198
  return { success: true };
@@ -260,7 +282,7 @@ class ConfigSyncManager {
260
282
 
261
283
  if (type === 'skills') {
262
284
  // Skills: copy directory, convert SKILL.md content
263
- const targetPath = path.join(this.codexDir, config.codexTarget, safeName);
285
+ const targetPath = joinNativeBasePath(this.codexDir, config.codexTarget, safeName);
264
286
  this._ensureDir(targetPath);
265
287
 
266
288
  // Copy all files, converting SKILL.md
@@ -288,8 +310,8 @@ class ConfigSyncManager {
288
310
  }
289
311
 
290
312
  // Target path in codex prompts (same relative path structure)
291
- const targetPath = path.join(this.codexDir, config.codexTarget, safeName);
292
- this._ensureDir(path.dirname(targetPath));
313
+ const targetPath = joinNativeBasePath(this.codexDir, config.codexTarget, safeName);
314
+ this._ensureDir(getNativePathDir(targetPath));
293
315
  fs.writeFileSync(targetPath, result.content, 'utf-8');
294
316
 
295
317
  console.log(`[ConfigSyncManager] Synced ${type}/${name} to Codex (prompt)`);
@@ -354,7 +376,7 @@ class ConfigSyncManager {
354
376
  }
355
377
  }
356
378
 
357
- const targetPath = path.join(this.codexDir, config.codexTarget, safeName);
379
+ const targetPath = joinNativeBasePath(this.codexDir, config.codexTarget, safeName);
358
380
 
359
381
  if (!fs.existsSync(targetPath)) {
360
382
  console.log(`[ConfigSyncManager] Target not found (already removed): ${targetPath}`);
@@ -372,7 +394,7 @@ class ConfigSyncManager {
372
394
  console.log(`[ConfigSyncManager] Removed ${type}/${name} from Codex (prompt)`);
373
395
 
374
396
  // Clean up empty parent directories
375
- this._cleanupEmptyParents(path.dirname(targetPath), path.join(this.codexDir, config.codexTarget));
397
+ this._cleanupEmptyParents(getNativePathDir(targetPath), joinNativeBasePath(this.codexDir, config.codexTarget));
376
398
  }
377
399
 
378
400
  return { success: true };
@@ -411,8 +433,8 @@ class ConfigSyncManager {
411
433
  }
412
434
 
413
435
  try {
414
- const targetPath = path.join(this.geminiDir, config.geminiTarget, safeName);
415
- this._ensureDir(path.dirname(targetPath));
436
+ const targetPath = joinNativeBasePath(this.geminiDir, config.geminiTarget, safeName);
437
+ this._ensureDir(getNativePathDir(targetPath));
416
438
  this._copyDirRecursive(sourcePath, targetPath);
417
439
  console.log(`[ConfigSyncManager] Synced ${type}/${name} to Gemini (directory)`);
418
440
  return { success: true, target: targetPath };
@@ -443,7 +465,7 @@ class ConfigSyncManager {
443
465
  return { success: true, skipped: true, reason: 'Not supported by Gemini' };
444
466
  }
445
467
 
446
- const targetPath = path.join(this.geminiDir, config.geminiTarget, safeName);
468
+ const targetPath = joinNativeBasePath(this.geminiDir, config.geminiTarget, safeName);
447
469
  if (!fs.existsSync(targetPath)) {
448
470
  console.log(`[ConfigSyncManager] Target not found (already removed): ${targetPath}`);
449
471
  return { success: true, message: 'Already removed' };
@@ -489,14 +511,14 @@ class ConfigSyncManager {
489
511
 
490
512
  try {
491
513
  const targetBaseDir = this._getOpenCodeTypeBaseDir(config);
492
- const targetPath = path.join(targetBaseDir, safeName);
514
+ const targetPath = joinNativeBasePath(targetBaseDir, safeName);
493
515
 
494
516
  if (config.isDirectory) {
495
- this._ensureDir(path.dirname(targetPath));
517
+ this._ensureDir(getNativePathDir(targetPath));
496
518
  this._copyDirRecursive(sourcePath, targetPath);
497
519
  console.log(`[ConfigSyncManager] Synced ${type}/${name} to OpenCode (directory)`);
498
520
  } else {
499
- this._ensureDir(path.dirname(targetPath));
521
+ this._ensureDir(getNativePathDir(targetPath));
500
522
  this._copyFile(sourcePath, targetPath);
501
523
  console.log(`[ConfigSyncManager] Synced ${type}/${name} to OpenCode (file)`);
502
524
  }
@@ -544,7 +566,7 @@ class ConfigSyncManager {
544
566
  } else {
545
567
  fs.unlinkSync(targetPath);
546
568
  console.log(`[ConfigSyncManager] Removed ${type}/${name} from OpenCode (file)`);
547
- this._cleanupEmptyParents(path.dirname(targetPath), targetBaseDir);
569
+ this._cleanupEmptyParents(getNativePathDir(targetPath), targetBaseDir);
548
570
  }
549
571
 
550
572
  return { success: true };
@@ -744,7 +766,7 @@ class ConfigSyncManager {
744
766
  * OpenCode supports both plural (new) and singular (legacy) folder names.
745
767
  */
746
768
  _getOpenCodeTypeBaseDir(config) {
747
- const modernDir = path.join(this.opencodeDir, config.opencodeTarget);
769
+ const modernDir = joinNativeBasePath(this.opencodeDir, config.opencodeTarget);
748
770
  // 技能目录强制使用 modern/plural 形式,避免 legacy 目录带来的跨平台历史污染
749
771
  if (config === this.configTypes.skills) {
750
772
  return modernDir;
@@ -754,7 +776,7 @@ class ConfigSyncManager {
754
776
  return modernDir;
755
777
  }
756
778
 
757
- const legacyDir = path.join(this.opencodeDir, config.opencodeLegacyTarget);
779
+ const legacyDir = joinNativeBasePath(this.opencodeDir, config.opencodeLegacyTarget);
758
780
  if (fs.existsSync(legacyDir) && !fs.existsSync(modernDir)) {
759
781
  return legacyDir;
760
782
  }
@@ -777,12 +799,11 @@ class ConfigSyncManager {
777
799
  * Clean up empty parent directories up to the base directory
778
800
  */
779
801
  _cleanupEmptyParents(dir, baseDir) {
780
- // Normalize paths for comparison
781
- const normalizedDir = path.resolve(dir);
782
- const normalizedBase = path.resolve(baseDir);
802
+ const normalizedDir = resolveNativeBasePath(dir);
803
+ const normalizedBase = resolveNativeBasePath(baseDir);
783
804
 
784
805
  // Don't go above base directory
785
- if (!normalizedDir.startsWith(normalizedBase) || normalizedDir === normalizedBase) {
806
+ if (!isPathInsideNativeDir(normalizedDir, normalizedBase) || normalizedDir === normalizedBase) {
786
807
  return;
787
808
  }
788
809
 
@@ -792,7 +813,7 @@ class ConfigSyncManager {
792
813
  fs.rmdirSync(dir);
793
814
  console.log(`[ConfigSyncManager] Removed empty directory: ${dir}`);
794
815
  // Recurse to parent
795
- this._cleanupEmptyParents(path.dirname(dir), baseDir);
816
+ this._cleanupEmptyParents(getNativePathDir(dir), baseDir);
796
817
  }
797
818
  } catch (err) {
798
819
  // Ignore errors (directory might not exist or permission issues)
@@ -810,7 +831,7 @@ class ConfigSyncManager {
810
831
  return null;
811
832
  }
812
833
 
813
- if (path.isAbsolute(raw) || raw.startsWith('/')) {
834
+ if (isNativeAbsolutePath(raw) || raw.startsWith('/')) {
814
835
  return null;
815
836
  }
816
837
 
@@ -860,35 +881,34 @@ class ConfigSyncManager {
860
881
  if (!normalized) return '';
861
882
 
862
883
  if (normalized.startsWith('~/')) {
863
- return path.join(HOME, normalized.slice(2));
884
+ return joinNativeBasePath(HOME_DIR, normalized.slice(2));
864
885
  }
865
886
 
866
- if (path.isAbsolute(normalized)) {
887
+ if (isNativeAbsolutePath(normalized)) {
867
888
  return normalized;
868
889
  }
869
890
 
870
- return path.resolve(path.dirname(CODEX_CONFIG_PATH), normalized);
891
+ return resolveNativeBasePath(CODEX_DIR, normalized);
871
892
  }
872
893
 
873
894
  _isManagedCodexAgentConfigPath(configPath) {
874
895
  const resolved = this._resolveCodexPath(configPath);
875
896
  if (!resolved) return false;
876
897
 
877
- const managedRoot = path.resolve(this._getCodexManagedAgentConfigDir()) + path.sep;
878
- return resolved.startsWith(managedRoot) || resolved === path.resolve(this._getCodexManagedAgentConfigDir());
898
+ return isPathInsideNativeDir(resolved, this._getCodexManagedAgentConfigDir());
879
899
  }
880
900
 
881
901
  _getCodexManagedAgentConfigDir() {
882
- return path.join(this.codexDir, 'agents');
902
+ return NATIVE_PATHS.codex.agents;
883
903
  }
884
904
 
885
905
  _getCodexManagedAgentConfigPath(fileName) {
886
- return path.join(this._getCodexManagedAgentConfigDir(), `${fileName}.toml`);
906
+ return joinNativeBasePath(this._getCodexManagedAgentConfigDir(), `${fileName}.toml`);
887
907
  }
888
908
 
889
909
  _writeCodexAgentConfigFile(configPath, data) {
890
910
  const resolved = this._resolveCodexPath(configPath);
891
- this._ensureDir(path.dirname(resolved));
911
+ this._ensureDir(getNativePathDir(resolved));
892
912
  const tempPath = `${resolved}.tmp-${process.pid}-${Date.now()}`;
893
913
  fs.writeFileSync(tempPath, tomlStringify(data), 'utf-8');
894
914
  fs.renameSync(tempPath, resolved);
@@ -923,7 +943,7 @@ class ConfigSyncManager {
923
943
  }
924
944
 
925
945
  _writeCodexConfigToml(config) {
926
- this._ensureDir(path.dirname(CODEX_CONFIG_PATH));
946
+ this._ensureDir(getNativePathDir(CODEX_CONFIG_PATH));
927
947
  const tempPath = `${CODEX_CONFIG_PATH}.tmp-${process.pid}-${Date.now()}`;
928
948
  fs.writeFileSync(tempPath, tomlStringify(config), 'utf-8');
929
949
  fs.renameSync(tempPath, CODEX_CONFIG_PATH);
@@ -15,10 +15,10 @@
15
15
 
16
16
  const fs = require('fs');
17
17
  const path = require('path');
18
- const { HOME_DIR } = require('../../config/paths');
18
+ const { NATIVE_PATHS, joinNativeBasePath } = require('../../config/paths');
19
19
 
20
20
  // 全局配置目录
21
- const GLOBAL_CONFIG_DIR = path.join(HOME_DIR, '.claude');
21
+ const GLOBAL_CONFIG_DIR = NATIVE_PATHS.claude.dir;
22
22
 
23
23
  // 配置类型定义
24
24
  const CONFIG_TYPES = {
@@ -96,7 +96,7 @@ class ConfigSyncService {
96
96
  let dir;
97
97
 
98
98
  if (source === 'global') {
99
- dir = path.join(this.globalConfigDir, config.globalDir);
99
+ dir = joinNativeBasePath(this.globalConfigDir, config.globalDir);
100
100
  } else if (source === 'workspace' && projectPath) {
101
101
  if (!config.projectDir) {
102
102
  // skills 不支持项目级
@@ -22,7 +22,7 @@ const { normalizeGatewaySourceType } = require('./base/proxy-utils');
22
22
 
23
23
  // 获取 Gemini 配置目录
24
24
  function getGeminiDir() {
25
- return path.dirname(NATIVE_PATHS.gemini.env);
25
+ return NATIVE_PATHS.gemini.dir;
26
26
  }
27
27
 
28
28
  // 获取渠道存储文件路径
@@ -35,7 +35,7 @@ function getChannelsFilePath() {
35
35
  }
36
36
 
37
37
  function readExistingGeminiEnv() {
38
- const envPath = path.join(getGeminiDir(), '.env');
38
+ const envPath = NATIVE_PATHS.gemini.env;
39
39
  if (!fs.existsSync(envPath)) {
40
40
  return {};
41
41
  }
@@ -60,7 +60,7 @@ function readExistingGeminiEnv() {
60
60
  }
61
61
 
62
62
  function writeGeminiEnv(env = {}) {
63
- const envPath = path.join(getGeminiDir(), '.env');
63
+ const envPath = NATIVE_PATHS.gemini.env;
64
64
  const content = Object.entries(env)
65
65
  .map(([key, value]) => `${key}=${value}`)
66
66
  .join('\n');
@@ -73,7 +73,7 @@ function writeGeminiEnv(env = {}) {
73
73
 
74
74
  // 检查是否在代理模式
75
75
  function isProxyConfig() {
76
- const envPath = path.join(getGeminiDir(), '.env');
76
+ const envPath = NATIVE_PATHS.gemini.env;
77
77
  if (!fs.existsSync(envPath)) {
78
78
  return false;
79
79
  }
@@ -128,7 +128,7 @@ function loadChannels() {
128
128
 
129
129
  // 从现有 .env 初始化渠道
130
130
  function initializeFromEnv() {
131
- const envPath = path.join(getGeminiDir(), '.env');
131
+ const envPath = NATIVE_PATHS.gemini.env;
132
132
 
133
133
  const defaultData = { channels: [] };
134
134
 
@@ -1,19 +1,18 @@
1
1
  const fs = require('fs');
2
- const path = require('path');
3
2
  const { NATIVE_PATHS } = require('../../config/paths');
4
3
 
5
4
  /**
6
5
  * 获取 Gemini 配置目录
7
6
  */
8
7
  function getGeminiDir() {
9
- return path.dirname(NATIVE_PATHS.gemini.env);
8
+ return NATIVE_PATHS.gemini.dir;
10
9
  }
11
10
 
12
11
  /**
13
12
  * 读取 .env 文件
14
13
  */
15
14
  function loadEnv() {
16
- const envPath = path.join(getGeminiDir(), '.env');
15
+ const envPath = NATIVE_PATHS.gemini.env;
17
16
 
18
17
  if (!fs.existsSync(envPath)) {
19
18
  return {};
@@ -44,7 +43,7 @@ function loadEnv() {
44
43
  * 读取 settings.json
45
44
  */
46
45
  function loadSettings() {
47
- const settingsPath = path.join(getGeminiDir(), 'settings.json');
46
+ const settingsPath = NATIVE_PATHS.gemini.settings;
48
47
 
49
48
  if (!fs.existsSync(settingsPath)) {
50
49
  return {};
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const crypto = require('crypto');
4
- const { HOME_DIR } = require('../../config/paths');
4
+ const { HOME_DIR, NATIVE_PATHS, joinNativeBasePath } = require('../../config/paths');
5
5
  const { getGeminiDir } = require('./gemini-config');
6
6
 
7
7
  // 路径映射缓存
@@ -13,7 +13,7 @@ const PATH_MAPPING_CACHE_TTL = 60000; // 1分钟缓存
13
13
  * 获取 Gemini tmp 目录(包含所有项目)
14
14
  */
15
15
  function getTmpDir() {
16
- return path.join(getGeminiDir(), 'tmp');
16
+ return NATIVE_PATHS?.gemini?.tmp || joinNativeBasePath(getGeminiDir(), 'tmp');
17
17
  }
18
18
 
19
19
  /**
@@ -63,7 +63,7 @@ function scanDirForHashes(dir, targetHashes, maxDepth, results, currentDepth = 0
63
63
 
64
64
  if (item.isDirectory()) {
65
65
  scanDirForHashes(
66
- path.join(dir, item.name),
66
+ joinNativeBasePath(dir, item.name),
67
67
  targetHashes,
68
68
  maxDepth,
69
69
  results,
@@ -108,17 +108,17 @@ function buildPathMapping() {
108
108
  // 深度说明:depth=3 表示可以扫描到 Desktop/a/b/c 这样的 4 层目录
109
109
  const searchPaths = [
110
110
  { dir: homeDir, depth: 0 }, // 只检查 home 目录本身
111
- { dir: path.join(homeDir, 'Desktop'), depth: 4 }, // Desktop 及 4 层子目录
112
- { dir: path.join(homeDir, 'Documents'), depth: 4 }, // Documents 及 4 层子目录
113
- { dir: path.join(homeDir, 'Downloads'), depth: 3 }, // Downloads 及 3 层子目录
114
- { dir: path.join(homeDir, 'Projects'), depth: 4 }, // Projects 及 4 层子目录
115
- { dir: path.join(homeDir, 'Code'), depth: 4 }, // Code 及 4 层子目录
116
- { dir: path.join(homeDir, 'workspace'), depth: 4 }, // workspace 及 4 层子目录
117
- { dir: path.join(homeDir, 'dev'), depth: 4 }, // dev 及 4 层子目录
118
- { dir: path.join(homeDir, 'src'), depth: 4 }, // src 及 4 层子目录
119
- { dir: path.join(homeDir, 'work'), depth: 4 }, // work 及 4 层子目录
120
- { dir: path.join(homeDir, 'repos'), depth: 4 }, // repos 及 4 层子目录
121
- { dir: path.join(homeDir, 'github'), depth: 4 }, // github 及 4 层子目录
111
+ { dir: joinNativeBasePath(homeDir, 'Desktop'), depth: 4 }, // Desktop 及 4 层子目录
112
+ { dir: joinNativeBasePath(homeDir, 'Documents'), depth: 4 }, // Documents 及 4 层子目录
113
+ { dir: joinNativeBasePath(homeDir, 'Downloads'), depth: 3 }, // Downloads 及 3 层子目录
114
+ { dir: joinNativeBasePath(homeDir, 'Projects'), depth: 4 }, // Projects 及 4 层子目录
115
+ { dir: joinNativeBasePath(homeDir, 'Code'), depth: 4 }, // Code 及 4 层子目录
116
+ { dir: joinNativeBasePath(homeDir, 'workspace'), depth: 4 }, // workspace 及 4 层子目录
117
+ { dir: joinNativeBasePath(homeDir, 'dev'), depth: 4 }, // dev 及 4 层子目录
118
+ { dir: joinNativeBasePath(homeDir, 'src'), depth: 4 }, // src 及 4 层子目录
119
+ { dir: joinNativeBasePath(homeDir, 'work'), depth: 4 }, // work 及 4 层子目录
120
+ { dir: joinNativeBasePath(homeDir, 'repos'), depth: 4 }, // repos 及 4 层子目录
121
+ { dir: joinNativeBasePath(homeDir, 'github'), depth: 4 }, // github 及 4 层子目录
122
122
  ];
123
123
 
124
124
  for (const { dir, depth } of searchPaths) {
@@ -164,7 +164,7 @@ function scanProjects() {
164
164
  * @returns {Array} 会话文件路径数组
165
165
  */
166
166
  function scanProjectSessions(projectHash) {
167
- const chatsDir = path.join(getTmpDir(), projectHash, 'chats');
167
+ const chatsDir = joinNativeBasePath(getTmpDir(), projectHash, 'chats');
168
168
 
169
169
  if (!fs.existsSync(chatsDir)) {
170
170
  return [];
@@ -175,7 +175,7 @@ function scanProjectSessions(projectHash) {
175
175
  return entries
176
176
  .filter(entry => entry.isFile() && entry.name.match(/^session-.*\.json$/))
177
177
  .map(entry => {
178
- const filePath = path.join(chatsDir, entry.name);
178
+ const filePath = joinNativeBasePath(chatsDir, entry.name);
179
179
  // 文件名格式:session-2025-11-23T02-09-87570eb4.json
180
180
  // session-{timestamp}-{shortId}.json
181
181
  const match = entry.name.match(/^session-(.*)-([a-f0-9]+)\.json$/);
@@ -451,7 +451,7 @@ function deleteSession(sessionId) {
451
451
  * @returns {Object} 删除结果
452
452
  */
453
453
  function deleteProject(projectHash) {
454
- const projectDir = path.join(getTmpDir(), projectHash);
454
+ const projectDir = joinNativeBasePath(getTmpDir(), projectHash);
455
455
 
456
456
  if (!fs.existsSync(projectDir)) {
457
457
  throw new Error('Project not found');
@@ -634,8 +634,8 @@ function forkSession(sessionId) {
634
634
  };
635
635
 
636
636
  // 写入新文件
637
- const chatsDir = path.join(getTmpDir(), sourceSession.projectHash, 'chats');
638
- const newFilePath = path.join(chatsDir, newFileName);
637
+ const chatsDir = joinNativeBasePath(getTmpDir(), sourceSession.projectHash, 'chats');
638
+ const newFilePath = joinNativeBasePath(chatsDir, newFileName);
639
639
 
640
640
  try {
641
641
  fs.writeFileSync(newFilePath, JSON.stringify(newSession, null, 2), 'utf8');
@@ -685,5 +685,8 @@ module.exports = {
685
685
  saveSessionOrder,
686
686
  forkSession,
687
687
  getProjectPath,
688
- getProjectAndSessionCounts
688
+ getProjectAndSessionCounts,
689
+ _test: {
690
+ getTmpDir
691
+ }
689
692
  };
@@ -1,5 +1,4 @@
1
1
  const fs = require('fs');
2
- const path = require('path');
3
2
  const { NATIVE_PATHS } = require('../../config/paths');
4
3
 
5
4
  // Gemini 配置文件路径
@@ -8,7 +7,7 @@ function getEnvPath() {
8
7
  }
9
8
 
10
9
  function getSettingsPath() {
11
- return path.join(path.dirname(NATIVE_PATHS.gemini.env), 'settings.json');
10
+ return NATIVE_PATHS.gemini.settings;
12
11
  }
13
12
 
14
13
  // 备份文件路径
@@ -17,7 +16,7 @@ function getEnvBackupPath() {
17
16
  }
18
17
 
19
18
  function getSettingsBackupPath() {
20
- return path.join(path.dirname(NATIVE_PATHS.gemini.env), 'settings.json.cc-tool-backup');
19
+ return NATIVE_PATHS.gemini.settingsBackup;
21
20
  }
22
21
 
23
22
  // 检查配置文件是否存在
@@ -6,29 +6,24 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
- const os = require('os');
10
9
  const toml = require('@iarna/toml');
11
10
  const { spawn } = require('child_process');
12
11
  const http = require('http');
13
12
  const https = require('https');
14
13
  const { McpClient, buildMissingCommandMessage, createMissingCommandHint } = require('./mcp-client');
15
- const { NATIVE_PATHS, PATHS } = require('../../config/paths');
16
- const { resolvePreferredHomeDir } = require('../../utils/home-dir');
17
-
18
- const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
14
+ const { NATIVE_PATHS, PATHS, HOME_DIR, getNativePathDir, joinNativeBasePath } = require('../../config/paths');
19
15
 
20
16
  // MCP 配置文件路径
21
17
  const MCP_SERVERS_FILE = PATHS.mcpServers;
22
18
 
23
19
  // 各平台配置文件路径
24
- const CLAUDE_CONFIG_PATH = path.join(HOME_DIR, '.claude.json');
20
+ const CLAUDE_CONFIG_PATH = joinNativeBasePath(HOME_DIR, '.claude.json');
25
21
  const CODEX_CONFIG_PATH = NATIVE_PATHS.codex.config;
26
- const GEMINI_CONFIG_PATH = path.join(path.dirname(NATIVE_PATHS.gemini.env), 'settings.json');
27
- const OPENCODE_CONFIG_DIR = NATIVE_PATHS.opencode.config;
22
+ const GEMINI_CONFIG_PATH = NATIVE_PATHS.gemini.settings;
28
23
  const OPENCODE_CONFIG_PATHS = {
29
- jsonc: path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc'),
30
- json: path.join(OPENCODE_CONFIG_DIR, 'opencode.json'),
31
- legacy: path.join(OPENCODE_CONFIG_DIR, 'config.json')
24
+ jsonc: NATIVE_PATHS.opencode.configJsonc,
25
+ json: NATIVE_PATHS.opencode.configJson,
26
+ legacy: NATIVE_PATHS.opencode.configLegacy
32
27
  };
33
28
 
34
29
  // MCP 客户端连接池
@@ -279,7 +274,7 @@ function readJsonFile(filePath, defaultValue = {}) {
279
274
  * 安全写入 JSON 文件(原子写入)
280
275
  */
281
276
  function writeJsonFile(filePath, data) {
282
- ensureDir(path.dirname(filePath));
277
+ ensureDir(getNativePathDir(filePath));
283
278
  const tempPath = filePath + '.tmp';
284
279
  fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf-8');
285
280
  fs.renameSync(tempPath, filePath);
@@ -305,7 +300,7 @@ function readTomlFile(filePath, defaultValue = {}) {
305
300
  * 安全写入 TOML 文件(原子写入)
306
301
  */
307
302
  function writeTomlFile(filePath, data) {
308
- ensureDir(path.dirname(filePath));
303
+ ensureDir(getNativePathDir(filePath));
309
304
  const tempPath = filePath + '.tmp';
310
305
  fs.writeFileSync(tempPath, toml.stringify(data), 'utf-8');
311
306
  fs.renameSync(tempPath, filePath);
@@ -412,7 +407,7 @@ function readOpenCodeConfig() {
412
407
  * 写入 OpenCode 配置(保持 JSON 格式)
413
408
  */
414
409
  function writeOpenCodeConfig(filePath, data) {
415
- ensureDir(path.dirname(filePath));
410
+ ensureDir(getNativePathDir(filePath));
416
411
  const tempPath = filePath + '.tmp';
417
412
  fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf-8');
418
413
  fs.renameSync(tempPath, filePath);
@@ -4,7 +4,7 @@ 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 { NATIVE_PATHS, PATHS } = require('../../config/paths');
7
+ const { NATIVE_PATHS, PATHS, getNativePathDir } = require('../../config/paths');
8
8
  const claudeSettingsManager = require('./settings-manager');
9
9
  const codexSettingsManager = require('./codex-settings-manager');
10
10
  const geminiSettingsManager = require('./gemini-settings-manager');
@@ -35,7 +35,7 @@ function ensureFileMode(filePath, mode = 0o600) {
35
35
  }
36
36
 
37
37
  function writeJsonFile(filePath, value) {
38
- ensureDir(path.dirname(filePath));
38
+ ensureDir(getNativePathDir(filePath));
39
39
  fs.writeFileSync(filePath, JSON.stringify(value, null, 2), 'utf8');
40
40
  ensureFileMode(filePath);
41
41
  }
@@ -562,7 +562,7 @@ function applyGeminiOAuth(credential) {
562
562
  }
563
563
  };
564
564
 
565
- ensureDir(path.dirname(NATIVE_PATHS.gemini.oauthCredentialsEncrypted));
565
+ ensureDir(getNativePathDir(NATIVE_PATHS.gemini.oauthCredentialsEncrypted));
566
566
  fs.writeFileSync(
567
567
  NATIVE_PATHS.gemini.oauthCredentialsEncrypted,
568
568
  encryptGeminiPayload(JSON.stringify(payload, null, 2)),
@@ -4,7 +4,7 @@ const os = require('os');
4
4
  const http = require('http');
5
5
  const https = require('https');
6
6
  const { execSync, execFileSync } = require('child_process');
7
- const { PATHS, NATIVE_PATHS } = require('../../config/paths');
7
+ const { PATHS, NATIVE_PATHS, getNativePathDir, joinNativeBasePath } = require('../../config/paths');
8
8
  const { loadConfig } = require('../../config/loader');
9
9
  const { loadUIConfig, saveUIConfig } = require('./ui-config');
10
10
  const codexSettingsManager = require('./codex-settings-manager');
@@ -19,7 +19,7 @@ function normalizeType(type) {
19
19
  }
20
20
 
21
21
  function ensureParentDir(filePath) {
22
- const dir = path.dirname(filePath);
22
+ const dir = getNativePathDir(filePath);
23
23
  if (!fs.existsSync(dir)) {
24
24
  fs.mkdirSync(dir, { recursive: true });
25
25
  }
@@ -269,7 +269,7 @@ function buildGeminiCommand(type) {
269
269
  }
270
270
 
271
271
  function getOpenCodeManagedPluginPath() {
272
- return path.join(NATIVE_PATHS.opencode.config, 'plugins', MANAGED_OPENCODE_PLUGIN_FILE);
272
+ return joinNativeBasePath(NATIVE_PATHS.opencode.plugins, MANAGED_OPENCODE_PLUGIN_FILE);
273
273
  }
274
274
 
275
275
  function buildOpenCodePluginContent(type) {