aicodeswitch 2.0.3 → 2.0.5
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 +4 -0
- package/bin/restore.js +8 -8
- package/dist/server/config-metadata.js +264 -0
- package/dist/server/main.js +120 -30
- package/dist/server/proxy-server.js +31 -5
- 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
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### 2.0.5 (2026-01-27)
|
|
6
|
+
|
|
7
|
+
### 2.0.4 (2026-01-27)
|
|
8
|
+
|
|
5
9
|
### 2.0.3 (2026-01-27)
|
|
6
10
|
|
|
7
11
|
### 2.0.2 (2026-01-27)
|
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);
|
|
@@ -1127,14 +1127,40 @@ class ProxyServer {
|
|
|
1127
1127
|
}
|
|
1128
1128
|
});
|
|
1129
1129
|
}
|
|
1130
|
+
/**
|
|
1131
|
+
* 对敏感的 header 值进行脱敏处理
|
|
1132
|
+
* @param key header 键(小写)
|
|
1133
|
+
* @param value header 值
|
|
1134
|
+
* @returns 脱敏后的值,如果 header 是敏感的则返回 32 个 *
|
|
1135
|
+
*/
|
|
1136
|
+
sanitizeHeaderValue(key, value) {
|
|
1137
|
+
// 需要脱敏的敏感 header 列表(不区分大小写)
|
|
1138
|
+
const sensitiveHeaders = [
|
|
1139
|
+
'authorization', // Bearer token
|
|
1140
|
+
'x-api-key', // API key
|
|
1141
|
+
'api-key', // API key
|
|
1142
|
+
'apikey', // API key
|
|
1143
|
+
'x-openai-api-key', // OpenAI API key
|
|
1144
|
+
'openai-api-key', // OpenAI API key
|
|
1145
|
+
'anthropic-api-key', // Anthropic API key
|
|
1146
|
+
'access-token', // Access token
|
|
1147
|
+
'x-anthropic-api-key', // Anthropic API key
|
|
1148
|
+
'refresh-token', // Refresh token
|
|
1149
|
+
];
|
|
1150
|
+
// 检查是否是敏感 header
|
|
1151
|
+
if (sensitiveHeaders.includes(key)) {
|
|
1152
|
+
return '********************************';
|
|
1153
|
+
}
|
|
1154
|
+
return value;
|
|
1155
|
+
}
|
|
1130
1156
|
normalizeHeaders(headers) {
|
|
1131
1157
|
const normalized = {};
|
|
1132
1158
|
for (const [key, value] of Object.entries(headers)) {
|
|
1133
1159
|
if (typeof value === 'string') {
|
|
1134
|
-
normalized[key] = value;
|
|
1160
|
+
normalized[key] = this.sanitizeHeaderValue(key.toLowerCase(), value);
|
|
1135
1161
|
}
|
|
1136
1162
|
else if (Array.isArray(value)) {
|
|
1137
|
-
normalized[key] = value.join(', ');
|
|
1163
|
+
normalized[key] = this.sanitizeHeaderValue(key.toLowerCase(), value.join(', '));
|
|
1138
1164
|
}
|
|
1139
1165
|
}
|
|
1140
1166
|
return normalized;
|
|
@@ -1144,13 +1170,13 @@ class ProxyServer {
|
|
|
1144
1170
|
for (const [key, value] of Object.entries(headers)) {
|
|
1145
1171
|
if (value !== null && value !== undefined) {
|
|
1146
1172
|
if (typeof value === 'string') {
|
|
1147
|
-
normalized[key] = value;
|
|
1173
|
+
normalized[key] = this.sanitizeHeaderValue(key.toLowerCase(), value);
|
|
1148
1174
|
}
|
|
1149
1175
|
else if (Array.isArray(value)) {
|
|
1150
|
-
normalized[key] = value.join(', ');
|
|
1176
|
+
normalized[key] = this.sanitizeHeaderValue(key.toLowerCase(), value.join(', '));
|
|
1151
1177
|
}
|
|
1152
1178
|
else {
|
|
1153
|
-
normalized[key] = String(value);
|
|
1179
|
+
normalized[key] = this.sanitizeHeaderValue(key.toLowerCase(), String(value));
|
|
1154
1180
|
}
|
|
1155
1181
|
}
|
|
1156
1182
|
}
|