aicodeswitch 2.0.3 → 2.0.4
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 +2 -0
- package/bin/restore.js +8 -8
- package/dist/server/config-metadata.js +264 -0
- package/dist/server/main.js +120 -30
- package/dist/ui/assets/{index-DtRNBdrH.js → index-kGCJkqIq.js} +23 -23
- package/dist/ui/index.html +1 -1
- package/dist/ui/migration.md +1 -1
- package/package.json +1 -1
- package/public/migration.md +1 -1
package/CHANGELOG.md
CHANGED
package/bin/restore.js
CHANGED
|
@@ -17,7 +17,7 @@ const restoreClaudeConfig = () => {
|
|
|
17
17
|
const homeDir = os.homedir();
|
|
18
18
|
const claudeDir = path.join(homeDir, '.claude');
|
|
19
19
|
const claudeSettingsPath = path.join(claudeDir, 'settings.json');
|
|
20
|
-
const claudeSettingsBakPath = path.join(claudeDir, 'settings.json.
|
|
20
|
+
const claudeSettingsBakPath = path.join(claudeDir, 'settings.json.aicodeswitch_backup');
|
|
21
21
|
|
|
22
22
|
// Restore settings.json
|
|
23
23
|
if (fs.existsSync(claudeSettingsBakPath)) {
|
|
@@ -27,7 +27,7 @@ const restoreClaudeConfig = () => {
|
|
|
27
27
|
fs.renameSync(claudeSettingsBakPath, claudeSettingsPath);
|
|
28
28
|
results.restored.push('settings.json');
|
|
29
29
|
} else {
|
|
30
|
-
results.notFound.push('settings.json.
|
|
30
|
+
results.notFound.push('settings.json.aicodeswitch_backup');
|
|
31
31
|
}
|
|
32
32
|
} catch (err) {
|
|
33
33
|
results.errors.push({ file: 'settings.json', error: err.message });
|
|
@@ -36,7 +36,7 @@ const restoreClaudeConfig = () => {
|
|
|
36
36
|
try {
|
|
37
37
|
const homeDir = os.homedir();
|
|
38
38
|
const claudeJsonPath = path.join(homeDir, '.claude.json');
|
|
39
|
-
const claudeJsonBakPath = path.join(homeDir, '.claude.json.
|
|
39
|
+
const claudeJsonBakPath = path.join(homeDir, '.claude.json.aicodeswitch_backup');
|
|
40
40
|
|
|
41
41
|
// Restore .claude.json
|
|
42
42
|
if (fs.existsSync(claudeJsonBakPath)) {
|
|
@@ -46,7 +46,7 @@ const restoreClaudeConfig = () => {
|
|
|
46
46
|
fs.renameSync(claudeJsonBakPath, claudeJsonPath);
|
|
47
47
|
results.restored.push('.claude.json');
|
|
48
48
|
} else {
|
|
49
|
-
results.notFound.push('.claude.json.
|
|
49
|
+
results.notFound.push('.claude.json.aicodeswitch_backup');
|
|
50
50
|
}
|
|
51
51
|
} catch (err) {
|
|
52
52
|
results.errors.push({ file: '.claude.json', error: err.message });
|
|
@@ -67,7 +67,7 @@ const restoreCodexConfig = () => {
|
|
|
67
67
|
const homeDir = os.homedir();
|
|
68
68
|
const codexDir = path.join(homeDir, '.codex');
|
|
69
69
|
const codexConfigPath = path.join(codexDir, 'config.toml');
|
|
70
|
-
const codexConfigBakPath = path.join(codexDir, 'config.toml.
|
|
70
|
+
const codexConfigBakPath = path.join(codexDir, 'config.toml.aicodeswitch_backup');
|
|
71
71
|
|
|
72
72
|
// Restore config.toml
|
|
73
73
|
if (fs.existsSync(codexConfigBakPath)) {
|
|
@@ -77,7 +77,7 @@ const restoreCodexConfig = () => {
|
|
|
77
77
|
fs.renameSync(codexConfigBakPath, codexConfigPath);
|
|
78
78
|
results.restored.push('config.toml');
|
|
79
79
|
} else {
|
|
80
|
-
results.notFound.push('config.toml.
|
|
80
|
+
results.notFound.push('config.toml.aicodeswitch_backup');
|
|
81
81
|
}
|
|
82
82
|
} catch (err) {
|
|
83
83
|
results.errors.push({ file: 'config.toml', error: err.message });
|
|
@@ -86,7 +86,7 @@ const restoreCodexConfig = () => {
|
|
|
86
86
|
try {
|
|
87
87
|
const homeDir = os.homedir();
|
|
88
88
|
const codexAuthPath = path.join(homeDir, '.codex', 'auth.json');
|
|
89
|
-
const codexAuthBakPath = path.join(homeDir, '.codex', 'auth.json.
|
|
89
|
+
const codexAuthBakPath = path.join(homeDir, '.codex', 'auth.json.aicodeswitch_backup');
|
|
90
90
|
|
|
91
91
|
// Restore auth.json
|
|
92
92
|
if (fs.existsSync(codexAuthBakPath)) {
|
|
@@ -96,7 +96,7 @@ const restoreCodexConfig = () => {
|
|
|
96
96
|
fs.renameSync(codexAuthBakPath, codexAuthPath);
|
|
97
97
|
results.restored.push('auth.json');
|
|
98
98
|
} else {
|
|
99
|
-
results.notFound.push('auth.json.
|
|
99
|
+
results.notFound.push('auth.json.aicodeswitch_backup');
|
|
100
100
|
}
|
|
101
101
|
} catch (err) {
|
|
102
102
|
results.errors.push({ file: 'auth.json', error: err.message });
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cleanupInvalidMetadata = exports.checkCodexConfigStatus = exports.checkClaudeConfigStatus = exports.deleteMetadata = exports.loadMetadata = exports.saveMetadata = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
/**
|
|
11
|
+
* 获取元数据文件路径
|
|
12
|
+
*/
|
|
13
|
+
const getMetadataFilePath = (configType) => {
|
|
14
|
+
const dataDir = path_1.default.join(os_1.default.homedir(), '.aicodeswitch/data');
|
|
15
|
+
return path_1.default.join(dataDir, `.${configType}-metadata.json`);
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* 保存配置元数据
|
|
19
|
+
*/
|
|
20
|
+
const saveMetadata = (metadata) => {
|
|
21
|
+
try {
|
|
22
|
+
const metadataPath = getMetadataFilePath(metadata.configType);
|
|
23
|
+
const dataDir = path_1.default.dirname(metadataPath);
|
|
24
|
+
// 确保目录存在
|
|
25
|
+
if (!fs_1.default.existsSync(dataDir)) {
|
|
26
|
+
fs_1.default.mkdirSync(dataDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
fs_1.default.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error(`Failed to save metadata for ${metadata.configType}:`, error);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
exports.saveMetadata = saveMetadata;
|
|
37
|
+
/**
|
|
38
|
+
* 读取配置元数据
|
|
39
|
+
*/
|
|
40
|
+
const loadMetadata = (configType) => {
|
|
41
|
+
try {
|
|
42
|
+
const metadataPath = getMetadataFilePath(configType);
|
|
43
|
+
if (!fs_1.default.existsSync(metadataPath)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const content = fs_1.default.readFileSync(metadataPath, 'utf-8');
|
|
47
|
+
return JSON.parse(content);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error(`Failed to load metadata for ${configType}:`, error);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
exports.loadMetadata = loadMetadata;
|
|
55
|
+
/**
|
|
56
|
+
* 删除配置元数据
|
|
57
|
+
*/
|
|
58
|
+
const deleteMetadata = (configType) => {
|
|
59
|
+
try {
|
|
60
|
+
const metadataPath = getMetadataFilePath(configType);
|
|
61
|
+
if (fs_1.default.existsSync(metadataPath)) {
|
|
62
|
+
fs_1.default.unlinkSync(metadataPath);
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error(`Failed to delete metadata for ${configType}:`, error);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
exports.deleteMetadata = deleteMetadata;
|
|
72
|
+
/**
|
|
73
|
+
* 计算文件的 SHA256 hash
|
|
74
|
+
*/
|
|
75
|
+
const calculateFileHash = (filePath) => {
|
|
76
|
+
try {
|
|
77
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const crypto = require('crypto');
|
|
81
|
+
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
82
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
console.error(`Failed to calculate hash for ${filePath}:`, error);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* 检查 Claude 配置文件是否包含我们的代理特征
|
|
91
|
+
*/
|
|
92
|
+
const isClaudeProxyConfig = (filePath) => {
|
|
93
|
+
var _a;
|
|
94
|
+
try {
|
|
95
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
99
|
+
const config = JSON.parse(content);
|
|
100
|
+
// 检查是否包含我们的 ANTHROPIC_BASE_URL 配置
|
|
101
|
+
// 用户可能会修改端口,所以我们只检查主机名部分
|
|
102
|
+
const baseUrl = (_a = config.env) === null || _a === void 0 ? void 0 : _a.ANTHROPIC_BASE_URL;
|
|
103
|
+
if (baseUrl && typeof baseUrl === 'string') {
|
|
104
|
+
// 允许的格式: http://127.0.0.1:4567/claude-code 或 http://localhost:4567/claude-code
|
|
105
|
+
return /https?:\/\/(127\.0\.0\.1|localhost)(:\d+)?\/claude-code/.test(baseUrl);
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error(`Failed to check Claude config:`, error);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* 检查 Codex 配置文件是否包含我们的代理特征
|
|
116
|
+
*/
|
|
117
|
+
const isCodexProxyConfig = (configPath) => {
|
|
118
|
+
try {
|
|
119
|
+
// 检查 config.toml
|
|
120
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
121
|
+
const content = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
122
|
+
// 检查是否包含我们的 model_provider 和 base_url 配置
|
|
123
|
+
const hasModelProvider = content.includes('model_provider = "aicodeswitch"');
|
|
124
|
+
const hasBaseUrl = /base_url = "https?:\/\/(127\.0\.0\.1|localhost)(:\d+)?\/codex"/.test(content);
|
|
125
|
+
if (hasModelProvider && hasBaseUrl) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.error(`Failed to check Codex config:`, error);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* 检查 Claude 配置状态
|
|
138
|
+
*/
|
|
139
|
+
const checkClaudeConfigStatus = () => {
|
|
140
|
+
var _a;
|
|
141
|
+
const homeDir = os_1.default.homedir();
|
|
142
|
+
const settingsPath = path_1.default.join(homeDir, '.claude/settings.json');
|
|
143
|
+
const settingsBakPath = path_1.default.join(homeDir, '.claude/settings.json.aicodeswitch_backup');
|
|
144
|
+
const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.aicodeswitch_backup');
|
|
145
|
+
// 检查备份文件是否存在
|
|
146
|
+
const hasBackup = fs_1.default.existsSync(settingsBakPath) || fs_1.default.existsSync(claudeJsonBakPath);
|
|
147
|
+
// 尝试加载元数据
|
|
148
|
+
const metadata = (0, exports.loadMetadata)('claude');
|
|
149
|
+
if (metadata) {
|
|
150
|
+
// 如果元数据存在,进行详细检查
|
|
151
|
+
const currentHash = calculateFileHash(settingsPath);
|
|
152
|
+
const isProxyConfig = isClaudeProxyConfig(settingsPath);
|
|
153
|
+
// 检查是否被修改
|
|
154
|
+
let isModified = false;
|
|
155
|
+
if (currentHash && ((_a = metadata.files[0]) === null || _a === void 0 ? void 0 : _a.currentHash)) {
|
|
156
|
+
isModified = currentHash !== metadata.files[0].currentHash;
|
|
157
|
+
}
|
|
158
|
+
// 如果当前配置不是我们的代理配置,说明用户已经恢复或修改了
|
|
159
|
+
const isOverwritten = isProxyConfig;
|
|
160
|
+
return {
|
|
161
|
+
isOverwritten,
|
|
162
|
+
isModified,
|
|
163
|
+
hasBackup,
|
|
164
|
+
metadata
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// 如果元数据不存在,降级到简单检查
|
|
168
|
+
if (hasBackup) {
|
|
169
|
+
// 有备份文件,但没有元数据(可能是旧版本)
|
|
170
|
+
// 检查当前配置是否是我们的代理配置
|
|
171
|
+
const isProxyConfig = isClaudeProxyConfig(settingsPath);
|
|
172
|
+
return {
|
|
173
|
+
isOverwritten: isProxyConfig,
|
|
174
|
+
isModified: false, // 无法判断是否被修改
|
|
175
|
+
hasBackup
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// 没有备份也没有元数据
|
|
179
|
+
// 检查当前配置是否恰好是我们的代理配置
|
|
180
|
+
const isProxyConfig = isClaudeProxyConfig(settingsPath);
|
|
181
|
+
return {
|
|
182
|
+
isOverwritten: isProxyConfig,
|
|
183
|
+
isModified: false,
|
|
184
|
+
hasBackup: false
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
exports.checkClaudeConfigStatus = checkClaudeConfigStatus;
|
|
188
|
+
/**
|
|
189
|
+
* 检查 Codex 配置状态
|
|
190
|
+
*/
|
|
191
|
+
const checkCodexConfigStatus = () => {
|
|
192
|
+
var _a;
|
|
193
|
+
const homeDir = os_1.default.homedir();
|
|
194
|
+
const configPath = path_1.default.join(homeDir, '.codex/config.toml');
|
|
195
|
+
const configBakPath = path_1.default.join(homeDir, '.codex/config.toml.aicodeswitch_backup');
|
|
196
|
+
const authBakPath = path_1.default.join(homeDir, '.codex/auth.json.aicodeswitch_backup');
|
|
197
|
+
// 检查备份文件是否存在
|
|
198
|
+
const hasBackup = fs_1.default.existsSync(configBakPath) || fs_1.default.existsSync(authBakPath);
|
|
199
|
+
// 尝试加载元数据
|
|
200
|
+
const metadata = (0, exports.loadMetadata)('codex');
|
|
201
|
+
if (metadata) {
|
|
202
|
+
// 如果元数据存在,进行详细检查
|
|
203
|
+
const currentHash = calculateFileHash(configPath);
|
|
204
|
+
const isProxyConfig = isCodexProxyConfig(configPath);
|
|
205
|
+
// 检查是否被修改
|
|
206
|
+
let isModified = false;
|
|
207
|
+
if (currentHash && ((_a = metadata.files[0]) === null || _a === void 0 ? void 0 : _a.currentHash)) {
|
|
208
|
+
isModified = currentHash !== metadata.files[0].currentHash;
|
|
209
|
+
}
|
|
210
|
+
// 如果当前配置不是我们的代理配置,说明用户已经恢复或修改了
|
|
211
|
+
const isOverwritten = isProxyConfig;
|
|
212
|
+
return {
|
|
213
|
+
isOverwritten,
|
|
214
|
+
isModified,
|
|
215
|
+
hasBackup,
|
|
216
|
+
metadata
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// 如果元数据不存在,降级到简单检查
|
|
220
|
+
if (hasBackup) {
|
|
221
|
+
// 有备份文件,但没有元数据(可能是旧版本)
|
|
222
|
+
// 检查当前配置是否是我们的代理配置
|
|
223
|
+
const isProxyConfig = isCodexProxyConfig(configPath);
|
|
224
|
+
return {
|
|
225
|
+
isOverwritten: isProxyConfig,
|
|
226
|
+
isModified: false, // 无法判断是否被修改
|
|
227
|
+
hasBackup
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
// 没有备份也没有元数据
|
|
231
|
+
// 检查当前配置是否恰好是我们的代理配置
|
|
232
|
+
const isProxyConfig = isCodexProxyConfig(configPath);
|
|
233
|
+
return {
|
|
234
|
+
isOverwritten: isProxyConfig,
|
|
235
|
+
isModified: false,
|
|
236
|
+
hasBackup: false
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
exports.checkCodexConfigStatus = checkCodexConfigStatus;
|
|
240
|
+
/**
|
|
241
|
+
* 清理无效的元数据
|
|
242
|
+
* 当备份文件不存在但元数据存在时,说明状态不一致,需要清理
|
|
243
|
+
*/
|
|
244
|
+
const cleanupInvalidMetadata = (configType) => {
|
|
245
|
+
try {
|
|
246
|
+
const metadata = (0, exports.loadMetadata)(configType);
|
|
247
|
+
if (!metadata) {
|
|
248
|
+
return true; // 没有元数据,无需清理
|
|
249
|
+
}
|
|
250
|
+
// 检查备份文件是否还存在
|
|
251
|
+
const hasAnyBackup = metadata.files.some(file => fs_1.default.existsSync(file.backupPath));
|
|
252
|
+
if (!hasAnyBackup) {
|
|
253
|
+
// 备份文件都不存在了,删除元数据
|
|
254
|
+
console.warn(`Cleaning up invalid metadata for ${configType}: no backup files found`);
|
|
255
|
+
return (0, exports.deleteMetadata)(configType);
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
console.error(`Failed to cleanup metadata for ${configType}:`, error);
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
exports.cleanupInvalidMetadata = cleanupInvalidMetadata;
|
package/dist/server/main.js
CHANGED
|
@@ -24,6 +24,7 @@ const os_1 = __importDefault(require("os"));
|
|
|
24
24
|
const auth_1 = require("./auth");
|
|
25
25
|
const version_check_1 = require("./version-check");
|
|
26
26
|
const utils_1 = require("./utils");
|
|
27
|
+
const config_metadata_1 = require("./config-metadata");
|
|
27
28
|
const dotenvPath = path_1.default.resolve(os_1.default.homedir(), '.aicodeswitch/aicodeswitch.conf');
|
|
28
29
|
if (fs_1.default.existsSync(dotenvPath)) {
|
|
29
30
|
dotenv_1.default.config({ path: dotenvPath });
|
|
@@ -46,15 +47,28 @@ const writeClaudeConfig = (dbManager) => __awaiter(void 0, void 0, void 0, funct
|
|
|
46
47
|
// Claude Code settings.json
|
|
47
48
|
const claudeDir = path_1.default.join(homeDir, '.claude');
|
|
48
49
|
const claudeSettingsPath = path_1.default.join(claudeDir, 'settings.json');
|
|
49
|
-
const claudeSettingsBakPath = path_1.default.join(claudeDir, 'settings.json.
|
|
50
|
-
const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
const claudeSettingsBakPath = path_1.default.join(claudeDir, 'settings.json.aicodeswitch_backup');
|
|
51
|
+
const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.aicodeswitch_backup');
|
|
52
|
+
// 使用新的配置状态检测来判断是否可以写入
|
|
53
|
+
const configStatus = (0, config_metadata_1.checkClaudeConfigStatus)();
|
|
54
|
+
// 只有当当前配置已经是代理配置时,才拒绝写入
|
|
55
|
+
if (configStatus.isOverwritten) {
|
|
56
|
+
console.error('Claude config has already been overwritten. Please restore the original config first.');
|
|
54
57
|
return false;
|
|
55
58
|
}
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
// 如果 .aicodeswitch_backup 文件不存在,才进行备份(避免覆盖已有备份)
|
|
60
|
+
let originalSettingsHash = undefined;
|
|
61
|
+
if (!fs_1.default.existsSync(claudeSettingsBakPath)) {
|
|
62
|
+
// 计算原始配置文件的 hash(如果存在)
|
|
63
|
+
if (fs_1.default.existsSync(claudeSettingsPath)) {
|
|
64
|
+
originalSettingsHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8')).digest('hex');
|
|
65
|
+
// 备份当前配置文件
|
|
66
|
+
fs_1.default.renameSync(claudeSettingsPath, claudeSettingsBakPath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// .aicodeswitch_backup 已存在,直接使用现有的备份文件
|
|
71
|
+
console.log('Backup file already exists, skipping backup step');
|
|
58
72
|
}
|
|
59
73
|
if (!fs_1.default.existsSync(claudeDir)) {
|
|
60
74
|
fs_1.default.mkdirSync(claudeDir, { recursive: true });
|
|
@@ -69,8 +83,11 @@ const writeClaudeConfig = (dbManager) => __awaiter(void 0, void 0, void 0, funct
|
|
|
69
83
|
fs_1.default.writeFileSync(claudeSettingsPath, JSON.stringify(claudeSettings, null, 2));
|
|
70
84
|
// Claude Code .claude.json
|
|
71
85
|
const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
// 同样处理 .claude.json 的备份
|
|
87
|
+
if (!fs_1.default.existsSync(claudeJsonBakPath)) {
|
|
88
|
+
if (fs_1.default.existsSync(claudeJsonPath)) {
|
|
89
|
+
fs_1.default.renameSync(claudeJsonPath, claudeJsonBakPath);
|
|
90
|
+
}
|
|
74
91
|
}
|
|
75
92
|
let claudeJson = {};
|
|
76
93
|
if (fs_1.default.existsSync(claudeJsonPath)) {
|
|
@@ -78,6 +95,26 @@ const writeClaudeConfig = (dbManager) => __awaiter(void 0, void 0, void 0, funct
|
|
|
78
95
|
}
|
|
79
96
|
claudeJson.hasCompletedOnboarding = true;
|
|
80
97
|
fs_1.default.writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
|
|
98
|
+
// 保存元数据
|
|
99
|
+
const currentSettingsHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(claudeSettingsPath, 'utf-8')).digest('hex');
|
|
100
|
+
const metadata = {
|
|
101
|
+
configType: 'claude',
|
|
102
|
+
timestamp: Date.now(),
|
|
103
|
+
originalHash: originalSettingsHash,
|
|
104
|
+
proxyMarker: `http://${host}:${port}/claude-code`,
|
|
105
|
+
files: [
|
|
106
|
+
{
|
|
107
|
+
originalPath: claudeSettingsPath,
|
|
108
|
+
backupPath: claudeSettingsBakPath,
|
|
109
|
+
currentHash: currentSettingsHash
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
originalPath: claudeJsonPath,
|
|
113
|
+
backupPath: claudeJsonBakPath
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
};
|
|
117
|
+
(0, config_metadata_1.saveMetadata)(metadata);
|
|
81
118
|
return true;
|
|
82
119
|
}
|
|
83
120
|
catch (error) {
|
|
@@ -93,15 +130,28 @@ const writeCodexConfig = (dbManager) => __awaiter(void 0, void 0, void 0, functi
|
|
|
93
130
|
// Codex config.toml
|
|
94
131
|
const codexDir = path_1.default.join(homeDir, '.codex');
|
|
95
132
|
const codexConfigPath = path_1.default.join(codexDir, 'config.toml');
|
|
96
|
-
const codexConfigBakPath = path_1.default.join(codexDir, 'config.toml.
|
|
97
|
-
const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
133
|
+
const codexConfigBakPath = path_1.default.join(codexDir, 'config.toml.aicodeswitch_backup');
|
|
134
|
+
const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.aicodeswitch_backup');
|
|
135
|
+
// 使用新的配置状态检测来判断是否可以写入
|
|
136
|
+
const configStatus = (0, config_metadata_1.checkCodexConfigStatus)();
|
|
137
|
+
// 只有当当前配置已经是代理配置时,才拒绝写入
|
|
138
|
+
if (configStatus.isOverwritten) {
|
|
139
|
+
console.error('Codex config has already been overwritten. Please restore the original config first.');
|
|
101
140
|
return false;
|
|
102
141
|
}
|
|
103
|
-
|
|
104
|
-
|
|
142
|
+
// 如果 .aicodeswitch_backup 文件不存在,才进行备份(避免覆盖已有备份)
|
|
143
|
+
let originalConfigHash = undefined;
|
|
144
|
+
if (!fs_1.default.existsSync(codexConfigBakPath)) {
|
|
145
|
+
// 计算原始配置文件的 hash(如果存在)
|
|
146
|
+
if (fs_1.default.existsSync(codexConfigPath)) {
|
|
147
|
+
originalConfigHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(codexConfigPath, 'utf-8')).digest('hex');
|
|
148
|
+
// 备份当前配置文件
|
|
149
|
+
fs_1.default.renameSync(codexConfigPath, codexConfigBakPath);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// .aicodeswitch_backup 已存在,直接使用现有的备份文件
|
|
154
|
+
console.log('Backup file already exists, skipping backup step');
|
|
105
155
|
}
|
|
106
156
|
if (!fs_1.default.existsSync(codexDir)) {
|
|
107
157
|
fs_1.default.mkdirSync(codexDir, { recursive: true });
|
|
@@ -121,13 +171,36 @@ requires_openai_auth = true
|
|
|
121
171
|
fs_1.default.writeFileSync(codexConfigPath, codexConfig);
|
|
122
172
|
// Codex auth.json
|
|
123
173
|
const codexAuthPath = path_1.default.join(codexDir, 'auth.json');
|
|
124
|
-
|
|
125
|
-
|
|
174
|
+
// 同样处理 auth.json 的备份
|
|
175
|
+
if (!fs_1.default.existsSync(codexAuthBakPath)) {
|
|
176
|
+
if (fs_1.default.existsSync(codexAuthPath)) {
|
|
177
|
+
fs_1.default.renameSync(codexAuthPath, codexAuthBakPath);
|
|
178
|
+
}
|
|
126
179
|
}
|
|
127
180
|
const codexAuth = {
|
|
128
181
|
OPENAI_API_KEY: config.apiKey || "api_key"
|
|
129
182
|
};
|
|
130
183
|
fs_1.default.writeFileSync(codexAuthPath, JSON.stringify(codexAuth, null, 2));
|
|
184
|
+
// 保存元数据
|
|
185
|
+
const currentConfigHash = (0, crypto_1.createHash)('sha256').update(fs_1.default.readFileSync(codexConfigPath, 'utf-8')).digest('hex');
|
|
186
|
+
const metadata = {
|
|
187
|
+
configType: 'codex',
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
originalHash: originalConfigHash,
|
|
190
|
+
proxyMarker: `http://${host}:${port}/codex`,
|
|
191
|
+
files: [
|
|
192
|
+
{
|
|
193
|
+
originalPath: codexConfigPath,
|
|
194
|
+
backupPath: codexConfigBakPath,
|
|
195
|
+
currentHash: currentConfigHash
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
originalPath: codexAuthPath,
|
|
199
|
+
backupPath: codexAuthBakPath
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
};
|
|
203
|
+
(0, config_metadata_1.saveMetadata)(metadata);
|
|
131
204
|
return true;
|
|
132
205
|
}
|
|
133
206
|
catch (error) {
|
|
@@ -141,7 +214,7 @@ const restoreClaudeConfig = () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
141
214
|
// Restore Claude Code settings.json
|
|
142
215
|
const claudeDir = path_1.default.join(homeDir, '.claude');
|
|
143
216
|
const claudeSettingsPath = path_1.default.join(claudeDir, 'settings.json');
|
|
144
|
-
const claudeSettingsBakPath = path_1.default.join(claudeDir, 'settings.json.
|
|
217
|
+
const claudeSettingsBakPath = path_1.default.join(claudeDir, 'settings.json.aicodeswitch_backup');
|
|
145
218
|
if (fs_1.default.existsSync(claudeSettingsBakPath)) {
|
|
146
219
|
if (fs_1.default.existsSync(claudeSettingsPath)) {
|
|
147
220
|
fs_1.default.unlinkSync(claudeSettingsPath);
|
|
@@ -150,13 +223,15 @@ const restoreClaudeConfig = () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
150
223
|
}
|
|
151
224
|
// Restore Claude Code .claude.json
|
|
152
225
|
const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
|
|
153
|
-
const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.
|
|
226
|
+
const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.aicodeswitch_backup');
|
|
154
227
|
if (fs_1.default.existsSync(claudeJsonBakPath)) {
|
|
155
228
|
if (fs_1.default.existsSync(claudeJsonPath)) {
|
|
156
229
|
fs_1.default.unlinkSync(claudeJsonPath);
|
|
157
230
|
}
|
|
158
231
|
fs_1.default.renameSync(claudeJsonBakPath, claudeJsonPath);
|
|
159
232
|
}
|
|
233
|
+
// 删除元数据
|
|
234
|
+
(0, config_metadata_1.deleteMetadata)('claude');
|
|
160
235
|
return true;
|
|
161
236
|
}
|
|
162
237
|
catch (error) {
|
|
@@ -170,7 +245,7 @@ const restoreCodexConfig = () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
170
245
|
// Restore Codex config.toml
|
|
171
246
|
const codexDir = path_1.default.join(homeDir, '.codex');
|
|
172
247
|
const codexConfigPath = path_1.default.join(codexDir, 'config.toml');
|
|
173
|
-
const codexConfigBakPath = path_1.default.join(codexDir, 'config.toml.
|
|
248
|
+
const codexConfigBakPath = path_1.default.join(codexDir, 'config.toml.aicodeswitch_backup');
|
|
174
249
|
if (fs_1.default.existsSync(codexConfigBakPath)) {
|
|
175
250
|
if (fs_1.default.existsSync(codexConfigPath)) {
|
|
176
251
|
fs_1.default.unlinkSync(codexConfigPath);
|
|
@@ -179,13 +254,15 @@ const restoreCodexConfig = () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
179
254
|
}
|
|
180
255
|
// Restore Codex auth.json
|
|
181
256
|
const codexAuthPath = path_1.default.join(codexDir, 'auth.json');
|
|
182
|
-
const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.
|
|
257
|
+
const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.aicodeswitch_backup');
|
|
183
258
|
if (fs_1.default.existsSync(codexAuthBakPath)) {
|
|
184
259
|
if (fs_1.default.existsSync(codexAuthPath)) {
|
|
185
260
|
fs_1.default.unlinkSync(codexAuthPath);
|
|
186
261
|
}
|
|
187
262
|
fs_1.default.renameSync(codexAuthBakPath, codexAuthPath);
|
|
188
263
|
}
|
|
264
|
+
// 删除元数据
|
|
265
|
+
(0, config_metadata_1.deleteMetadata)('codex');
|
|
189
266
|
return true;
|
|
190
267
|
}
|
|
191
268
|
catch (error) {
|
|
@@ -195,10 +272,12 @@ const restoreCodexConfig = () => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
195
272
|
});
|
|
196
273
|
const checkClaudeBackupExists = () => {
|
|
197
274
|
try {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
275
|
+
// 清理可能的无效元数据
|
|
276
|
+
(0, config_metadata_1.cleanupInvalidMetadata)('claude');
|
|
277
|
+
// 使用新的配置状态检测
|
|
278
|
+
const status = (0, config_metadata_1.checkClaudeConfigStatus)();
|
|
279
|
+
// 返回是否已被覆盖(用于向后兼容)
|
|
280
|
+
return status.isOverwritten;
|
|
202
281
|
}
|
|
203
282
|
catch (error) {
|
|
204
283
|
console.error('Failed to check Claude backup files:', error);
|
|
@@ -207,10 +286,12 @@ const checkClaudeBackupExists = () => {
|
|
|
207
286
|
};
|
|
208
287
|
const checkCodexBackupExists = () => {
|
|
209
288
|
try {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
289
|
+
// 清理可能的无效元数据
|
|
290
|
+
(0, config_metadata_1.cleanupInvalidMetadata)('codex');
|
|
291
|
+
// 使用新的配置状态检测
|
|
292
|
+
const status = (0, config_metadata_1.checkCodexConfigStatus)();
|
|
293
|
+
// 返回是否已被覆盖(用于向后兼容)
|
|
294
|
+
return status.isOverwritten;
|
|
214
295
|
}
|
|
215
296
|
catch (error) {
|
|
216
297
|
console.error('Failed to check Codex backup files:', error);
|
|
@@ -396,6 +477,15 @@ const registerRoutes = (dbManager, proxyServer) => {
|
|
|
396
477
|
app.get('/api/check-backup/codex', (_req, res) => {
|
|
397
478
|
res.json({ exists: checkCodexBackupExists() });
|
|
398
479
|
});
|
|
480
|
+
// 新的详细配置状态 API 端点
|
|
481
|
+
app.get('/api/config-status/claude', (_req, res) => {
|
|
482
|
+
const status = (0, config_metadata_1.checkClaudeConfigStatus)();
|
|
483
|
+
res.json(status);
|
|
484
|
+
});
|
|
485
|
+
app.get('/api/config-status/codex', (_req, res) => {
|
|
486
|
+
const status = (0, config_metadata_1.checkCodexConfigStatus)();
|
|
487
|
+
res.json(status);
|
|
488
|
+
});
|
|
399
489
|
app.post('/api/export', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
400
490
|
const { password } = req.body;
|
|
401
491
|
const data = yield dbManager.exportData(password);
|