coding-tool-x 3.3.7 → 3.3.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 (89) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +253 -326
  3. package/dist/web/assets/{Analytics-IW6eAy9u.js → Analytics-D6LzK9hk.js} +1 -1
  4. package/dist/web/assets/{ConfigTemplates-BPtkTMSc.js → ConfigTemplates-BUDYuxRi.js} +1 -1
  5. package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
  6. package/dist/web/assets/Home-D7KX7iF8.js +1 -0
  7. package/dist/web/assets/{PluginManager-BGx9MSDV.js → PluginManager-DTgQ--vB.js} +1 -1
  8. package/dist/web/assets/{ProjectList-BCn-mrCx.js → ProjectList-DMCiGmCT.js} +1 -1
  9. package/dist/web/assets/{SessionList-CzLfebJQ.js → SessionList-CRBsdVRe.js} +1 -1
  10. package/dist/web/assets/{SkillManager-CXz2vBQx.js → SkillManager-DMwx2Q4k.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-CHtgMfKc.js → WorkspaceManager-DapB4ljL.js} +1 -1
  12. package/dist/web/assets/{icons-B29onFfZ.js → icons-B5Pl4lrD.js} +1 -1
  13. package/dist/web/assets/index-CL-qpoJ_.js +2 -0
  14. package/dist/web/assets/index-D_5dRFOL.css +1 -0
  15. package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
  16. package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
  17. package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
  18. package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
  19. package/dist/web/index.html +7 -7
  20. package/docs/home.png +0 -0
  21. package/package.json +14 -5
  22. package/src/commands/daemon.js +3 -2
  23. package/src/commands/security.js +1 -2
  24. package/src/commands/toggle-proxy.js +100 -5
  25. package/src/config/paths.js +718 -90
  26. package/src/server/api/agents.js +1 -1
  27. package/src/server/api/channels.js +9 -0
  28. package/src/server/api/claude-hooks.js +13 -8
  29. package/src/server/api/codex-channels.js +9 -0
  30. package/src/server/api/codex-proxy.js +27 -15
  31. package/src/server/api/gemini-proxy.js +22 -11
  32. package/src/server/api/hooks.js +45 -0
  33. package/src/server/api/oauth-credentials.js +163 -0
  34. package/src/server/api/opencode-proxy.js +22 -10
  35. package/src/server/api/plugins.js +2 -1
  36. package/src/server/api/proxy.js +39 -44
  37. package/src/server/api/skills.js +91 -13
  38. package/src/server/api/ui-config.js +5 -0
  39. package/src/server/codex-proxy-server.js +90 -70
  40. package/src/server/gemini-proxy-server.js +107 -88
  41. package/src/server/index.js +2 -0
  42. package/src/server/opencode-proxy-server.js +381 -225
  43. package/src/server/proxy-server.js +86 -60
  44. package/src/server/services/alias.js +3 -3
  45. package/src/server/services/channels.js +21 -24
  46. package/src/server/services/codex-channels.js +158 -255
  47. package/src/server/services/codex-config.js +2 -5
  48. package/src/server/services/codex-env-manager.js +423 -0
  49. package/src/server/services/codex-settings-manager.js +21 -357
  50. package/src/server/services/codex-statistics-service.js +3 -27
  51. package/src/server/services/config-export-service.js +43 -9
  52. package/src/server/services/config-registry-service.js +3 -2
  53. package/src/server/services/config-sync-manager.js +1 -1
  54. package/src/server/services/favorites.js +4 -3
  55. package/src/server/services/gemini-channels.js +14 -12
  56. package/src/server/services/gemini-statistics-service.js +3 -25
  57. package/src/server/services/mcp-service.js +35 -19
  58. package/src/server/services/model-detector.js +4 -3
  59. package/src/server/services/native-keychain.js +243 -0
  60. package/src/server/services/native-oauth-adapters.js +891 -0
  61. package/src/server/services/network-access.js +39 -1
  62. package/src/server/services/notification-hooks.js +951 -0
  63. package/src/server/services/oauth-credentials-service.js +786 -0
  64. package/src/server/services/oauth-utils.js +49 -0
  65. package/src/server/services/opencode-channels.js +19 -15
  66. package/src/server/services/opencode-sessions.js +2 -2
  67. package/src/server/services/opencode-settings-manager.js +169 -16
  68. package/src/server/services/opencode-statistics-service.js +3 -27
  69. package/src/server/services/plugins-service.js +115 -15
  70. package/src/server/services/prompts-service.js +2 -3
  71. package/src/server/services/proxy-log-helper.js +242 -0
  72. package/src/server/services/proxy-runtime.js +6 -4
  73. package/src/server/services/repo-scanner-base.js +12 -4
  74. package/src/server/services/request-logger.js +7 -7
  75. package/src/server/services/security-config.js +4 -4
  76. package/src/server/services/session-cache.js +2 -2
  77. package/src/server/services/sessions.js +2 -2
  78. package/src/server/services/settings-manager.js +13 -0
  79. package/src/server/services/skill-service.js +867 -368
  80. package/src/server/services/statistics-service.js +5 -5
  81. package/src/server/services/ui-config.js +4 -3
  82. package/src/server/services/workspace-service.js +1 -1
  83. package/src/server/websocket-server.js +5 -4
  84. package/dist/web/assets/Home-BsSioaaB.css +0 -1
  85. package/dist/web/assets/Home-obifg_9E.js +0 -1
  86. package/dist/web/assets/index-C7LPdVsN.js +0 -2
  87. package/dist/web/assets/index-eEmjZKWP.css +0 -1
  88. package/docs/bannel.png +0 -0
  89. package/docs/model-redirection.md +0 -251
@@ -3,7 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
- const { resolvePreferredHomeDir } = require('../utils/home-dir');
6
+ const { resolvePreferredHomeDir, isWindowsLikePlatform } = require('../utils/home-dir');
7
7
 
8
8
  const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
9
9
 
@@ -11,6 +11,31 @@ const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homed
11
11
  const CC_TOOL_BASE_DIR = path.join(HOME_DIR, '.cc-tool');
12
12
  // 兼容旧变量名,避免外部调用方断裂
13
13
  const CTX_BASE_DIR = CC_TOOL_BASE_DIR;
14
+ const CONFIG_DIR = path.join(CC_TOOL_BASE_DIR, 'config');
15
+ const CONFIGS_DIR = path.join(CC_TOOL_BASE_DIR, 'configs');
16
+ const STORAGE_DIR = path.join(CC_TOOL_BASE_DIR, 'storage');
17
+ const CHANNELS_DIR = path.join(STORAGE_DIR, 'channels');
18
+ const ACTIVE_CHANNELS_DIR = path.join(CHANNELS_DIR, 'active');
19
+ const STATS_DIR = path.join(STORAGE_DIR, 'stats');
20
+ const DAILY_STATS_DIR = path.join(STATS_DIR, 'daily');
21
+ const REQUEST_LOGS_DIR = path.join(STATS_DIR, 'request-logs');
22
+ const RUNTIME_DIR = path.join(STORAGE_DIR, 'runtime');
23
+ const CACHE_DIR = path.join(STORAGE_DIR, 'cache');
24
+ const CACHE_SKILLS_DIR = path.join(CACHE_DIR, 'skills');
25
+ const CACHE_PLUGINS_DIR = path.join(CACHE_DIR, 'plugins');
26
+ const REPOS_DIR = path.join(STORAGE_DIR, 'repos');
27
+ const REPOS_SKILLS_DIR = path.join(REPOS_DIR, 'skills');
28
+ const REPOS_PLUGINS_DIR = path.join(REPOS_DIR, 'plugins');
29
+ const LOCAL_DIR = path.join(STORAGE_DIR, 'local');
30
+ const LOCAL_SKILLS_DIR = path.join(LOCAL_DIR, 'skills');
31
+ const REQUESTS_DIR = path.join(STORAGE_DIR, 'requests');
32
+ const BACKUPS_DIR = path.join(STORAGE_DIR, 'backups');
33
+ const SCRIPTS_DIR = path.join(STORAGE_DIR, 'scripts');
34
+ const LEGACY_DIR = path.join(STORAGE_DIR, 'legacy');
35
+ const LEGACY_CONFLICTS_DIR = path.join(LEGACY_DIR, 'root-conflicts');
36
+ const LEGACY_ROOT_BACKUPS_DIR = path.join(LEGACY_DIR, 'root-backups');
37
+ const LEGACY_IMPORT_STATE_FILE = path.join(LEGACY_DIR, 'import-state.json');
38
+ const LEGACY_STATS_DIR = path.join(LEGACY_DIR, 'stats');
14
39
 
15
40
  // 旧目录(升级时自动合并到 ~/.cc-tool)
16
41
  const LEGACY_BASE_DIRS = [
@@ -41,148 +66,744 @@ function mergeDirectory(sourceDir, targetDir) {
41
66
  }
42
67
  }
43
68
 
44
- function ensureStorageDirMigrated() {
45
- if (migrationChecked) {
46
- return CC_TOOL_BASE_DIR;
69
+ function ensureDir(dirPath) {
70
+ if (!fs.existsSync(dirPath)) {
71
+ fs.mkdirSync(dirPath, { recursive: true });
47
72
  }
48
- migrationChecked = true;
73
+ }
49
74
 
50
- if (!fs.existsSync(CC_TOOL_BASE_DIR)) {
51
- fs.mkdirSync(CC_TOOL_BASE_DIR, { recursive: true });
75
+ function isPlainObject(value) {
76
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
77
+ }
78
+
79
+ function cloneJsonValue(value) {
80
+ if (value === undefined) {
81
+ return value;
52
82
  }
83
+ return JSON.parse(JSON.stringify(value));
84
+ }
53
85
 
54
- LEGACY_BASE_DIRS.forEach((legacyDir) => {
55
- if (!fs.existsSync(legacyDir)) return;
86
+ function filesAreEqual(sourcePath, targetPath) {
87
+ try {
88
+ const sourceStat = fs.statSync(sourcePath);
89
+ const targetStat = fs.statSync(targetPath);
90
+ if (!sourceStat.isFile() || !targetStat.isFile()) {
91
+ return false;
92
+ }
93
+ if (sourceStat.size !== targetStat.size) {
94
+ return false;
95
+ }
96
+ return fs.readFileSync(sourcePath).equals(fs.readFileSync(targetPath));
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ function getUniquePath(targetPath) {
103
+ if (!fs.existsSync(targetPath)) {
104
+ return targetPath;
105
+ }
106
+
107
+ const timestamp = Date.now();
108
+ let counter = 1;
109
+ let candidate = `${targetPath}.bak-${timestamp}`;
110
+ while (fs.existsSync(candidate)) {
111
+ candidate = `${targetPath}.bak-${timestamp}-${counter}`;
112
+ counter += 1;
113
+ }
114
+ return candidate;
115
+ }
116
+
117
+ function tryRemoveEmptyDir(dirPath) {
118
+ try {
119
+ if (!fs.existsSync(dirPath)) return;
120
+ const stat = fs.statSync(dirPath);
121
+ if (!stat.isDirectory()) return;
122
+ if (fs.readdirSync(dirPath).length === 0) {
123
+ fs.rmdirSync(dirPath);
124
+ }
125
+ } catch {
126
+ // ignore cleanup failures
127
+ }
128
+ }
129
+
130
+ function getRelativeLegacyPath(entryPath) {
131
+ const relativePath = path.relative(CC_TOOL_BASE_DIR, entryPath);
132
+ if (!relativePath || relativePath.startsWith('..')) {
133
+ return path.basename(entryPath);
134
+ }
135
+ return relativePath;
136
+ }
137
+
138
+ function archiveLegacyEntry(entryPath, archiveRootDir) {
139
+ if (!fs.existsSync(entryPath)) {
140
+ return '';
141
+ }
142
+ const archivePath = getUniquePath(path.join(archiveRootDir, getRelativeLegacyPath(entryPath)));
143
+ try {
144
+ moveFile(entryPath, archivePath);
145
+ } catch (error) {
146
+ if (!fs.existsSync(entryPath)) {
147
+ return '';
148
+ }
149
+ throw error;
150
+ }
151
+ return archivePath;
152
+ }
153
+
154
+ function moveFile(sourcePath, targetPath) {
155
+ ensureDir(path.dirname(targetPath));
156
+ try {
157
+ fs.renameSync(sourcePath, targetPath);
158
+ return;
159
+ } catch (error) {
160
+ fs.copyFileSync(sourcePath, targetPath);
56
161
  try {
57
- mergeDirectory(legacyDir, CC_TOOL_BASE_DIR);
58
- } catch (error) {
59
- console.warn(`[paths] 迁移目录失败: ${legacyDir} -> ${CC_TOOL_BASE_DIR}`, error.message);
162
+ fs.unlinkSync(sourcePath);
163
+ } catch {
164
+ console.warn(`[paths] 清理旧文件失败: ${sourcePath}`);
60
165
  }
61
- });
166
+ }
167
+ }
62
168
 
63
- return CC_TOOL_BASE_DIR;
169
+ function readJsonFileSafe(filePath) {
170
+ try {
171
+ return {
172
+ ok: true,
173
+ value: JSON.parse(fs.readFileSync(filePath, 'utf8'))
174
+ };
175
+ } catch {
176
+ return {
177
+ ok: false,
178
+ value: null
179
+ };
180
+ }
64
181
  }
65
182
 
66
- // 路径配置
67
- const PATHS = {
68
- // 基础目录
69
- base: CC_TOOL_BASE_DIR,
183
+ function buildArrayItemIdentity(item) {
184
+ if (item === null) {
185
+ return 'null';
186
+ }
187
+ if (typeof item !== 'object') {
188
+ return `${typeof item}:${String(item)}`;
189
+ }
190
+ if (item.id) {
191
+ return `id:${item.id}`;
192
+ }
193
+ if (item.key) {
194
+ return `key:${item.key}`;
195
+ }
196
+ if (item.repoUrl) {
197
+ return `repo:${item.repoUrl}`;
198
+ }
199
+ if (item.path) {
200
+ return `path:${item.path}`;
201
+ }
202
+ if (item.url) {
203
+ return `url:${item.url}`;
204
+ }
205
+ if (item.owner && item.name) {
206
+ return `repo:${item.owner}/${item.name}`;
207
+ }
208
+ if (item.name) {
209
+ return `name:${item.name}`;
210
+ }
211
+ return `json:${JSON.stringify(item)}`;
212
+ }
70
213
 
71
- // 项目目录(存储项目配置和会话)
72
- projects: path.join(CC_TOOL_BASE_DIR, 'projects'),
214
+ function mergeJsonValues(primaryValue, secondaryValue) {
215
+ if (primaryValue === undefined) {
216
+ return cloneJsonValue(secondaryValue);
217
+ }
218
+ if (secondaryValue === undefined) {
219
+ return cloneJsonValue(primaryValue);
220
+ }
73
221
 
74
- // 配置文件目录
75
- config: path.join(CC_TOOL_BASE_DIR, 'config'),
76
- configFile: path.join(CC_TOOL_BASE_DIR, 'config.json'),
222
+ if (Array.isArray(primaryValue) && Array.isArray(secondaryValue)) {
223
+ const merged = [];
224
+ const seen = new Map();
225
+
226
+ const appendItem = (item) => {
227
+ const identity = buildArrayItemIdentity(item);
228
+ if (!seen.has(identity)) {
229
+ seen.set(identity, merged.length);
230
+ merged.push(cloneJsonValue(item));
231
+ return;
232
+ }
233
+
234
+ const index = seen.get(identity);
235
+ merged[index] = mergeJsonValues(merged[index], item);
236
+ };
237
+
238
+ primaryValue.forEach(appendItem);
239
+ secondaryValue.forEach(appendItem);
240
+ return merged;
241
+ }
77
242
 
78
- // 日志目录
79
- logs: path.join(CC_TOOL_BASE_DIR, 'logs'),
243
+ if (isPlainObject(primaryValue) && isPlainObject(secondaryValue)) {
244
+ const merged = cloneJsonValue(primaryValue);
245
+ Object.keys(secondaryValue).forEach((key) => {
246
+ if (!(key in merged)) {
247
+ merged[key] = cloneJsonValue(secondaryValue[key]);
248
+ return;
249
+ }
250
+ merged[key] = mergeJsonValues(merged[key], secondaryValue[key]);
251
+ });
252
+ return merged;
253
+ }
80
254
 
81
- // 别名存储
82
- aliases: path.join(CC_TOOL_BASE_DIR, 'aliases.json'),
255
+ return cloneJsonValue(primaryValue);
256
+ }
257
+
258
+ function writeJsonFile(targetPath, value) {
259
+ ensureDir(path.dirname(targetPath));
260
+ fs.writeFileSync(targetPath, JSON.stringify(value, null, 2), 'utf8');
261
+ }
83
262
 
84
- // 收藏夹存储
85
- favorites: path.join(CC_TOOL_BASE_DIR, 'favorites.json'),
263
+ function copyFilePreserveMode(sourcePath, targetPath) {
264
+ ensureDir(path.dirname(targetPath));
265
+ fs.copyFileSync(sourcePath, targetPath);
266
+ try {
267
+ fs.chmodSync(targetPath, fs.statSync(sourcePath).mode);
268
+ } catch {
269
+ // ignore permission sync failures
270
+ }
271
+ }
272
+
273
+ function resolveFileConflict(sourcePath, targetPath) {
274
+ if (!fs.existsSync(sourcePath) || !fs.existsSync(targetPath)) {
275
+ return;
276
+ }
277
+
278
+ const sourceStat = fs.statSync(sourcePath);
279
+ const targetStat = fs.statSync(targetPath);
280
+ const preferSource = sourceStat.mtimeMs >= targetStat.mtimeMs;
281
+ const primaryPath = preferSource ? sourcePath : targetPath;
282
+ const secondaryPath = preferSource ? targetPath : sourcePath;
283
+ const primaryJson = readJsonFileSafe(primaryPath);
284
+ const secondaryJson = readJsonFileSafe(secondaryPath);
285
+
286
+ if (primaryJson.ok && secondaryJson.ok) {
287
+ const merged = mergeJsonValues(primaryJson.value, secondaryJson.value);
288
+ writeJsonFile(targetPath, merged);
289
+ const archivedPath = archiveLegacyEntry(sourcePath, LEGACY_CONFLICTS_DIR);
290
+ if (archivedPath) {
291
+ console.warn(`[paths] 已归档并合并冲突旧文件: ${sourcePath} -> ${archivedPath}`);
292
+ }
293
+ return;
294
+ }
295
+
296
+ if (preferSource) {
297
+ copyFilePreserveMode(sourcePath, targetPath);
298
+ }
299
+
300
+ const archivedPath = archiveLegacyEntry(sourcePath, LEGACY_CONFLICTS_DIR);
301
+ if (archivedPath) {
302
+ console.warn(`[paths] 已归档冲突旧文件,保留较新版本: ${sourcePath} -> ${archivedPath}`);
303
+ }
304
+ }
305
+
306
+ function relocateEntry(sourcePath, targetPath) {
307
+ if (!sourcePath || !targetPath || sourcePath === targetPath || !fs.existsSync(sourcePath)) {
308
+ return;
309
+ }
310
+
311
+ let sourceStat;
312
+ try {
313
+ sourceStat = fs.statSync(sourcePath);
314
+ } catch {
315
+ return;
316
+ }
317
+
318
+ if (sourceStat.isDirectory()) {
319
+ if (!fs.existsSync(targetPath)) {
320
+ ensureDir(path.dirname(targetPath));
321
+ try {
322
+ fs.renameSync(sourcePath, targetPath);
323
+ return;
324
+ } catch {
325
+ ensureDir(targetPath);
326
+ }
327
+ } else {
328
+ ensureDir(targetPath);
329
+ }
330
+
331
+ const entries = fs.readdirSync(sourcePath);
332
+ entries.forEach((entry) => {
333
+ relocateEntry(path.join(sourcePath, entry), path.join(targetPath, entry));
334
+ });
335
+ tryRemoveEmptyDir(sourcePath);
336
+ return;
337
+ }
338
+
339
+ if (!fs.existsSync(targetPath)) {
340
+ moveFile(sourcePath, targetPath);
341
+ return;
342
+ }
343
+
344
+ if (filesAreEqual(sourcePath, targetPath)) {
345
+ try {
346
+ fs.unlinkSync(sourcePath);
347
+ } catch {
348
+ // ignore duplicate cleanup failures
349
+ }
350
+ return;
351
+ }
352
+
353
+ resolveFileConflict(sourcePath, targetPath);
354
+ }
355
+
356
+ function rootEntry(name) {
357
+ return path.join(CC_TOOL_BASE_DIR, name);
358
+ }
359
+
360
+ function getRepoScannerReposPath(type) {
361
+ return path.join(REPOS_DIR, `${type}.json`);
362
+ }
363
+
364
+ function getRepoScannerCachePath(type) {
365
+ return path.join(CACHE_DIR, `${type}-cache.json`);
366
+ }
367
+
368
+ const PATHS = {
369
+ // 基础目录
370
+ base: CC_TOOL_BASE_DIR,
371
+ config: CONFIG_DIR,
372
+ configs: CONFIGS_DIR,
373
+ storage: STORAGE_DIR,
374
+ logs: path.join(CC_TOOL_BASE_DIR, 'logs'),
375
+ projects: path.join(CC_TOOL_BASE_DIR, 'projects'),
376
+ plugins: path.join(CC_TOOL_BASE_DIR, 'plugins'),
377
+
378
+ // 全局配置
379
+ configFile: path.join(CONFIG_DIR, 'config.json'),
380
+ uiConfig: path.join(CONFIG_DIR, 'ui-config.json'),
381
+ prompts: path.join(CONFIG_DIR, 'prompts.json'),
382
+ mcpConfig: path.join(CONFIG_DIR, 'mcp-servers.json'),
383
+ mcpServers: path.join(CONFIG_DIR, 'mcp-servers.json'),
384
+ configRegistry: path.join(CONFIG_DIR, 'config-registry.json'),
385
+ oauthCredentials: path.join(CONFIG_DIR, 'oauth-credentials.json'),
386
+ security: path.join(CONFIG_DIR, 'security.json'),
387
+ workspaces: path.join(CONFIG_DIR, 'workspaces.json'),
388
+ aliases: path.join(CONFIG_DIR, 'aliases.json'),
389
+ favorites: path.join(CONFIG_DIR, 'favorites.json'),
390
+ projectOrder: path.join(CONFIG_DIR, 'project-order.json'),
391
+ sessionOrder: path.join(CONFIG_DIR, 'session-order.json'),
392
+ forkRelations: path.join(CONFIG_DIR, 'fork-relations.json'),
393
+ opencodeProjectOrder: path.join(CONFIG_DIR, 'opencode-project-order.json'),
394
+ opencodeSessionOrder: path.join(CONFIG_DIR, 'opencode-session-order.json'),
86
395
 
87
396
  // 渠道配置
397
+ channelsDir: CHANNELS_DIR,
88
398
  channels: {
89
- claude: path.join(CC_TOOL_BASE_DIR, 'channels.json'),
90
- codex: path.join(CC_TOOL_BASE_DIR, 'codex-channels.json'),
91
- gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-channels.json'),
92
- opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-channels.json')
399
+ claude: path.join(CHANNELS_DIR, 'claude.json'),
400
+ codex: path.join(CHANNELS_DIR, 'codex.json'),
401
+ gemini: path.join(CHANNELS_DIR, 'gemini.json'),
402
+ opencode: path.join(CHANNELS_DIR, 'opencode.json')
93
403
  },
94
-
95
- // 激活渠道标记
404
+ activeChannelDir: ACTIVE_CHANNELS_DIR,
96
405
  activeChannel: {
97
- claude: path.join(CC_TOOL_BASE_DIR, 'active-channel.json'),
98
- codex: path.join(CC_TOOL_BASE_DIR, 'codex-active-channel.json'),
99
- gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-active-channel.json'),
100
- opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-active-channel.json')
406
+ claude: path.join(ACTIVE_CHANNELS_DIR, 'claude.json'),
407
+ codex: path.join(ACTIVE_CHANNELS_DIR, 'codex.json'),
408
+ gemini: path.join(ACTIVE_CHANNELS_DIR, 'gemini.json'),
409
+ opencode: path.join(ACTIVE_CHANNELS_DIR, 'opencode.json')
101
410
  },
102
411
 
103
- // 统计数据
412
+ // 统计与日志快照
104
413
  statistics: {
105
- claude: path.join(CC_TOOL_BASE_DIR, 'statistics.json'),
106
- codex: path.join(CC_TOOL_BASE_DIR, 'codex-statistics.json'),
107
- gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-statistics.json'),
108
- opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-statistics.json'),
109
- dailyStats: {
110
- claude: path.join(CC_TOOL_BASE_DIR, 'daily-stats'),
111
- codex: path.join(CC_TOOL_BASE_DIR, 'codex-daily-stats'),
112
- gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-daily-stats'),
113
- opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-daily-stats')
414
+ dir: STATS_DIR,
415
+ summary: path.join(STATS_DIR, 'statistics.json'),
416
+ dailyStats: DAILY_STATS_DIR,
417
+ requestLogs: REQUEST_LOGS_DIR,
418
+ proxyLogs: path.join(STATS_DIR, 'proxy-logs.json'),
419
+ legacy: {
420
+ claudeSummary: path.join(LEGACY_STATS_DIR, 'statistics.json'),
421
+ codexSummary: path.join(LEGACY_STATS_DIR, 'codex-statistics.json'),
422
+ geminiSummary: path.join(LEGACY_STATS_DIR, 'gemini-statistics.json'),
423
+ opencodeSummary: path.join(LEGACY_STATS_DIR, 'opencode-statistics.json'),
424
+ codexDaily: path.join(LEGACY_STATS_DIR, 'codex-daily-stats'),
425
+ geminiDaily: path.join(LEGACY_STATS_DIR, 'gemini-daily-stats'),
426
+ opencodeDaily: path.join(LEGACY_STATS_DIR, 'opencode-daily-stats')
114
427
  }
115
428
  },
116
429
 
117
- // 会话缓存
118
- sessionCache: path.join(CC_TOOL_BASE_DIR, 'session-cache.json'),
119
-
120
- // 项目顺序
121
- projectOrder: path.join(CC_TOOL_BASE_DIR, 'project-order.json'),
430
+ // 运行时、缓存、备份
431
+ sessionCache: path.join(CACHE_DIR, 'session-cache.json'),
432
+ sessionHasCache: path.join(CACHE_DIR, 'session-has-cache.json'),
433
+ channelModels: path.join(CACHE_DIR, 'channel-models.json'),
434
+ envBackups: path.join(BACKUPS_DIR, 'env'),
435
+ proxyRuntime: {
436
+ claude: path.join(RUNTIME_DIR, 'claude-proxy.json'),
437
+ codex: path.join(RUNTIME_DIR, 'codex-proxy.json'),
438
+ gemini: path.join(RUNTIME_DIR, 'gemini-proxy.json'),
439
+ opencode: path.join(RUNTIME_DIR, 'opencode-proxy.json')
440
+ },
122
441
 
123
- // 环境备份
124
- envBackups: path.join(CC_TOOL_BASE_DIR, 'env-backups'),
442
+ // 请求记录
443
+ requestSnapshots: {
444
+ claude: path.join(REQUESTS_DIR, 'claude.jsonl'),
445
+ codex: path.join(REQUESTS_DIR, 'codex.jsonl'),
446
+ gemini: path.join(REQUESTS_DIR, 'gemini.jsonl'),
447
+ opencode: path.join(REQUESTS_DIR, 'opencode.jsonl')
448
+ },
449
+ claudeRequestTemplate: path.join(REQUESTS_DIR, 'claude-request-template.json'),
125
450
 
126
- // UI 配置
127
- uiConfig: path.join(CC_TOOL_BASE_DIR, 'ui-config.json'),
451
+ // 脚本
452
+ notifyHook: path.join(SCRIPTS_DIR, 'notify-hook.js'),
128
453
 
129
- // 飞书通知脚本
130
- notifyHook: path.join(CC_TOOL_BASE_DIR, 'notify-hook.js'),
454
+ // 技能与插件(cc-tool 托管部分)
455
+ localSkills: {
456
+ claude: path.join(LOCAL_SKILLS_DIR, 'claude'),
457
+ codex: path.join(LOCAL_SKILLS_DIR, 'codex'),
458
+ gemini: path.join(LOCAL_SKILLS_DIR, 'gemini'),
459
+ opencode: path.join(LOCAL_SKILLS_DIR, 'opencode')
460
+ },
461
+ skillRepos: {
462
+ claude: path.join(REPOS_SKILLS_DIR, 'claude.json'),
463
+ codex: path.join(REPOS_SKILLS_DIR, 'codex.json'),
464
+ gemini: path.join(REPOS_SKILLS_DIR, 'gemini.json'),
465
+ opencode: path.join(REPOS_SKILLS_DIR, 'opencode.json')
466
+ },
467
+ skillCaches: {
468
+ claude: path.join(CACHE_SKILLS_DIR, 'claude.json'),
469
+ codex: path.join(CACHE_SKILLS_DIR, 'codex.json'),
470
+ gemini: path.join(CACHE_SKILLS_DIR, 'gemini.json'),
471
+ opencode: path.join(CACHE_SKILLS_DIR, 'opencode.json')
472
+ },
473
+ pluginRepos: {
474
+ claude: path.join(REPOS_PLUGINS_DIR, 'claude.json'),
475
+ opencode: path.join(REPOS_PLUGINS_DIR, 'opencode.json')
476
+ },
477
+ pluginMarketCache: {
478
+ claude: path.join(CACHE_PLUGINS_DIR, 'claude-market.json'),
479
+ opencode: path.join(CACHE_PLUGINS_DIR, 'opencode-market.json')
480
+ },
131
481
 
132
- // Skills 安装目录(注意:这个仍使用 Claude 原生路径)
482
+ // 原生路径兼容
133
483
  skills: path.join(HOME_DIR, '.claude', 'skills'),
134
484
 
135
- // MCP 配置(注意:这个仍使用 Claude 原生路径)
136
- mcpConfig: path.join(CC_TOOL_BASE_DIR, 'mcp-config.json'),
137
-
138
- // Prompts
139
- prompts: path.join(CC_TOOL_BASE_DIR, 'prompts.json'),
485
+ // 旧版本遗留文件搬迁目标
486
+ legacy: {
487
+ dir: LEGACY_DIR,
488
+ rootConflicts: LEGACY_CONFLICTS_DIR,
489
+ rootBackups: LEGACY_ROOT_BACKUPS_DIR,
490
+ importState: LEGACY_IMPORT_STATE_FILE,
491
+ oauthTokens: path.join(LEGACY_DIR, 'oauth-tokens.json')
492
+ },
140
493
 
141
- // 代理运行时状态
142
- proxyRuntime: {
143
- claude: path.join(CC_TOOL_BASE_DIR, 'proxy-runtime.json'),
144
- codex: path.join(CC_TOOL_BASE_DIR, 'codex-proxy-runtime.json'),
145
- gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-proxy-runtime.json'),
146
- opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-proxy-runtime.json')
494
+ // RepoScannerBase 的通用路径
495
+ repoScanner: {
496
+ reposDir: REPOS_DIR,
497
+ cacheDir: CACHE_DIR
147
498
  }
148
499
  };
149
500
 
501
+ const LEGACY_STORAGE_RELOCATIONS = [
502
+ // 全局配置文件
503
+ { source: rootEntry('config.json'), target: PATHS.configFile },
504
+ { source: rootEntry('ui-config.json'), target: PATHS.uiConfig },
505
+ { source: rootEntry('prompts.json'), target: PATHS.prompts },
506
+ { source: rootEntry('mcp-servers.json'), target: PATHS.mcpServers },
507
+ { source: rootEntry('mcp-config.json'), target: PATHS.mcpServers },
508
+ { source: rootEntry('config-registry.json'), target: PATHS.configRegistry },
509
+ { source: rootEntry('oauth-credentials.json'), target: PATHS.oauthCredentials },
510
+ { source: rootEntry('security.json'), target: PATHS.security },
511
+ { source: rootEntry('workspaces.json'), target: PATHS.workspaces },
512
+ { source: rootEntry('aliases.json'), target: PATHS.aliases },
513
+ { source: rootEntry('favorites.json'), target: PATHS.favorites },
514
+ { source: rootEntry('project-order.json'), target: PATHS.projectOrder },
515
+ { source: rootEntry('session-order.json'), target: PATHS.sessionOrder },
516
+ { source: rootEntry('fork-relations.json'), target: PATHS.forkRelations },
517
+ { source: rootEntry('opencode-project-order.json'), target: PATHS.opencodeProjectOrder },
518
+ { source: rootEntry('opencode-session-order.json'), target: PATHS.opencodeSessionOrder },
519
+
520
+ // 渠道相关
521
+ { source: rootEntry('channels.json'), target: PATHS.channels.claude },
522
+ { source: rootEntry('codex-channels.json'), target: PATHS.channels.codex },
523
+ { source: rootEntry('gemini-channels.json'), target: PATHS.channels.gemini },
524
+ { source: rootEntry('opencode-channels.json'), target: PATHS.channels.opencode },
525
+ { source: rootEntry('active-channel.json'), target: PATHS.activeChannel.claude },
526
+ { source: rootEntry('codex-active-channel.json'), target: PATHS.activeChannel.codex },
527
+ { source: rootEntry('gemini-active-channel.json'), target: PATHS.activeChannel.gemini },
528
+ { source: rootEntry('opencode-active-channel.json'), target: PATHS.activeChannel.opencode },
529
+
530
+ // 统计与日志
531
+ { source: rootEntry('statistics.json'), target: PATHS.statistics.summary },
532
+ { source: rootEntry('daily-stats'), target: PATHS.statistics.dailyStats },
533
+ { source: rootEntry('request-logs'), target: PATHS.statistics.requestLogs },
534
+ { source: rootEntry('proxy-logs.json'), target: PATHS.statistics.proxyLogs },
535
+ { source: rootEntry('codex-statistics.json'), target: PATHS.statistics.legacy.codexSummary },
536
+ { source: rootEntry('gemini-statistics.json'), target: PATHS.statistics.legacy.geminiSummary },
537
+ { source: rootEntry('opencode-statistics.json'), target: PATHS.statistics.legacy.opencodeSummary },
538
+ { source: rootEntry('codex-daily-stats'), target: PATHS.statistics.legacy.codexDaily },
539
+ { source: rootEntry('gemini-daily-stats'), target: PATHS.statistics.legacy.geminiDaily },
540
+ { source: rootEntry('opencode-daily-stats'), target: PATHS.statistics.legacy.opencodeDaily },
541
+
542
+ // 缓存、运行时、脚本
543
+ { source: rootEntry('session-cache.json'), target: PATHS.sessionCache },
544
+ { source: rootEntry('session-has-cache.json'), target: PATHS.sessionHasCache },
545
+ { source: rootEntry('channel-models.json'), target: PATHS.channelModels },
546
+ { source: rootEntry('proxy-runtime.json'), target: PATHS.proxyRuntime.claude },
547
+ { source: rootEntry('claude-proxy-runtime.json'), target: PATHS.proxyRuntime.claude },
548
+ { source: rootEntry('codex-proxy-runtime.json'), target: PATHS.proxyRuntime.codex },
549
+ { source: rootEntry('gemini-proxy-runtime.json'), target: PATHS.proxyRuntime.gemini },
550
+ { source: rootEntry('opencode-proxy-runtime.json'), target: PATHS.proxyRuntime.opencode },
551
+ { source: rootEntry('notify-hook.js'), target: PATHS.notifyHook },
552
+ { source: rootEntry('claude-request-template.json'), target: PATHS.claudeRequestTemplate },
553
+ { source: rootEntry('claude-requests.jsonl'), target: PATHS.requestSnapshots.claude },
554
+ { source: rootEntry('codex-requests.jsonl'), target: PATHS.requestSnapshots.codex },
555
+ { source: rootEntry('gemini-requests.jsonl'), target: PATHS.requestSnapshots.gemini },
556
+ { source: rootEntry('opencode-requests.jsonl'), target: PATHS.requestSnapshots.opencode },
557
+
558
+ // 备份
559
+ { source: rootEntry('env-backups'), target: PATHS.envBackups },
560
+
561
+ // 技能托管
562
+ { source: rootEntry('skills'), target: PATHS.localSkills.claude },
563
+ { source: rootEntry('codex-skills'), target: PATHS.localSkills.codex },
564
+ { source: rootEntry('gemini-skills'), target: PATHS.localSkills.gemini },
565
+ { source: rootEntry('opencode-skills'), target: PATHS.localSkills.opencode },
566
+ { source: rootEntry('skill-repos.json'), target: PATHS.skillRepos.claude },
567
+ { source: rootEntry('codex-skill-repos.json'), target: PATHS.skillRepos.codex },
568
+ { source: rootEntry('gemini-skill-repos.json'), target: PATHS.skillRepos.gemini },
569
+ { source: rootEntry('opencode-skill-repos.json'), target: PATHS.skillRepos.opencode },
570
+ { source: rootEntry('skills-cache.json'), target: PATHS.skillCaches.claude },
571
+ { source: rootEntry('codex-skills-cache.json'), target: PATHS.skillCaches.codex },
572
+ { source: rootEntry('gemini-skills-cache.json'), target: PATHS.skillCaches.gemini },
573
+ { source: rootEntry('opencode-skills-cache.json'), target: PATHS.skillCaches.opencode },
574
+
575
+ // 插件仓库缓存
576
+ { source: rootEntry('plugin-repos.json'), target: PATHS.pluginRepos.claude },
577
+ { source: rootEntry('opencode-plugin-repos.json'), target: PATHS.pluginRepos.opencode },
578
+ { source: rootEntry('plugins-market-cache.json'), target: PATHS.pluginMarketCache.claude },
579
+ { source: rootEntry('opencode-plugins-market-cache.json'), target: PATHS.pluginMarketCache.opencode },
580
+
581
+ // 已废弃但需要保留的数据
582
+ { source: rootEntry('oauth-tokens.json'), target: PATHS.legacy.oauthTokens }
583
+ ];
584
+
585
+ function cleanupLegacyRootBackups() {
586
+ if (!fs.existsSync(CC_TOOL_BASE_DIR)) {
587
+ return;
588
+ }
589
+
590
+ const rootEntries = fs.readdirSync(CC_TOOL_BASE_DIR);
591
+ rootEntries.forEach((entry) => {
592
+ if (!/\.bak-\d/.test(entry)) {
593
+ return;
594
+ }
595
+
596
+ const entryPath = path.join(CC_TOOL_BASE_DIR, entry);
597
+ try {
598
+ const stat = fs.statSync(entryPath);
599
+ if (!stat.isFile()) {
600
+ return;
601
+ }
602
+ const archivedPath = archiveLegacyEntry(entryPath, LEGACY_ROOT_BACKUPS_DIR);
603
+ if (archivedPath) {
604
+ console.warn(`[paths] 已迁移旧备份文件: ${entryPath} -> ${archivedPath}`);
605
+ }
606
+ } catch (error) {
607
+ console.warn(`[paths] 迁移旧备份文件失败: ${entryPath}`, error.message);
608
+ }
609
+ });
610
+ }
611
+
612
+ function cleanupEmptyLegacyRoots() {
613
+ const legacyRoots = new Set(
614
+ LEGACY_STORAGE_RELOCATIONS
615
+ .map(({ source }) => source)
616
+ .filter((sourcePath) => path.dirname(sourcePath) === CC_TOOL_BASE_DIR)
617
+ );
618
+
619
+ legacyRoots.forEach((entryPath) => {
620
+ tryRemoveEmptyDir(entryPath);
621
+ });
622
+ }
623
+
624
+ function loadLegacyImportState() {
625
+ const state = readJsonFileSafe(LEGACY_IMPORT_STATE_FILE);
626
+ if (!state.ok || !isPlainObject(state.value)) {
627
+ return { importedSources: {} };
628
+ }
629
+
630
+ return {
631
+ importedSources: isPlainObject(state.value.importedSources) ? state.value.importedSources : {}
632
+ };
633
+ }
634
+
635
+ function saveLegacyImportState(state) {
636
+ writeJsonFile(LEGACY_IMPORT_STATE_FILE, {
637
+ importedSources: isPlainObject(state?.importedSources) ? state.importedSources : {}
638
+ });
639
+ }
640
+
641
+ function importLegacyBaseDirsOnce() {
642
+ const importState = loadLegacyImportState();
643
+ let stateChanged = false;
644
+
645
+ LEGACY_BASE_DIRS.forEach((legacyDir) => {
646
+ if (!fs.existsSync(legacyDir)) {
647
+ return;
648
+ }
649
+
650
+ if (importState.importedSources[legacyDir]?.importedAt) {
651
+ return;
652
+ }
653
+
654
+ try {
655
+ mergeDirectory(legacyDir, CC_TOOL_BASE_DIR);
656
+ importState.importedSources[legacyDir] = {
657
+ importedAt: new Date().toISOString()
658
+ };
659
+ stateChanged = true;
660
+ } catch (error) {
661
+ console.warn(`[paths] 迁移目录失败: ${legacyDir} -> ${CC_TOOL_BASE_DIR}`, error.message);
662
+ }
663
+ });
664
+
665
+ if (stateChanged) {
666
+ saveLegacyImportState(importState);
667
+ }
668
+ }
669
+
670
+ function ensureStorageDirMigrated() {
671
+ if (migrationChecked) {
672
+ return CC_TOOL_BASE_DIR;
673
+ }
674
+ migrationChecked = true;
675
+
676
+ ensureDir(CC_TOOL_BASE_DIR);
677
+
678
+ importLegacyBaseDirsOnce();
679
+
680
+ LEGACY_STORAGE_RELOCATIONS.forEach(({ source, target }) => {
681
+ try {
682
+ relocateEntry(source, target);
683
+ } catch (error) {
684
+ console.warn(`[paths] 迁移存储项失败: ${source} -> ${target}`, error.message);
685
+ }
686
+ });
687
+
688
+ cleanupLegacyRootBackups();
689
+ cleanupEmptyLegacyRoots();
690
+
691
+ return CC_TOOL_BASE_DIR;
692
+ }
693
+
694
+ function resolveExistingEnvPath(envValue) {
695
+ if (typeof envValue !== 'string') {
696
+ return '';
697
+ }
698
+ const trimmed = envValue.trim();
699
+ return trimmed || '';
700
+ }
701
+
702
+ function pickExistingDir(candidates, fallback) {
703
+ for (const candidate of candidates) {
704
+ if (candidate && fs.existsSync(candidate)) {
705
+ return candidate;
706
+ }
707
+ }
708
+ return fallback;
709
+ }
710
+
711
+ function getClaudeConfigDir() {
712
+ return resolveExistingEnvPath(process.env.CLAUDE_CONFIG_DIR) || path.join(HOME_DIR, '.claude');
713
+ }
714
+
715
+ function getCodexDir() {
716
+ return resolveExistingEnvPath(process.env.CODEX_HOME) || path.join(HOME_DIR, '.codex');
717
+ }
718
+
719
+ function getGeminiDir() {
720
+ return path.join(HOME_DIR, '.gemini');
721
+ }
722
+
723
+ function getOpenCodeDataDir() {
724
+ if (isWindowsLikePlatform(process.platform, process.env)) {
725
+ const localAppData = resolveExistingEnvPath(process.env.LOCALAPPDATA);
726
+ return path.join(localAppData || path.join(HOME_DIR, 'AppData', 'Local'), 'opencode');
727
+ }
728
+
729
+ const xdgDataHome = resolveExistingEnvPath(process.env.XDG_DATA_HOME);
730
+ const preferredDir = path.join(xdgDataHome || path.join(HOME_DIR, '.local', 'share'), 'opencode');
731
+
732
+ if (process.platform === 'darwin') {
733
+ const legacyDarwinDir = path.join(HOME_DIR, 'Library', 'Application Support', 'opencode');
734
+ return pickExistingDir([preferredDir, legacyDarwinDir], preferredDir);
735
+ }
736
+
737
+ return preferredDir;
738
+ }
739
+
740
+ function getOpenCodeConfigDir() {
741
+ if (isWindowsLikePlatform(process.platform, process.env)) {
742
+ const appData = resolveExistingEnvPath(process.env.APPDATA);
743
+ return path.join(appData || path.join(HOME_DIR, 'AppData', 'Roaming'), 'opencode');
744
+ }
745
+
746
+ const xdgConfigHome = resolveExistingEnvPath(process.env.XDG_CONFIG_HOME);
747
+ const preferredDir = path.join(xdgConfigHome || path.join(HOME_DIR, '.config'), 'opencode');
748
+
749
+ if (process.platform === 'darwin') {
750
+ const xdgDataHome = resolveExistingEnvPath(process.env.XDG_DATA_HOME);
751
+ const legacyDataDir = path.join(xdgDataHome || path.join(HOME_DIR, '.local', 'share'), 'opencode');
752
+ const legacyDarwinDir = path.join(HOME_DIR, 'Library', 'Application Support', 'opencode');
753
+ return pickExistingDir([preferredDir, legacyDarwinDir, legacyDataDir], preferredDir);
754
+ }
755
+
756
+ return preferredDir;
757
+ }
758
+
150
759
  // 工具特定的原生配置路径(不改变)
151
760
  const NATIVE_PATHS = {
152
761
  // Claude Code 原生配置
153
762
  claude: {
154
- settings: path.join(HOME_DIR, '.claude', 'settings.json'),
155
- settingsBackup: path.join(HOME_DIR, '.claude', 'settings.json.cc-tool-backup'),
156
- projects: path.join(HOME_DIR, '.claude', 'projects')
763
+ dir: getClaudeConfigDir(),
764
+ settings: path.join(getClaudeConfigDir(), 'settings.json'),
765
+ settingsBackup: path.join(getClaudeConfigDir(), 'settings.json.cc-tool-backup'),
766
+ projects: path.join(getClaudeConfigDir(), 'projects'),
767
+ credentials: path.join(getClaudeConfigDir(), '.credentials.json')
157
768
  },
158
769
 
159
770
  // Codex 原生配置
160
771
  codex: {
161
- config: path.join(HOME_DIR, '.codex', 'config.toml'),
162
- configBackup: path.join(HOME_DIR, '.codex', 'config.toml.cc-tool-backup'),
163
- auth: path.join(HOME_DIR, '.codex', 'auth.json'),
164
- authBackup: path.join(HOME_DIR, '.codex', 'auth.json.cc-tool-backup'),
165
- sessions: path.join(HOME_DIR, '.codex', 'sessions')
772
+ dir: getCodexDir(),
773
+ config: path.join(getCodexDir(), 'config.toml'),
774
+ configBackup: path.join(getCodexDir(), 'config.toml.cc-tool-backup'),
775
+ auth: path.join(getCodexDir(), 'auth.json'),
776
+ authBackup: path.join(getCodexDir(), 'auth.json.cc-tool-backup'),
777
+ sessions: path.join(getCodexDir(), 'sessions')
166
778
  },
167
779
 
168
780
  // Gemini 原生配置
169
781
  gemini: {
170
- env: path.join(HOME_DIR, '.gemini', '.env'),
171
- envBackup: path.join(HOME_DIR, '.gemini', '.env.cc-tool-backup'),
172
- tmp: path.join(HOME_DIR, '.gemini', 'tmp')
782
+ dir: getGeminiDir(),
783
+ env: path.join(getGeminiDir(), '.env'),
784
+ envBackup: path.join(getGeminiDir(), '.env.cc-tool-backup'),
785
+ tmp: path.join(getGeminiDir(), 'tmp'),
786
+ settings: path.join(getGeminiDir(), 'settings.json'),
787
+ settingsBackup: path.join(getGeminiDir(), 'settings.json.cc-tool-backup'),
788
+ googleAccounts: path.join(getGeminiDir(), 'google_accounts.json'),
789
+ oauthCredentialsLegacy: path.join(getGeminiDir(), 'oauth_creds.json'),
790
+ oauthCredentialsEncrypted: path.join(getGeminiDir(), 'mcp-oauth-tokens-v2.json')
173
791
  },
174
792
 
175
793
  // OpenCode 原生配置
176
794
  opencode: {
177
- data: path.join(HOME_DIR, '.local', 'share', 'opencode'),
178
- config: path.join(HOME_DIR, '.config', 'opencode'),
179
- sessions: path.join(HOME_DIR, '.local', 'share', 'opencode', 'storage', 'session'),
180
- projects: path.join(HOME_DIR, '.local', 'share', 'opencode', 'storage', 'project'),
181
- messages: path.join(HOME_DIR, '.local', 'share', 'opencode', 'storage', 'message'),
182
- log: path.join(HOME_DIR, '.local', 'share', 'opencode', 'log')
795
+ data: getOpenCodeDataDir(),
796
+ config: getOpenCodeConfigDir(),
797
+ sessions: path.join(getOpenCodeDataDir(), 'storage', 'session'),
798
+ projects: path.join(getOpenCodeDataDir(), 'storage', 'project'),
799
+ messages: path.join(getOpenCodeDataDir(), 'storage', 'message'),
800
+ log: path.join(getOpenCodeDataDir(), 'log'),
801
+ auth: path.join(getOpenCodeDataDir(), 'auth.json')
183
802
  }
184
803
  };
185
804
 
805
+ ensureStorageDirMigrated();
806
+
186
807
  module.exports = {
187
808
  PATHS,
188
809
  NATIVE_PATHS,
@@ -190,5 +811,12 @@ module.exports = {
190
811
  CTX_BASE_DIR,
191
812
  CC_TOOL_BASE_DIR,
192
813
  LEGACY_BASE_DIRS,
193
- ensureStorageDirMigrated
814
+ ensureStorageDirMigrated,
815
+ getRepoScannerReposPath,
816
+ getRepoScannerCachePath,
817
+ getClaudeConfigDir,
818
+ getCodexDir,
819
+ getGeminiDir,
820
+ getOpenCodeDataDir,
821
+ getOpenCodeConfigDir
194
822
  };