openclawapi 1.2.1 → 1.3.1

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 +135 -139
  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,12 +185,14 @@ async function main() {
162
185
  name: 'action',
163
186
  message: '请选择操作:',
164
187
  choices: [
165
- { name: '🌐 选择节点', value: 'select_node' },
166
- { name: '🤖 选择默认模型', value: 'select_model' },
167
- { name: '🔑 设置 API Key', value: 'set_apikey' },
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(),
168
194
  { name: '📋 查看当前配置', value: 'view_config' },
169
195
  { name: '🔄 恢复默认配置', value: 'restore' },
170
- new inquirer.Separator(),
171
196
  { name: '❌ 退出', value: 'exit' }
172
197
  ]
173
198
  }]);
@@ -176,14 +201,17 @@ async function main() {
176
201
 
177
202
  try {
178
203
  switch (action) {
179
- case 'select_node':
180
- await selectNode(paths);
204
+ case 'select_claude':
205
+ await selectNode(paths, 'claude');
181
206
  break;
182
- case 'select_model':
183
- await selectModel(paths);
207
+ case 'select_codex':
208
+ await selectNode(paths, 'codex');
184
209
  break;
185
- case 'set_apikey':
186
- await setApiKey(paths);
210
+ case 'activate_claude':
211
+ await activate(paths, 'claude');
212
+ break;
213
+ case 'activate_codex':
214
+ await activate(paths, 'codex');
187
215
  break;
188
216
  case 'view_config':
189
217
  await viewConfig(paths);
@@ -203,13 +231,16 @@ async function main() {
203
231
  }
204
232
  }
205
233
 
206
- // ============ 选择节点 ============
207
- async function selectNode(paths) {
208
- console.log(chalk.cyan('📡 节点测速中...\n'));
234
+ // ============ 选择节点 (Claude/Codex) ============
235
+ async function selectNode(paths, type) {
236
+ const typeLabel = type === 'claude' ? 'Claude' : 'Codex';
237
+ const models = type === 'claude' ? CLAUDE_MODELS : CODEX_MODELS;
238
+ const apiConfig = API_CONFIG[type];
239
+
240
+ console.log(chalk.cyan(`📡 ${typeLabel} 节点测速中...\n`));
209
241
 
210
242
  const results = await testAllEndpoints();
211
243
 
212
- // 按延迟排序
213
244
  const sorted = results
214
245
  .filter(r => r.success)
215
246
  .sort((a, b) => a.latency - b.latency);
@@ -239,19 +270,18 @@ async function selectNode(paths) {
239
270
  const primaryIndex = selectedIndex === -1 ? 0 : selectedIndex;
240
271
  const selectedEndpoint = sorted[primaryIndex];
241
272
 
242
- // 读取或创建配置
243
- let config = readConfig(paths.openclawConfig) || {};
273
+ // 选择模型
274
+ const { selectedModel } = await inquirer.prompt([{
275
+ type: 'list',
276
+ name: 'selectedModel',
277
+ message: `选择 ${typeLabel} 模型:`,
278
+ choices: models.map(m => ({ name: m.name, value: m.id }))
279
+ }]);
244
280
 
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];
281
+ const modelConfig = models.find(m => m.id === selectedModel);
248
282
 
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
- }
283
+ // 读取或创建配置
284
+ let config = readConfig(paths.openclawConfig) || {};
255
285
 
256
286
  // 初始化结构
257
287
  if (!config.models) config.models = {};
@@ -261,119 +291,62 @@ async function selectNode(paths) {
261
291
  if (!config.agents.defaults.model) config.agents.defaults.model = {};
262
292
  if (!config.agents.defaults.models) config.agents.defaults.models = {};
263
293
 
264
- // 添加选中的节点
265
- const providerName = 'yunyi-001';
266
- config.models.providers[providerName] = {
267
- baseUrl: buildFullUrl(selectedEndpoint.url),
268
- api: 'anthropic-messages',
294
+ // 保留旧的 API Key
295
+ const oldProvider = config.models.providers[apiConfig.providerName];
296
+ const oldApiKey = oldProvider?.apiKey;
297
+
298
+ // 添加/更新节点
299
+ config.models.providers[apiConfig.providerName] = {
300
+ baseUrl: buildFullUrl(selectedEndpoint.url, type),
301
+ api: apiConfig.api,
302
+ apiKey: oldApiKey,
269
303
  models: [{
270
304
  id: modelConfig.id,
271
305
  name: modelConfig.name,
272
- contextWindow: 200000,
273
- maxTokens: 8192
306
+ contextWindow: apiConfig.contextWindow,
307
+ maxTokens: apiConfig.maxTokens
274
308
  }]
275
309
  };
276
310
 
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
- };
311
+ // 注册模型
312
+ const modelKey = `${apiConfig.providerName}/${modelConfig.id}`;
313
+ config.agents.defaults.models[modelKey] = { alias: apiConfig.providerName };
283
314
 
284
315
  writeConfig(paths.openclawConfig, config);
285
316
 
286
- console.log(chalk.green(`\n✅ 配置完成!`));
317
+ console.log(chalk.green(`\n✅ ${typeLabel} 节点配置完成!`));
287
318
  console.log(chalk.cyan(` 节点: ${selectedEndpoint.name} (${selectedEndpoint.url})`));
288
319
  console.log(chalk.gray(` 模型: ${modelConfig.name}`));
320
+ console.log(chalk.gray(` API Key: ${oldApiKey ? '已设置' : '未设置'}`));
289
321
  }
290
322
 
291
- // ============ 选择模型 ============
292
- async function selectModel(paths) {
293
- console.log(chalk.cyan('🤖 选择默认模型\n'));
323
+ // ============ 激活 (Claude/Codex) ============
324
+ async function activate(paths, type) {
325
+ const typeLabel = type === 'claude' ? 'Claude' : 'Codex';
326
+ const apiConfig = API_CONFIG[type];
327
+ const models = type === 'claude' ? CLAUDE_MODELS : CODEX_MODELS;
294
328
 
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
329
  let config = readConfig(paths.openclawConfig);
304
330
 
305
- if (!config?.models?.providers) {
306
- console.log(chalk.yellow('⚠️ 请先选择节点'));
331
+ if (!config?.models?.providers?.[apiConfig.providerName]) {
332
+ console.log(chalk.yellow(`⚠️ 请先选择 ${typeLabel} 节点`));
307
333
  return;
308
334
  }
309
335
 
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
- }
327
-
328
- writeConfig(paths.openclawConfig, config);
329
- console.log(chalk.green(`\n✅ 模型已切换为: ${modelConfig.name}`));
330
- }
331
-
332
- // ============ 设置 API Key ============
333
- async function setApiKey(paths) {
334
- console.log(chalk.cyan('🔑 设置 API Key\n'));
335
-
336
- const { apiKey } = await inquirer.prompt([{
337
- type: 'password',
338
- name: 'apiKey',
339
- message: '请输入 API Key:',
340
- mask: '*',
341
- validate: input => input.trim() !== '' || 'API Key 不能为空'
342
- }]);
343
-
344
- let config = readConfig(paths.openclawConfig);
345
-
346
- if (!config?.models?.providers) {
347
- console.log(chalk.yellow('⚠️ 请先选择节点'));
348
- return;
349
- }
336
+ const provider = config.models.providers[apiConfig.providerName];
337
+ const currentModelId = provider.models?.[0]?.id || models[0].id;
338
+ const modelConfig = models.find(m => m.id === currentModelId) || models[0];
350
339
 
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
- }
340
+ // 设置为主模型
341
+ const modelKey = `${apiConfig.providerName}/${modelConfig.id}`;
342
+ config.agents.defaults.model.primary = modelKey;
343
+ config.agents.defaults.model.fallbacks = [];
356
344
 
357
345
  writeConfig(paths.openclawConfig, config);
358
346
 
359
- // 同时写入 auth-profiles
360
- const authDir = path.dirname(paths.authProfiles);
361
- if (!fs.existsSync(authDir)) {
362
- fs.mkdirSync(authDir, { recursive: true });
363
- }
364
-
365
- let authProfiles = {};
366
- if (fs.existsSync(paths.authProfiles)) {
367
- try { authProfiles = JSON.parse(fs.readFileSync(paths.authProfiles, 'utf8')); } catch {}
368
- }
369
-
370
- if (providerName) {
371
- authProfiles[`${providerName}:default`] = { apiKey: apiKey.trim() };
372
- }
373
-
374
- fs.writeFileSync(paths.authProfiles, JSON.stringify(authProfiles, null, 2), 'utf8');
375
-
376
- console.log(chalk.green('\n✅ API Key 已保存'));
347
+ console.log(chalk.green(`✅ 已激活 ${typeLabel}`));
348
+ console.log(chalk.cyan(` 节点: ${provider.baseUrl}`));
349
+ console.log(chalk.gray(` 模型: ${modelConfig.name}`));
377
350
  }
378
351
 
379
352
  // ============ 查看配置 ============
@@ -387,23 +360,46 @@ async function viewConfig(paths) {
387
360
  return;
388
361
  }
389
362
 
390
- // 主模型
363
+ // 当前激活
391
364
  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
- }
365
+ const isClaudeActive = primary.startsWith('yunyi-claude');
366
+ const isCodexActive = primary.startsWith('yunyi-codex');
367
+
368
+ console.log(chalk.yellow('当前激活:'));
369
+ if (isClaudeActive) {
370
+ console.log(chalk.blue(` 🔵 Claude: ${primary}`));
371
+ } else if (isCodexActive) {
372
+ console.log(chalk.green(` 🟢 Codex: ${primary}`));
373
+ } else {
374
+ console.log(` ${primary}`);
375
+ }
376
+ console.log('');
377
+
378
+ // Claude 节点
379
+ console.log(chalk.yellow('Claude 节点:'));
380
+ const claudeProvider = config.models?.providers?.['yunyi-claude'];
381
+ if (claudeProvider) {
382
+ const hasKey = claudeProvider.apiKey ? chalk.green('✓') : chalk.red('✗');
383
+ const model = claudeProvider.models?.[0]?.name || 'N/A';
384
+ console.log(` URL: ${claudeProvider.baseUrl}`);
385
+ console.log(` 模型: ${model}`);
386
+ console.log(` API Key: ${hasKey}`);
387
+ } else {
388
+ console.log(chalk.gray(' 未配置'));
389
+ }
390
+ console.log('');
391
+
392
+ // Codex 节点
393
+ console.log(chalk.yellow('Codex 节点:'));
394
+ const codexProvider = config.models?.providers?.['yunyi-codex'];
395
+ if (codexProvider) {
396
+ const hasKey = codexProvider.apiKey ? chalk.green('✓') : chalk.red('✗');
397
+ const model = codexProvider.models?.[0]?.name || 'N/A';
398
+ console.log(` URL: ${codexProvider.baseUrl}`);
399
+ console.log(` 模型: ${model}`);
400
+ console.log(` API Key: ${hasKey}`);
401
+ } else {
402
+ console.log(chalk.gray(' 未配置'));
407
403
  }
408
404
  console.log('');
409
405
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclawapi",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "cli.js",
6
6
  "bin": {