openclawapi 1.3.4 → 1.3.6

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 +198 -0
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -189,6 +189,7 @@ async function main() {
189
189
  { name: '🟢 选择 Codex 节点', value: 'select_codex' },
190
190
  { name: '⚡ 激活 Claude', value: 'activate_claude' },
191
191
  { name: '⚡ 激活 Codex', value: 'activate_codex' },
192
+ { name: '🧪 测试连接', value: 'test_connection' },
192
193
  { name: '📋 查看当前配置', value: 'view_config' },
193
194
  { name: '🔄 恢复默认配置', value: 'restore' },
194
195
  { name: '❌ 退出', value: 'exit' }
@@ -211,6 +212,9 @@ async function main() {
211
212
  case 'activate_codex':
212
213
  await activate(paths, 'codex');
213
214
  break;
215
+ case 'test_connection':
216
+ await testConnection(paths);
217
+ break;
214
218
  case 'view_config':
215
219
  await viewConfig(paths);
216
220
  break;
@@ -375,6 +379,200 @@ async function activate(paths, type) {
375
379
  console.log(chalk.gray(` API Key: 已设置`));
376
380
  }
377
381
 
382
+ // ============ 测试连接 ============
383
+ async function testConnection(paths) {
384
+ console.log(chalk.cyan('🧪 测试 API 连接\n'));
385
+
386
+ const config = readConfig(paths.openclawConfig);
387
+
388
+ if (!config) {
389
+ console.log(chalk.yellow('配置文件不存在,请先选择节点'));
390
+ return;
391
+ }
392
+
393
+ // 检查当前激活的是哪个
394
+ const primary = config.agents?.defaults?.model?.primary || '';
395
+ const isClaudeActive = primary.startsWith('yunyi-claude');
396
+ const isCodexActive = primary.startsWith('yunyi-codex');
397
+
398
+ if (!isClaudeActive && !isCodexActive) {
399
+ console.log(chalk.yellow('⚠️ 请先激活 Claude 或 Codex'));
400
+ return;
401
+ }
402
+
403
+ const type = isClaudeActive ? 'claude' : 'codex';
404
+ const typeLabel = isClaudeActive ? 'Claude' : 'Codex';
405
+ const apiConfig = API_CONFIG[type];
406
+ const provider = config.models?.providers?.[apiConfig.providerName];
407
+
408
+ if (!provider) {
409
+ console.log(chalk.yellow(`⚠️ ${typeLabel} 节点未配置`));
410
+ return;
411
+ }
412
+
413
+ if (!provider.apiKey) {
414
+ console.log(chalk.yellow(`⚠️ ${typeLabel} API Key 未设置`));
415
+ return;
416
+ }
417
+
418
+ console.log(chalk.gray(`测试 ${typeLabel} API...`));
419
+ console.log(chalk.gray(`节点: ${provider.baseUrl}`));
420
+ console.log(chalk.gray(`模型: ${provider.models?.[0]?.id || 'N/A'}\n`));
421
+
422
+ try {
423
+ const startTime = Date.now();
424
+
425
+ if (type === 'claude') {
426
+ // Claude API 测试
427
+ const result = await testClaudeApi(provider.baseUrl, provider.apiKey, provider.models?.[0]?.id);
428
+ const latency = Date.now() - startTime;
429
+
430
+ if (result.success) {
431
+ console.log(chalk.green(`✅ Claude API 连接成功!`));
432
+ console.log(chalk.cyan(` 响应时间: ${latency}ms`));
433
+ console.log(chalk.gray(` 回复: ${result.message}`));
434
+ } else {
435
+ console.log(chalk.red(`❌ Claude API 连接失败`));
436
+ console.log(chalk.red(` 错误: ${result.error}`));
437
+ }
438
+ } else {
439
+ // Codex API 测试
440
+ const result = await testCodexApi(provider.baseUrl, provider.apiKey, provider.models?.[0]?.id);
441
+ const latency = Date.now() - startTime;
442
+
443
+ if (result.success) {
444
+ console.log(chalk.green(`✅ Codex API 连接成功!`));
445
+ console.log(chalk.cyan(` 响应时间: ${latency}ms`));
446
+ console.log(chalk.gray(` 回复: ${result.message}`));
447
+ } else {
448
+ console.log(chalk.red(`❌ Codex API 连接失败`));
449
+ console.log(chalk.red(` 错误: ${result.error}`));
450
+ }
451
+ }
452
+ } catch (error) {
453
+ console.log(chalk.red(`❌ 测试失败: ${error.message}`));
454
+ }
455
+ }
456
+
457
+ // Claude API 测试
458
+ function testClaudeApi(baseUrl, apiKey, model) {
459
+ return new Promise((resolve) => {
460
+ const urlObj = new URL(baseUrl);
461
+ const protocol = urlObj.protocol === 'https:' ? https : http;
462
+
463
+ const postData = JSON.stringify({
464
+ model: model || 'claude-sonnet-4-5',
465
+ max_tokens: 100,
466
+ messages: [{ role: 'user', content: '你是什么模型?请简短回答。' }]
467
+ });
468
+
469
+ const options = {
470
+ hostname: urlObj.hostname,
471
+ port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
472
+ path: urlObj.pathname,
473
+ method: 'POST',
474
+ timeout: 30000,
475
+ rejectUnauthorized: false,
476
+ headers: {
477
+ 'Content-Type': 'application/json',
478
+ 'x-api-key': apiKey,
479
+ 'anthropic-version': '2023-06-01',
480
+ 'Content-Length': Buffer.byteLength(postData)
481
+ }
482
+ };
483
+
484
+ const req = protocol.request(options, (res) => {
485
+ let data = '';
486
+ res.on('data', chunk => data += chunk);
487
+ res.on('end', () => {
488
+ try {
489
+ const json = JSON.parse(data);
490
+ if (json.content && json.content[0]) {
491
+ resolve({ success: true, message: json.content[0].text?.substring(0, 100) || 'OK' });
492
+ } else if (json.error) {
493
+ resolve({ success: false, error: json.error.message || JSON.stringify(json.error) });
494
+ } else {
495
+ resolve({ success: false, error: `HTTP ${res.statusCode}: ${data.substring(0, 200)}` });
496
+ }
497
+ } catch {
498
+ resolve({ success: false, error: `HTTP ${res.statusCode}: ${data.substring(0, 200)}` });
499
+ }
500
+ });
501
+ });
502
+
503
+ req.on('timeout', () => {
504
+ req.destroy();
505
+ resolve({ success: false, error: '请求超时 (30s)' });
506
+ });
507
+
508
+ req.on('error', (e) => {
509
+ resolve({ success: false, error: e.message });
510
+ });
511
+
512
+ req.write(postData);
513
+ req.end();
514
+ });
515
+ }
516
+
517
+ // Codex API 测试
518
+ function testCodexApi(baseUrl, apiKey, model) {
519
+ return new Promise((resolve) => {
520
+ const urlObj = new URL(baseUrl);
521
+ const protocol = urlObj.protocol === 'https:' ? https : http;
522
+
523
+ const postData = JSON.stringify({
524
+ model: model || 'gpt-5.2',
525
+ input: '你是什么模型?请简短回答。'
526
+ });
527
+
528
+ const options = {
529
+ hostname: urlObj.hostname,
530
+ port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
531
+ path: urlObj.pathname,
532
+ method: 'POST',
533
+ timeout: 30000,
534
+ rejectUnauthorized: false,
535
+ headers: {
536
+ 'Content-Type': 'application/json',
537
+ 'Authorization': `Bearer ${apiKey}`,
538
+ 'Content-Length': Buffer.byteLength(postData)
539
+ }
540
+ };
541
+
542
+ const req = protocol.request(options, (res) => {
543
+ let data = '';
544
+ res.on('data', chunk => data += chunk);
545
+ res.on('end', () => {
546
+ try {
547
+ const json = JSON.parse(data);
548
+ if (json.output && json.output[0]) {
549
+ const text = json.output[0].content?.[0]?.text || json.output[0].text || 'OK';
550
+ resolve({ success: true, message: text.substring(0, 100) });
551
+ } else if (json.error) {
552
+ resolve({ success: false, error: json.error.message || JSON.stringify(json.error) });
553
+ } else {
554
+ resolve({ success: false, error: `HTTP ${res.statusCode}: ${data.substring(0, 200)}` });
555
+ }
556
+ } catch {
557
+ resolve({ success: false, error: `HTTP ${res.statusCode}: ${data.substring(0, 200)}` });
558
+ }
559
+ });
560
+ });
561
+
562
+ req.on('timeout', () => {
563
+ req.destroy();
564
+ resolve({ success: false, error: '请求超时 (30s)' });
565
+ });
566
+
567
+ req.on('error', (e) => {
568
+ resolve({ success: false, error: e.message });
569
+ });
570
+
571
+ req.write(postData);
572
+ req.end();
573
+ });
574
+ }
575
+
378
576
  // ============ 查看配置 ============
379
577
  async function viewConfig(paths) {
380
578
  console.log(chalk.cyan('📋 当前配置\n'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclawapi",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "cli.js",
6
6
  "bin": {