yymaxapi 1.0.104 → 1.0.108
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
|
@@ -8,8 +8,14 @@
|
|
|
8
8
|
```bash
|
|
9
9
|
npx yymaxapi@latest
|
|
10
10
|
```
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
按提示选择模型并输入 Key 即可,默认会直接使用预设默认节点。
|
|
12
|
+
主菜单已简化为:一键配置 / 配置外部工具 / 测速节点 / 测试连接 / 查看配置 / 恢复 / 退出。
|
|
13
|
+
|
|
14
|
+
**需要手动测速时**
|
|
15
|
+
```bash
|
|
16
|
+
npx yymaxapi@latest speed-test
|
|
17
|
+
```
|
|
18
|
+
或在具体配置命令后追加 `--speed-test`,按测速结果手动选点。
|
|
13
19
|
|
|
14
20
|
**方式二:一键配置 Claude**
|
|
15
21
|
```bash
|
package/bin/yymaxapi.js
CHANGED
|
@@ -607,6 +607,77 @@ async function testFallbackEndpoints() {
|
|
|
607
607
|
return testAllEndpoints(FALLBACK_EPS, { label: '检测备用节点', autoFallback: false });
|
|
608
608
|
}
|
|
609
609
|
|
|
610
|
+
function shouldRunEndpointSpeedTest(args = {}) {
|
|
611
|
+
const raw = args['speed-test'] !== undefined ? args['speed-test'] : args.speedTest;
|
|
612
|
+
if (raw === undefined) return false;
|
|
613
|
+
if (typeof raw === 'string') {
|
|
614
|
+
return !['false', '0', 'no'].includes(raw.toLowerCase());
|
|
615
|
+
}
|
|
616
|
+
return !!raw;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function resolveEndpointSelection(args = {}, options = {}) {
|
|
620
|
+
const defaultEndpoint = options.defaultEndpoint || ENDPOINTS[0];
|
|
621
|
+
if (!defaultEndpoint) return null;
|
|
622
|
+
if (!shouldRunEndpointSpeedTest(args)) return defaultEndpoint;
|
|
623
|
+
|
|
624
|
+
const speedIntro = options.speedIntro || '📡 开始测速节点...\n';
|
|
625
|
+
const proceedMessage = options.proceedMessage || '仍要写入默认节点配置吗?';
|
|
626
|
+
const chooseMessage = options.chooseMessage || '选择节点:';
|
|
627
|
+
|
|
628
|
+
console.log(chalk.cyan(`${speedIntro}`));
|
|
629
|
+
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
630
|
+
const sorted = speedResult.ranked || [];
|
|
631
|
+
|
|
632
|
+
if (sorted.length > 0) {
|
|
633
|
+
const { selectedIndex } = await inquirer.prompt([{
|
|
634
|
+
type: 'list',
|
|
635
|
+
name: 'selectedIndex',
|
|
636
|
+
message: chooseMessage,
|
|
637
|
+
choices: [
|
|
638
|
+
{ name: `* 使用默认节点 (${defaultEndpoint.name})`, value: -1 },
|
|
639
|
+
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
640
|
+
...sorted.map((endpoint, index) => ({
|
|
641
|
+
name: `${endpoint.name} - ${endpoint.latency}ms (评分:${endpoint.score})`,
|
|
642
|
+
value: index
|
|
643
|
+
}))
|
|
644
|
+
]
|
|
645
|
+
}]);
|
|
646
|
+
|
|
647
|
+
const selectedEndpoint = selectedIndex === -1 ? defaultEndpoint : sorted[selectedIndex];
|
|
648
|
+
if (speedResult.usedFallback) {
|
|
649
|
+
console.log(chalk.yellow('\n⚠ 当前使用备用节点\n'));
|
|
650
|
+
}
|
|
651
|
+
return selectedEndpoint;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
console.log(chalk.red('\n⚠️ 所有节点(含备用)均不可达'));
|
|
655
|
+
const { proceed } = await inquirer.prompt([{
|
|
656
|
+
type: 'confirm',
|
|
657
|
+
name: 'proceed',
|
|
658
|
+
message: proceedMessage,
|
|
659
|
+
default: false
|
|
660
|
+
}]);
|
|
661
|
+
if (!proceed) {
|
|
662
|
+
console.log(chalk.gray('已取消'));
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
return defaultEndpoint;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async function speedTestNodes() {
|
|
669
|
+
console.log(chalk.cyan.bold('\n📡 节点测速\n'));
|
|
670
|
+
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
671
|
+
const defaultEndpoint = ENDPOINTS[0];
|
|
672
|
+
|
|
673
|
+
if (defaultEndpoint) {
|
|
674
|
+
console.log(chalk.gray(`\n默认配置当前直连: ${defaultEndpoint.name} (${defaultEndpoint.url})`));
|
|
675
|
+
}
|
|
676
|
+
if (speedResult.best && defaultEndpoint && speedResult.best.url !== defaultEndpoint.url) {
|
|
677
|
+
console.log(chalk.yellow('如需按测速结果选点,可在对应配置命令后追加 --speed-test'));
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
610
681
|
// ============ API Key 验证 ============
|
|
611
682
|
function httpGetJson(url, headers = {}, timeout = 10000) {
|
|
612
683
|
return new Promise((resolve, reject) => {
|
|
@@ -967,11 +1038,13 @@ function writeClaudeCodeSettings(baseUrl, apiKey, modelId = getDefaultClaudeMode
|
|
|
967
1038
|
if (fs.existsSync(settingsPath)) {
|
|
968
1039
|
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch { settings = {}; }
|
|
969
1040
|
}
|
|
970
|
-
|
|
1041
|
+
const normalizedBaseUrl = baseUrl.replace(/\/+$/, '');
|
|
1042
|
+
settings.apiBaseUrl = normalizedBaseUrl;
|
|
971
1043
|
if (!settings.env) settings.env = {};
|
|
972
|
-
settings.env.
|
|
1044
|
+
settings.env.ANTHROPIC_API_KEY = apiKey;
|
|
1045
|
+
settings.env.ANTHROPIC_BASE_URL = normalizedBaseUrl;
|
|
1046
|
+
delete settings.env.ANTHROPIC_AUTH_TOKEN;
|
|
973
1047
|
delete settings.availableModels;
|
|
974
|
-
delete settings.env.ANTHROPIC_BASE_URL;
|
|
975
1048
|
delete settings.env.ANTHROPIC_MODEL;
|
|
976
1049
|
if (!fs.existsSync(claudeDir)) fs.mkdirSync(claudeDir, { recursive: true });
|
|
977
1050
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
@@ -3795,6 +3868,32 @@ function readHermesYamlConfig(configPath = getHermesConfigPath()) {
|
|
|
3795
3868
|
};
|
|
3796
3869
|
}
|
|
3797
3870
|
|
|
3871
|
+
function readHermesCliConfig() {
|
|
3872
|
+
const yamlConfig = readHermesYamlConfig();
|
|
3873
|
+
const envPath = getHermesEnvPath();
|
|
3874
|
+
const envEntries = parseEnvFile(readTextIfExists(envPath));
|
|
3875
|
+
const modelConfig = yamlConfig.config?.model && typeof yamlConfig.config.model === 'object'
|
|
3876
|
+
? yamlConfig.config.model
|
|
3877
|
+
: {};
|
|
3878
|
+
const modelId = String(modelConfig.default || '').trim() || getDefaultClaudeModel().id;
|
|
3879
|
+
const provider = String(modelConfig.provider || '').trim().toLowerCase();
|
|
3880
|
+
const type = resolveHermesModelType(modelId, provider);
|
|
3881
|
+
const apiKey = type === 'codex'
|
|
3882
|
+
? (envEntries.OPENAI_API_KEY || process.env.OPENAI_API_KEY || process.env.OPENAI_AUTH_TOKEN || '')
|
|
3883
|
+
: (envEntries.ANTHROPIC_API_KEY || envEntries.ANTHROPIC_TOKEN || process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_TOKEN || process.env.CLAUDE_CODE_OAUTH_TOKEN || '');
|
|
3884
|
+
|
|
3885
|
+
return {
|
|
3886
|
+
configPath: yamlConfig.configPath,
|
|
3887
|
+
envPath,
|
|
3888
|
+
configured: yamlConfig.configured || fs.existsSync(envPath),
|
|
3889
|
+
modelId,
|
|
3890
|
+
provider,
|
|
3891
|
+
type,
|
|
3892
|
+
baseUrl: String(modelConfig.base_url || '').trim(),
|
|
3893
|
+
apiKey
|
|
3894
|
+
};
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3798
3897
|
function getOpencodeConfigPath() {
|
|
3799
3898
|
const home = os.homedir();
|
|
3800
3899
|
return process.platform === 'win32'
|
|
@@ -3813,11 +3912,20 @@ function getCodexCliPaths() {
|
|
|
3813
3912
|
function readClaudeCodeCliConfig() {
|
|
3814
3913
|
const settingsPath = getClaudeCodeSettingsPath();
|
|
3815
3914
|
const settings = readJsonIfExists(settingsPath) || {};
|
|
3915
|
+
const settingsEnv = settings.env && typeof settings.env === 'object' ? settings.env : {};
|
|
3916
|
+
const settingsApiKey = String(settingsEnv.ANTHROPIC_API_KEY || '').trim();
|
|
3917
|
+
const settingsLegacyAuthToken = String(settingsEnv.ANTHROPIC_AUTH_TOKEN || '').trim();
|
|
3918
|
+
const settingsBaseUrl = String(settingsEnv.ANTHROPIC_BASE_URL || settings.apiBaseUrl || '').trim();
|
|
3816
3919
|
return {
|
|
3817
3920
|
settingsPath,
|
|
3818
|
-
modelId: settings.model ||
|
|
3819
|
-
baseUrl:
|
|
3820
|
-
apiKey:
|
|
3921
|
+
modelId: settings.model || settingsEnv.ANTHROPIC_MODEL || process.env.ANTHROPIC_MODEL || getDefaultClaudeModel().id,
|
|
3922
|
+
baseUrl: settingsBaseUrl || process.env.ANTHROPIC_BASE_URL || '',
|
|
3923
|
+
apiKey: settingsApiKey || settingsLegacyAuthToken || process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || process.env.CLAUDE_API_KEY || '',
|
|
3924
|
+
settingsBaseUrl,
|
|
3925
|
+
settingsApiKey,
|
|
3926
|
+
settingsLegacyAuthToken,
|
|
3927
|
+
hasLegacyAuthTokenInSettings: !!settingsLegacyAuthToken,
|
|
3928
|
+
usesLegacyAuthTokenOnly: !settingsApiKey && !!settingsLegacyAuthToken,
|
|
3821
3929
|
configured: fs.existsSync(settingsPath)
|
|
3822
3930
|
};
|
|
3823
3931
|
}
|
|
@@ -3872,6 +3980,23 @@ function summarizeCliTestOutput(text) {
|
|
|
3872
3980
|
return lines[0].slice(0, 160);
|
|
3873
3981
|
}
|
|
3874
3982
|
|
|
3983
|
+
function extractHermesCliReply(text) {
|
|
3984
|
+
const lines = cleanCliTestOutput(text)
|
|
3985
|
+
.split('\n')
|
|
3986
|
+
.map(line => line.trim())
|
|
3987
|
+
.filter(Boolean)
|
|
3988
|
+
.filter(line => !/^session_id:/i.test(line))
|
|
3989
|
+
.filter(line => !/^resume this session/i.test(line))
|
|
3990
|
+
.filter(line => !/^session:/i.test(line))
|
|
3991
|
+
.filter(line => !/^duration:/i.test(line))
|
|
3992
|
+
.filter(line => !/^messages:/i.test(line))
|
|
3993
|
+
.filter(line => !/^[╭╰│─]+/.test(line));
|
|
3994
|
+
|
|
3995
|
+
const okLine = lines.find(line => /^OK$/i.test(line));
|
|
3996
|
+
if (okLine) return okLine;
|
|
3997
|
+
return lines[0] || '';
|
|
3998
|
+
}
|
|
3999
|
+
|
|
3875
4000
|
function buildReadableAgentError(text, fallback = '') {
|
|
3876
4001
|
const cleaned = cleanCliTestOutput(text);
|
|
3877
4002
|
if (!cleaned) return fallback;
|
|
@@ -3887,6 +4012,11 @@ function looksLikeCliTestError(text) {
|
|
|
3887
4012
|
'all models failed',
|
|
3888
4013
|
'auth_permanent',
|
|
3889
4014
|
'auth issue',
|
|
4015
|
+
'authentication_error',
|
|
4016
|
+
'failed to authenticate',
|
|
4017
|
+
'invalid bearer token',
|
|
4018
|
+
'invalid api key',
|
|
4019
|
+
'fix external api key',
|
|
3890
4020
|
'unauthorized',
|
|
3891
4021
|
'forbidden',
|
|
3892
4022
|
'missing environment variable',
|
|
@@ -3914,17 +4044,28 @@ function testClaudeCodeCliConnection() {
|
|
|
3914
4044
|
|
|
3915
4045
|
const config = readClaudeCodeCliConfig();
|
|
3916
4046
|
if (!config.configured) return { name: 'Claude Code CLI', status: 'skipped', detail: '未检测到 ~/.claude/settings.json' };
|
|
3917
|
-
if (
|
|
4047
|
+
if (config.usesLegacyAuthTokenOnly) {
|
|
4048
|
+
return {
|
|
4049
|
+
name: 'Claude Code CLI',
|
|
4050
|
+
status: 'failed',
|
|
4051
|
+
detail: '检测到旧版 Claude Code 配置字段 ANTHROPIC_AUTH_TOKEN,请重新运行 yymaxapi 以改写为 ANTHROPIC_API_KEY'
|
|
4052
|
+
};
|
|
4053
|
+
}
|
|
4054
|
+
if (!config.settingsBaseUrl || !config.settingsApiKey) {
|
|
4055
|
+
return { name: 'Claude Code CLI', status: 'failed', detail: 'Claude Code 配置缺少 settings.json 中的 Base URL 或 API Key' };
|
|
4056
|
+
}
|
|
3918
4057
|
|
|
3919
4058
|
const env = {
|
|
3920
4059
|
...process.env,
|
|
3921
4060
|
PATH: extendPathEnv(null),
|
|
3922
|
-
ANTHROPIC_BASE_URL: config.baseUrl,
|
|
3923
|
-
ANTHROPIC_AUTH_TOKEN: config.apiKey,
|
|
3924
|
-
ANTHROPIC_MODEL: config.modelId,
|
|
3925
4061
|
NODE_TLS_REJECT_UNAUTHORIZED: '0',
|
|
3926
4062
|
NODE_NO_WARNINGS: '1'
|
|
3927
4063
|
};
|
|
4064
|
+
delete env.ANTHROPIC_BASE_URL;
|
|
4065
|
+
delete env.ANTHROPIC_API_KEY;
|
|
4066
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
4067
|
+
delete env.ANTHROPIC_MODEL;
|
|
4068
|
+
delete env.CLAUDE_API_KEY;
|
|
3928
4069
|
|
|
3929
4070
|
return runCliTestCandidates('Claude Code CLI', [
|
|
3930
4071
|
`${shellQuote(cliBinary)} -p --model ${shellQuote(config.modelId)} --output-format text ${shellQuote('请只回复 OK')}`,
|
|
@@ -3976,14 +4117,62 @@ function testCodexCliConnection() {
|
|
|
3976
4117
|
], env);
|
|
3977
4118
|
}
|
|
3978
4119
|
|
|
4120
|
+
function testHermesCliConnection() {
|
|
4121
|
+
const cliBinary = resolveCommandBinary('hermes');
|
|
4122
|
+
if (!cliBinary) return { name: 'Hermes CLI', status: 'skipped', detail: '未安装 hermes 命令' };
|
|
4123
|
+
|
|
4124
|
+
const config = readHermesCliConfig();
|
|
4125
|
+
if (!config.configured) return { name: 'Hermes CLI', status: 'skipped', detail: '未检测到 ~/.hermes/config.yaml 或 ~/.hermes/.env' };
|
|
4126
|
+
if (!config.apiKey) return { name: 'Hermes CLI', status: 'failed', detail: 'Hermes 配置缺少 API Key' };
|
|
4127
|
+
|
|
4128
|
+
const env = {
|
|
4129
|
+
...process.env,
|
|
4130
|
+
PATH: extendPathEnv(null),
|
|
4131
|
+
NODE_TLS_REJECT_UNAUTHORIZED: '0',
|
|
4132
|
+
NODE_NO_WARNINGS: '1'
|
|
4133
|
+
};
|
|
4134
|
+
|
|
4135
|
+
if (config.type === 'codex') {
|
|
4136
|
+
env.OPENAI_API_KEY = config.apiKey;
|
|
4137
|
+
delete env.ANTHROPIC_API_KEY;
|
|
4138
|
+
delete env.ANTHROPIC_TOKEN;
|
|
4139
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
4140
|
+
} else {
|
|
4141
|
+
env.ANTHROPIC_API_KEY = config.apiKey;
|
|
4142
|
+
if (config.baseUrl) env.ANTHROPIC_BASE_URL = config.baseUrl;
|
|
4143
|
+
delete env.ANTHROPIC_TOKEN;
|
|
4144
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
4145
|
+
}
|
|
4146
|
+
|
|
4147
|
+
const providerArg = config.provider === 'anthropic' ? ' --provider anthropic' : '';
|
|
4148
|
+
const result = safeExec(`${shellQuote(cliBinary)} chat -Q -q ${shellQuote('请只回复 OK')}${providerArg}`, {
|
|
4149
|
+
timeout: 120000,
|
|
4150
|
+
env,
|
|
4151
|
+
maxBuffer: 1024 * 1024
|
|
4152
|
+
});
|
|
4153
|
+
const combined = cleanCliTestOutput(`${result.output || ''}\n${result.stdout || ''}\n${result.stderr || ''}`);
|
|
4154
|
+
const reply = extractHermesCliReply(combined);
|
|
4155
|
+
|
|
4156
|
+
if (result.ok && /^OK$/i.test(reply) && !looksLikeCliTestError(combined)) {
|
|
4157
|
+
return { name: 'Hermes CLI', status: 'success', detail: 'OK' };
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
return {
|
|
4161
|
+
name: 'Hermes CLI',
|
|
4162
|
+
status: 'failed',
|
|
4163
|
+
detail: reply || summarizeCliTestOutput(combined) || result.error || '命令执行失败'
|
|
4164
|
+
};
|
|
4165
|
+
}
|
|
4166
|
+
|
|
3979
4167
|
async function testAdditionalCliConnections(args = {}, options = {}) {
|
|
3980
4168
|
if (args['no-app-test'] || args.noAppTest) return;
|
|
3981
4169
|
|
|
3982
|
-
const requested = new Set((options.only || ['claude', 'opencode', 'codex']).map(item => String(item)));
|
|
4170
|
+
const requested = new Set((options.only || ['claude', 'opencode', 'codex', 'hermes']).map(item => String(item)));
|
|
3983
4171
|
const results = [];
|
|
3984
4172
|
if (requested.has('claude')) results.push(testClaudeCodeCliConnection());
|
|
3985
4173
|
if (requested.has('opencode')) results.push(testOpencodeCliConnection());
|
|
3986
4174
|
if (requested.has('codex')) results.push(testCodexCliConnection());
|
|
4175
|
+
if (requested.has('hermes')) results.push(testHermesCliConnection());
|
|
3987
4176
|
if (results.length === 0) return;
|
|
3988
4177
|
|
|
3989
4178
|
console.log(chalk.cyan('\n附加测试: 其他 CLI 连接...'));
|
|
@@ -4670,47 +4859,15 @@ async function quickSetup(paths, args = {}) {
|
|
|
4670
4859
|
}
|
|
4671
4860
|
|
|
4672
4861
|
async function presetClaude(paths, args = {}) {
|
|
4673
|
-
console.log(chalk.cyan.bold('\n🚀 Claude
|
|
4862
|
+
console.log(chalk.cyan.bold('\n🚀 Claude 快速配置(默认节点直连)\n'));
|
|
4674
4863
|
|
|
4675
4864
|
const apiConfig = API_CONFIG.claude;
|
|
4676
4865
|
const providerPrefix = (args['provider-prefix'] || args.prefix || apiConfig.providerName).toString().trim() || apiConfig.providerName;
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
if (
|
|
4682
|
-
console.log(chalk.cyan('📡 开始测速 Claude 节点...\n'));
|
|
4683
|
-
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
4684
|
-
|
|
4685
|
-
const sorted = speedResult.ranked || [];
|
|
4686
|
-
if (sorted.length > 0) {
|
|
4687
|
-
const defaultEp = ENDPOINTS[0];
|
|
4688
|
-
const { selectedIndex } = await inquirer.prompt([{
|
|
4689
|
-
type: 'list',
|
|
4690
|
-
name: 'selectedIndex',
|
|
4691
|
-
message: '选择节点:',
|
|
4692
|
-
choices: [
|
|
4693
|
-
{ name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
|
|
4694
|
-
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
4695
|
-
...sorted.map((e, i) => ({
|
|
4696
|
-
name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
|
|
4697
|
-
value: i
|
|
4698
|
-
}))
|
|
4699
|
-
]
|
|
4700
|
-
}]);
|
|
4701
|
-
selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
|
|
4702
|
-
if (speedResult.usedFallback) {
|
|
4703
|
-
console.log(chalk.yellow(`\n⚠ 当前使用备用节点\n`));
|
|
4704
|
-
}
|
|
4705
|
-
} else {
|
|
4706
|
-
console.log(chalk.red('\n⚠️ 所有节点(含备用)均不可达'));
|
|
4707
|
-
const { proceed } = await inquirer.prompt([{
|
|
4708
|
-
type: 'confirm', name: 'proceed',
|
|
4709
|
-
message: '仍要写入默认节点配置吗?', default: false
|
|
4710
|
-
}]);
|
|
4711
|
-
if (!proceed) { console.log(chalk.gray('已取消')); return; }
|
|
4712
|
-
}
|
|
4713
|
-
}
|
|
4866
|
+
const selectedEndpoint = await resolveEndpointSelection(args, {
|
|
4867
|
+
speedIntro: '📡 开始测速 Claude 节点...\n',
|
|
4868
|
+
proceedMessage: '仍要写入默认节点配置吗?'
|
|
4869
|
+
});
|
|
4870
|
+
if (!selectedEndpoint) return;
|
|
4714
4871
|
|
|
4715
4872
|
const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
|
|
4716
4873
|
const repairResult = applyConfigRepairsWithSync(config, paths);
|
|
@@ -4722,6 +4879,8 @@ async function presetClaude(paths, args = {}) {
|
|
|
4722
4879
|
|
|
4723
4880
|
const apiKeyEnvFallbacks = [
|
|
4724
4881
|
'OPENCLAW_CLAUDE_KEY',
|
|
4882
|
+
'ANTHROPIC_API_KEY',
|
|
4883
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
4725
4884
|
'CLAUDE_API_KEY',
|
|
4726
4885
|
'OPENCLAW_API_KEY'
|
|
4727
4886
|
];
|
|
@@ -4853,48 +5012,16 @@ async function presetClaude(paths, args = {}) {
|
|
|
4853
5012
|
}
|
|
4854
5013
|
|
|
4855
5014
|
async function presetCodex(paths, args = {}) {
|
|
4856
|
-
console.log(chalk.cyan.bold('\n🚀 Codex
|
|
5015
|
+
console.log(chalk.cyan.bold('\n🚀 Codex 快速配置(默认节点直连)\n'));
|
|
4857
5016
|
|
|
4858
5017
|
const apiConfig = API_CONFIG.codex;
|
|
4859
5018
|
const providerPrefix = (args['provider-prefix'] || args.prefix || apiConfig.providerName).toString().trim() || apiConfig.providerName;
|
|
4860
5019
|
const providerName = (args['provider-name'] || args.provider || providerPrefix).toString().trim() || apiConfig.providerName;
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
if (
|
|
4866
|
-
console.log(chalk.cyan('📡 开始测速 Codex 节点...\n'));
|
|
4867
|
-
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
4868
|
-
|
|
4869
|
-
const sorted = speedResult.ranked || [];
|
|
4870
|
-
if (sorted.length > 0) {
|
|
4871
|
-
const defaultEp = ENDPOINTS[0];
|
|
4872
|
-
const { selectedIndex } = await inquirer.prompt([{
|
|
4873
|
-
type: 'list',
|
|
4874
|
-
name: 'selectedIndex',
|
|
4875
|
-
message: '选择节点:',
|
|
4876
|
-
choices: [
|
|
4877
|
-
{ name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
|
|
4878
|
-
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
4879
|
-
...sorted.map((e, i) => ({
|
|
4880
|
-
name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
|
|
4881
|
-
value: i
|
|
4882
|
-
}))
|
|
4883
|
-
]
|
|
4884
|
-
}]);
|
|
4885
|
-
selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
|
|
4886
|
-
if (speedResult.usedFallback) {
|
|
4887
|
-
console.log(chalk.yellow(`\n⚠ 当前使用备用节点\n`));
|
|
4888
|
-
}
|
|
4889
|
-
} else {
|
|
4890
|
-
console.log(chalk.red('\n⚠️ 所有节点(含备用)均不可达'));
|
|
4891
|
-
const { proceed } = await inquirer.prompt([{
|
|
4892
|
-
type: 'confirm', name: 'proceed',
|
|
4893
|
-
message: '仍要写入默认节点配置吗?', default: false
|
|
4894
|
-
}]);
|
|
4895
|
-
if (!proceed) { console.log(chalk.gray('已取消')); return; }
|
|
4896
|
-
}
|
|
4897
|
-
}
|
|
5020
|
+
const selectedEndpoint = await resolveEndpointSelection(args, {
|
|
5021
|
+
speedIntro: '📡 开始测速 Codex 节点...\n',
|
|
5022
|
+
proceedMessage: '仍要写入默认节点配置吗?'
|
|
5023
|
+
});
|
|
5024
|
+
if (!selectedEndpoint) return;
|
|
4898
5025
|
|
|
4899
5026
|
const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
|
|
4900
5027
|
const repairResult = applyConfigRepairsWithSync(config, paths);
|
|
@@ -5048,6 +5175,8 @@ async function autoActivate(paths, args = {}) {
|
|
|
5048
5175
|
const apiKeyEnvFallbacks = [
|
|
5049
5176
|
'OPENCLAW_CLAUDE_KEY',
|
|
5050
5177
|
'OPENCLAW_CODEX_KEY',
|
|
5178
|
+
'ANTHROPIC_API_KEY',
|
|
5179
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
5051
5180
|
'CLAUDE_API_KEY',
|
|
5052
5181
|
'OPENAI_API_KEY',
|
|
5053
5182
|
'OPENCLAW_API_KEY'
|
|
@@ -5258,40 +5387,10 @@ async function autoActivate(paths, args = {}) {
|
|
|
5258
5387
|
async function activateClaudeCode(paths, args = {}) {
|
|
5259
5388
|
console.log(chalk.cyan.bold('\n🔧 配置 Claude Code CLI\n'));
|
|
5260
5389
|
const selectedModel = await promptClaudeModelSelection(args);
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
if (shouldTest) {
|
|
5267
|
-
console.log(chalk.cyan('📡 开始测速节点...\n'));
|
|
5268
|
-
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
5269
|
-
const sorted = speedResult.ranked || [];
|
|
5270
|
-
if (sorted.length > 0) {
|
|
5271
|
-
const defaultEp = ENDPOINTS[0];
|
|
5272
|
-
const { selectedIndex } = await inquirer.prompt([{
|
|
5273
|
-
type: 'list',
|
|
5274
|
-
name: 'selectedIndex',
|
|
5275
|
-
message: '选择节点:',
|
|
5276
|
-
choices: [
|
|
5277
|
-
{ name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
|
|
5278
|
-
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
5279
|
-
...sorted.map((e, i) => ({
|
|
5280
|
-
name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
|
|
5281
|
-
value: i
|
|
5282
|
-
}))
|
|
5283
|
-
]
|
|
5284
|
-
}]);
|
|
5285
|
-
selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
|
|
5286
|
-
} else {
|
|
5287
|
-
console.log(chalk.red('\n⚠️ 所有节点均不可达'));
|
|
5288
|
-
const { proceed } = await inquirer.prompt([{
|
|
5289
|
-
type: 'confirm', name: 'proceed',
|
|
5290
|
-
message: '仍要使用默认节点配置吗?', default: false
|
|
5291
|
-
}]);
|
|
5292
|
-
if (!proceed) { console.log(chalk.gray('已取消')); return; }
|
|
5293
|
-
}
|
|
5294
|
-
}
|
|
5390
|
+
const selectedEndpoint = await resolveEndpointSelection(args, {
|
|
5391
|
+
proceedMessage: '仍要使用默认节点配置吗?'
|
|
5392
|
+
});
|
|
5393
|
+
if (!selectedEndpoint) return;
|
|
5295
5394
|
|
|
5296
5395
|
// ---- API Key ----
|
|
5297
5396
|
const directKey = (args['api-key'] || args.apiKey || args.key || '').toString().trim();
|
|
@@ -5299,7 +5398,7 @@ async function activateClaudeCode(paths, args = {}) {
|
|
|
5299
5398
|
if (directKey) {
|
|
5300
5399
|
apiKey = directKey;
|
|
5301
5400
|
} else {
|
|
5302
|
-
const envKey = process.env.ANTHROPIC_AUTH_TOKEN || process.env.CLAUDE_API_KEY || '';
|
|
5401
|
+
const envKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || process.env.CLAUDE_API_KEY || '';
|
|
5303
5402
|
apiKey = await promptApiKey('请输入 API Key:', envKey);
|
|
5304
5403
|
}
|
|
5305
5404
|
if (!apiKey) { console.log(chalk.gray('已取消')); return; }
|
|
@@ -5327,7 +5426,7 @@ async function activateClaudeCode(paths, args = {}) {
|
|
|
5327
5426
|
console.log(chalk.gray(' API Key: 已设置'));
|
|
5328
5427
|
console.log(chalk.gray('\n 已写入:'));
|
|
5329
5428
|
console.log(chalk.gray(' • ~/.claude/settings.json'));
|
|
5330
|
-
console.log(chalk.gray(' • 最小字段: apiBaseUrl + env.
|
|
5429
|
+
console.log(chalk.gray(' • 最小字段: apiBaseUrl + env.ANTHROPIC_API_KEY + env.ANTHROPIC_BASE_URL'));
|
|
5331
5430
|
console.log(chalk.yellow('\n 提示: 如果 Claude Code 已在运行,重新打开一个会话即可读取新配置'));
|
|
5332
5431
|
|
|
5333
5432
|
if (await confirmImmediateTest(args, '是否立即测试 Claude Code CLI 连接?')) {
|
|
@@ -5339,40 +5438,10 @@ async function activateClaudeCode(paths, args = {}) {
|
|
|
5339
5438
|
async function activateOpencode(paths, args = {}) {
|
|
5340
5439
|
console.log(chalk.cyan.bold('\n🔧 配置 Opencode\n'));
|
|
5341
5440
|
const defaultModel = await promptOpencodeDefaultModelSelection(args);
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
if (shouldTest) {
|
|
5348
|
-
console.log(chalk.cyan('📡 开始测速节点...\n'));
|
|
5349
|
-
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
5350
|
-
const sorted = speedResult.ranked || [];
|
|
5351
|
-
if (sorted.length > 0) {
|
|
5352
|
-
const defaultEp = ENDPOINTS[0];
|
|
5353
|
-
const { selectedIndex } = await inquirer.prompt([{
|
|
5354
|
-
type: 'list',
|
|
5355
|
-
name: 'selectedIndex',
|
|
5356
|
-
message: '选择节点:',
|
|
5357
|
-
choices: [
|
|
5358
|
-
{ name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
|
|
5359
|
-
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
5360
|
-
...sorted.map((e, i) => ({
|
|
5361
|
-
name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
|
|
5362
|
-
value: i
|
|
5363
|
-
}))
|
|
5364
|
-
]
|
|
5365
|
-
}]);
|
|
5366
|
-
selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
|
|
5367
|
-
} else {
|
|
5368
|
-
console.log(chalk.red('\n⚠️ 所有节点均不可达'));
|
|
5369
|
-
const { proceed } = await inquirer.prompt([{
|
|
5370
|
-
type: 'confirm', name: 'proceed',
|
|
5371
|
-
message: '仍要使用默认节点配置吗?', default: false
|
|
5372
|
-
}]);
|
|
5373
|
-
if (!proceed) { console.log(chalk.gray('已取消')); return; }
|
|
5374
|
-
}
|
|
5375
|
-
}
|
|
5441
|
+
const selectedEndpoint = await resolveEndpointSelection(args, {
|
|
5442
|
+
proceedMessage: '仍要使用默认节点配置吗?'
|
|
5443
|
+
});
|
|
5444
|
+
if (!selectedEndpoint) return;
|
|
5376
5445
|
|
|
5377
5446
|
// ---- API Key ----
|
|
5378
5447
|
const directKey = (args['api-key'] || args.apiKey || args.key || '').toString().trim();
|
|
@@ -5380,7 +5449,7 @@ async function activateOpencode(paths, args = {}) {
|
|
|
5380
5449
|
if (directKey) {
|
|
5381
5450
|
apiKey = directKey;
|
|
5382
5451
|
} else {
|
|
5383
|
-
const envKey = process.env.ANTHROPIC_AUTH_TOKEN || process.env.CLAUDE_API_KEY || '';
|
|
5452
|
+
const envKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || process.env.CLAUDE_API_KEY || '';
|
|
5384
5453
|
apiKey = await promptApiKey('请输入 API Key:', envKey);
|
|
5385
5454
|
}
|
|
5386
5455
|
if (!apiKey) { console.log(chalk.gray('已取消')); return; }
|
|
@@ -5436,39 +5505,10 @@ async function activateCodex(paths, args = {}) {
|
|
|
5436
5505
|
}
|
|
5437
5506
|
const modelConfig = CODEX_MODELS.find(m => m.id === modelId) || { id: modelId, name: modelId };
|
|
5438
5507
|
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
if (shouldTest) {
|
|
5444
|
-
console.log(chalk.cyan('📡 开始测速节点...\n'));
|
|
5445
|
-
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
5446
|
-
const sorted = speedResult.ranked || [];
|
|
5447
|
-
if (sorted.length > 0) {
|
|
5448
|
-
const defaultEp = ENDPOINTS[0];
|
|
5449
|
-
const { selectedIndex } = await inquirer.prompt([{
|
|
5450
|
-
type: 'list',
|
|
5451
|
-
name: 'selectedIndex',
|
|
5452
|
-
message: '选择节点:',
|
|
5453
|
-
choices: [
|
|
5454
|
-
{ name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
|
|
5455
|
-
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
5456
|
-
...sorted.map((e, i) => ({
|
|
5457
|
-
name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
|
|
5458
|
-
value: i
|
|
5459
|
-
}))
|
|
5460
|
-
]
|
|
5461
|
-
}]);
|
|
5462
|
-
selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
|
|
5463
|
-
} else {
|
|
5464
|
-
console.log(chalk.red('\n⚠️ 所有节点均不可达'));
|
|
5465
|
-
const { proceed } = await inquirer.prompt([{
|
|
5466
|
-
type: 'confirm', name: 'proceed',
|
|
5467
|
-
message: '仍要使用默认节点配置吗?', default: false
|
|
5468
|
-
}]);
|
|
5469
|
-
if (!proceed) { console.log(chalk.gray('已取消')); return; }
|
|
5470
|
-
}
|
|
5471
|
-
}
|
|
5508
|
+
const selectedEndpoint = await resolveEndpointSelection(args, {
|
|
5509
|
+
proceedMessage: '仍要使用默认节点配置吗?'
|
|
5510
|
+
});
|
|
5511
|
+
if (!selectedEndpoint) return;
|
|
5472
5512
|
|
|
5473
5513
|
// ---- API Key ----
|
|
5474
5514
|
const directKey = (args['api-key'] || args.apiKey || args.key || '').toString().trim();
|
|
@@ -5518,39 +5558,10 @@ async function activateHermes(paths, args = {}) {
|
|
|
5518
5558
|
const selection = await promptHermesModelSelection(args, '选择 Hermes 默认模型:');
|
|
5519
5559
|
const selectedModel = selection.model;
|
|
5520
5560
|
const selectedType = selection.type;
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
if (shouldTest) {
|
|
5526
|
-
console.log(chalk.cyan('📡 开始测速节点...\n'));
|
|
5527
|
-
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
5528
|
-
const sorted = speedResult.ranked || [];
|
|
5529
|
-
if (sorted.length > 0) {
|
|
5530
|
-
const defaultEp = ENDPOINTS[0];
|
|
5531
|
-
const { selectedIndex } = await inquirer.prompt([{
|
|
5532
|
-
type: 'list',
|
|
5533
|
-
name: 'selectedIndex',
|
|
5534
|
-
message: '选择节点:',
|
|
5535
|
-
choices: [
|
|
5536
|
-
{ name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
|
|
5537
|
-
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
5538
|
-
...sorted.map((e, i) => ({
|
|
5539
|
-
name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
|
|
5540
|
-
value: i
|
|
5541
|
-
}))
|
|
5542
|
-
]
|
|
5543
|
-
}]);
|
|
5544
|
-
selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
|
|
5545
|
-
} else {
|
|
5546
|
-
console.log(chalk.red('\n⚠️ 所有节点均不可达'));
|
|
5547
|
-
const { proceed } = await inquirer.prompt([{
|
|
5548
|
-
type: 'confirm', name: 'proceed',
|
|
5549
|
-
message: '仍要使用默认节点配置吗?', default: false
|
|
5550
|
-
}]);
|
|
5551
|
-
if (!proceed) { console.log(chalk.gray('已取消')); return; }
|
|
5552
|
-
}
|
|
5553
|
-
}
|
|
5561
|
+
const selectedEndpoint = await resolveEndpointSelection(args, {
|
|
5562
|
+
proceedMessage: '仍要使用默认节点配置吗?'
|
|
5563
|
+
});
|
|
5564
|
+
if (!selectedEndpoint) return;
|
|
5554
5565
|
|
|
5555
5566
|
const directKey = (args['api-key'] || args.apiKey || args.key || '').toString().trim();
|
|
5556
5567
|
let apiKey;
|
|
@@ -5592,8 +5603,12 @@ async function activateHermes(paths, args = {}) {
|
|
|
5592
5603
|
if (hermesPaths.wslConfigPath && hermesPaths.wslEnvPath) {
|
|
5593
5604
|
console.log(chalk.gray(' • 已额外同步到 WSL ~/.hermes'));
|
|
5594
5605
|
}
|
|
5595
|
-
console.log(chalk.yellow('\n 提示: Hermes
|
|
5596
|
-
console.log(chalk.yellow(' 建议:
|
|
5606
|
+
console.log(chalk.yellow('\n 提示: Hermes doctor 当前会固定检查官方 Anthropic /v1/models,不会读取 model.base_url;第三方 Anthropic 中转可能被误报为 invalid API key'));
|
|
5607
|
+
console.log(chalk.yellow(' 建议: 以 yymaxapi 的 Hermes CLI 运行时测试,或 `hermes chat -Q -q "请只回复 OK"` 的结果为准'));
|
|
5608
|
+
|
|
5609
|
+
if (await confirmImmediateTest(args, '是否立即测试 Hermes CLI 连接?')) {
|
|
5610
|
+
await testAdditionalCliConnections(args, { only: ['hermes'] });
|
|
5611
|
+
}
|
|
5597
5612
|
}
|
|
5598
5613
|
|
|
5599
5614
|
// ============ 主程序 ============
|
|
@@ -5635,6 +5650,10 @@ async function main() {
|
|
|
5635
5650
|
await testConnection(paths, args);
|
|
5636
5651
|
return;
|
|
5637
5652
|
}
|
|
5653
|
+
if (args._.includes('speed-test') || args._.includes('speedtest')) {
|
|
5654
|
+
await speedTestNodes();
|
|
5655
|
+
return;
|
|
5656
|
+
}
|
|
5638
5657
|
|
|
5639
5658
|
while (true) {
|
|
5640
5659
|
// 显示当前状态
|
|
@@ -5661,6 +5680,7 @@ async function main() {
|
|
|
5661
5680
|
new inquirer.Separator(' -- 工具 --'),
|
|
5662
5681
|
{ name: ' 切换 OpenClaw 模型', value: 'switch_model' },
|
|
5663
5682
|
{ name: ` 权限管理${getToolsProfileTag(paths)}`, value: 'tools_profile' },
|
|
5683
|
+
{ name: ' 测速节点', value: 'speed_test_nodes' },
|
|
5664
5684
|
{ name: ' 测试连接', value: 'test_connection' },
|
|
5665
5685
|
{ name: ' 查看配置', value: 'view_config' },
|
|
5666
5686
|
{ name: ' 恢复默认', value: 'restore' },
|
|
@@ -5684,6 +5704,9 @@ async function main() {
|
|
|
5684
5704
|
case 'test_connection':
|
|
5685
5705
|
await testConnection(paths, {});
|
|
5686
5706
|
break;
|
|
5707
|
+
case 'speed_test_nodes':
|
|
5708
|
+
await speedTestNodes();
|
|
5709
|
+
break;
|
|
5687
5710
|
case 'switch_model':
|
|
5688
5711
|
await switchModel(paths);
|
|
5689
5712
|
break;
|
|
@@ -6888,6 +6911,8 @@ function testGatewayViaAgent(model, agentId = '') {
|
|
|
6888
6911
|
}
|
|
6889
6912
|
const nodeInfo = findCompatibleNode(nodeMajor);
|
|
6890
6913
|
const env = { ...process.env, PATH: extendPathEnv(nodeInfo ? nodeInfo.path : null), NODE_NO_WARNINGS: '1' };
|
|
6914
|
+
delete env.ANTHROPIC_API_KEY;
|
|
6915
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
6891
6916
|
delete env.CLAUDE_API_KEY;
|
|
6892
6917
|
delete env.OPENCLAW_CLAUDE_KEY;
|
|
6893
6918
|
delete env.OPENCLAW_API_KEY;
|
|
@@ -155,7 +155,8 @@ npx yymaxapi@latest
|
|
|
155
155
|
{
|
|
156
156
|
"apiBaseUrl": "https://yunyi.rdzhvip.com/claude",
|
|
157
157
|
"env": {
|
|
158
|
-
"
|
|
158
|
+
"ANTHROPIC_API_KEY": "<你的云翼 API Key>",
|
|
159
|
+
"ANTHROPIC_BASE_URL": "https://yunyi.rdzhvip.com/claude"
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
```
|