aicodeswitch 2.0.2 → 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 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.4 (2026-01-27)
6
+
7
+ ### 2.0.3 (2026-01-27)
8
+
5
9
  ### 2.0.2 (2026-01-27)
6
10
 
7
11
  ### 2.0.1 (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.bak');
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.bak');
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.bak');
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.bak');
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.bak');
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.bak');
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.bak');
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.bak');
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;
@@ -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.bak');
50
- const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.bak');
51
- // Check if any backup file already exists
52
- if (fs_1.default.existsSync(claudeSettingsBakPath) || fs_1.default.existsSync(claudeJsonBakPath)) {
53
- console.error('Claude backup files already exist, refusing to overwrite');
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
- if (fs_1.default.existsSync(claudeSettingsPath)) {
57
- fs_1.default.renameSync(claudeSettingsPath, claudeSettingsBakPath);
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
- if (fs_1.default.existsSync(claudeJsonPath)) {
73
- fs_1.default.renameSync(claudeJsonPath, claudeJsonBakPath);
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.bak');
97
- const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.bak');
98
- // Check if any backup file already exists
99
- if (fs_1.default.existsSync(codexConfigBakPath) || fs_1.default.existsSync(codexAuthBakPath)) {
100
- console.error('Codex backup files already exist, refusing to overwrite');
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
- if (fs_1.default.existsSync(codexConfigPath)) {
104
- fs_1.default.renameSync(codexConfigPath, codexConfigBakPath);
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
- if (fs_1.default.existsSync(codexAuthPath)) {
125
- fs_1.default.renameSync(codexAuthPath, codexAuthBakPath);
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.bak');
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.bak');
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.bak');
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.bak');
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
- const homeDir = os_1.default.homedir();
199
- const claudeSettingsBakPath = path_1.default.join(homeDir, '.claude', 'settings.json.bak');
200
- const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.bak');
201
- return fs_1.default.existsSync(claudeSettingsBakPath) || fs_1.default.existsSync(claudeJsonBakPath);
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
- const homeDir = os_1.default.homedir();
211
- const codexConfigBakPath = path_1.default.join(homeDir, '.codex', 'config.toml.bak');
212
- const codexAuthBakPath = path_1.default.join(homeDir, '.codex', 'auth.json.bak');
213
- return fs_1.default.existsSync(codexConfigBakPath) || fs_1.default.existsSync(codexAuthBakPath);
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);
@@ -502,10 +592,15 @@ const registerRoutes = (dbManager, proxyServer) => {
502
592
  const currentHash = (0, crypto_1.createHash)('sha256').update(content).digest('hex');
503
593
  // 读取之前保存的 hash
504
594
  const hashPath = getMigrationHashPath();
505
- let savedHash = '';
506
- if (fs_1.default.existsSync(hashPath)) {
507
- savedHash = fs_1.default.readFileSync(hashPath, 'utf-8').trim();
595
+ // 如果 hash 文件不存在,说明是第一次安装
596
+ if (!fs_1.default.existsSync(hashPath)) {
597
+ // 第一次安装,直接保存当前 hash,不显示弹窗
598
+ fs_1.default.writeFileSync(hashPath, currentHash, 'utf-8');
599
+ res.json({ shouldShow: false, content: '' });
600
+ return;
508
601
  }
602
+ // 读取已保存的 hash
603
+ const savedHash = fs_1.default.readFileSync(hashPath, 'utf-8').trim();
509
604
  // 如果 hash 不同,需要显示弹窗
510
605
  const shouldShow = savedHash !== currentHash;
511
606
  res.json({ shouldShow, content: shouldShow ? content : '' });