yymaxapi 1.0.102 → 1.0.103
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/bin/yymaxapi.js +288 -155
- package/package.json +1 -1
package/bin/yymaxapi.js
CHANGED
|
@@ -1075,6 +1075,55 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, defaultModelKe
|
|
|
1075
1075
|
return configPath;
|
|
1076
1076
|
}
|
|
1077
1077
|
|
|
1078
|
+
function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id) {
|
|
1079
|
+
const dataDir = getHermesDataDir();
|
|
1080
|
+
const configPath = path.join(dataDir, 'config.yaml');
|
|
1081
|
+
const envPath = getHermesEnvPath();
|
|
1082
|
+
const existingConfigPath = getHermesConfigPath();
|
|
1083
|
+
const wslMirror = getHermesWslMirrorInfo();
|
|
1084
|
+
|
|
1085
|
+
let existingConfigRaw = readTextIfExists(existingConfigPath);
|
|
1086
|
+
if (!existingConfigRaw && wslMirror.sourceConfigPath) {
|
|
1087
|
+
existingConfigRaw = readWslTextFile(wslMirror.sourceConfigPath);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
let existingEnvRaw = readTextIfExists(envPath);
|
|
1091
|
+
if (!existingEnvRaw && wslMirror.envPath) {
|
|
1092
|
+
existingEnvRaw = readWslTextFile(wslMirror.envPath);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
const normalizedBaseUrl = trimClaudeMessagesSuffix(String(baseUrl || '').trim()).replace(/\/+$/, '');
|
|
1096
|
+
const nextConfigRaw = upsertSimpleYamlConfig(existingConfigRaw, {
|
|
1097
|
+
provider: 'anthropic',
|
|
1098
|
+
model: modelId,
|
|
1099
|
+
base_url: normalizedBaseUrl
|
|
1100
|
+
});
|
|
1101
|
+
const nextEnvRaw = upsertEnvFile(existingEnvRaw, {
|
|
1102
|
+
ANTHROPIC_API_KEY: String(apiKey || '').trim()
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
1106
|
+
fs.writeFileSync(configPath, nextConfigRaw, 'utf8');
|
|
1107
|
+
fs.writeFileSync(envPath, nextEnvRaw, 'utf8');
|
|
1108
|
+
|
|
1109
|
+
if (wslMirror.configPath) {
|
|
1110
|
+
try { syncFileToWsl(configPath, wslMirror.configPath); } catch { /* best-effort */ }
|
|
1111
|
+
}
|
|
1112
|
+
if (wslMirror.envPath) {
|
|
1113
|
+
try { syncFileToWsl(envPath, wslMirror.envPath); } catch { /* best-effort */ }
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
return {
|
|
1117
|
+
dataDir,
|
|
1118
|
+
configPath,
|
|
1119
|
+
envPath,
|
|
1120
|
+
wslConfigPath: wslMirror.configPath,
|
|
1121
|
+
wslEnvPath: wslMirror.envPath,
|
|
1122
|
+
modelId,
|
|
1123
|
+
baseUrl: normalizedBaseUrl
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1078
1127
|
function syncExternalTools(type, baseUrl, apiKey, extra = {}) {
|
|
1079
1128
|
const synced = [];
|
|
1080
1129
|
try {
|
|
@@ -3400,6 +3449,177 @@ function getClaudeCodeSettingsPath() {
|
|
|
3400
3449
|
return path.join(os.homedir(), '.claude', 'settings.json');
|
|
3401
3450
|
}
|
|
3402
3451
|
|
|
3452
|
+
function readTextIfExists(filePath) {
|
|
3453
|
+
if (!filePath || !fs.existsSync(filePath)) return '';
|
|
3454
|
+
try {
|
|
3455
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
3456
|
+
} catch {
|
|
3457
|
+
return '';
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
function getHermesDataDir() {
|
|
3462
|
+
const envDir = String(process.env.HERMES_DATA_DIR || '').trim();
|
|
3463
|
+
return envDir || path.join(os.homedir(), '.hermes');
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
function getHermesConfigPath() {
|
|
3467
|
+
const dataDir = getHermesDataDir();
|
|
3468
|
+
const candidates = [
|
|
3469
|
+
path.join(dataDir, 'config.yaml'),
|
|
3470
|
+
path.join(dataDir, 'config.yml')
|
|
3471
|
+
];
|
|
3472
|
+
return candidates.find(filePath => fs.existsSync(filePath)) || candidates[0];
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
function getHermesEnvPath() {
|
|
3476
|
+
return path.join(getHermesDataDir(), '.env');
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
function getHermesWslMirrorInfo() {
|
|
3480
|
+
if (process.platform !== 'win32' || !isWslAvailable()) {
|
|
3481
|
+
return {
|
|
3482
|
+
dataDir: null,
|
|
3483
|
+
sourceConfigPath: null,
|
|
3484
|
+
configPath: null,
|
|
3485
|
+
envPath: null
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
const wslHome = getWslHome() || '/root';
|
|
3490
|
+
const dataDir = path.posix.join(wslHome, '.hermes');
|
|
3491
|
+
const sourceConfigPath = findExistingWslFile([
|
|
3492
|
+
path.posix.join(dataDir, 'config.yaml'),
|
|
3493
|
+
path.posix.join(dataDir, 'config.yml')
|
|
3494
|
+
]);
|
|
3495
|
+
|
|
3496
|
+
return {
|
|
3497
|
+
dataDir,
|
|
3498
|
+
sourceConfigPath,
|
|
3499
|
+
configPath: path.posix.join(dataDir, 'config.yaml'),
|
|
3500
|
+
envPath: path.posix.join(dataDir, '.env')
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
function readWslTextFile(filePath) {
|
|
3505
|
+
if (process.platform !== 'win32' || !filePath) return '';
|
|
3506
|
+
const quoted = shellQuote(filePath);
|
|
3507
|
+
const result = safeExec(`wsl -- bash -lc "cat ${quoted} 2>/dev/null"`, { timeout: 10000 });
|
|
3508
|
+
return result.ok ? (result.output || result.stdout || '') : '';
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
function parseSimpleYamlConfig(text) {
|
|
3512
|
+
const config = {};
|
|
3513
|
+
for (const line of String(text || '').split(/\r?\n/)) {
|
|
3514
|
+
const match = line.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*?)\s*$/);
|
|
3515
|
+
if (!match) continue;
|
|
3516
|
+
let value = match[2].trim();
|
|
3517
|
+
if (
|
|
3518
|
+
(value.startsWith('"') && value.endsWith('"'))
|
|
3519
|
+
|| (value.startsWith('\'') && value.endsWith('\''))
|
|
3520
|
+
) {
|
|
3521
|
+
try {
|
|
3522
|
+
value = JSON.parse(value);
|
|
3523
|
+
} catch {
|
|
3524
|
+
value = value.slice(1, -1);
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
config[match[1]] = value;
|
|
3528
|
+
}
|
|
3529
|
+
return config;
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
function serializeYamlScalar(value) {
|
|
3533
|
+
return JSON.stringify(String(value ?? ''));
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
function upsertSimpleYamlConfig(text, entries) {
|
|
3537
|
+
const lines = String(text || '').replace(/\r\n/g, '\n').split('\n');
|
|
3538
|
+
const normalized = lines.length === 1 && lines[0] === '' ? [] : [...lines];
|
|
3539
|
+
const seen = new Set();
|
|
3540
|
+
|
|
3541
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
3542
|
+
const match = normalized[i].match(/^([A-Za-z0-9_-]+)\s*:\s*.*$/);
|
|
3543
|
+
if (!match) continue;
|
|
3544
|
+
const key = match[1];
|
|
3545
|
+
if (!Object.prototype.hasOwnProperty.call(entries, key)) continue;
|
|
3546
|
+
normalized[i] = `${key}: ${serializeYamlScalar(entries[key])}`;
|
|
3547
|
+
seen.add(key);
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
for (const key of Object.keys(entries)) {
|
|
3551
|
+
if (!seen.has(key)) {
|
|
3552
|
+
normalized.push(`${key}: ${serializeYamlScalar(entries[key])}`);
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
while (normalized.length > 0 && normalized[normalized.length - 1] === '') {
|
|
3557
|
+
normalized.pop();
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
return normalized.join('\n') + '\n';
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3563
|
+
function parseEnvFile(text) {
|
|
3564
|
+
const entries = {};
|
|
3565
|
+
for (const line of String(text || '').split(/\r?\n/)) {
|
|
3566
|
+
const trimmed = line.trim();
|
|
3567
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
3568
|
+
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
3569
|
+
if (!match) continue;
|
|
3570
|
+
let value = match[2].trim();
|
|
3571
|
+
if (
|
|
3572
|
+
(value.startsWith('"') && value.endsWith('"'))
|
|
3573
|
+
|| (value.startsWith('\'') && value.endsWith('\''))
|
|
3574
|
+
) {
|
|
3575
|
+
try {
|
|
3576
|
+
value = JSON.parse(value);
|
|
3577
|
+
} catch {
|
|
3578
|
+
value = value.slice(1, -1);
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
entries[match[1]] = value;
|
|
3582
|
+
}
|
|
3583
|
+
return entries;
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
function upsertEnvFile(text, entries) {
|
|
3587
|
+
const lines = String(text || '').replace(/\r\n/g, '\n').split('\n');
|
|
3588
|
+
const normalized = lines.length === 1 && lines[0] === '' ? [] : [...lines];
|
|
3589
|
+
const seen = new Set();
|
|
3590
|
+
|
|
3591
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
3592
|
+
const match = normalized[i].match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
3593
|
+
if (!match) continue;
|
|
3594
|
+
const key = match[1];
|
|
3595
|
+
if (!Object.prototype.hasOwnProperty.call(entries, key)) continue;
|
|
3596
|
+
normalized[i] = `${key}=${JSON.stringify(String(entries[key] ?? ''))}`;
|
|
3597
|
+
seen.add(key);
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
for (const key of Object.keys(entries)) {
|
|
3601
|
+
if (!seen.has(key)) {
|
|
3602
|
+
normalized.push(`${key}=${JSON.stringify(String(entries[key] ?? ''))}`);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
while (normalized.length > 0 && normalized[normalized.length - 1] === '') {
|
|
3607
|
+
normalized.pop();
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
return normalized.join('\n') + '\n';
|
|
3611
|
+
}
|
|
3612
|
+
|
|
3613
|
+
function readHermesYamlConfig(configPath = getHermesConfigPath()) {
|
|
3614
|
+
const raw = readTextIfExists(configPath);
|
|
3615
|
+
return {
|
|
3616
|
+
configPath,
|
|
3617
|
+
configured: !!raw || fs.existsSync(configPath),
|
|
3618
|
+
config: parseSimpleYamlConfig(raw),
|
|
3619
|
+
raw
|
|
3620
|
+
};
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3403
3623
|
function getOpencodeConfigPath() {
|
|
3404
3624
|
const home = os.homedir();
|
|
3405
3625
|
return process.platform === 'win32'
|
|
@@ -5096,183 +5316,88 @@ async function activateCodex(paths, args = {}) {
|
|
|
5096
5316
|
}
|
|
5097
5317
|
}
|
|
5098
5318
|
|
|
5099
|
-
// ============
|
|
5100
|
-
async function
|
|
5101
|
-
console.log(chalk.cyan.bold('\n
|
|
5319
|
+
// ============ 单独配置 Hermes ============
|
|
5320
|
+
async function activateHermes(paths, args = {}) {
|
|
5321
|
+
console.log(chalk.cyan.bold('\n🔧 配置 Hermes\n'));
|
|
5322
|
+
const selectedModel = await promptClaudeModelSelection(args, '选择 Hermes 默认 Claude 模型:');
|
|
5102
5323
|
|
|
5103
|
-
const
|
|
5104
|
-
|
|
5105
|
-
const claudeProviderName = claudeApiConfig.providerName;
|
|
5106
|
-
const codexProviderName = codexApiConfig.providerName;
|
|
5324
|
+
const shouldTest = !(args['no-test'] || args.noTest);
|
|
5325
|
+
let selectedEndpoint = ENDPOINTS[0];
|
|
5107
5326
|
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5327
|
+
if (shouldTest) {
|
|
5328
|
+
console.log(chalk.cyan('📡 开始测速节点...\n'));
|
|
5329
|
+
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
5330
|
+
const sorted = speedResult.ranked || [];
|
|
5331
|
+
if (sorted.length > 0) {
|
|
5332
|
+
const defaultEp = ENDPOINTS[0];
|
|
5333
|
+
const { selectedIndex } = await inquirer.prompt([{
|
|
5334
|
+
type: 'list',
|
|
5335
|
+
name: 'selectedIndex',
|
|
5336
|
+
message: '选择节点:',
|
|
5337
|
+
choices: [
|
|
5338
|
+
{ name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
|
|
5339
|
+
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
5340
|
+
...sorted.map((e, i) => ({
|
|
5341
|
+
name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
|
|
5342
|
+
value: i
|
|
5343
|
+
}))
|
|
5344
|
+
]
|
|
5345
|
+
}]);
|
|
5346
|
+
selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
|
|
5347
|
+
} else {
|
|
5348
|
+
console.log(chalk.red('\n⚠️ 所有节点均不可达'));
|
|
5349
|
+
const { proceed } = await inquirer.prompt([{
|
|
5350
|
+
type: 'confirm', name: 'proceed',
|
|
5351
|
+
message: '仍要使用默认节点配置吗?', default: false
|
|
5352
|
+
}]);
|
|
5353
|
+
if (!proceed) { console.log(chalk.gray('已取消')); return; }
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5111
5356
|
|
|
5112
|
-
// 1. CLI 参数
|
|
5113
|
-
const args = parseArgs(process.argv.slice(2));
|
|
5114
5357
|
const directKey = (args['api-key'] || args.apiKey || args.key || '').toString().trim();
|
|
5358
|
+
let apiKey;
|
|
5115
5359
|
if (directKey) {
|
|
5116
5360
|
apiKey = directKey;
|
|
5117
|
-
keySource = '命令行参数';
|
|
5118
|
-
}
|
|
5119
|
-
|
|
5120
|
-
// 2. 环境变量
|
|
5121
|
-
if (!apiKey) {
|
|
5122
|
-
const envKeys = ['OPENCLAW_CLAUDE_KEY', 'OPENCLAW_CODEX_KEY', 'CLAUDE_API_KEY', 'OPENAI_API_KEY', 'OPENCLAW_API_KEY'];
|
|
5123
|
-
for (const k of envKeys) {
|
|
5124
|
-
if (process.env[k] && process.env[k].trim()) {
|
|
5125
|
-
apiKey = process.env[k].trim();
|
|
5126
|
-
keySource = `环境变量 ${k}`;
|
|
5127
|
-
break;
|
|
5128
|
-
}
|
|
5129
|
-
}
|
|
5130
|
-
}
|
|
5131
|
-
|
|
5132
|
-
// 3. 已有 OpenClaw 配置(云翼 Claude Code 密钥)
|
|
5133
|
-
if (!apiKey) {
|
|
5134
|
-
try {
|
|
5135
|
-
const config = readConfig(paths.openclawConfig);
|
|
5136
|
-
if (config && config.models && config.models.providers) {
|
|
5137
|
-
// 优先取 claude-yunyi 的 key
|
|
5138
|
-
const preferredOrder = [claudeProviderName, codexProviderName];
|
|
5139
|
-
for (const name of preferredOrder) {
|
|
5140
|
-
const p = config.models.providers[name];
|
|
5141
|
-
if (p && p.apiKey && p.apiKey.trim()) {
|
|
5142
|
-
apiKey = p.apiKey.trim();
|
|
5143
|
-
keySource = `已有配置 (${name})`;
|
|
5144
|
-
break;
|
|
5145
|
-
}
|
|
5146
|
-
}
|
|
5147
|
-
// 其他 provider 的 key
|
|
5148
|
-
if (!apiKey) {
|
|
5149
|
-
for (const [name, p] of Object.entries(config.models.providers)) {
|
|
5150
|
-
if (p.apiKey && p.apiKey.trim()) {
|
|
5151
|
-
apiKey = p.apiKey.trim();
|
|
5152
|
-
keySource = `已有配置 (${name})`;
|
|
5153
|
-
break;
|
|
5154
|
-
}
|
|
5155
|
-
}
|
|
5156
|
-
}
|
|
5157
|
-
}
|
|
5158
|
-
} catch { /* ignore */ }
|
|
5159
|
-
}
|
|
5160
|
-
|
|
5161
|
-
// 4. 都没有,提示输入
|
|
5162
|
-
if (apiKey) {
|
|
5163
|
-
const masked = apiKey.length > 8 ? apiKey.slice(0, 5) + '***' + apiKey.slice(-3) : '***';
|
|
5164
|
-
console.log(chalk.green(`✓ 已检测到 API Key: ${masked} (来源: ${keySource})`));
|
|
5165
5361
|
} else {
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
}
|
|
5169
|
-
|
|
5170
|
-
// ---- 静默测速选最快节点 ----
|
|
5171
|
-
const speedSpinner = ora({ text: '正在测速选择最快节点...', spinner: 'dots' }).start();
|
|
5172
|
-
let selectedEndpoint = ENDPOINTS[0];
|
|
5173
|
-
try {
|
|
5174
|
-
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
5175
|
-
if (speedResult.ranked && speedResult.ranked.length > 0) {
|
|
5176
|
-
selectedEndpoint = speedResult.ranked[0];
|
|
5177
|
-
}
|
|
5178
|
-
speedSpinner.succeed(`节点: ${selectedEndpoint.name}`);
|
|
5179
|
-
} catch {
|
|
5180
|
-
speedSpinner.succeed(`节点: ${selectedEndpoint.name} (默认)`);
|
|
5362
|
+
const envKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || process.env.CLAUDE_API_KEY || '';
|
|
5363
|
+
apiKey = await promptApiKey('请输入 API Key:', envKey);
|
|
5181
5364
|
}
|
|
5365
|
+
if (!apiKey) { console.log(chalk.gray('已取消')); return; }
|
|
5182
5366
|
|
|
5183
|
-
|
|
5367
|
+
console.log('');
|
|
5184
5368
|
const validation = await validateApiKey(selectedEndpoint.url, apiKey);
|
|
5185
5369
|
if (!validation.valid) {
|
|
5186
|
-
|
|
5370
|
+
const { continueAnyway } = await inquirer.prompt([{
|
|
5371
|
+
type: 'confirm', name: 'continueAnyway',
|
|
5372
|
+
message: 'API Key 验证失败,是否仍然继续写入配置?', default: false
|
|
5373
|
+
}]);
|
|
5374
|
+
if (!continueAnyway) { console.log(chalk.gray('已取消')); return; }
|
|
5187
5375
|
}
|
|
5188
5376
|
|
|
5189
|
-
// ---- 写入两套配置 ----
|
|
5190
|
-
const config = ensureConfigStructure(readConfig(paths.openclawConfig) || {});
|
|
5191
|
-
|
|
5192
|
-
// 增量写入: 只添加/更新云翼 provider,保留其他已有配置
|
|
5193
|
-
|
|
5194
|
-
// Claude 侧
|
|
5195
5377
|
const claudeBaseUrl = buildFullUrl(selectedEndpoint.url, 'claude');
|
|
5196
|
-
const
|
|
5197
|
-
const
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
config.models.providers[claudeProviderName] = {
|
|
5201
|
-
baseUrl: claudeBaseUrl,
|
|
5202
|
-
auth: DEFAULT_AUTH_MODE,
|
|
5203
|
-
api: claudeApiConfig.api,
|
|
5204
|
-
headers: {},
|
|
5205
|
-
authHeader: false,
|
|
5206
|
-
apiKey: apiKey.trim(),
|
|
5207
|
-
models: [{ id: claudeModel.id, name: claudeModel.name, contextWindow: claudeApiConfig.contextWindow, maxTokens: claudeApiConfig.maxTokens }]
|
|
5208
|
-
};
|
|
5209
|
-
config.auth.profiles[`${claudeProviderName}:default`] = { provider: claudeProviderName, mode: 'api_key' };
|
|
5210
|
-
config.agents.defaults.models[claudeModelKey] = { alias: claudeProviderName };
|
|
5211
|
-
|
|
5212
|
-
// Codex 侧
|
|
5213
|
-
const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
|
|
5214
|
-
const codexModelId = CODEX_MODELS[0]?.id || 'gpt-5.4';
|
|
5215
|
-
const codexModel = CODEX_MODELS.find(m => m.id === codexModelId) || { id: codexModelId, name: 'GPT 5.4' };
|
|
5216
|
-
const codexModelKey = `${codexProviderName}/${codexModelId}`;
|
|
5217
|
-
|
|
5218
|
-
config.models.providers[codexProviderName] = {
|
|
5219
|
-
baseUrl: codexBaseUrl,
|
|
5220
|
-
auth: DEFAULT_AUTH_MODE,
|
|
5221
|
-
api: codexApiConfig.api,
|
|
5222
|
-
headers: {},
|
|
5223
|
-
authHeader: codexApiConfig.api.startsWith('openai'),
|
|
5224
|
-
apiKey: apiKey.trim(),
|
|
5225
|
-
models: [{ id: codexModel.id, name: codexModel.name, contextWindow: codexApiConfig.contextWindow, maxTokens: codexApiConfig.maxTokens }]
|
|
5226
|
-
};
|
|
5227
|
-
config.auth.profiles[`${codexProviderName}:default`] = { provider: codexProviderName, mode: 'api_key' };
|
|
5228
|
-
config.agents.defaults.models[codexModelKey] = { alias: codexProviderName };
|
|
5229
|
-
|
|
5230
|
-
// 默认主力: Codex, 备用: Claude
|
|
5231
|
-
config.agents.defaults.model.primary = codexModelKey;
|
|
5232
|
-
config.agents.defaults.model.fallbacks = [claudeModelKey];
|
|
5233
|
-
const yunyiLayoutResult = applyManagedYunyiOpenClawLayout(config, {
|
|
5234
|
-
force: true,
|
|
5235
|
-
endpointUrl: selectedEndpoint.url,
|
|
5236
|
-
apiKey
|
|
5237
|
-
});
|
|
5378
|
+
const writeSpinner = ora({ text: '正在写入 Hermes 配置...', spinner: 'dots' }).start();
|
|
5379
|
+
const hermesPaths = writeHermesConfig(claudeBaseUrl, apiKey, selectedModel.id);
|
|
5380
|
+
writeSpinner.succeed('Hermes 配置写入完成');
|
|
5238
5381
|
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
if (
|
|
5248
|
-
|
|
5382
|
+
console.log(chalk.green('\n✅ Hermes 配置完成!'));
|
|
5383
|
+
console.log(chalk.cyan(` Base URL: ${hermesPaths.baseUrl}`));
|
|
5384
|
+
console.log(chalk.gray(` 模型: ${selectedModel.name} (${selectedModel.id})`));
|
|
5385
|
+
console.log(chalk.gray(' Provider: anthropic'));
|
|
5386
|
+
console.log(chalk.gray(' API Key: 已设置'));
|
|
5387
|
+
console.log(chalk.gray('\n 已写入:'));
|
|
5388
|
+
console.log(chalk.gray(` • ${hermesPaths.configPath}`));
|
|
5389
|
+
console.log(chalk.gray(` • ${hermesPaths.envPath}`));
|
|
5390
|
+
if (hermesPaths.wslConfigPath && hermesPaths.wslEnvPath) {
|
|
5391
|
+
console.log(chalk.gray(' • 已额外同步到 WSL ~/.hermes'));
|
|
5249
5392
|
}
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
writeSpinner.succeed('配置写入完成');
|
|
5253
|
-
|
|
5254
|
-
// ---- 结果 ----
|
|
5255
|
-
console.log(chalk.green('\n✅ 配置完成!'));
|
|
5256
|
-
console.log(chalk.cyan(` Claude Code: ${claudeModel.name}`));
|
|
5257
|
-
console.log(chalk.cyan(` Codex CLI: ${codexModel.name}`));
|
|
5258
|
-
printYunyiOpenClawSwitchHint(yunyiLayoutResult);
|
|
5259
|
-
console.log('');
|
|
5393
|
+
console.log(chalk.yellow('\n 提示: Hermes Desktop 的“测试连接”按钮当前可能仍按 /v1/models 检测;按钮不绿,不代表 runtime 不能正常对话'));
|
|
5394
|
+
console.log(chalk.yellow(' 建议: 重启 Hermes Desktop,或手动执行 hermes server --port 8787 验证 Hermes runtime'));
|
|
5260
5395
|
}
|
|
5261
5396
|
|
|
5262
|
-
|
|
5263
5397
|
// ============ 主程序 ============
|
|
5264
5398
|
async function main() {
|
|
5265
5399
|
console.clear();
|
|
5266
5400
|
|
|
5267
|
-
// yycode 精简模式:检测到 yycode CLI 时直接走零交互流程
|
|
5268
|
-
const isYYCode = path.basename(process.argv[1] || '').replace(/\.js$/, '') === 'yycode';
|
|
5269
|
-
if (isYYCode) {
|
|
5270
|
-
const paths = getConfigPath();
|
|
5271
|
-
backupOriginalConfig(paths.openclawConfig, paths.configDir);
|
|
5272
|
-
await yycodeQuickSetup(paths);
|
|
5273
|
-
return;
|
|
5274
|
-
}
|
|
5275
|
-
|
|
5276
5401
|
console.log(chalk.cyan.bold('\n🔧 OpenClaw API 配置工具\n'));
|
|
5277
5402
|
|
|
5278
5403
|
const paths = getConfigPath();
|
|
@@ -5296,6 +5421,10 @@ async function main() {
|
|
|
5296
5421
|
await activateClaudeCode(paths, args);
|
|
5297
5422
|
return;
|
|
5298
5423
|
}
|
|
5424
|
+
if (args.preset === 'hermes' || args._.includes('preset-hermes') || args._.includes('hermes-preset')) {
|
|
5425
|
+
await activateHermes(paths, args);
|
|
5426
|
+
return;
|
|
5427
|
+
}
|
|
5299
5428
|
if (args.preset === 'codex' || args._.includes('preset-codex') || args._.includes('codex-preset')) {
|
|
5300
5429
|
await autoActivate(paths, { ...args, primary: 'codex' });
|
|
5301
5430
|
return;
|
|
@@ -5324,6 +5453,7 @@ async function main() {
|
|
|
5324
5453
|
new inquirer.Separator(' -- 一键配置 --'),
|
|
5325
5454
|
{ name: ' 配置 OpenClaw(Claude + Codex)', value: 'auto_activate' },
|
|
5326
5455
|
{ name: ' 配置 Claude Code', value: 'activate_claude_code' },
|
|
5456
|
+
{ name: ' 配置 Hermes', value: 'activate_hermes' },
|
|
5327
5457
|
{ name: ' 配置 Opencode', value: 'activate_opencode' },
|
|
5328
5458
|
{ name: ' 配置 Codex CLI', value: 'activate_codex' },
|
|
5329
5459
|
new inquirer.Separator(' -- 工具 --'),
|
|
@@ -5367,6 +5497,9 @@ async function main() {
|
|
|
5367
5497
|
case 'activate_claude_code':
|
|
5368
5498
|
await activateClaudeCode(paths);
|
|
5369
5499
|
break;
|
|
5500
|
+
case 'activate_hermes':
|
|
5501
|
+
await activateHermes(paths);
|
|
5502
|
+
break;
|
|
5370
5503
|
case 'activate_opencode':
|
|
5371
5504
|
await activateOpencode(paths);
|
|
5372
5505
|
break;
|