easy-cc-api-switch 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.
- package/LICENSE +21 -0
- package/README.md +146 -0
- package/health.js +294 -0
- package/index.js +646 -0
- package/notify.js +516 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bluemomo112
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Easy CC API Switch
|
|
2
|
+
|
|
3
|
+
Claude Code API 配置快速切换工具
|
|
4
|
+
|
|
5
|
+
这是 `claude-config-switch` 的增强版本,在原有功能基础上新增了**快速循环切换**功能。
|
|
6
|
+
|
|
7
|
+
## ✨ 新增功能
|
|
8
|
+
|
|
9
|
+
### 快速循环切换
|
|
10
|
+
使用 `ccs next` 或 `ccs n` 命令可以快速切换到下一个 API 配置,无需交互确认。
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# 切换到下一个配置
|
|
14
|
+
ccs next
|
|
15
|
+
|
|
16
|
+
# 或使用简写
|
|
17
|
+
ccs n
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
当切换到最后一个配置时,会自动循环回到第一个配置。
|
|
21
|
+
|
|
22
|
+
## 📦 安装
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g easy-cc-api-switch
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 🚀 使用方法
|
|
29
|
+
|
|
30
|
+
### 1. 配置 API 列表
|
|
31
|
+
|
|
32
|
+
首先需要创建 API 配置文件:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
ccs o api
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
这会打开 `~/.claude/apiConfigs.json` 文件,按以下格式添加你的 API 配置:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
[
|
|
42
|
+
{
|
|
43
|
+
"name": "官方API",
|
|
44
|
+
"config": {
|
|
45
|
+
"env": {
|
|
46
|
+
"ANTHROPIC_AUTH_TOKEN": "sk-ant-xxx",
|
|
47
|
+
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
|
|
48
|
+
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
|
|
49
|
+
},
|
|
50
|
+
"permissions": {
|
|
51
|
+
"allow": [],
|
|
52
|
+
"deny": []
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "代理API",
|
|
58
|
+
"config": {
|
|
59
|
+
"env": {
|
|
60
|
+
"ANTHROPIC_AUTH_TOKEN": "your-proxy-token",
|
|
61
|
+
"ANTHROPIC_BASE_URL": "https://your-proxy.com/api",
|
|
62
|
+
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
|
|
63
|
+
},
|
|
64
|
+
"permissions": {
|
|
65
|
+
"allow": [],
|
|
66
|
+
"deny": []
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. 切换配置
|
|
74
|
+
|
|
75
|
+
#### 交互式选择(原有功能)
|
|
76
|
+
```bash
|
|
77
|
+
ccs ls
|
|
78
|
+
# 或
|
|
79
|
+
ccs list
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
会显示所有可用配置,通过上下键或输入序号选择。
|
|
83
|
+
|
|
84
|
+
#### 快速循环切换(新功能)⭐
|
|
85
|
+
```bash
|
|
86
|
+
ccs next
|
|
87
|
+
# 或
|
|
88
|
+
ccs n
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
直接切换到下一个配置,无需确认。适合频繁切换场景。
|
|
92
|
+
|
|
93
|
+
### 3. 其他命令
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# 查看帮助
|
|
97
|
+
ccs --help
|
|
98
|
+
|
|
99
|
+
# 打开 API 配置文件
|
|
100
|
+
ccs o api
|
|
101
|
+
|
|
102
|
+
# 打开 settings 配置文件
|
|
103
|
+
ccs o setting
|
|
104
|
+
|
|
105
|
+
# 检查 API 健康状态
|
|
106
|
+
ccs health
|
|
107
|
+
|
|
108
|
+
# 配置企微通知
|
|
109
|
+
ccs notify
|
|
110
|
+
|
|
111
|
+
# 查看版本
|
|
112
|
+
ccs -v
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 📋 配置文件说明
|
|
116
|
+
|
|
117
|
+
工具使用两个配置文件:
|
|
118
|
+
|
|
119
|
+
- `~/.claude/apiConfigs.json` - 存储所有可用的 API 配置列表
|
|
120
|
+
- `~/.claude/settings.json` - 当前激活的配置(由 Claude Code 使用)
|
|
121
|
+
|
|
122
|
+
切换配置时,工具会自动更新 `settings.json`,同时保留你的 hooks 等其他设置。
|
|
123
|
+
|
|
124
|
+
## 🔄 工作原理
|
|
125
|
+
|
|
126
|
+
1. **交互式切换** (`ccs ls`):显示配置列表,让你选择要切换的配置
|
|
127
|
+
2. **快速切换** (`ccs next`):自动切换到下一个配置,循环往复
|
|
128
|
+
|
|
129
|
+
## 🤝 贡献
|
|
130
|
+
|
|
131
|
+
欢迎提交 Issue 和 Pull Request!
|
|
132
|
+
|
|
133
|
+
## 📄 许可证
|
|
134
|
+
|
|
135
|
+
MIT License
|
|
136
|
+
|
|
137
|
+
## 🙏 致谢
|
|
138
|
+
|
|
139
|
+
本项目基于 [claude-config-switch](https://github.com/canglong/claude-code-switch) 开发,感谢原作者的贡献。
|
|
140
|
+
|
|
141
|
+
## 📝 更新日志
|
|
142
|
+
|
|
143
|
+
### v1.0.0
|
|
144
|
+
- ✨ 新增 `ccs next` / `ccs n` 命令,支持快速循环切换配置
|
|
145
|
+
- 🔧 保持原有交互式切换功能
|
|
146
|
+
- 📦 重新打包发布
|
package/health.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 健康检查模块
|
|
3
|
+
* 提供 ccs health 命令:读取 ~/.claude/apiConfigs.json,依次检测各端点健康与网络延迟
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const https = require('https');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
|
|
12
|
+
// 配置路径
|
|
13
|
+
const CONFIG_DIR = path.join(os.homedir(), '.claude');
|
|
14
|
+
const API_CONFIGS_FILE = path.join(CONFIG_DIR, 'apiConfigs.json');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 读取API配置文件
|
|
18
|
+
* @returns {Array} API配置数组(可能为空)
|
|
19
|
+
*/
|
|
20
|
+
function readApiConfigs() {
|
|
21
|
+
try {
|
|
22
|
+
if (!fs.existsSync(API_CONFIGS_FILE)) {
|
|
23
|
+
console.log(chalk.yellow(`警告: API配置文件不存在 (${API_CONFIGS_FILE})`));
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
const data = fs.readFileSync(API_CONFIGS_FILE, 'utf8');
|
|
27
|
+
return JSON.parse(data);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(chalk.red(`读取API配置文件失败: ${error.message}`));
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 探测指定端点
|
|
36
|
+
* - 将 2xx 与 4xx 视为可达(成功)
|
|
37
|
+
* - 将 5xx 或网络错误视为失败
|
|
38
|
+
* - 返回 { ok, statusCode, latencyMs, error, endpoint }
|
|
39
|
+
* @param {string} baseUrl 基础URL,如 https://api.anthropic.com
|
|
40
|
+
* @param {string} authToken API密钥用于鉴权
|
|
41
|
+
* @param {string} endpoint 端点路径,如 '/v1/models'
|
|
42
|
+
* @param {object} options 额外选项
|
|
43
|
+
* @returns {Promise<{ok:boolean,statusCode:number|undefined,latencyMs:number|undefined,error:Error|undefined,endpoint:string}>}
|
|
44
|
+
*/
|
|
45
|
+
function probeEndpoint(baseUrl, authToken, endpoint = '/v1/models', options = {}) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
let urlString = baseUrl.endsWith('/') ? `${baseUrl}${endpoint.substring(1)}` : `${baseUrl}${endpoint}`;
|
|
48
|
+
let timedOut = false;
|
|
49
|
+
const start = Date.now();
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const urlObj = new URL(urlString);
|
|
53
|
+
const requestOptions = {
|
|
54
|
+
hostname: urlObj.hostname,
|
|
55
|
+
port: urlObj.port || 443,
|
|
56
|
+
path: urlObj.pathname + urlObj.search,
|
|
57
|
+
method: options.method || 'GET',
|
|
58
|
+
headers: {
|
|
59
|
+
'Accept': 'application/json',
|
|
60
|
+
'Authorization': `Bearer ${authToken}`,
|
|
61
|
+
// 'x-api-key': authToken,
|
|
62
|
+
...(options.includeAnthropicVersion !== false && { 'anthropic-version': '2023-06-01' }),
|
|
63
|
+
...(options.contentType && { 'Content-Type': options.contentType }),
|
|
64
|
+
...options.extraHeaders
|
|
65
|
+
},
|
|
66
|
+
timeout: 30000
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const req = https.request(requestOptions, (res) => {
|
|
70
|
+
// 消耗响应体以完成请求
|
|
71
|
+
res.on('data', () => {});
|
|
72
|
+
res.on('end', () => {
|
|
73
|
+
const latencyMs = Date.now() - start;
|
|
74
|
+
const status = res.statusCode || 0;
|
|
75
|
+
const ok = status < 500; // 2xx/4xx 视为可达
|
|
76
|
+
resolve({ ok, statusCode: status, latencyMs, error: undefined, endpoint });
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
req.on('timeout', () => {
|
|
81
|
+
timedOut = true;
|
|
82
|
+
req.destroy(new Error('Request timeout'));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
req.on('error', (err) => {
|
|
86
|
+
const latencyMs = Date.now() - start;
|
|
87
|
+
resolve({ ok: false, statusCode: undefined, latencyMs, error: timedOut ? new Error('timeout') : err, endpoint });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (options.body) {
|
|
91
|
+
req.write(options.body);
|
|
92
|
+
}
|
|
93
|
+
req.end();
|
|
94
|
+
} catch (e) {
|
|
95
|
+
resolve({ ok: false, statusCode: undefined, latencyMs: undefined, error: e, endpoint });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 对单个配置执行智能健康检查
|
|
102
|
+
* 如果 /v1/models 返回 404,会尝试其他常见端点
|
|
103
|
+
* @param {object} configItem apiConfigs.json中单项
|
|
104
|
+
* @returns {Promise<{name:string, baseUrl:string, tokenPresent:boolean, healthy:boolean, statusCodes:number[], latencies:number[], error:Error|undefined, endpoint:string}>}
|
|
105
|
+
*/
|
|
106
|
+
async function checkConfigHealth(configItem) {
|
|
107
|
+
const name = configItem?.name || 'unknown';
|
|
108
|
+
const env = configItem?.config?.env || {};
|
|
109
|
+
const baseUrl = env.ANTHROPIC_BASE_URL || '';
|
|
110
|
+
const tokenPresent = Boolean(env.ANTHROPIC_AUTH_TOKEN);
|
|
111
|
+
const authToken = env.ANTHROPIC_AUTH_TOKEN || '';
|
|
112
|
+
|
|
113
|
+
// 定义要尝试的端点
|
|
114
|
+
const endpointsToTry = [
|
|
115
|
+
{ path: '/v1/models', description: 'Claude Models API' },
|
|
116
|
+
{ path: '/v1/chat/completions', description: 'OpenAI Compatible API',
|
|
117
|
+
options: {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
contentType: 'application/json',
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
model: 'claude-3-sonnet-20240229',
|
|
122
|
+
messages: [{ role: 'user', content: 'test' }],
|
|
123
|
+
max_tokens: 1
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{ path: '/v1/models', description: 'No Anthropic Version', options: { includeAnthropicVersion: false } },
|
|
128
|
+
{ path: '/', description: 'Root Path' },
|
|
129
|
+
{ path: '/health', description: 'Health Check' },
|
|
130
|
+
{ path: '/api/v1/models', description: 'Alternative API Path' }
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
let bestResult = null;
|
|
134
|
+
let testedEndpoints = [];
|
|
135
|
+
|
|
136
|
+
for (const endpointConfig of endpointsToTry) {
|
|
137
|
+
const result = await probeEndpoint(baseUrl, authToken, endpointConfig.path, endpointConfig.options || {});
|
|
138
|
+
testedEndpoints.push(`${endpointConfig.path}:${result.statusCode}`);
|
|
139
|
+
|
|
140
|
+
// 如果是 2xx 状态码,直接返回成功结果
|
|
141
|
+
if (result.statusCode >= 200 && result.statusCode < 300) {
|
|
142
|
+
return {
|
|
143
|
+
name,
|
|
144
|
+
baseUrl,
|
|
145
|
+
tokenPresent,
|
|
146
|
+
healthy: true,
|
|
147
|
+
statusCodes: [result.statusCode],
|
|
148
|
+
latencies: [result.latencyMs],
|
|
149
|
+
error: undefined,
|
|
150
|
+
endpoint: `${endpointConfig.path} (${endpointConfig.description})`
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 记录最好的结果(最低的错误代码或最早的可达结果)
|
|
155
|
+
if (!bestResult || (result.ok && !bestResult.ok) ||
|
|
156
|
+
(result.statusCode && (!bestResult.statusCode || result.statusCode < bestResult.statusCode))) {
|
|
157
|
+
bestResult = { ...result, description: endpointConfig.description };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 如果没有找到 2xx 响应,返回最好的结果
|
|
162
|
+
const healthy = bestResult ? bestResult.ok : false;
|
|
163
|
+
return {
|
|
164
|
+
name,
|
|
165
|
+
baseUrl,
|
|
166
|
+
tokenPresent,
|
|
167
|
+
healthy,
|
|
168
|
+
statusCodes: bestResult ? [bestResult.statusCode] : [],
|
|
169
|
+
latencies: bestResult ? [bestResult.latencyMs] : [],
|
|
170
|
+
error: bestResult?.error,
|
|
171
|
+
endpoint: bestResult ? `${bestResult.endpoint} (${bestResult.description})` : 'All endpoints failed'
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 掩码显示API Key,显示前7位+****
|
|
177
|
+
* @param {boolean} present 是否存在
|
|
178
|
+
* @param {string} token 原始token
|
|
179
|
+
* @returns {string} 掩码显示
|
|
180
|
+
*/
|
|
181
|
+
function maskToken(present, token) {
|
|
182
|
+
if (!present) return 'N/A';
|
|
183
|
+
if (!token || token.length < 7) return '****';
|
|
184
|
+
return `${token.slice(0, 7)}****`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 注册 health 命令
|
|
189
|
+
* @param {import('commander').Command} program Commander实例
|
|
190
|
+
*/
|
|
191
|
+
function registerHealthCommands(program) {
|
|
192
|
+
program
|
|
193
|
+
.command('health')
|
|
194
|
+
.description('检查各API端点的可用性与网络延迟')
|
|
195
|
+
.action(async () => {
|
|
196
|
+
const apiConfigs = readApiConfigs();
|
|
197
|
+
if (apiConfigs.length === 0) {
|
|
198
|
+
console.log(chalk.yellow('没有找到可用的API配置'));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(chalk.cyan('开始健康检查 (/v1/models)...\n'));
|
|
203
|
+
|
|
204
|
+
// 去重URL,只检测每个URL的第一个配置
|
|
205
|
+
const uniqueUrls = new Set();
|
|
206
|
+
const configsToCheck = [];
|
|
207
|
+
|
|
208
|
+
apiConfigs.forEach(item => {
|
|
209
|
+
const env = item?.config?.env || {};
|
|
210
|
+
const baseUrl = env.ANTHROPIC_BASE_URL || '';
|
|
211
|
+
const token = env.ANTHROPIC_AUTH_TOKEN || '';
|
|
212
|
+
|
|
213
|
+
// 只处理首次出现的URL
|
|
214
|
+
if (!uniqueUrls.has(baseUrl) && baseUrl) {
|
|
215
|
+
uniqueUrls.add(baseUrl);
|
|
216
|
+
configsToCheck.push({
|
|
217
|
+
item,
|
|
218
|
+
token,
|
|
219
|
+
baseUrl
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// 固定宽度设置
|
|
225
|
+
const nameWidth = 18;
|
|
226
|
+
const fixedUrlWidth = 30;
|
|
227
|
+
const tokenWidth = 12;
|
|
228
|
+
const statusWidth = 22;
|
|
229
|
+
|
|
230
|
+
// 表头
|
|
231
|
+
const nameHeader = 'Name'.padEnd(nameWidth);
|
|
232
|
+
const urlHeader = 'Base URL'.padEnd(fixedUrlWidth);
|
|
233
|
+
const tokenHeader = 'Token'.padEnd(tokenWidth);
|
|
234
|
+
const statusHeader = 'Status'.padEnd(statusWidth);
|
|
235
|
+
const latencyHeader = 'Latency';
|
|
236
|
+
|
|
237
|
+
console.log(chalk.bold(`| ${nameHeader} | ${urlHeader} | ${tokenHeader} | ${statusHeader} | ${latencyHeader} |`));
|
|
238
|
+
console.log(`|${'-'.repeat(nameWidth + 2)}|${'-'.repeat(fixedUrlWidth + 2)}|${'-'.repeat(tokenWidth + 2)}|${'-'.repeat(statusWidth + 2)}|${'-'.repeat(10)}|`);
|
|
239
|
+
|
|
240
|
+
// 逐个检测并输出
|
|
241
|
+
for (const config of configsToCheck) {
|
|
242
|
+
const name = (config.item?.name || 'unknown').length > nameWidth
|
|
243
|
+
? (config.item?.name || 'unknown').substring(0, nameWidth - 3) + '...'
|
|
244
|
+
: (config.item?.name || 'unknown').padEnd(nameWidth);
|
|
245
|
+
const url = config.baseUrl.length > fixedUrlWidth
|
|
246
|
+
? config.baseUrl.substring(0, fixedUrlWidth - 3) + '...'
|
|
247
|
+
: config.baseUrl.padEnd(fixedUrlWidth);
|
|
248
|
+
const tokenMasked = maskToken(Boolean(config.token), config.token).padEnd(tokenWidth);
|
|
249
|
+
|
|
250
|
+
// 显示检测中状态
|
|
251
|
+
const checkingStatus = chalk.yellow('Checking...').padEnd(statusWidth + (chalk.yellow('Checking...').length - 'Checking...'.length));
|
|
252
|
+
process.stdout.write(`| ${name} | ${url} | ${tokenMasked} | ${checkingStatus} | ... |\r`);
|
|
253
|
+
|
|
254
|
+
// 执行检测
|
|
255
|
+
const endpointResult = await checkConfigHealth(config.item);
|
|
256
|
+
|
|
257
|
+
const latencyText = endpointResult.latencies.length
|
|
258
|
+
? `${Math.round(endpointResult.latencies[0])}ms`
|
|
259
|
+
: 'N/A';
|
|
260
|
+
|
|
261
|
+
const statusCode = endpointResult.statusCodes.length ? endpointResult.statusCodes[0] : 'N/A';
|
|
262
|
+
const healthStatus = endpointResult.healthy ? 'Healthy' : 'Unhealthy';
|
|
263
|
+
const statusText = `${healthStatus} (status: ${statusCode})`;
|
|
264
|
+
|
|
265
|
+
// 根据状态码着色
|
|
266
|
+
let coloredStatus;
|
|
267
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
268
|
+
coloredStatus = chalk.green(statusText);
|
|
269
|
+
} else if (statusCode >= 400 || (typeof statusCode === 'string' && statusCode !== 'N/A')) {
|
|
270
|
+
coloredStatus = chalk.red(statusText);
|
|
271
|
+
} else {
|
|
272
|
+
coloredStatus = chalk.yellow(statusText);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const statusFormatted = coloredStatus.padEnd(statusWidth + (coloredStatus.length - statusText.length));
|
|
276
|
+
|
|
277
|
+
// 清除当前行并输出最终结果
|
|
278
|
+
process.stdout.write('\r\x1b[K');
|
|
279
|
+
console.log(`| ${name} | ${url} | ${tokenMasked} | ${statusFormatted} | ${latencyText} |`);
|
|
280
|
+
|
|
281
|
+
if (!endpointResult.healthy && endpointResult.error) {
|
|
282
|
+
console.log(chalk.gray(` Error: ${endpointResult.error.message}`));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// // 显示找到的可用端点信息
|
|
286
|
+
// if (endpointResult.endpoint && endpointResult.endpoint !== '/v1/models (Claude Models API)') {
|
|
287
|
+
// console.log(chalk.cyan(` → Found working endpoint: ${endpointResult.endpoint}`));
|
|
288
|
+
// }
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
module.exports = { registerHealthCommands };
|
|
294
|
+
|