aicodeswitch 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,382 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const express_1 = __importDefault(require("express"));
16
+ const cors_1 = __importDefault(require("cors"));
17
+ const dotenv_1 = __importDefault(require("dotenv"));
18
+ const fs_1 = __importDefault(require("fs"));
19
+ const path_1 = __importDefault(require("path"));
20
+ const database_1 = require("./database");
21
+ const proxy_server_1 = require("./proxy-server");
22
+ const os_1 = __importDefault(require("os"));
23
+ const auth_1 = require("./auth");
24
+ const dotenvPath = path_1.default.resolve(os_1.default.homedir(), '.aicodeswitch/aicodeswitch.conf');
25
+ if (fs_1.default.existsSync(dotenvPath)) {
26
+ dotenv_1.default.config({ path: dotenvPath });
27
+ }
28
+ const host = process.env.HOST || '127.0.0.1';
29
+ const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 4567;
30
+ const dataDir = process.env.DATA_DIR ? path_1.default.resolve(process.cwd(), process.env.DATA_DIR) : path_1.default.join(os_1.default.homedir(), '.aicodeswitch/data');
31
+ const app = (0, express_1.default)();
32
+ app.use((0, cors_1.default)());
33
+ app.use(express_1.default.json({ limit: '10mb' }));
34
+ app.use(express_1.default.urlencoded({ extended: true }));
35
+ const asyncHandler = (handler) => (req, res, next) => {
36
+ Promise.resolve(handler(req, res, next)).catch(next);
37
+ };
38
+ const writeClaudeConfig = (dbManager) => __awaiter(void 0, void 0, void 0, function* () {
39
+ try {
40
+ const homeDir = os_1.default.homedir();
41
+ const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 4567;
42
+ const config = dbManager.getConfig();
43
+ // Claude Code settings.json
44
+ const claudeDir = path_1.default.join(homeDir, '.claude');
45
+ const claudeSettingsPath = path_1.default.join(claudeDir, 'settings.json');
46
+ const claudeSettingsBakPath = path_1.default.join(claudeDir, 'settings.json.bak');
47
+ if (fs_1.default.existsSync(claudeSettingsPath)) {
48
+ fs_1.default.renameSync(claudeSettingsPath, claudeSettingsBakPath);
49
+ }
50
+ if (!fs_1.default.existsSync(claudeDir)) {
51
+ fs_1.default.mkdirSync(claudeDir, { recursive: true });
52
+ }
53
+ const claudeSettings = {
54
+ env: {
55
+ ANTHROPIC_AUTH_TOKEN: config.apiKey || "api_key",
56
+ ANTHROPIC_BASE_URL: `http://${host}:${port}/claude-code`,
57
+ API_TIMEOUT_MS: "3000000"
58
+ },
59
+ hasCompletedOnboarding: true
60
+ };
61
+ fs_1.default.writeFileSync(claudeSettingsPath, JSON.stringify(claudeSettings, null, 2));
62
+ // Claude Code .claude.json
63
+ const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
64
+ const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.bak');
65
+ if (fs_1.default.existsSync(claudeJsonPath)) {
66
+ fs_1.default.renameSync(claudeJsonPath, claudeJsonBakPath);
67
+ }
68
+ const claudeJson = {
69
+ hasCompletedOnboarding: true
70
+ };
71
+ fs_1.default.writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
72
+ return true;
73
+ }
74
+ catch (error) {
75
+ console.error('Failed to write Claude config files:', error);
76
+ return false;
77
+ }
78
+ });
79
+ const writeCodexConfig = (dbManager) => __awaiter(void 0, void 0, void 0, function* () {
80
+ try {
81
+ const homeDir = os_1.default.homedir();
82
+ const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 4567;
83
+ const config = dbManager.getConfig();
84
+ // Codex config.toml
85
+ const codexDir = path_1.default.join(homeDir, '.codex');
86
+ const codexConfigPath = path_1.default.join(codexDir, 'config.toml');
87
+ const codexConfigBakPath = path_1.default.join(codexDir, 'config.toml.bak');
88
+ if (fs_1.default.existsSync(codexConfigPath)) {
89
+ fs_1.default.renameSync(codexConfigPath, codexConfigBakPath);
90
+ }
91
+ if (!fs_1.default.existsSync(codexDir)) {
92
+ fs_1.default.mkdirSync(codexDir, { recursive: true });
93
+ }
94
+ const codexConfig = `model_provider = "aicodeswitch"
95
+ model = "gpt-5.1-codex"
96
+ model_reasoning_effort = "high"
97
+ disable_response_storage = true
98
+
99
+
100
+ [model_providers.aicodeswitch]
101
+ name = "aicodeswitch"
102
+ base_url = "http://${host}:${port}/codex"
103
+ wire_api = "responses"
104
+ requires_openai_auth = true
105
+ `;
106
+ fs_1.default.writeFileSync(codexConfigPath, codexConfig);
107
+ // Codex auth.json
108
+ const codexAuthPath = path_1.default.join(codexDir, 'auth.json');
109
+ const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.bak');
110
+ if (fs_1.default.existsSync(codexAuthPath)) {
111
+ fs_1.default.renameSync(codexAuthPath, codexAuthBakPath);
112
+ }
113
+ const codexAuth = {
114
+ OPENAI_API_KEY: config.apiKey || "api_key"
115
+ };
116
+ fs_1.default.writeFileSync(codexAuthPath, JSON.stringify(codexAuth, null, 2));
117
+ return true;
118
+ }
119
+ catch (error) {
120
+ console.error('Failed to write Codex config files:', error);
121
+ return false;
122
+ }
123
+ });
124
+ const restoreClaudeConfig = () => __awaiter(void 0, void 0, void 0, function* () {
125
+ try {
126
+ const homeDir = os_1.default.homedir();
127
+ // Restore Claude Code settings.json
128
+ const claudeDir = path_1.default.join(homeDir, '.claude');
129
+ const claudeSettingsPath = path_1.default.join(claudeDir, 'settings.json');
130
+ const claudeSettingsBakPath = path_1.default.join(claudeDir, 'settings.json.bak');
131
+ if (fs_1.default.existsSync(claudeSettingsBakPath)) {
132
+ if (fs_1.default.existsSync(claudeSettingsPath)) {
133
+ fs_1.default.unlinkSync(claudeSettingsPath);
134
+ }
135
+ fs_1.default.renameSync(claudeSettingsBakPath, claudeSettingsPath);
136
+ }
137
+ // Restore Claude Code .claude.json
138
+ const claudeJsonPath = path_1.default.join(homeDir, '.claude.json');
139
+ const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.bak');
140
+ if (fs_1.default.existsSync(claudeJsonBakPath)) {
141
+ if (fs_1.default.existsSync(claudeJsonPath)) {
142
+ fs_1.default.unlinkSync(claudeJsonPath);
143
+ }
144
+ fs_1.default.renameSync(claudeJsonBakPath, claudeJsonPath);
145
+ }
146
+ return true;
147
+ }
148
+ catch (error) {
149
+ console.error('Failed to restore Claude config files:', error);
150
+ return false;
151
+ }
152
+ });
153
+ const restoreCodexConfig = () => __awaiter(void 0, void 0, void 0, function* () {
154
+ try {
155
+ const homeDir = os_1.default.homedir();
156
+ // Restore Codex config.toml
157
+ const codexDir = path_1.default.join(homeDir, '.codex');
158
+ const codexConfigPath = path_1.default.join(codexDir, 'config.toml');
159
+ const codexConfigBakPath = path_1.default.join(codexDir, 'config.toml.bak');
160
+ if (fs_1.default.existsSync(codexConfigBakPath)) {
161
+ if (fs_1.default.existsSync(codexConfigPath)) {
162
+ fs_1.default.unlinkSync(codexConfigPath);
163
+ }
164
+ fs_1.default.renameSync(codexConfigBakPath, codexConfigPath);
165
+ }
166
+ // Restore Codex auth.json
167
+ const codexAuthPath = path_1.default.join(codexDir, 'auth.json');
168
+ const codexAuthBakPath = path_1.default.join(codexDir, 'auth.json.bak');
169
+ if (fs_1.default.existsSync(codexAuthBakPath)) {
170
+ if (fs_1.default.existsSync(codexAuthPath)) {
171
+ fs_1.default.unlinkSync(codexAuthPath);
172
+ }
173
+ fs_1.default.renameSync(codexAuthBakPath, codexAuthPath);
174
+ }
175
+ return true;
176
+ }
177
+ catch (error) {
178
+ console.error('Failed to restore Codex config files:', error);
179
+ return false;
180
+ }
181
+ });
182
+ const checkClaudeBackupExists = () => {
183
+ try {
184
+ const homeDir = os_1.default.homedir();
185
+ const claudeSettingsBakPath = path_1.default.join(homeDir, '.claude', 'settings.json.bak');
186
+ const claudeJsonBakPath = path_1.default.join(homeDir, '.claude.json.bak');
187
+ return fs_1.default.existsSync(claudeSettingsBakPath) || fs_1.default.existsSync(claudeJsonBakPath);
188
+ }
189
+ catch (error) {
190
+ console.error('Failed to check Claude backup files:', error);
191
+ return false;
192
+ }
193
+ };
194
+ const checkCodexBackupExists = () => {
195
+ try {
196
+ const homeDir = os_1.default.homedir();
197
+ const codexConfigBakPath = path_1.default.join(homeDir, '.codex', 'config.toml.bak');
198
+ const codexAuthBakPath = path_1.default.join(homeDir, '.codex', 'auth.json.bak');
199
+ return fs_1.default.existsSync(codexConfigBakPath) || fs_1.default.existsSync(codexAuthBakPath);
200
+ }
201
+ catch (error) {
202
+ console.error('Failed to check Codex backup files:', error);
203
+ return false;
204
+ }
205
+ };
206
+ const registerRoutes = (dbManager, proxyServer) => {
207
+ app.get('/health', (_req, res) => res.json({ status: 'ok' }));
208
+ // 鉴权相关路由 - 公开访问
209
+ app.get('/api/auth/status', (_req, res) => {
210
+ const response = { enabled: (0, auth_1.isAuthEnabled)() };
211
+ res.json(response);
212
+ });
213
+ app.post('/api/auth/login', (req, res) => {
214
+ const { authCode } = req.body;
215
+ if (!authCode) {
216
+ res.status(400).json({ error: 'Auth code is required' });
217
+ return;
218
+ }
219
+ if ((0, auth_1.verifyAuthCode)(authCode)) {
220
+ const token = (0, auth_1.generateToken)();
221
+ const response = { token };
222
+ res.json(response);
223
+ }
224
+ else {
225
+ res.status(401).json({ error: 'Invalid auth code' });
226
+ }
227
+ });
228
+ // 鉴权中间件 - 保护所有 /api/* 路由 (除了 /api/auth/*)
229
+ app.use('/api', (req, res, next) => {
230
+ if (req.path.startsWith('/auth/')) {
231
+ next(); // /api/auth/* 路由不需要鉴权
232
+ }
233
+ else {
234
+ (0, auth_1.authMiddleware)(req, res, next);
235
+ }
236
+ });
237
+ app.get('/api/vendors', (_req, res) => res.json(dbManager.getVendors()));
238
+ app.post('/api/vendors', (req, res) => res.json(dbManager.createVendor(req.body)));
239
+ app.put('/api/vendors/:id', (req, res) => res.json(dbManager.updateVendor(req.params.id, req.body)));
240
+ app.delete('/api/vendors/:id', (req, res) => res.json(dbManager.deleteVendor(req.params.id)));
241
+ app.get('/api/services', (req, res) => {
242
+ const vendorId = typeof req.query.vendorId === 'string' ? req.query.vendorId : undefined;
243
+ res.json(dbManager.getAPIServices(vendorId));
244
+ });
245
+ app.post('/api/services', (req, res) => res.json(dbManager.createAPIService(req.body)));
246
+ app.put('/api/services/:id', (req, res) => res.json(dbManager.updateAPIService(req.params.id, req.body)));
247
+ app.delete('/api/services/:id', (req, res) => res.json(dbManager.deleteAPIService(req.params.id)));
248
+ app.get('/api/routes', (_req, res) => res.json(dbManager.getRoutes()));
249
+ app.post('/api/routes', (req, res) => res.json(dbManager.createRoute(req.body)));
250
+ app.put('/api/routes/:id', (req, res) => res.json(dbManager.updateRoute(req.params.id, req.body)));
251
+ app.delete('/api/routes/:id', (req, res) => res.json(dbManager.deleteRoute(req.params.id)));
252
+ app.post('/api/routes/:id/activate', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
253
+ const result = dbManager.activateRoute(req.params.id);
254
+ if (result) {
255
+ yield proxyServer.reloadRoutes();
256
+ }
257
+ res.json(result);
258
+ })));
259
+ app.post('/api/routes/:id/deactivate', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
260
+ const result = dbManager.deactivateRoute(req.params.id);
261
+ if (result) {
262
+ yield proxyServer.reloadRoutes();
263
+ }
264
+ res.json(result);
265
+ })));
266
+ app.get('/api/rules', (req, res) => {
267
+ const routeId = typeof req.query.routeId === 'string' ? req.query.routeId : undefined;
268
+ res.json(dbManager.getRules(routeId));
269
+ });
270
+ app.post('/api/rules', (req, res) => res.json(dbManager.createRule(req.body)));
271
+ app.put('/api/rules/:id', (req, res) => res.json(dbManager.updateRule(req.params.id, req.body)));
272
+ app.delete('/api/rules/:id', (req, res) => res.json(dbManager.deleteRule(req.params.id)));
273
+ app.get('/api/logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
274
+ const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
275
+ const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
276
+ const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
277
+ const offset = Number.isFinite(rawOffset) ? rawOffset : 0;
278
+ const logs = yield dbManager.getLogs(limit, offset);
279
+ res.json(logs);
280
+ })));
281
+ app.delete('/api/logs', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
282
+ yield dbManager.clearLogs();
283
+ res.json(true);
284
+ })));
285
+ app.get('/api/access-logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
286
+ const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
287
+ const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
288
+ const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
289
+ const offset = Number.isFinite(rawOffset) ? rawOffset : 0;
290
+ const logs = yield dbManager.getAccessLogs(limit, offset);
291
+ res.json(logs);
292
+ })));
293
+ app.delete('/api/access-logs', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
294
+ yield dbManager.clearAccessLogs();
295
+ res.json(true);
296
+ })));
297
+ app.get('/api/error-logs', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
298
+ const rawLimit = typeof req.query.limit === 'string' ? parseInt(req.query.limit, 10) : NaN;
299
+ const rawOffset = typeof req.query.offset === 'string' ? parseInt(req.query.offset, 10) : NaN;
300
+ const limit = Number.isFinite(rawLimit) ? rawLimit : 100;
301
+ const offset = Number.isFinite(rawOffset) ? rawOffset : 0;
302
+ const logs = yield dbManager.getErrorLogs(limit, offset);
303
+ res.json(logs);
304
+ })));
305
+ app.delete('/api/error-logs', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
306
+ yield dbManager.clearErrorLogs();
307
+ res.json(true);
308
+ })));
309
+ app.get('/api/config', (_req, res) => res.json(dbManager.getConfig()));
310
+ app.put('/api/config', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
311
+ const config = req.body;
312
+ const result = dbManager.updateConfig(config);
313
+ if (result) {
314
+ yield proxyServer.updateConfig(config);
315
+ }
316
+ res.json(result);
317
+ })));
318
+ app.post('/api/write-config/claude', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
319
+ const result = yield writeClaudeConfig(dbManager);
320
+ res.json(result);
321
+ })));
322
+ app.post('/api/write-config/codex', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
323
+ const result = yield writeCodexConfig(dbManager);
324
+ res.json(result);
325
+ })));
326
+ app.post('/api/restore-config/claude', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
327
+ const result = yield restoreClaudeConfig();
328
+ res.json(result);
329
+ })));
330
+ app.post('/api/restore-config/codex', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
331
+ const result = yield restoreCodexConfig();
332
+ res.json(result);
333
+ })));
334
+ app.get('/api/check-backup/claude', (_req, res) => {
335
+ res.json({ exists: checkClaudeBackupExists() });
336
+ });
337
+ app.get('/api/check-backup/codex', (_req, res) => {
338
+ res.json({ exists: checkCodexBackupExists() });
339
+ });
340
+ app.post('/api/export', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
341
+ const { password } = req.body;
342
+ const data = yield dbManager.exportData(password);
343
+ res.json({ data });
344
+ })));
345
+ app.post('/api/import', asyncHandler((req, res) => __awaiter(void 0, void 0, void 0, function* () {
346
+ const { encryptedData, password } = req.body;
347
+ const result = yield dbManager.importData(encryptedData, password);
348
+ if (result) {
349
+ yield proxyServer.reloadRoutes();
350
+ }
351
+ res.json(result);
352
+ })));
353
+ app.use(express_1.default.static(path_1.default.resolve(__dirname, '../ui')));
354
+ };
355
+ const start = () => __awaiter(void 0, void 0, void 0, function* () {
356
+ fs_1.default.mkdirSync(dataDir, { recursive: true });
357
+ const dbManager = new database_1.DatabaseManager(dataDir);
358
+ yield dbManager.initialize();
359
+ const proxyServer = new proxy_server_1.ProxyServer(dbManager, app);
360
+ // Register admin routes first
361
+ registerRoutes(dbManager, proxyServer);
362
+ // Initialize proxy server and register proxy routes last
363
+ yield proxyServer.initialize();
364
+ const adminServer = app.listen(port, host, () => {
365
+ console.log(`Admin server running on http://${host}:${port}`);
366
+ });
367
+ const shutdown = () => __awaiter(void 0, void 0, void 0, function* () {
368
+ console.log('Shutting down server...');
369
+ dbManager.close();
370
+ adminServer.close(() => process.exit(0));
371
+ });
372
+ process.on('SIGINT', shutdown);
373
+ process.on('SIGTERM', shutdown);
374
+ });
375
+ app.use((err, _req, res, _next) => {
376
+ console.error(err);
377
+ res.status(500).json({ error: err.message || 'Internal server error' });
378
+ });
379
+ start().catch((error) => {
380
+ console.error('Failed to start server:', error);
381
+ process.exit(1);
382
+ });