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.
- package/CHANGELOG.md +20 -0
- package/README.md +253 -326
- package/dist/web/assets/{Analytics-IW6eAy9u.js → Analytics-D6LzK9hk.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-BPtkTMSc.js → ConfigTemplates-BUDYuxRi.js} +1 -1
- package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
- package/dist/web/assets/Home-D7KX7iF8.js +1 -0
- package/dist/web/assets/{PluginManager-BGx9MSDV.js → PluginManager-DTgQ--vB.js} +1 -1
- package/dist/web/assets/{ProjectList-BCn-mrCx.js → ProjectList-DMCiGmCT.js} +1 -1
- package/dist/web/assets/{SessionList-CzLfebJQ.js → SessionList-CRBsdVRe.js} +1 -1
- package/dist/web/assets/{SkillManager-CXz2vBQx.js → SkillManager-DMwx2Q4k.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-CHtgMfKc.js → WorkspaceManager-DapB4ljL.js} +1 -1
- package/dist/web/assets/{icons-B29onFfZ.js → icons-B5Pl4lrD.js} +1 -1
- package/dist/web/assets/index-CL-qpoJ_.js +2 -0
- package/dist/web/assets/index-D_5dRFOL.css +1 -0
- package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
- package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
- package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
- package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
- package/dist/web/index.html +7 -7
- package/docs/home.png +0 -0
- package/package.json +14 -5
- package/src/commands/daemon.js +3 -2
- package/src/commands/security.js +1 -2
- package/src/commands/toggle-proxy.js +100 -5
- package/src/config/paths.js +718 -90
- package/src/server/api/agents.js +1 -1
- package/src/server/api/channels.js +9 -0
- package/src/server/api/claude-hooks.js +13 -8
- package/src/server/api/codex-channels.js +9 -0
- package/src/server/api/codex-proxy.js +27 -15
- package/src/server/api/gemini-proxy.js +22 -11
- package/src/server/api/hooks.js +45 -0
- package/src/server/api/oauth-credentials.js +163 -0
- package/src/server/api/opencode-proxy.js +22 -10
- package/src/server/api/plugins.js +2 -1
- package/src/server/api/proxy.js +39 -44
- package/src/server/api/skills.js +91 -13
- package/src/server/api/ui-config.js +5 -0
- package/src/server/codex-proxy-server.js +90 -70
- package/src/server/gemini-proxy-server.js +107 -88
- package/src/server/index.js +2 -0
- package/src/server/opencode-proxy-server.js +381 -225
- package/src/server/proxy-server.js +86 -60
- package/src/server/services/alias.js +3 -3
- package/src/server/services/channels.js +21 -24
- package/src/server/services/codex-channels.js +158 -255
- package/src/server/services/codex-config.js +2 -5
- package/src/server/services/codex-env-manager.js +423 -0
- package/src/server/services/codex-settings-manager.js +21 -357
- package/src/server/services/codex-statistics-service.js +3 -27
- package/src/server/services/config-export-service.js +43 -9
- package/src/server/services/config-registry-service.js +3 -2
- package/src/server/services/config-sync-manager.js +1 -1
- package/src/server/services/favorites.js +4 -3
- package/src/server/services/gemini-channels.js +14 -12
- package/src/server/services/gemini-statistics-service.js +3 -25
- package/src/server/services/mcp-service.js +35 -19
- package/src/server/services/model-detector.js +4 -3
- package/src/server/services/native-keychain.js +243 -0
- package/src/server/services/native-oauth-adapters.js +891 -0
- package/src/server/services/network-access.js +39 -1
- package/src/server/services/notification-hooks.js +951 -0
- package/src/server/services/oauth-credentials-service.js +786 -0
- package/src/server/services/oauth-utils.js +49 -0
- package/src/server/services/opencode-channels.js +19 -15
- package/src/server/services/opencode-sessions.js +2 -2
- package/src/server/services/opencode-settings-manager.js +169 -16
- package/src/server/services/opencode-statistics-service.js +3 -27
- package/src/server/services/plugins-service.js +115 -15
- package/src/server/services/prompts-service.js +2 -3
- package/src/server/services/proxy-log-helper.js +242 -0
- package/src/server/services/proxy-runtime.js +6 -4
- package/src/server/services/repo-scanner-base.js +12 -4
- package/src/server/services/request-logger.js +7 -7
- package/src/server/services/security-config.js +4 -4
- package/src/server/services/session-cache.js +2 -2
- package/src/server/services/sessions.js +2 -2
- package/src/server/services/settings-manager.js +13 -0
- package/src/server/services/skill-service.js +867 -368
- package/src/server/services/statistics-service.js +5 -5
- package/src/server/services/ui-config.js +4 -3
- package/src/server/services/workspace-service.js +1 -1
- package/src/server/websocket-server.js +5 -4
- package/dist/web/assets/Home-BsSioaaB.css +0 -1
- package/dist/web/assets/Home-obifg_9E.js +0 -1
- package/dist/web/assets/index-C7LPdVsN.js +0 -2
- package/dist/web/assets/index-eEmjZKWP.css +0 -1
- package/docs/bannel.png +0 -0
- package/docs/model-redirection.md +0 -251
package/src/config/paths.js
CHANGED
|
@@ -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
|
|
45
|
-
if (
|
|
46
|
-
|
|
69
|
+
function ensureDir(dirPath) {
|
|
70
|
+
if (!fs.existsSync(dirPath)) {
|
|
71
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
47
72
|
}
|
|
48
|
-
|
|
73
|
+
}
|
|
49
74
|
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
} catch
|
|
59
|
-
console.warn(`[paths]
|
|
162
|
+
fs.unlinkSync(sourcePath);
|
|
163
|
+
} catch {
|
|
164
|
+
console.warn(`[paths] 清理旧文件失败: ${sourcePath}`);
|
|
60
165
|
}
|
|
61
|
-
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
62
168
|
|
|
63
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
90
|
-
codex: path.join(
|
|
91
|
-
gemini: path.join(
|
|
92
|
-
opencode: path.join(
|
|
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(
|
|
98
|
-
codex: path.join(
|
|
99
|
-
gemini: path.join(
|
|
100
|
-
opencode: path.join(
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
127
|
-
|
|
451
|
+
// 脚本
|
|
452
|
+
notifyHook: path.join(SCRIPTS_DIR, 'notify-hook.js'),
|
|
128
453
|
|
|
129
|
-
//
|
|
130
|
-
|
|
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
|
-
//
|
|
482
|
+
// 原生路径兼容
|
|
133
483
|
skills: path.join(HOME_DIR, '.claude', 'skills'),
|
|
134
484
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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:
|
|
178
|
-
config:
|
|
179
|
-
sessions: path.join(
|
|
180
|
-
projects: path.join(
|
|
181
|
-
messages: path.join(
|
|
182
|
-
log: path.join(
|
|
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
|
};
|