openclawapi 1.2.0 → 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 -137
- 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');
|
|
181
211
|
break;
|
|
182
|
-
case '
|
|
183
|
-
await
|
|
212
|
+
case 'activate_claude':
|
|
213
|
+
await activate(paths, 'claude');
|
|
214
|
+
break;
|
|
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);
|
|
@@ -221,11 +257,11 @@ async function selectNode(paths) {
|
|
|
221
257
|
|
|
222
258
|
console.log(chalk.green(`\n🏆 最快节点: ${sorted[0].name} (${sorted[0].latency}ms)\n`));
|
|
223
259
|
|
|
224
|
-
//
|
|
260
|
+
// 选择节点
|
|
225
261
|
const { selectedIndex } = await inquirer.prompt([{
|
|
226
262
|
type: 'list',
|
|
227
263
|
name: 'selectedIndex',
|
|
228
|
-
message: '
|
|
264
|
+
message: '选择节点:',
|
|
229
265
|
choices: [
|
|
230
266
|
{ name: `🚀 使用最快节点 (${sorted[0].name})`, value: -1 },
|
|
231
267
|
new inquirer.Separator('--- 或手动选择 ---'),
|
|
@@ -237,22 +273,20 @@ async function selectNode(paths) {
|
|
|
237
273
|
}]);
|
|
238
274
|
|
|
239
275
|
const primaryIndex = selectedIndex === -1 ? 0 : selectedIndex;
|
|
240
|
-
const
|
|
241
|
-
const fallbackEndpoints = sorted.filter((_, i) => i !== primaryIndex);
|
|
276
|
+
const selectedEndpoint = sorted[primaryIndex];
|
|
242
277
|
|
|
243
|
-
//
|
|
244
|
-
|
|
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
|
+
}]);
|
|
245
285
|
|
|
246
|
-
|
|
247
|
-
const currentModelId = config.agents?.defaults?.model?.primary?.split('/')[1] || MODELS[0].id;
|
|
248
|
-
const modelConfig = MODELS.find(m => m.id === currentModelId) || MODELS[0];
|
|
286
|
+
const modelConfig = models.find(m => m.id === selectedModel);
|
|
249
287
|
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
Object.keys(config.models.providers).forEach(key => {
|
|
253
|
-
if (key.startsWith('yunyi-')) delete config.models.providers[key];
|
|
254
|
-
});
|
|
255
|
-
}
|
|
288
|
+
// 读取或创建配置
|
|
289
|
+
let config = readConfig(paths.openclawConfig) || {};
|
|
256
290
|
|
|
257
291
|
// 初始化结构
|
|
258
292
|
if (!config.models) config.models = {};
|
|
@@ -262,101 +296,93 @@ async function selectNode(paths) {
|
|
|
262
296
|
if (!config.agents.defaults.model) config.agents.defaults.model = {};
|
|
263
297
|
if (!config.agents.defaults.models) config.agents.defaults.models = {};
|
|
264
298
|
|
|
265
|
-
//
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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,
|
|
270
308
|
models: [{
|
|
271
309
|
id: modelConfig.id,
|
|
272
310
|
name: modelConfig.name,
|
|
273
|
-
contextWindow:
|
|
274
|
-
maxTokens:
|
|
311
|
+
contextWindow: apiConfig.contextWindow,
|
|
312
|
+
maxTokens: apiConfig.maxTokens
|
|
275
313
|
}]
|
|
276
314
|
};
|
|
277
315
|
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
config.agents.defaults.models[
|
|
281
|
-
|
|
282
|
-
// 添加备用节点
|
|
283
|
-
const fallbacks = [];
|
|
284
|
-
fallbackEndpoints.forEach((endpoint, i) => {
|
|
285
|
-
const name = `yunyi-${String(i + 2).padStart(3, '0')}`;
|
|
286
|
-
config.models.providers[name] = {
|
|
287
|
-
baseUrl: buildFullUrl(endpoint.url),
|
|
288
|
-
api: 'anthropic-messages',
|
|
289
|
-
models: [{
|
|
290
|
-
id: modelConfig.id,
|
|
291
|
-
name: modelConfig.name,
|
|
292
|
-
contextWindow: 200000,
|
|
293
|
-
maxTokens: 8192
|
|
294
|
-
}]
|
|
295
|
-
};
|
|
296
|
-
fallbacks.push(`${name}/${modelConfig.id}`);
|
|
297
|
-
config.agents.defaults.models[`${name}/${modelConfig.id}`] = { alias: name };
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
config.agents.defaults.model.fallbacks = fallbacks;
|
|
316
|
+
// 注册模型
|
|
317
|
+
const modelKey = `${apiConfig.providerName}/${modelConfig.id}`;
|
|
318
|
+
config.agents.defaults.models[modelKey] = { alias: apiConfig.providerName };
|
|
301
319
|
|
|
302
320
|
writeConfig(paths.openclawConfig, config);
|
|
303
321
|
|
|
304
|
-
console.log(chalk.green(`\n✅
|
|
305
|
-
console.log(chalk.cyan(`
|
|
306
|
-
console.log(chalk.gray(`
|
|
307
|
-
console.log(chalk.gray(`
|
|
322
|
+
console.log(chalk.green(`\n✅ ${typeLabel} 节点配置完成!`));
|
|
323
|
+
console.log(chalk.cyan(` 节点: ${selectedEndpoint.name} (${selectedEndpoint.url})`));
|
|
324
|
+
console.log(chalk.gray(` 模型: ${modelConfig.name}`));
|
|
325
|
+
console.log(chalk.gray(` API Key: ${oldApiKey ? '已设置' : '未设置'}`));
|
|
308
326
|
}
|
|
309
327
|
|
|
310
|
-
// ============
|
|
311
|
-
async function
|
|
312
|
-
|
|
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;
|
|
313
333
|
|
|
314
|
-
const { selectedModel } = await inquirer.prompt([{
|
|
315
|
-
type: 'list',
|
|
316
|
-
name: 'selectedModel',
|
|
317
|
-
message: '选择模型:',
|
|
318
|
-
choices: MODELS.map(m => ({ name: m.name, value: m.id }))
|
|
319
|
-
}]);
|
|
320
|
-
|
|
321
|
-
const modelConfig = MODELS.find(m => m.id === selectedModel);
|
|
322
334
|
let config = readConfig(paths.openclawConfig);
|
|
323
335
|
|
|
324
|
-
if (!config?.models?.providers) {
|
|
325
|
-
console.log(chalk.yellow(
|
|
336
|
+
if (!config?.models?.providers?.[apiConfig.providerName]) {
|
|
337
|
+
console.log(chalk.yellow(`⚠️ 请先选择 ${typeLabel} 节点`));
|
|
326
338
|
return;
|
|
327
339
|
}
|
|
328
340
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
config.models.providers[name].models = [{
|
|
333
|
-
id: modelConfig.id,
|
|
334
|
-
name: modelConfig.name,
|
|
335
|
-
contextWindow: 200000,
|
|
336
|
-
maxTokens: 8192
|
|
337
|
-
}];
|
|
338
|
-
}
|
|
339
|
-
});
|
|
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];
|
|
340
344
|
|
|
341
|
-
//
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
config.agents.defaults.model.fallbacks = providers.slice(1).map(p => `${p}/${modelConfig.id}`);
|
|
346
|
-
config.agents.defaults.models = {};
|
|
347
|
-
providers.forEach(p => {
|
|
348
|
-
config.agents.defaults.models[`${p}/${modelConfig.id}`] = { alias: p };
|
|
349
|
-
});
|
|
350
|
-
}
|
|
345
|
+
// 设置为主模型
|
|
346
|
+
const modelKey = `${apiConfig.providerName}/${modelConfig.id}`;
|
|
347
|
+
config.agents.defaults.model.primary = modelKey;
|
|
348
|
+
config.agents.defaults.model.fallbacks = [];
|
|
351
349
|
|
|
352
350
|
writeConfig(paths.openclawConfig, config);
|
|
353
|
-
|
|
351
|
+
|
|
352
|
+
console.log(chalk.green(`✅ 已激活 ${typeLabel}`));
|
|
353
|
+
console.log(chalk.cyan(` 节点: ${provider.baseUrl}`));
|
|
354
|
+
console.log(chalk.gray(` 模型: ${modelConfig.name}`));
|
|
354
355
|
}
|
|
355
356
|
|
|
356
357
|
// ============ 设置 API Key ============
|
|
357
358
|
async function setApiKey(paths) {
|
|
358
359
|
console.log(chalk.cyan('🔑 设置 API Key\n'));
|
|
359
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
|
+
|
|
360
386
|
const { apiKey } = await inquirer.prompt([{
|
|
361
387
|
type: 'password',
|
|
362
388
|
name: 'apiKey',
|
|
@@ -365,17 +391,13 @@ async function setApiKey(paths) {
|
|
|
365
391
|
validate: input => input.trim() !== '' || 'API Key 不能为空'
|
|
366
392
|
}]);
|
|
367
393
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (
|
|
371
|
-
console.log(chalk.yellow('⚠️ 请先选择节点'));
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
394
|
+
const providers = [];
|
|
395
|
+
if (target === 'claude' || target === 'both') providers.push('yunyi-claude');
|
|
396
|
+
if (target === 'codex' || target === 'both') providers.push('yunyi-codex');
|
|
374
397
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
config.models.providers[name].apiKey = apiKey.trim();
|
|
398
|
+
providers.forEach(providerName => {
|
|
399
|
+
if (config.models.providers[providerName]) {
|
|
400
|
+
config.models.providers[providerName].apiKey = apiKey.trim();
|
|
379
401
|
}
|
|
380
402
|
});
|
|
381
403
|
|
|
@@ -392,10 +414,8 @@ async function setApiKey(paths) {
|
|
|
392
414
|
try { authProfiles = JSON.parse(fs.readFileSync(paths.authProfiles, 'utf8')); } catch {}
|
|
393
415
|
}
|
|
394
416
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
authProfiles[`${name}:default`] = { apiKey: apiKey.trim() };
|
|
398
|
-
}
|
|
417
|
+
providers.forEach(providerName => {
|
|
418
|
+
authProfiles[`${providerName}:default`] = { apiKey: apiKey.trim() };
|
|
399
419
|
});
|
|
400
420
|
|
|
401
421
|
fs.writeFileSync(paths.authProfiles, JSON.stringify(authProfiles, null, 2), 'utf8');
|
|
@@ -414,30 +434,46 @@ async function viewConfig(paths) {
|
|
|
414
434
|
return;
|
|
415
435
|
}
|
|
416
436
|
|
|
417
|
-
//
|
|
437
|
+
// 当前激活
|
|
418
438
|
const primary = config.agents?.defaults?.model?.primary || '未设置';
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
+
}
|
|
427
450
|
console.log('');
|
|
428
451
|
|
|
429
|
-
//
|
|
430
|
-
console.log(chalk.yellow('
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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(' 未配置'));
|
|
441
477
|
}
|
|
442
478
|
console.log('');
|
|
443
479
|
|