openclawapi 1.2.1 → 1.3.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/cli.js +173 -103
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -20,13 +20,37 @@ const ENDPOINTS = [
|
|
|
20
20
|
{ name: '备用节点2', url: 'http://47.97.100.10' }
|
|
21
21
|
];
|
|
22
22
|
|
|
23
|
-
// 模型预设
|
|
24
|
-
const
|
|
23
|
+
// Claude 模型预设
|
|
24
|
+
const CLAUDE_MODELS = [
|
|
25
25
|
{ id: 'claude-opus-4-5', name: 'Claude Opus 4.5' },
|
|
26
26
|
{ id: 'claude-sonnet-4-5', name: 'Claude Sonnet 4.5' },
|
|
27
27
|
{ id: 'claude-haiku-4-5', name: 'Claude Haiku 4.5' }
|
|
28
28
|
];
|
|
29
29
|
|
|
30
|
+
// Codex 模型预设
|
|
31
|
+
const CODEX_MODELS = [
|
|
32
|
+
{ id: 'gpt-5.2', name: 'GPT 5.2' },
|
|
33
|
+
{ id: 'gpt-5.2-codex', name: 'GPT 5.2 Codex' }
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// API 类型配置
|
|
37
|
+
const API_CONFIG = {
|
|
38
|
+
claude: {
|
|
39
|
+
urlSuffix: '/claude/v1/messages',
|
|
40
|
+
api: 'anthropic-messages',
|
|
41
|
+
contextWindow: 200000,
|
|
42
|
+
maxTokens: 8192,
|
|
43
|
+
providerName: 'yunyi-claude'
|
|
44
|
+
},
|
|
45
|
+
codex: {
|
|
46
|
+
urlSuffix: '/codex/response',
|
|
47
|
+
api: 'openai-responses',
|
|
48
|
+
contextWindow: 128000,
|
|
49
|
+
maxTokens: 32768,
|
|
50
|
+
providerName: 'yunyi-codex'
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
30
54
|
// 备份文件名
|
|
31
55
|
const BACKUP_FILENAME = 'openclaw-default.json.bak';
|
|
32
56
|
|
|
@@ -88,7 +112,6 @@ function getConfigPath() {
|
|
|
88
112
|
|
|
89
113
|
const configDir = path.dirname(openclawConfig);
|
|
90
114
|
|
|
91
|
-
// 查找 auth-profiles 路径
|
|
92
115
|
const authCandidates = [
|
|
93
116
|
path.join(openclawStateDir, 'agents', 'main', 'agent', 'auth-profiles.json'),
|
|
94
117
|
path.join(clawdbotStateDir, 'agents', 'main', 'agent', 'auth-profiles.json'),
|
|
@@ -136,17 +159,17 @@ function restoreDefaultConfig(configPath, configDir) {
|
|
|
136
159
|
}
|
|
137
160
|
|
|
138
161
|
// ============ URL 构建 ============
|
|
139
|
-
function buildFullUrl(baseUrl) {
|
|
162
|
+
function buildFullUrl(baseUrl, type) {
|
|
140
163
|
const trimmed = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
141
|
-
|
|
142
|
-
if (trimmed.includes(
|
|
143
|
-
return trimmed +
|
|
164
|
+
const suffix = API_CONFIG[type].urlSuffix;
|
|
165
|
+
if (trimmed.includes(suffix)) return trimmed;
|
|
166
|
+
return trimmed + suffix;
|
|
144
167
|
}
|
|
145
168
|
|
|
146
169
|
// ============ 主程序 ============
|
|
147
170
|
async function main() {
|
|
148
171
|
console.clear();
|
|
149
|
-
console.log(chalk.cyan.bold('\n🔧 OpenClaw
|
|
172
|
+
console.log(chalk.cyan.bold('\n🔧 OpenClaw 配置工具\n'));
|
|
150
173
|
|
|
151
174
|
const paths = getConfigPath();
|
|
152
175
|
console.log(chalk.gray(`配置文件: ${paths.openclawConfig}\n`));
|
|
@@ -162,8 +185,12 @@ async function main() {
|
|
|
162
185
|
name: 'action',
|
|
163
186
|
message: '请选择操作:',
|
|
164
187
|
choices: [
|
|
165
|
-
{ name: '
|
|
166
|
-
{ name: '
|
|
188
|
+
{ name: '🔵 选择 Claude 节点', value: 'select_claude' },
|
|
189
|
+
{ name: '🟢 选择 Codex 节点', value: 'select_codex' },
|
|
190
|
+
new inquirer.Separator(),
|
|
191
|
+
{ name: '⚡ 激活 Claude', value: 'activate_claude' },
|
|
192
|
+
{ name: '⚡ 激活 Codex', value: 'activate_codex' },
|
|
193
|
+
new inquirer.Separator(),
|
|
167
194
|
{ name: '🔑 设置 API Key', value: 'set_apikey' },
|
|
168
195
|
{ name: '📋 查看当前配置', value: 'view_config' },
|
|
169
196
|
{ name: '🔄 恢复默认配置', value: 'restore' },
|
|
@@ -176,11 +203,17 @@ async function main() {
|
|
|
176
203
|
|
|
177
204
|
try {
|
|
178
205
|
switch (action) {
|
|
179
|
-
case '
|
|
180
|
-
await selectNode(paths);
|
|
206
|
+
case 'select_claude':
|
|
207
|
+
await selectNode(paths, 'claude');
|
|
208
|
+
break;
|
|
209
|
+
case 'select_codex':
|
|
210
|
+
await selectNode(paths, 'codex');
|
|
211
|
+
break;
|
|
212
|
+
case 'activate_claude':
|
|
213
|
+
await activate(paths, 'claude');
|
|
181
214
|
break;
|
|
182
|
-
case '
|
|
183
|
-
await
|
|
215
|
+
case 'activate_codex':
|
|
216
|
+
await activate(paths, 'codex');
|
|
184
217
|
break;
|
|
185
218
|
case 'set_apikey':
|
|
186
219
|
await setApiKey(paths);
|
|
@@ -203,13 +236,16 @@ async function main() {
|
|
|
203
236
|
}
|
|
204
237
|
}
|
|
205
238
|
|
|
206
|
-
// ============ 选择节点 ============
|
|
207
|
-
async function selectNode(paths) {
|
|
208
|
-
|
|
239
|
+
// ============ 选择节点 (Claude/Codex) ============
|
|
240
|
+
async function selectNode(paths, type) {
|
|
241
|
+
const typeLabel = type === 'claude' ? 'Claude' : 'Codex';
|
|
242
|
+
const models = type === 'claude' ? CLAUDE_MODELS : CODEX_MODELS;
|
|
243
|
+
const apiConfig = API_CONFIG[type];
|
|
244
|
+
|
|
245
|
+
console.log(chalk.cyan(`📡 ${typeLabel} 节点测速中...\n`));
|
|
209
246
|
|
|
210
247
|
const results = await testAllEndpoints();
|
|
211
248
|
|
|
212
|
-
// 按延迟排序
|
|
213
249
|
const sorted = results
|
|
214
250
|
.filter(r => r.success)
|
|
215
251
|
.sort((a, b) => a.latency - b.latency);
|
|
@@ -239,19 +275,18 @@ async function selectNode(paths) {
|
|
|
239
275
|
const primaryIndex = selectedIndex === -1 ? 0 : selectedIndex;
|
|
240
276
|
const selectedEndpoint = sorted[primaryIndex];
|
|
241
277
|
|
|
242
|
-
//
|
|
243
|
-
|
|
278
|
+
// 选择模型
|
|
279
|
+
const { selectedModel } = await inquirer.prompt([{
|
|
280
|
+
type: 'list',
|
|
281
|
+
name: 'selectedModel',
|
|
282
|
+
message: `选择 ${typeLabel} 模型:`,
|
|
283
|
+
choices: models.map(m => ({ name: m.name, value: m.id }))
|
|
284
|
+
}]);
|
|
244
285
|
|
|
245
|
-
|
|
246
|
-
const currentModelId = config.agents?.defaults?.model?.primary?.split('/')[1] || MODELS[0].id;
|
|
247
|
-
const modelConfig = MODELS.find(m => m.id === currentModelId) || MODELS[0];
|
|
286
|
+
const modelConfig = models.find(m => m.id === selectedModel);
|
|
248
287
|
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
Object.keys(config.models.providers).forEach(key => {
|
|
252
|
-
if (key.startsWith('yunyi-')) delete config.models.providers[key];
|
|
253
|
-
});
|
|
254
|
-
}
|
|
288
|
+
// 读取或创建配置
|
|
289
|
+
let config = readConfig(paths.openclawConfig) || {};
|
|
255
290
|
|
|
256
291
|
// 初始化结构
|
|
257
292
|
if (!config.models) config.models = {};
|
|
@@ -261,78 +296,93 @@ async function selectNode(paths) {
|
|
|
261
296
|
if (!config.agents.defaults.model) config.agents.defaults.model = {};
|
|
262
297
|
if (!config.agents.defaults.models) config.agents.defaults.models = {};
|
|
263
298
|
|
|
264
|
-
//
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
299
|
+
// 保留旧的 API Key
|
|
300
|
+
const oldProvider = config.models.providers[apiConfig.providerName];
|
|
301
|
+
const oldApiKey = oldProvider?.apiKey;
|
|
302
|
+
|
|
303
|
+
// 添加/更新节点
|
|
304
|
+
config.models.providers[apiConfig.providerName] = {
|
|
305
|
+
baseUrl: buildFullUrl(selectedEndpoint.url, type),
|
|
306
|
+
api: apiConfig.api,
|
|
307
|
+
apiKey: oldApiKey,
|
|
269
308
|
models: [{
|
|
270
309
|
id: modelConfig.id,
|
|
271
310
|
name: modelConfig.name,
|
|
272
|
-
contextWindow:
|
|
273
|
-
maxTokens:
|
|
311
|
+
contextWindow: apiConfig.contextWindow,
|
|
312
|
+
maxTokens: apiConfig.maxTokens
|
|
274
313
|
}]
|
|
275
314
|
};
|
|
276
315
|
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
config.agents.defaults.
|
|
280
|
-
config.agents.defaults.models = {
|
|
281
|
-
[`${providerName}/${modelConfig.id}`]: { alias: providerName }
|
|
282
|
-
};
|
|
316
|
+
// 注册模型
|
|
317
|
+
const modelKey = `${apiConfig.providerName}/${modelConfig.id}`;
|
|
318
|
+
config.agents.defaults.models[modelKey] = { alias: apiConfig.providerName };
|
|
283
319
|
|
|
284
320
|
writeConfig(paths.openclawConfig, config);
|
|
285
321
|
|
|
286
|
-
console.log(chalk.green(`\n✅
|
|
322
|
+
console.log(chalk.green(`\n✅ ${typeLabel} 节点配置完成!`));
|
|
287
323
|
console.log(chalk.cyan(` 节点: ${selectedEndpoint.name} (${selectedEndpoint.url})`));
|
|
288
324
|
console.log(chalk.gray(` 模型: ${modelConfig.name}`));
|
|
325
|
+
console.log(chalk.gray(` API Key: ${oldApiKey ? '已设置' : '未设置'}`));
|
|
289
326
|
}
|
|
290
327
|
|
|
291
|
-
// ============
|
|
292
|
-
async function
|
|
293
|
-
|
|
328
|
+
// ============ 激活 (Claude/Codex) ============
|
|
329
|
+
async function activate(paths, type) {
|
|
330
|
+
const typeLabel = type === 'claude' ? 'Claude' : 'Codex';
|
|
331
|
+
const apiConfig = API_CONFIG[type];
|
|
332
|
+
const models = type === 'claude' ? CLAUDE_MODELS : CODEX_MODELS;
|
|
294
333
|
|
|
295
|
-
const { selectedModel } = await inquirer.prompt([{
|
|
296
|
-
type: 'list',
|
|
297
|
-
name: 'selectedModel',
|
|
298
|
-
message: '选择模型:',
|
|
299
|
-
choices: MODELS.map(m => ({ name: m.name, value: m.id }))
|
|
300
|
-
}]);
|
|
301
|
-
|
|
302
|
-
const modelConfig = MODELS.find(m => m.id === selectedModel);
|
|
303
334
|
let config = readConfig(paths.openclawConfig);
|
|
304
335
|
|
|
305
|
-
if (!config?.models?.providers) {
|
|
306
|
-
console.log(chalk.yellow(
|
|
336
|
+
if (!config?.models?.providers?.[apiConfig.providerName]) {
|
|
337
|
+
console.log(chalk.yellow(`⚠️ 请先选择 ${typeLabel} 节点`));
|
|
307
338
|
return;
|
|
308
339
|
}
|
|
309
340
|
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}];
|
|
319
|
-
|
|
320
|
-
// 更新主模型(无备用)
|
|
321
|
-
config.agents.defaults.model.primary = `${providerName}/${modelConfig.id}`;
|
|
322
|
-
config.agents.defaults.model.fallbacks = [];
|
|
323
|
-
config.agents.defaults.models = {
|
|
324
|
-
[`${providerName}/${modelConfig.id}`]: { alias: providerName }
|
|
325
|
-
};
|
|
326
|
-
}
|
|
341
|
+
const provider = config.models.providers[apiConfig.providerName];
|
|
342
|
+
const currentModelId = provider.models?.[0]?.id || models[0].id;
|
|
343
|
+
const modelConfig = models.find(m => m.id === currentModelId) || models[0];
|
|
344
|
+
|
|
345
|
+
// 设置为主模型
|
|
346
|
+
const modelKey = `${apiConfig.providerName}/${modelConfig.id}`;
|
|
347
|
+
config.agents.defaults.model.primary = modelKey;
|
|
348
|
+
config.agents.defaults.model.fallbacks = [];
|
|
327
349
|
|
|
328
350
|
writeConfig(paths.openclawConfig, config);
|
|
329
|
-
|
|
351
|
+
|
|
352
|
+
console.log(chalk.green(`✅ 已激活 ${typeLabel}`));
|
|
353
|
+
console.log(chalk.cyan(` 节点: ${provider.baseUrl}`));
|
|
354
|
+
console.log(chalk.gray(` 模型: ${modelConfig.name}`));
|
|
330
355
|
}
|
|
331
356
|
|
|
332
357
|
// ============ 设置 API Key ============
|
|
333
358
|
async function setApiKey(paths) {
|
|
334
359
|
console.log(chalk.cyan('🔑 设置 API Key\n'));
|
|
335
360
|
|
|
361
|
+
let config = readConfig(paths.openclawConfig);
|
|
362
|
+
|
|
363
|
+
// 检查已配置的节点
|
|
364
|
+
const claudeConfigured = config?.models?.providers?.['yunyi-claude'];
|
|
365
|
+
const codexConfigured = config?.models?.providers?.['yunyi-codex'];
|
|
366
|
+
|
|
367
|
+
if (!claudeConfigured && !codexConfigured) {
|
|
368
|
+
console.log(chalk.yellow('⚠️ 请先选择节点'));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const choices = [];
|
|
373
|
+
if (claudeConfigured) choices.push({ name: '🔵 Claude', value: 'claude' });
|
|
374
|
+
if (codexConfigured) choices.push({ name: '🟢 Codex', value: 'codex' });
|
|
375
|
+
if (claudeConfigured && codexConfigured) {
|
|
376
|
+
choices.push({ name: '🔷 两者都设置 (相同 Key)', value: 'both' });
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const { target } = await inquirer.prompt([{
|
|
380
|
+
type: 'list',
|
|
381
|
+
name: 'target',
|
|
382
|
+
message: '为哪个设置 API Key?',
|
|
383
|
+
choices
|
|
384
|
+
}]);
|
|
385
|
+
|
|
336
386
|
const { apiKey } = await inquirer.prompt([{
|
|
337
387
|
type: 'password',
|
|
338
388
|
name: 'apiKey',
|
|
@@ -341,18 +391,15 @@ async function setApiKey(paths) {
|
|
|
341
391
|
validate: input => input.trim() !== '' || 'API Key 不能为空'
|
|
342
392
|
}]);
|
|
343
393
|
|
|
344
|
-
|
|
394
|
+
const providers = [];
|
|
395
|
+
if (target === 'claude' || target === 'both') providers.push('yunyi-claude');
|
|
396
|
+
if (target === 'codex' || target === 'both') providers.push('yunyi-codex');
|
|
345
397
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
// 为 yunyi 节点设置 API Key
|
|
352
|
-
const providerName = Object.keys(config.models.providers).find(k => k.startsWith('yunyi-'));
|
|
353
|
-
if (providerName) {
|
|
354
|
-
config.models.providers[providerName].apiKey = apiKey.trim();
|
|
355
|
-
}
|
|
398
|
+
providers.forEach(providerName => {
|
|
399
|
+
if (config.models.providers[providerName]) {
|
|
400
|
+
config.models.providers[providerName].apiKey = apiKey.trim();
|
|
401
|
+
}
|
|
402
|
+
});
|
|
356
403
|
|
|
357
404
|
writeConfig(paths.openclawConfig, config);
|
|
358
405
|
|
|
@@ -367,9 +414,9 @@ async function setApiKey(paths) {
|
|
|
367
414
|
try { authProfiles = JSON.parse(fs.readFileSync(paths.authProfiles, 'utf8')); } catch {}
|
|
368
415
|
}
|
|
369
416
|
|
|
370
|
-
|
|
417
|
+
providers.forEach(providerName => {
|
|
371
418
|
authProfiles[`${providerName}:default`] = { apiKey: apiKey.trim() };
|
|
372
|
-
}
|
|
419
|
+
});
|
|
373
420
|
|
|
374
421
|
fs.writeFileSync(paths.authProfiles, JSON.stringify(authProfiles, null, 2), 'utf8');
|
|
375
422
|
|
|
@@ -387,23 +434,46 @@ async function viewConfig(paths) {
|
|
|
387
434
|
return;
|
|
388
435
|
}
|
|
389
436
|
|
|
390
|
-
//
|
|
437
|
+
// 当前激活
|
|
391
438
|
const primary = config.agents?.defaults?.model?.primary || '未设置';
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
439
|
+
const isClaudeActive = primary.startsWith('yunyi-claude');
|
|
440
|
+
const isCodexActive = primary.startsWith('yunyi-codex');
|
|
441
|
+
|
|
442
|
+
console.log(chalk.yellow('当前激活:'));
|
|
443
|
+
if (isClaudeActive) {
|
|
444
|
+
console.log(chalk.blue(` 🔵 Claude: ${primary}`));
|
|
445
|
+
} else if (isCodexActive) {
|
|
446
|
+
console.log(chalk.green(` 🟢 Codex: ${primary}`));
|
|
447
|
+
} else {
|
|
448
|
+
console.log(` ${primary}`);
|
|
449
|
+
}
|
|
450
|
+
console.log('');
|
|
451
|
+
|
|
452
|
+
// Claude 节点
|
|
453
|
+
console.log(chalk.yellow('Claude 节点:'));
|
|
454
|
+
const claudeProvider = config.models?.providers?.['yunyi-claude'];
|
|
455
|
+
if (claudeProvider) {
|
|
456
|
+
const hasKey = claudeProvider.apiKey ? chalk.green('✓') : chalk.red('✗');
|
|
457
|
+
const model = claudeProvider.models?.[0]?.name || 'N/A';
|
|
458
|
+
console.log(` URL: ${claudeProvider.baseUrl}`);
|
|
459
|
+
console.log(` 模型: ${model}`);
|
|
460
|
+
console.log(` API Key: ${hasKey}`);
|
|
461
|
+
} else {
|
|
462
|
+
console.log(chalk.gray(' 未配置'));
|
|
463
|
+
}
|
|
464
|
+
console.log('');
|
|
465
|
+
|
|
466
|
+
// Codex 节点
|
|
467
|
+
console.log(chalk.yellow('Codex 节点:'));
|
|
468
|
+
const codexProvider = config.models?.providers?.['yunyi-codex'];
|
|
469
|
+
if (codexProvider) {
|
|
470
|
+
const hasKey = codexProvider.apiKey ? chalk.green('✓') : chalk.red('✗');
|
|
471
|
+
const model = codexProvider.models?.[0]?.name || 'N/A';
|
|
472
|
+
console.log(` URL: ${codexProvider.baseUrl}`);
|
|
473
|
+
console.log(` 模型: ${model}`);
|
|
474
|
+
console.log(` API Key: ${hasKey}`);
|
|
475
|
+
} else {
|
|
476
|
+
console.log(chalk.gray(' 未配置'));
|
|
407
477
|
}
|
|
408
478
|
console.log('');
|
|
409
479
|
|