aicodeswitch 5.2.9 → 5.2.10

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/README.md CHANGED
@@ -35,50 +35,26 @@ AI Code Switch 是帮助你在本地管理 AI 编程工具接入大模型的工
35
35
  * 数据完全本地,自主可控
36
36
  * 特殊语法:在发送的提示词最前面添加`[!]`来直接切换为高智商模型服务(`[x]`关闭),简单快捷
37
37
 
38
+ ![](public/aicodeswitch-flow.svg)
39
+
38
40
  ## 桌面客户端
39
41
 
40
42
  [进入下载](https://github.com/tangshuang/aicodeswitch/releases)
41
43
 
42
44
  ## 命令行工具
43
45
 
44
- ### 安装
45
-
46
46
  ```
47
+ # 安装
47
48
  npm install -g aicodeswitch
48
- ```
49
-
50
- ### 使用方法
51
-
52
- **启动服务**
53
-
54
- ```
49
+ # 启动服务
55
50
  aicos start
56
- ```
57
-
58
- 或者直接运行
59
-
60
- ```
51
+ # http://127.0.0.1:4567
52
+ # 启动服务并打开管理界面
61
53
  aicos ui
62
- ```
63
-
64
- **停止服务**
65
-
66
- ```
54
+ # 停止服务
67
55
  aicos stop
68
56
  ```
69
57
 
70
- **进入管理界面**
71
-
72
- ```
73
- # 自动启动服务和打开界面
74
- aicos ui
75
- ```
76
-
77
- ```
78
- # 手动在浏览器打开管理界面
79
- http://127.0.0.1:4567
80
- ```
81
-
82
58
  ## 管理界面
83
59
 
84
60
  **配置供应商**
@@ -257,6 +233,7 @@ PORT=4567
257
233
  ## 关联资源
258
234
 
259
235
  * [Claude Code 深度教程](https://claudecode.tangshuang.net): 100%免费的Claude Code入门到精通教程
236
+ * [AICodingBus](https://aicodingbus.24x7.to): AI Tokens 交换平台
260
237
 
261
238
  ## 支持我
262
239
 
package/bin/cli.js CHANGED
@@ -7,6 +7,7 @@ const commands = {
7
7
  start: require('./start'),
8
8
  stop: require('./stop'),
9
9
  restart: require('./restart'),
10
+ status: require('./status'),
10
11
  upgrade: require('./upgrade'),
11
12
  restore: require('./restore'),
12
13
  version: require('./version'),
@@ -21,6 +22,7 @@ Commands:
21
22
  start Start the AI Code Switch server
22
23
  stop Stop the AI Code Switch server
23
24
  restart Restart the AI Code Switch server
25
+ status Show server status, running address and port
24
26
  ui Open the web UI in browser (starts server if needed)
25
27
  upgrade Upgrade to the latest version and restart
26
28
  restore Restore original configuration files
@@ -30,6 +32,7 @@ Example:
30
32
  aicos start
31
33
  aicos stop
32
34
  aicos restart
35
+ aicos status
33
36
  aicos ui
34
37
  aicos upgrade
35
38
  aicos restore
package/bin/status.js ADDED
@@ -0,0 +1,84 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const os = require('os');
4
+ const chalk = require('chalk');
5
+ const boxen = require('boxen');
6
+ const { isServerRunning, getServerInfo } = require('./utils/get-server');
7
+ const { findPidByPort, getProcessInfo } = require('./utils/port-utils');
8
+
9
+ const PID_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.pid');
10
+ const LOG_FILE = path.join(os.homedir(), '.aicodeswitch', 'server.log');
11
+
12
+ const status = async () => {
13
+ console.log('\n');
14
+
15
+ // 读取配置的 host/port
16
+ const { host, port } = getServerInfo();
17
+ const url = `http://${host}:${port}`;
18
+
19
+ // 优先通过 PID 文件判断
20
+ let pidFromFile = null;
21
+ if (fs.existsSync(PID_FILE)) {
22
+ try {
23
+ pidFromFile = parseInt(fs.readFileSync(PID_FILE, 'utf-8'), 10);
24
+ } catch (err) {
25
+ pidFromFile = null;
26
+ }
27
+ }
28
+
29
+ const runningByPidFile = isServerRunning();
30
+ // 通过端口检测(兜底:PID 文件丢失但服务仍在监听的情况)
31
+ const pidByPort = await findPidByPort(port);
32
+ const isRunning = runningByPidFile || !!pidByPort;
33
+
34
+ if (isRunning) {
35
+ // PID 优先使用 PID 文件记录的,其次使用端口检测到的
36
+ const pid = runningByPidFile ? pidFromFile : pidByPort;
37
+ const processInfo = await getProcessInfo(pid);
38
+
39
+ console.log(boxen(
40
+ chalk.green.bold('🟢 AI Code Switch Server\n\n') +
41
+ chalk.white('Status: ') + chalk.green.bold('● Running\n') +
42
+ chalk.white('Host: ') + chalk.cyan(host) + '\n' +
43
+ chalk.white('Port: ') + chalk.cyan.bold(port) + '\n' +
44
+ chalk.white('URL: ') + chalk.cyan.bold(url) + '\n' +
45
+ chalk.white('PID: ') + chalk.yellow(pid) + '\n' +
46
+ chalk.white('Process: ') + chalk.gray(processInfo) + '\n' +
47
+ chalk.white('Logs: ') + chalk.gray(LOG_FILE) + '\n\n' +
48
+ chalk.gray('Open the URL in your browser to access the dashboard'),
49
+ {
50
+ padding: 1,
51
+ margin: 1,
52
+ borderStyle: 'double',
53
+ borderColor: 'green'
54
+ }
55
+ ));
56
+
57
+ console.log(chalk.cyan('💡 Tips:\n'));
58
+ console.log(chalk.white(' • Open browser: ') + chalk.cyan(url));
59
+ console.log(chalk.white(' • View logs: ') + chalk.gray(`tail -f ${LOG_FILE}`));
60
+ console.log(chalk.white(' • Stop server: ') + chalk.yellow('aicos stop'));
61
+ console.log(chalk.white(' • Restart: ') + chalk.yellow('aicos restart'));
62
+ console.log('\n');
63
+ } else {
64
+ console.log(boxen(
65
+ chalk.gray('AI Code Switch Server\n\n') +
66
+ chalk.white('Status: ') + chalk.red('● Stopped\n\n') +
67
+ chalk.white('Host: ') + chalk.gray(host) + '\n' +
68
+ chalk.white('Port: ') + chalk.gray(port) + '\n' +
69
+ chalk.white('URL: ') + chalk.gray(url) + ' ' + chalk.gray('(not listening)'),
70
+ {
71
+ padding: 1,
72
+ margin: 1,
73
+ borderStyle: 'round',
74
+ borderColor: 'gray'
75
+ }
76
+ ));
77
+
78
+ console.log(chalk.white('Use ') + chalk.cyan('aicos start') + chalk.white(' to start the server.\n'));
79
+ }
80
+
81
+ process.exit(0);
82
+ };
83
+
84
+ module.exports = status;
@@ -3288,21 +3288,23 @@ ${instruction}
3288
3288
  }
3289
3289
  }
3290
3290
  });
3291
+ // listen 就绪标志:区分"启动阶段"与"运行阶段",启动期致命异常应让进程退出
3292
+ let listenReady = false;
3291
3293
  const start = () => __awaiter(void 0, void 0, void 0, function* () {
3292
3294
  fs_1.default.mkdirSync(dataDir, { recursive: true });
3293
3295
  // 自动检测数据库类型并执行迁移(如果需要)
3294
- console.log('[Server] Initializing database...');
3296
+ console.time('[Server] step "database-init"');
3295
3297
  const dbManager = yield database_factory_1.DatabaseFactory.createAuto(dataDir, legacyDataDir);
3296
- console.log('[Server] Database initialized successfully');
3298
+ console.timeEnd('[Server] step "database-init"');
3297
3299
  // 服务启动时自动同步配置文件(适用于 CLI 和 dev:server)
3298
- console.log('[Server] Syncing tool configs with global settings...');
3300
+ console.time('[Server] step "sync-configs"');
3299
3301
  try {
3300
3302
  yield syncConfigsOnServerStartup(dbManager);
3301
- console.log('[Server] Tool config sync completed');
3302
3303
  }
3303
3304
  catch (error) {
3304
3305
  console.error('[Server] Tool config sync failed:', error);
3305
3306
  }
3307
+ console.timeEnd('[Server] step "sync-configs"');
3306
3308
  // 清理旧的迁移临时文件
3307
3309
  try {
3308
3310
  (0, session_launcher_1.cleanupOldTempFiles)();
@@ -3328,8 +3330,10 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
3328
3330
  // Initialize proxy server and register proxy routes last
3329
3331
  proxyServer.initialize();
3330
3332
  // Register admin routes first
3333
+ console.time('[Server] step "register-routes"');
3331
3334
  yield registerRoutes(dbManager, proxyServer);
3332
3335
  yield proxyServer.registerProxyRoutes();
3336
+ console.timeEnd('[Server] step "register-routes"');
3333
3337
  app.use(express_1.default.static(path_1.default.resolve(__dirname, '../ui')));
3334
3338
  // 404 处理程序 - 确保返回 JSON 而不是 HTML(放在所有路由和静态文件之后)
3335
3339
  app.use((_req, res) => {
@@ -3347,14 +3351,28 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
3347
3351
  console.error(`端口 ${port} 已被占用,无法启动服务。请执行 aicos stop 后重启。`);
3348
3352
  process.exit(1);
3349
3353
  }
3354
+ console.time('[Server] step "listen"');
3350
3355
  const server = app.listen(port, host, () => {
3356
+ listenReady = true;
3351
3357
  console.log(`Admin server running on http://${host}:${port}`);
3358
+ console.timeEnd('[Server] step "listen"');
3352
3359
  // 启动后异步执行延迟维护任务(分片校验/修复、日志清理、会话索引构建)
3353
3360
  // 不阻塞服务启动,后台静默执行
3354
3361
  dbManager.deferredMaintenance().catch(err => {
3355
3362
  console.error('[Server] Deferred maintenance error:', err);
3356
3363
  });
3357
3364
  });
3365
+ // 显式处理 listen 错误(EADDRINUSE/权限不足等),打印明确日志并退出,
3366
+ // 避免被全局 uncaughtException 静默吞掉导致"进程在但不 listen"
3367
+ server.on('error', (err) => {
3368
+ if (err.code === 'EADDRINUSE') {
3369
+ console.error(`[Server] 端口 ${port} 已被占用(EADDRINUSE)。请执行 aicos stop 后重启,或更换端口(PORT 环境变量)。`);
3370
+ }
3371
+ else {
3372
+ console.error('[Server] 监听失败:', err);
3373
+ }
3374
+ setImmediate(() => process.exit(1));
3375
+ });
3358
3376
  // 创建 WebSocket 服务器用于工具安装
3359
3377
  const toolInstallWss = (0, websocket_service_1.createToolInstallationWSServer)();
3360
3378
  // 设置黑名单检查函数,用于在规则状态同步时检查黑名单是否已过期
@@ -3435,11 +3453,19 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
3435
3453
  process.on('uncaughtException', (error) => {
3436
3454
  console.error('[Uncaught Exception] 服务遇到未捕获的异常:', error);
3437
3455
  console.error('[Uncaught Exception] 堆栈信息:', error.stack);
3438
- // 不退出进程,继续运行
3456
+ // 启动阶段(listen 之前)的异常通常是致命的(依赖加载失败、初始化崩溃等),
3457
+ // 静默吞掉会导致"进程在但不 listen",Tauri 只能干等超时;此时退出让上层重新探测/诊断。
3458
+ if (!listenReady) {
3459
+ console.error('[Uncaught Exception] 发生在服务监听之前,退出进程');
3460
+ process.exit(1);
3461
+ }
3439
3462
  });
3440
3463
  process.on('unhandledRejection', (reason) => {
3441
3464
  console.error('[Unhandled Rejection] 服务遇到未处理的 Promise 拒绝:', reason);
3442
- // 不退出进程,继续运行
3465
+ if (!listenReady) {
3466
+ console.error('[Unhandled Rejection] 发生在服务监听之前,退出进程');
3467
+ process.exit(1);
3468
+ }
3443
3469
  });
3444
3470
  start().catch((error) => {
3445
3471
  console.error('Failed to start server:', error);
@@ -22,7 +22,7 @@ const os_1 = require("os");
22
22
  function which(cmd) {
23
23
  try {
24
24
  const command = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
25
- (0, child_process_1.execSync)(command, { stdio: 'ignore' });
25
+ (0, child_process_1.execSync)(command, { stdio: 'ignore', windowsHide: true }); // 隐藏 Windows 命令行窗口,避免检测时闪窗
26
26
  return true;
27
27
  }
28
28
  catch (_a) {
@@ -29,6 +29,7 @@ function checkToolInstalled(toolName) {
29
29
  const child = (0, child_process_1.spawn)(command, ['--version'], {
30
30
  shell: true,
31
31
  stdio: ['ignore', 'pipe', 'pipe'],
32
+ windowsHide: true, // 隐藏 Windows 命令行窗口,避免检测时闪窗
32
33
  });
33
34
  let stdout = '';
34
35
  let stderr = '';
@@ -9,13 +9,27 @@ Object.defineProperty(exports, "isLastMessageCompact", { enumerable: true, get:
9
9
  Object.defineProperty(exports, "isCodexCompactRequest", { enumerable: true, get: function () { return compact_1.isCodexCompactRequest; } });
10
10
  function checkPortUsable(port) {
11
11
  return new Promise((resolve) => {
12
+ let settled = false;
12
13
  const server = net.createConnection({ port });
13
- server.on('connect', () => {
14
- server.end();
15
- resolve(false);
16
- });
17
- server.on('error', () => {
18
- resolve(true);
14
+ const finish = (val) => {
15
+ if (settled)
16
+ return;
17
+ settled = true;
18
+ try {
19
+ server.destroy();
20
+ }
21
+ catch ( /* ignore */_a) { /* ignore */ }
22
+ resolve(val);
23
+ };
24
+ // 正常:连得上 = 端口被占;连不上(ECONNREFUSED) = 端口可用
25
+ server.on('connect', () => finish(false));
26
+ server.on('error', () => finish(true));
27
+ // 兜底:网络栈异常(防火墙/杀软 hook)时 connect/error 可能都不触发,
28
+ // 1.5s 后强制按可用处理,避免 start() 永久卡死。误判由 app.listen 的 EADDRINUSE 兜底。
29
+ server.setTimeout(1500);
30
+ server.once('timeout', () => {
31
+ console.warn(`[checkPortUsable] 探测端口 ${port} 超时(1.5s),按可用处理`);
32
+ finish(true);
19
33
  });
20
34
  });
21
35
  }
@@ -0,0 +1,360 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1400 860" font-family="ui-sans-serif, system-ui, -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif" text-rendering="geometricPrecision">
2
+ <title>AICodeSwitch 转流架构图</title>
3
+ <desc>展示 AI 编程工具(Claude Code / Codex / Cursor 等)的请求,经 AICodeSwitch 本地代理完成鉴权、路由、规则匹配、格式转换后,转流到上游 LLM 服务商(Anthropic / OpenAI / Gemini / DeepSeek 等),响应原路回流。</desc>
4
+
5
+ <defs>
6
+ <linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1">
7
+ <stop offset="0" stop-color="#0c1326"/>
8
+ <stop offset=".5" stop-color="#111c3a"/>
9
+ <stop offset="1" stop-color="#090f1f"/>
10
+ </linearGradient>
11
+ <radialGradient id="bgGlow" cx=".5" cy=".06" r=".78">
12
+ <stop offset="0" stop-color="#22d3ee" stop-opacity=".18"/>
13
+ <stop offset=".45" stop-color="#7c3aed" stop-opacity=".10"/>
14
+ <stop offset="1" stop-color="#090f1f" stop-opacity="0"/>
15
+ </radialGradient>
16
+ <linearGradient id="titleGrad" x1="0" y1="0" x2="1" y2="0">
17
+ <stop offset="0" stop-color="#5eead4"/>
18
+ <stop offset=".45" stop-color="#22d3ee"/>
19
+ <stop offset=".75" stop-color="#a855f7"/>
20
+ <stop offset="1" stop-color="#ec4899"/>
21
+ </linearGradient>
22
+ <linearGradient id="coreStroke" x1="0" y1="0" x2="1" y2="1">
23
+ <stop offset="0" stop-color="#22d3ee"/>
24
+ <stop offset=".5" stop-color="#a855f7"/>
25
+ <stop offset="1" stop-color="#ec4899"/>
26
+ </linearGradient>
27
+ <linearGradient id="coreFill" x1="0" y1="0" x2="0" y2="1">
28
+ <stop offset="0" stop-color="#0f1830" stop-opacity=".92"/>
29
+ <stop offset="1" stop-color="#0b1124" stop-opacity=".92"/>
30
+ </linearGradient>
31
+ <linearGradient id="panelFill" x1="0" y1="0" x2="1" y2="0">
32
+ <stop offset="0" stop-color="#16234c" stop-opacity=".78"/>
33
+ <stop offset="1" stop-color="#101935" stop-opacity=".78"/>
34
+ </linearGradient>
35
+ <radialGradient id="dotCyan">
36
+ <stop offset="0" stop-color="#cffafe"/>
37
+ <stop offset=".45" stop-color="#22d3ee"/>
38
+ <stop offset="1" stop-color="#22d3ee" stop-opacity="0"/>
39
+ </radialGradient>
40
+ <radialGradient id="dotPurple">
41
+ <stop offset="0" stop-color="#f3e8ff"/>
42
+ <stop offset=".45" stop-color="#c084fc"/>
43
+ <stop offset="1" stop-color="#a855f7" stop-opacity="0"/>
44
+ </radialGradient>
45
+ <marker id="arrowCyan" viewBox="0 0 10 10" refX="8.5" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
46
+ <path d="M0,1 L9,5 L0,9 z" fill="#22d3ee"/>
47
+ </marker>
48
+ <marker id="arrowPurple" viewBox="0 0 10 10" refX="8.5" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
49
+ <path d="M0,1 L9,5 L0,9 z" fill="#c084fc"/>
50
+ </marker>
51
+ <style>
52
+ @media (prefers-reduced-motion: reduce){
53
+ animate,animateTransform,animateMotion{display:none}
54
+ }
55
+ </style>
56
+ </defs>
57
+
58
+ <!-- ============ background ============ -->
59
+ <rect width="1400" height="860" fill="url(#bgGrad)"/>
60
+ <rect width="1400" height="860" fill="url(#bgGlow)"/>
61
+
62
+ <!-- ============ title ============ -->
63
+ <text x="700" y="60" text-anchor="middle" font-size="32" font-weight="800" fill="url(#titleGrad)" letter-spacing="0.5">AICodeSwitch · 转流架构</text>
64
+ <text x="700" y="89" text-anchor="middle" font-size="14.5" fill="#8c9bc4">本地代理 · 把 AI 编程工具的请求智能路由并转换格式,转流到任意模型 API</text>
65
+
66
+ <!-- column captions -->
67
+ <text x="169" y="196" text-anchor="middle" font-size="12" font-weight="700" fill="#6f7da6" letter-spacing="2">客户端 · AI 编程工具</text>
68
+ <text x="1231" y="158" text-anchor="middle" font-size="12" font-weight="700" fill="#6f7da6" letter-spacing="2">上游 · LLM 服务商</text>
69
+
70
+ <!-- ============ LEFT column · clients ============ -->
71
+ <!-- Claude Code -->
72
+ <g>
73
+ <rect x="64" y="220" width="210" height="118" rx="16" fill="#111a33" stroke="#243156"/>
74
+ <rect x="66" y="238" width="4" height="82" rx="2" fill="#a855f7"/>
75
+ <circle cx="108" cy="264" r="22" fill="#a855f7" fill-opacity=".16" stroke="#a855f7" stroke-width="1.5"/>
76
+ <text x="108" y="271" text-anchor="middle" font-size="18" font-weight="800" fill="#d8b4fe">C</text>
77
+ <text x="142" y="258" font-size="16.5" font-weight="700" fill="#e6edf7">Claude Code</text>
78
+ <text x="142" y="280" font-size="12" fill="#8c9bc4">/v1/messages</text>
79
+ <g transform="translate(82,298)">
80
+ <rect width="118" height="24" rx="12" fill="#a855f7" fill-opacity=".12" stroke="#a855f7" stroke-opacity=".5"/>
81
+ <text x="59" y="16" text-anchor="middle" font-size="11" fill="#d8b4fe">Anthropic 协议</text>
82
+ </g>
83
+ </g>
84
+
85
+ <!-- Codex -->
86
+ <g>
87
+ <rect x="64" y="372" width="210" height="118" rx="16" fill="#111a33" stroke="#243156"/>
88
+ <rect x="66" y="390" width="4" height="82" rx="2" fill="#10b981"/>
89
+ <circle cx="108" cy="416" r="22" fill="#10b981" fill-opacity=".16" stroke="#10b981" stroke-width="1.5"/>
90
+ <text x="108" y="423" text-anchor="middle" font-size="15" font-weight="800" fill="#a7f3d0">Cx</text>
91
+ <text x="142" y="410" font-size="16.5" font-weight="700" fill="#e6edf7">Codex</text>
92
+ <text x="142" y="432" font-size="12" fill="#8c9bc4">/v1/responses</text>
93
+ <g transform="translate(82,450)">
94
+ <rect width="118" height="24" rx="12" fill="#10b981" fill-opacity=".12" stroke="#10b981" stroke-opacity=".5"/>
95
+ <text x="59" y="16" text-anchor="middle" font-size="11" fill="#a7f3d0">OpenAI 协议</text>
96
+ </g>
97
+ </g>
98
+
99
+ <!-- Cursor / others -->
100
+ <g>
101
+ <rect x="64" y="524" width="210" height="118" rx="16" fill="#111a33" stroke="#243156"/>
102
+ <rect x="66" y="542" width="4" height="82" rx="2" fill="#22d3ee"/>
103
+ <circle cx="108" cy="568" r="22" fill="#22d3ee" fill-opacity=".16" stroke="#22d3ee" stroke-width="1.5"/>
104
+ <text x="108" y="576" text-anchor="middle" font-size="20" font-weight="800" fill="#a5f3fc">+</text>
105
+ <text x="142" y="562" font-size="16.5" font-weight="700" fill="#e6edf7">Cursor · 其他</text>
106
+ <text x="142" y="584" font-size="12" fill="#8c9bc4">/v1/chat/completions</text>
107
+ <g transform="translate(82,602)">
108
+ <rect width="132" height="24" rx="12" fill="#22d3ee" fill-opacity=".12" stroke="#22d3ee" stroke-opacity=".5"/>
109
+ <text x="66" y="16" text-anchor="middle" font-size="11" fill="#a5f3fc">OpenAI 兼容</text>
110
+ </g>
111
+ </g>
112
+
113
+ <!-- ============ CENTER · core engine ============ -->
114
+ <g>
115
+ <rect x="360" y="150" width="680" height="550" rx="22" fill="url(#coreFill)" stroke="url(#coreStroke)" stroke-width="1.6">
116
+ <animate attributeName="stroke-opacity" values="0.55;0.95;0.55" dur="4.5s" repeatCount="indefinite"/>
117
+ </rect>
118
+ <!-- top highlight -->
119
+ <path d="M382,150 H1018 a22,22 0 0 1 22,22 V192 H360 V172 a22,22 0 0 1 22,-22 z" fill="#ffffff" fill-opacity=".035"/>
120
+ <!-- header -->
121
+ <rect x="386" y="166" width="16" height="16" rx="4" fill="url(#coreStroke)"/>
122
+ <text x="410" y="180" font-size="17" font-weight="800" fill="#e6edf7">AICodeSwitch Proxy Core</text>
123
+ <text x="1014" y="180" text-anchor="end" font-size="11.5" fill="#6f7da6">localhost · 中转引擎</text>
124
+
125
+ <!-- ── M1 Auth ── -->
126
+ <g>
127
+ <rect x="380" y="210" width="640" height="86" rx="14" fill="url(#panelFill)" stroke="#1f2c52"/>
128
+ <circle cx="416" cy="253" r="18" fill="#22d3ee" fill-opacity=".14" stroke="#22d3ee" stroke-width="1.4"/>
129
+ <g transform="translate(408,243)" stroke="#67e8f9" stroke-width="1.6" fill="none" stroke-linecap="round">
130
+ <rect x="2" y="7" width="12" height="9" rx="2"/>
131
+ <path d="M5,7 V5 a3,3 0 0 1 6,0 V7"/>
132
+ </g>
133
+ <text x="446" y="244" font-size="15.5" font-weight="700" fill="#e6edf7">① 鉴权 Auth</text>
134
+ <text x="446" y="266" font-size="12" fill="#8c9bc4">API Key 解析 · sk_ 池密钥 / skr_ 路由密钥 · JWT</text>
135
+ <g transform="translate(818,242)">
136
+ <rect width="202" height="22" rx="11" fill="#22d3ee" fill-opacity=".10" stroke="#22d3ee" stroke-opacity=".4"/>
137
+ <text x="101" y="15" text-anchor="middle" font-size="10" fill="#67e8f9">Authorization · x-api-key · x-goog-api-key</text>
138
+ </g>
139
+ </g>
140
+
141
+ <!-- ── M2 Route + Rules ── -->
142
+ <g>
143
+ <rect x="380" y="308" width="640" height="148" rx="14" fill="url(#panelFill)" stroke="#1f2c52"/>
144
+ <circle cx="416" cy="338" r="18" fill="#a855f7" fill-opacity=".14" stroke="#a855f7" stroke-width="1.4"/>
145
+ <g transform="translate(407,329)" stroke="#d8b4fe" stroke-width="1.4" fill="none" stroke-linecap="round" stroke-linejoin="round">
146
+ <circle cx="9" cy="9" r="7"/>
147
+ <path d="M9,4 L11,9 L9,14 L7,9 z" fill="#d8b4fe" stroke="none"/>
148
+ </g>
149
+ <text x="446" y="334" font-size="15.5" font-weight="700" fill="#e6edf7">② 路由 &amp; 规则 Route · Rules</text>
150
+ <text x="446" y="356" font-size="12" fill="#8c9bc4">按内容类型分发到候选上游池,失败自动回退(fallback)</text>
151
+
152
+ <!-- content-type pills -->
153
+ <g font-size="11" font-weight="600">
154
+ <g transform="translate(396,378)">
155
+ <rect width="68" height="24" rx="12" fill="#94a3b8" fill-opacity=".10" stroke="#94a3b8" stroke-opacity=".5"/>
156
+ <text x="34" y="16" text-anchor="middle" fill="#cbd5e1">default</text>
157
+ </g>
158
+ <g transform="translate(472,378)">
159
+ <rect width="72" height="24" rx="12" fill="#a855f7" fill-opacity=".12" stroke="#a855f7" stroke-opacity=".55"/>
160
+ <text x="36" y="16" text-anchor="middle" fill="#d8b4fe">thinking</text>
161
+ </g>
162
+ <g transform="translate(554,378)">
163
+ <rect width="100" height="24" rx="12" fill="#3b82f6" fill-opacity=".12" stroke="#3b82f6" stroke-opacity=".55"/>
164
+ <text x="50" y="16" text-anchor="middle" fill="#bfdbfe">long-context</text>
165
+ </g>
166
+ <g transform="translate(396,410)">
167
+ <rect width="140" height="24" rx="12" fill="#ec4899" fill-opacity=".12" stroke="#ec4899" stroke-opacity=".55"/>
168
+ <text x="70" y="16" text-anchor="middle" fill="#f9a8d4">image-understanding</text>
169
+ </g>
170
+ <g transform="translate(544,410)">
171
+ <rect width="86" height="24" rx="12" fill="#06b6d4" fill-opacity=".12" stroke="#06b6d4" stroke-opacity=".55"/>
172
+ <text x="43" y="16" text-anchor="middle" fill="#a5f3fc">background</text>
173
+ </g>
174
+ <g transform="translate(638,410)">
175
+ <rect width="66" height="24" rx="12" fill="#f59e0b" fill-opacity=".12" stroke="#f59e0b" stroke-opacity=".55"/>
176
+ <text x="33" y="16" text-anchor="middle" fill="#fcd34d">high-iq</text>
177
+ </g>
178
+ </g>
179
+ </g>
180
+
181
+ <!-- ── M3 Transformers ── -->
182
+ <g>
183
+ <rect x="380" y="468" width="640" height="104" rx="14" fill="url(#panelFill)" stroke="#1f2c52"/>
184
+ <circle cx="416" cy="500" r="18" fill="#ec4899" fill-opacity=".14" stroke="#ec4899" stroke-width="1.4"/>
185
+ <g transform="translate(407,491)" stroke="#f9a8d4" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
186
+ <path d="M3,5 H15 M12,2 L15,5 L12,8"/>
187
+ <path d="M15,13 H3 M6,10 L3,13 L6,16"/>
188
+ </g>
189
+ <text x="446" y="498" font-size="15.5" font-weight="700" fill="#e6edf7">③ 格式转换 Transformers</text>
190
+ <text x="446" y="520" font-size="12" fill="#8c9bc4">Claude ↔ OpenAI ↔ Gemini ↔ DeepSeek · 双向转换 · SSE 流式</text>
191
+ <g transform="translate(446,536)" font-size="10.5" font-weight="700">
192
+ <rect x="0" y="0" width="56" height="22" rx="6" fill="#a855f7" fill-opacity=".16" stroke="#a855f7" stroke-opacity=".5"/>
193
+ <text x="28" y="15" text-anchor="middle" fill="#d8b4fe">Claude</text>
194
+ <text x="64" y="15" fill="#6f7da6">↔</text>
195
+ <rect x="78" y="0" width="58" height="22" rx="6" fill="#10b981" fill-opacity=".16" stroke="#10b981" stroke-opacity=".5"/>
196
+ <text x="107" y="15" text-anchor="middle" fill="#a7f3d0">OpenAI</text>
197
+ <text x="144" y="15" fill="#6f7da6">↔</text>
198
+ <rect x="158" y="0" width="58" height="22" rx="6" fill="#3b82f6" fill-opacity=".16" stroke="#3b82f6" stroke-opacity=".5"/>
199
+ <text x="187" y="15" text-anchor="middle" fill="#bfdbfe">Gemini</text>
200
+ <text x="224" y="15" fill="#6f7da6">↔</text>
201
+ <rect x="238" y="0" width="68" height="22" rx="6" fill="#8b5cf6" fill-opacity=".16" stroke="#8b5cf6" stroke-opacity=".5"/>
202
+ <text x="272" y="15" text-anchor="middle" fill="#c4b5fd">DeepSeek</text>
203
+ </g>
204
+ <g transform="translate(912,490)">
205
+ <rect width="96" height="22" rx="11" fill="#ec4899" fill-opacity=".12" stroke="#ec4899" stroke-opacity=".45"/>
206
+ <text x="48" y="15" text-anchor="middle" font-size="10.5" fill="#f9a8d4">SSE 流式</text>
207
+ </g>
208
+ </g>
209
+
210
+ <!-- ── M4 Logging ── -->
211
+ <g>
212
+ <rect x="380" y="584" width="640" height="92" rx="14" fill="url(#panelFill)" stroke="#1f2c52"/>
213
+ <circle cx="416" cy="616" r="18" fill="#10b981" fill-opacity=".14" stroke="#10b981" stroke-width="1.4"/>
214
+ <g transform="translate(407,607)" stroke="#6ee7b7" stroke-width="1.4" fill="none" stroke-linecap="round">
215
+ <path d="M3,15 H17"/>
216
+ <rect x="5" y="9" width="3" height="6" fill="#6ee7b7" stroke="none"/>
217
+ <rect x="10" y="5" width="3" height="10" fill="#6ee7b7" stroke="none"/>
218
+ <rect x="15" y="11" width="3" height="4" fill="#6ee7b7" stroke="none"/>
219
+ </g>
220
+ <text x="446" y="614" font-size="15.5" font-weight="700" fill="#e6edf7">④ 日志 / 会话 / 配额 Logging</text>
221
+ <text x="446" y="636" font-size="12" fill="#8c9bc4">请求日志 · 会话追踪 · 多维配额(Token / RPM / 并发)</text>
222
+ <g transform="translate(842,606)">
223
+ <rect width="78" height="22" rx="11" fill="#10b981" fill-opacity=".10" stroke="#10b981" stroke-opacity=".4"/>
224
+ <text x="39" y="15" text-anchor="middle" font-size="10.5" fill="#6ee7b7">Token 计费</text>
225
+ </g>
226
+ <g transform="translate(928,606)">
227
+ <rect width="78" height="22" rx="11" fill="#10b981" fill-opacity=".10" stroke="#10b981" stroke-opacity=".4"/>
228
+ <text x="39" y="15" text-anchor="middle" font-size="10.5" fill="#6ee7b7">配额限速</text>
229
+ </g>
230
+ </g>
231
+ </g>
232
+
233
+ <!-- ============ RIGHT column · upstream ============ -->
234
+ <!-- Anthropic -->
235
+ <g>
236
+ <rect x="1126" y="180" width="210" height="92" rx="14" fill="#111a33" stroke="#243156"/>
237
+ <rect x="1330" y="200" width="4" height="52" rx="2" fill="#f59e0b"/>
238
+ <circle cx="1158" cy="226" r="18" fill="#f59e0b" fill-opacity=".16" stroke="#f59e0b" stroke-width="1.5"/>
239
+ <text x="1158" y="232" text-anchor="middle" font-size="15" font-weight="800" fill="#fcd34d">A</text>
240
+ <text x="1188" y="221" font-size="14.5" font-weight="700" fill="#e6edf7">Anthropic</text>
241
+ <text x="1188" y="240" font-size="11.5" fill="#8c9bc4">Claude · claude-*</text>
242
+ </g>
243
+ <!-- OpenAI -->
244
+ <g>
245
+ <rect x="1126" y="288" width="210" height="92" rx="14" fill="#111a33" stroke="#243156"/>
246
+ <rect x="1330" y="308" width="4" height="52" rx="2" fill="#10b981"/>
247
+ <circle cx="1158" cy="334" r="18" fill="#10b981" fill-opacity=".16" stroke="#10b981" stroke-width="1.5"/>
248
+ <text x="1158" y="340" text-anchor="middle" font-size="15" font-weight="800" fill="#a7f3d0">O</text>
249
+ <text x="1188" y="329" font-size="14.5" font-weight="700" fill="#e6edf7">OpenAI</text>
250
+ <text x="1188" y="348" font-size="11.5" fill="#8c9bc4">GPT · o-*</text>
251
+ </g>
252
+ <!-- Gemini -->
253
+ <g>
254
+ <rect x="1126" y="396" width="210" height="92" rx="14" fill="#111a33" stroke="#243156"/>
255
+ <rect x="1330" y="416" width="4" height="52" rx="2" fill="#3b82f6"/>
256
+ <circle cx="1158" cy="442" r="18" fill="#3b82f6" fill-opacity=".16" stroke="#3b82f6" stroke-width="1.5"/>
257
+ <text x="1158" y="448" text-anchor="middle" font-size="15" font-weight="800" fill="#bfdbfe">G</text>
258
+ <text x="1188" y="437" font-size="14.5" font-weight="700" fill="#e6edf7">Google</text>
259
+ <text x="1188" y="456" font-size="11.5" fill="#8c9bc4">Gemini · gemini-*</text>
260
+ </g>
261
+ <!-- DeepSeek -->
262
+ <g>
263
+ <rect x="1126" y="504" width="210" height="92" rx="14" fill="#111a33" stroke="#243156"/>
264
+ <rect x="1330" y="524" width="4" height="52" rx="2" fill="#8b5cf6"/>
265
+ <circle cx="1158" cy="550" r="18" fill="#8b5cf6" fill-opacity=".16" stroke="#8b5cf6" stroke-width="1.5"/>
266
+ <text x="1158" y="556" text-anchor="middle" font-size="15" font-weight="800" fill="#c4b5fd">D</text>
267
+ <text x="1188" y="545" font-size="14.5" font-weight="700" fill="#e6edf7">DeepSeek</text>
268
+ <text x="1188" y="564" font-size="11.5" fill="#8c9bc4">deepseek-*</text>
269
+ </g>
270
+ <!-- Qwen / others -->
271
+ <g>
272
+ <rect x="1126" y="612" width="210" height="92" rx="14" fill="#111a33" stroke="#243156"/>
273
+ <rect x="1330" y="632" width="4" height="52" rx="2" fill="#06b6d4"/>
274
+ <circle cx="1158" cy="658" r="18" fill="#06b6d4" fill-opacity=".16" stroke="#06b6d4" stroke-width="1.5"/>
275
+ <text x="1158" y="664" text-anchor="middle" font-size="15" font-weight="800" fill="#a5f3fc">Q</text>
276
+ <text x="1188" y="653" font-size="14.5" font-weight="700" fill="#e6edf7">Qwen · Kimi 等</text>
277
+ <text x="1188" y="672" font-size="11.5" fill="#8c9bc4">兼容 OpenAI 协议</text>
278
+ </g>
279
+
280
+ <!-- ============ flow lines · LEFT (clients → core) ============ -->
281
+ <g fill="none">
282
+ <!-- L1 -->
283
+ <path d="M274,279 C314,279 322,375 360,375" stroke="#1c284f" stroke-width="2.2"/>
284
+ <path d="M274,279 C314,279 322,375 360,375" stroke="#22d3ee" stroke-width="2" stroke-linecap="round" stroke-dasharray="5 9" marker-end="url(#arrowCyan)" opacity=".92">
285
+ <animate attributeName="stroke-dashoffset" from="14" to="0" dur="1.1s" repeatCount="indefinite"/>
286
+ </path>
287
+ <circle r="3.8" fill="url(#dotCyan)">
288
+ <animateMotion dur="2.4s" repeatCount="indefinite" path="M274,279 C314,279 322,375 360,375"/>
289
+ </circle>
290
+ <!-- L2 -->
291
+ <path d="M274,431 C320,431 322,375 360,375" stroke="#1c284f" stroke-width="2.2"/>
292
+ <path d="M274,431 C320,431 322,375 360,375" stroke="#22d3ee" stroke-width="2" stroke-linecap="round" stroke-dasharray="5 9" marker-end="url(#arrowCyan)" opacity=".92">
293
+ <animate attributeName="stroke-dashoffset" from="14" to="0" dur="1.1s" begin="0.35s" repeatCount="indefinite"/>
294
+ </path>
295
+ <circle r="3.8" fill="url(#dotCyan)">
296
+ <animateMotion dur="2.4s" begin="0.8s" repeatCount="indefinite" path="M274,431 C320,431 322,375 360,375"/>
297
+ </circle>
298
+ <!-- L3 -->
299
+ <path d="M274,583 C326,583 324,375 360,375" stroke="#1c284f" stroke-width="2.2"/>
300
+ <path d="M274,583 C326,583 324,375 360,375" stroke="#22d3ee" stroke-width="2" stroke-linecap="round" stroke-dasharray="5 9" marker-end="url(#arrowCyan)" opacity=".92">
301
+ <animate attributeName="stroke-dashoffset" from="14" to="0" dur="1.1s" begin="0.7s" repeatCount="indefinite"/>
302
+ </path>
303
+ <circle r="3.8" fill="url(#dotCyan)">
304
+ <animateMotion dur="2.4s" begin="1.6s" repeatCount="indefinite" path="M274,583 C326,583 324,375 360,375"/>
305
+ </circle>
306
+ </g>
307
+
308
+ <!-- ============ flow lines · RIGHT (core → upstream) ============ -->
309
+ <g fill="none">
310
+ <!-- R1 -->
311
+ <path d="M1040,430 C1082,430 1088,226 1126,226" stroke="#1c284f" stroke-width="2.2"/>
312
+ <path d="M1040,430 C1082,430 1088,226 1126,226" stroke="#c084fc" stroke-width="2" stroke-linecap="round" stroke-dasharray="5 9" marker-end="url(#arrowPurple)" opacity=".92">
313
+ <animate attributeName="stroke-dashoffset" from="14" to="0" dur="1.2s" repeatCount="indefinite"/>
314
+ </path>
315
+ <circle r="3.8" fill="url(#dotPurple)">
316
+ <animateMotion dur="2.8s" repeatCount="indefinite" path="M1040,430 C1082,430 1088,226 1126,226"/>
317
+ </circle>
318
+ <!-- R2 -->
319
+ <path d="M1040,430 C1082,430 1088,334 1126,334" stroke="#1c284f" stroke-width="2.2"/>
320
+ <path d="M1040,430 C1082,430 1088,334 1126,334" stroke="#c084fc" stroke-width="2" stroke-linecap="round" stroke-dasharray="5 9" marker-end="url(#arrowPurple)" opacity=".92">
321
+ <animate attributeName="stroke-dashoffset" from="14" to="0" dur="1.2s" begin="0.3s" repeatCount="indefinite"/>
322
+ </path>
323
+ <circle r="3.8" fill="url(#dotPurple)">
324
+ <animateMotion dur="2.8s" begin="0.56s" repeatCount="indefinite" path="M1040,430 C1082,430 1088,334 1126,334"/>
325
+ </circle>
326
+ <!-- R3 -->
327
+ <path d="M1040,430 C1082,430 1088,442 1126,442" stroke="#1c284f" stroke-width="2.2"/>
328
+ <path d="M1040,430 C1082,430 1088,442 1126,442" stroke="#c084fc" stroke-width="2" stroke-linecap="round" stroke-dasharray="5 9" marker-end="url(#arrowPurple)" opacity=".92">
329
+ <animate attributeName="stroke-dashoffset" from="14" to="0" dur="1.2s" begin="0.6s" repeatCount="indefinite"/>
330
+ </path>
331
+ <circle r="3.8" fill="url(#dotPurple)">
332
+ <animateMotion dur="2.8s" begin="1.12s" repeatCount="indefinite" path="M1040,430 C1082,430 1088,442 1126,442"/>
333
+ </circle>
334
+ <!-- R4 -->
335
+ <path d="M1040,430 C1084,430 1088,550 1126,550" stroke="#1c284f" stroke-width="2.2"/>
336
+ <path d="M1040,430 C1084,430 1088,550 1126,550" stroke="#c084fc" stroke-width="2" stroke-linecap="round" stroke-dasharray="5 9" marker-end="url(#arrowPurple)" opacity=".92">
337
+ <animate attributeName="stroke-dashoffset" from="14" to="0" dur="1.2s" begin="0.9s" repeatCount="indefinite"/>
338
+ </path>
339
+ <circle r="3.8" fill="url(#dotPurple)">
340
+ <animateMotion dur="2.8s" begin="1.68s" repeatCount="indefinite" path="M1040,430 C1084,430 1088,550 1126,550"/>
341
+ </circle>
342
+ <!-- R5 -->
343
+ <path d="M1040,430 C1086,430 1088,658 1126,658" stroke="#1c284f" stroke-width="2.2"/>
344
+ <path d="M1040,430 C1086,430 1088,658 1126,658" stroke="#c084fc" stroke-width="2" stroke-linecap="round" stroke-dasharray="5 9" marker-end="url(#arrowPurple)" opacity=".92">
345
+ <animate attributeName="stroke-dashoffset" from="14" to="0" dur="1.2s" begin="1.2s" repeatCount="indefinite"/>
346
+ </path>
347
+ <circle r="3.8" fill="url(#dotPurple)">
348
+ <animateMotion dur="2.8s" begin="2.24s" repeatCount="indefinite" path="M1040,430 C1086,430 1088,658 1126,658"/>
349
+ </circle>
350
+ </g>
351
+
352
+ <!-- ============ footer caption + legend ============ -->
353
+ <text x="700" y="778" text-anchor="middle" font-size="12.5" fill="#7c89ab">流向:客户端发起请求 → 鉴权 → 路由 / 规则匹配 → 格式转换 → 上游模型;响应原路回流</text>
354
+ <g transform="translate(592,800)" font-size="11" fill="#8c9bc4">
355
+ <line x1="0" y1="0" x2="34" y2="0" stroke="#22d3ee" stroke-width="2" stroke-dasharray="5 9"/>
356
+ <text x="42" y="4">请求汇聚</text>
357
+ <line x1="130" y1="0" x2="164" y2="0" stroke="#c084fc" stroke-width="2" stroke-dasharray="5 9"/>
358
+ <text x="172" y="4">分发上游</text>
359
+ </g>
360
+ </svg>
@@ -176,50 +176,26 @@ AI Code Switch 是帮助你在本地管理 AI 编程工具接入大模型的工
176
176
  * 数据完全本地,自主可控
177
177
  * 特殊语法:在发送的提示词最前面添加\`[!]\`来直接切换为高智商模型服务(\`[x]\`关闭),简单快捷
178
178
 
179
+ ![](public/aicodeswitch-flow.svg)
180
+
179
181
  ## 桌面客户端
180
182
 
181
183
  [进入下载](https://github.com/tangshuang/aicodeswitch/releases)
182
184
 
183
185
  ## 命令行工具
184
186
 
185
- ### 安装
186
-
187
187
  \`\`\`
188
+ # 安装
188
189
  npm install -g aicodeswitch
189
- \`\`\`
190
-
191
- ### 使用方法
192
-
193
- **启动服务**
194
-
195
- \`\`\`
190
+ # 启动服务
196
191
  aicos start
197
- \`\`\`
198
-
199
- 或者直接运行
200
-
201
- \`\`\`
192
+ # http://127.0.0.1:4567
193
+ # 启动服务并打开管理界面
202
194
  aicos ui
203
- \`\`\`
204
-
205
- **停止服务**
206
-
207
- \`\`\`
195
+ # 停止服务
208
196
  aicos stop
209
197
  \`\`\`
210
198
 
211
- **进入管理界面**
212
-
213
- \`\`\`
214
- # 自动启动服务和打开界面
215
- aicos ui
216
- \`\`\`
217
-
218
- \`\`\`
219
- # 手动在浏览器打开管理界面
220
- http://127.0.0.1:4567
221
- \`\`\`
222
-
223
199
  ## 管理界面
224
200
 
225
201
  **配置供应商**
@@ -398,6 +374,7 @@ PORT=4567
398
374
  ## 关联资源
399
375
 
400
376
  * [Claude Code 深度教程](https://claudecode.tangshuang.net): 100%免费的Claude Code入门到精通教程
377
+ * [AICodingBus](https://aicodingbus.24x7.to): AI Tokens 交换平台
401
378
 
402
379
  ## 支持我
403
380
 
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>AI Code Switch</title>
7
- <script type="module" crossorigin src="./assets/index-BKBriExY.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-NlzYhf99.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="./assets/index-MjMlew6J.css">
9
9
  </head>
10
10
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicodeswitch",
3
- "version": "5.2.9",
3
+ "version": "5.2.10",
4
4
  "description": "A tool to help you manage AI programming tools to access large language models locally. It allows your Claude Code, Codex and other tools to no longer be limited to official models.",
5
5
  "author": "tangshuang",
6
6
  "license": "GPL-3.0",