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.
Files changed (2) hide show
  1. package/cli.js +173 -103
  2. 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 MODELS = [
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
- if (trimmed.includes('/claude/v1/messages')) return trimmed;
142
- if (trimmed.includes('/claude')) return trimmed + '/v1/messages';
143
- return trimmed + '/claude/v1/messages';
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 配置工具 (简化版)\n'));
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: '🌐 选择节点', value: 'select_node' },
166
- { name: '🤖 选择默认模型', value: 'select_model' },
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 'select_node':
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 'select_model':
183
- await selectModel(paths);
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
- console.log(chalk.cyan('📡 节点测速中...\n'));
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
- let config = readConfig(paths.openclawConfig) || {};
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
- // 清除旧的 yunyi 配置
250
- if (config.models?.providers) {
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 providerName = 'yunyi-001';
266
- config.models.providers[providerName] = {
267
- baseUrl: buildFullUrl(selectedEndpoint.url),
268
- api: 'anthropic-messages',
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: 200000,
273
- maxTokens: 8192
311
+ contextWindow: apiConfig.contextWindow,
312
+ maxTokens: apiConfig.maxTokens
274
313
  }]
275
314
  };
276
315
 
277
- // 设置主模型(无备用)
278
- config.agents.defaults.model.primary = `${providerName}/${modelConfig.id}`;
279
- config.agents.defaults.model.fallbacks = [];
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 selectModel(paths) {
293
- console.log(chalk.cyan('🤖 选择默认模型\n'));
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
- // 更新 yunyi 节点的模型
311
- const providerName = Object.keys(config.models.providers).find(k => k.startsWith('yunyi-'));
312
- if (providerName) {
313
- config.models.providers[providerName].models = [{
314
- id: modelConfig.id,
315
- name: modelConfig.name,
316
- contextWindow: 200000,
317
- maxTokens: 8192
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
- console.log(chalk.green(`\n✅ 模型已切换为: ${modelConfig.name}`));
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
- let config = readConfig(paths.openclawConfig);
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
- if (!config?.models?.providers) {
347
- console.log(chalk.yellow('⚠️ 请先选择节点'));
348
- return;
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
- if (providerName) {
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
- console.log(chalk.yellow('当前模型:'));
393
- console.log(` ${primary}\n`);
394
-
395
- // 节点信息
396
- console.log(chalk.yellow('当前节点:'));
397
- if (config.models?.providers) {
398
- const provider = Object.entries(config.models.providers).find(([name]) => name.startsWith('yunyi-'));
399
- if (provider) {
400
- const [name, data] = provider;
401
- const hasKey = data.apiKey ? chalk.green('✓ 已设置') : chalk.red('✗ 未设置');
402
- console.log(` ${name}: ${data.baseUrl}`);
403
- console.log(` API Key: ${hasKey}`);
404
- } else {
405
- console.log(' 未配置');
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclawapi",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "cli.js",
6
6
  "bin": {