coding-tool-x 3.3.8 → 3.4.0

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 (79) hide show
  1. package/CHANGELOG.md +17 -2
  2. package/README.md +253 -326
  3. package/dist/web/assets/{Analytics-DLpoDZ2M.js → Analytics-DEjfL5Jx.js} +4 -4
  4. package/dist/web/assets/Analytics-RNn1BUbG.css +1 -0
  5. package/dist/web/assets/{ConfigTemplates-D_hRb55W.js → ConfigTemplates-DkRL_-tf.js} +1 -1
  6. package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
  7. package/dist/web/assets/Home-CF-L640I.js +1 -0
  8. package/dist/web/assets/{PluginManager-JXsyym1s.js → PluginManager-BzNYTdNB.js} +1 -1
  9. package/dist/web/assets/{ProjectList-DZWSeb-q.js → ProjectList-C0-JgHMM.js} +1 -1
  10. package/dist/web/assets/{SessionList-Cs624DR3.js → SessionList-CkZUdX5N.js} +1 -1
  11. package/dist/web/assets/{SkillManager-bEliz7qz.js → SkillManager-Cak0-4d4.js} +1 -1
  12. package/dist/web/assets/{WorkspaceManager-J3RecFGn.js → WorkspaceManager-CGDJzwEr.js} +1 -1
  13. package/dist/web/assets/{icons-Cuc23WS7.js → icons-B5Pl4lrD.js} +1 -1
  14. package/dist/web/assets/index-D_WItvHE.js +2 -0
  15. package/dist/web/assets/index-Dz7v9OM0.css +1 -0
  16. package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
  17. package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
  18. package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
  19. package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
  20. package/dist/web/index.html +7 -7
  21. package/docs/home.png +0 -0
  22. package/package.json +13 -5
  23. package/src/commands/daemon.js +3 -2
  24. package/src/commands/security.js +1 -2
  25. package/src/config/paths.js +638 -93
  26. package/src/server/api/agents.js +1 -1
  27. package/src/server/api/claude-hooks.js +13 -8
  28. package/src/server/api/codex-proxy.js +5 -4
  29. package/src/server/api/hooks.js +45 -0
  30. package/src/server/api/plugins.js +0 -1
  31. package/src/server/api/statistics.js +4 -4
  32. package/src/server/api/ui-config.js +5 -0
  33. package/src/server/api/workspaces.js +1 -3
  34. package/src/server/codex-proxy-server.js +89 -59
  35. package/src/server/gemini-proxy-server.js +107 -88
  36. package/src/server/index.js +1 -0
  37. package/src/server/opencode-proxy-server.js +381 -225
  38. package/src/server/proxy-server.js +86 -60
  39. package/src/server/services/alias.js +3 -3
  40. package/src/server/services/channels.js +3 -2
  41. package/src/server/services/codex-channels.js +38 -87
  42. package/src/server/services/codex-env-manager.js +426 -0
  43. package/src/server/services/codex-settings-manager.js +15 -15
  44. package/src/server/services/codex-statistics-service.js +3 -27
  45. package/src/server/services/config-export-service.js +20 -7
  46. package/src/server/services/config-registry-service.js +3 -2
  47. package/src/server/services/config-sync-manager.js +1 -1
  48. package/src/server/services/favorites.js +4 -3
  49. package/src/server/services/gemini-channels.js +3 -3
  50. package/src/server/services/gemini-statistics-service.js +3 -25
  51. package/src/server/services/mcp-service.js +2 -3
  52. package/src/server/services/model-detector.js +4 -3
  53. package/src/server/services/native-oauth-adapters.js +2 -1
  54. package/src/server/services/network-access.js +39 -1
  55. package/src/server/services/notification-hooks.js +951 -0
  56. package/src/server/services/opencode-channels.js +6 -6
  57. package/src/server/services/opencode-sessions.js +2 -2
  58. package/src/server/services/opencode-statistics-service.js +3 -27
  59. package/src/server/services/plugins-service.js +110 -31
  60. package/src/server/services/prompts-service.js +2 -3
  61. package/src/server/services/proxy-log-helper.js +242 -0
  62. package/src/server/services/proxy-runtime.js +6 -4
  63. package/src/server/services/repo-scanner-base.js +12 -4
  64. package/src/server/services/request-logger.js +7 -7
  65. package/src/server/services/security-config.js +4 -4
  66. package/src/server/services/session-cache.js +2 -2
  67. package/src/server/services/sessions.js +2 -2
  68. package/src/server/services/skill-service.js +174 -55
  69. package/src/server/services/statistics-service.js +10 -6
  70. package/src/server/services/ui-config.js +4 -3
  71. package/src/server/services/workspace-service.js +101 -156
  72. package/src/server/websocket-server.js +5 -4
  73. package/dist/web/assets/Analytics-DuYvId7u.css +0 -1
  74. package/dist/web/assets/Home-BMoFdAwy.css +0 -1
  75. package/dist/web/assets/Home-DNwp-0J-.js +0 -1
  76. package/dist/web/assets/index-BXeSvAwU.js +0 -2
  77. package/dist/web/assets/index-DWAC3Tdv.css +0 -1
  78. package/docs/bannel.png +0 -0
  79. package/docs/model-redirection.md +0 -251
@@ -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,25 +66,628 @@ 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
+ }
74
+
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;
82
+ }
83
+ return JSON.parse(JSON.stringify(value));
84
+ }
85
+
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);
161
+ try {
162
+ fs.unlinkSync(sourcePath);
163
+ } catch {
164
+ console.warn(`[paths] 清理旧文件失败: ${sourcePath}`);
165
+ }
166
+ }
167
+ }
168
+
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
+ }
181
+ }
182
+
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
+ }
213
+
214
+ function mergeJsonValues(primaryValue, secondaryValue) {
215
+ if (primaryValue === undefined) {
216
+ return cloneJsonValue(secondaryValue);
217
+ }
218
+ if (secondaryValue === undefined) {
219
+ return cloneJsonValue(primaryValue);
220
+ }
221
+
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
+ }
242
+
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
+ }
254
+
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
+ }
262
+
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'),
395
+
396
+ // 渠道配置
397
+ channelsDir: CHANNELS_DIR,
398
+ channels: {
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')
403
+ },
404
+ activeChannelDir: ACTIVE_CHANNELS_DIR,
405
+ activeChannel: {
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')
410
+ },
411
+
412
+ // 统计与日志快照
413
+ statistics: {
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')
427
+ }
428
+ },
429
+
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
+ },
441
+
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'),
450
+
451
+ // 脚本
452
+ notifyHook: path.join(SCRIPTS_DIR, 'notify-hook.js'),
453
+
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
+ },
481
+
482
+ // 原生路径兼容
483
+ skills: path.join(HOME_DIR, '.claude', 'skills'),
484
+
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
+ },
49
493
 
494
+ // RepoScannerBase 的通用路径
495
+ repoScanner: {
496
+ reposDir: REPOS_DIR,
497
+ cacheDir: CACHE_DIR
498
+ }
499
+ };
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() {
50
586
  if (!fs.existsSync(CC_TOOL_BASE_DIR)) {
51
- fs.mkdirSync(CC_TOOL_BASE_DIR, { recursive: true });
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: {} };
52
628
  }
53
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
+
54
645
  LEGACY_BASE_DIRS.forEach((legacyDir) => {
55
- if (!fs.existsSync(legacyDir)) return;
646
+ if (!fs.existsSync(legacyDir)) {
647
+ return;
648
+ }
649
+
650
+ if (importState.importedSources[legacyDir]?.importedAt) {
651
+ return;
652
+ }
653
+
56
654
  try {
57
655
  mergeDirectory(legacyDir, CC_TOOL_BASE_DIR);
656
+ importState.importedSources[legacyDir] = {
657
+ importedAt: new Date().toISOString()
658
+ };
659
+ stateChanged = true;
58
660
  } catch (error) {
59
661
  console.warn(`[paths] 迁移目录失败: ${legacyDir} -> ${CC_TOOL_BASE_DIR}`, error.message);
60
662
  }
61
663
  });
62
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
+
63
691
  return CC_TOOL_BASE_DIR;
64
692
  }
65
693
 
@@ -128,93 +756,6 @@ function getOpenCodeConfigDir() {
128
756
  return preferredDir;
129
757
  }
130
758
 
131
- // 路径配置
132
- const PATHS = {
133
- // 基础目录
134
- base: CC_TOOL_BASE_DIR,
135
-
136
- // 项目目录(存储项目配置和会话)
137
- projects: path.join(CC_TOOL_BASE_DIR, 'projects'),
138
-
139
- // 配置文件目录
140
- config: path.join(CC_TOOL_BASE_DIR, 'config'),
141
- configFile: path.join(CC_TOOL_BASE_DIR, 'config.json'),
142
-
143
- // 日志目录
144
- logs: path.join(CC_TOOL_BASE_DIR, 'logs'),
145
-
146
- // 别名存储
147
- aliases: path.join(CC_TOOL_BASE_DIR, 'aliases.json'),
148
-
149
- // 收藏夹存储
150
- favorites: path.join(CC_TOOL_BASE_DIR, 'favorites.json'),
151
-
152
- // 渠道配置
153
- channels: {
154
- claude: path.join(CC_TOOL_BASE_DIR, 'channels.json'),
155
- codex: path.join(CC_TOOL_BASE_DIR, 'codex-channels.json'),
156
- gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-channels.json'),
157
- opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-channels.json')
158
- },
159
-
160
- // 激活渠道标记
161
- activeChannel: {
162
- claude: path.join(CC_TOOL_BASE_DIR, 'active-channel.json'),
163
- codex: path.join(CC_TOOL_BASE_DIR, 'codex-active-channel.json'),
164
- gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-active-channel.json'),
165
- opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-active-channel.json')
166
- },
167
-
168
- // 统计数据
169
- statistics: {
170
- claude: path.join(CC_TOOL_BASE_DIR, 'statistics.json'),
171
- codex: path.join(CC_TOOL_BASE_DIR, 'codex-statistics.json'),
172
- gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-statistics.json'),
173
- opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-statistics.json'),
174
- dailyStats: {
175
- claude: path.join(CC_TOOL_BASE_DIR, 'daily-stats'),
176
- codex: path.join(CC_TOOL_BASE_DIR, 'codex-daily-stats'),
177
- gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-daily-stats'),
178
- opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-daily-stats')
179
- }
180
- },
181
-
182
- // 会话缓存
183
- sessionCache: path.join(CC_TOOL_BASE_DIR, 'session-cache.json'),
184
-
185
- // 项目顺序
186
- projectOrder: path.join(CC_TOOL_BASE_DIR, 'project-order.json'),
187
-
188
- // 环境备份
189
- envBackups: path.join(CC_TOOL_BASE_DIR, 'env-backups'),
190
-
191
- // UI 配置
192
- uiConfig: path.join(CC_TOOL_BASE_DIR, 'ui-config.json'),
193
-
194
- // OAuth 凭证注册表
195
- oauthCredentials: path.join(CC_TOOL_BASE_DIR, 'oauth-credentials.json'),
196
-
197
- // 飞书通知脚本
198
- notifyHook: path.join(CC_TOOL_BASE_DIR, 'notify-hook.js'),
199
-
200
- // Skills 安装目录(注意:这个仍使用 Claude 原生路径)
201
- skills: path.join(HOME_DIR, '.claude', 'skills'),
202
-
203
- // MCP 配置(注意:这个仍使用 Claude 原生路径)
204
- mcpConfig: path.join(CC_TOOL_BASE_DIR, 'mcp-config.json'),
205
-
206
- // Prompts
207
- prompts: path.join(CC_TOOL_BASE_DIR, 'prompts.json'),
208
-
209
- // 代理运行时状态
210
- proxyRuntime: {
211
- claude: path.join(CC_TOOL_BASE_DIR, 'proxy-runtime.json'),
212
- codex: path.join(CC_TOOL_BASE_DIR, 'codex-proxy-runtime.json'),
213
- gemini: path.join(CC_TOOL_BASE_DIR, 'gemini-proxy-runtime.json'),
214
- opencode: path.join(CC_TOOL_BASE_DIR, 'opencode-proxy-runtime.json')
215
- }
216
- };
217
-
218
759
  // 工具特定的原生配置路径(不改变)
219
760
  const NATIVE_PATHS = {
220
761
  // Claude Code 原生配置
@@ -261,6 +802,8 @@ const NATIVE_PATHS = {
261
802
  }
262
803
  };
263
804
 
805
+ ensureStorageDirMigrated();
806
+
264
807
  module.exports = {
265
808
  PATHS,
266
809
  NATIVE_PATHS,
@@ -269,6 +812,8 @@ module.exports = {
269
812
  CC_TOOL_BASE_DIR,
270
813
  LEGACY_BASE_DIRS,
271
814
  ensureStorageDirMigrated,
815
+ getRepoScannerReposPath,
816
+ getRepoScannerCachePath,
272
817
  getClaudeConfigDir,
273
818
  getCodexDir,
274
819
  getGeminiDir,