@tokenaut/opentoken 1.4.10 → 1.4.11

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/README.md CHANGED
@@ -151,7 +151,8 @@ opentoken cx -k <api_key> -u https://gw.opentoken.io/v1 -m <model_id>
151
151
  验证各工具的 API Key、Base URL、模型是否配置正确,发送最小请求检测连通性:
152
152
 
153
153
  ```bash
154
- opentoken cc test
154
+ opentoken cc test
155
+ opentoken cc test -m <model_id>
155
156
  opentoken cdt test
156
157
  opentoken ac test
157
158
  opentoken hm test
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenaut/opentoken",
3
- "version": "1.4.10",
3
+ "version": "1.4.11",
4
4
  "description": "OpenToken 一键接入 AI 模型 · https://docs.opentoken.io/",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -102,7 +102,8 @@ function printHelp() {
102
102
  console.log(' opentoken cdt -k <k> -u <u> 一键设置 Key 与 URL');
103
103
  console.log('');
104
104
  console.log(chalk.bold('连接测试:'));
105
- console.log(' opentoken cc test 测试 Claude Code 连接');
105
+ console.log(' opentoken cc test 测试 Claude Code 所有模型连接');
106
+ console.log(' opentoken cc test -m <model> 测试 Claude Code 指定模型连接');
106
107
  console.log(' opentoken cdt test 测试 Claude Desktop 连接');
107
108
  console.log(' opentoken ac test 测试 Atomcode 连接');
108
109
  console.log(' opentoken hm test 测试 Hermes 连接');
@@ -277,7 +278,7 @@ async function run() {
277
278
  if (args.subcommand === 'test') {
278
279
  clearScreen();
279
280
  printLogo();
280
- if (isCcCommand(args.tool)) { await claude.test(); return; }
281
+ if (isCcCommand(args.tool)) { await claude.test({ model: args.model }); return; }
281
282
  if (isClaudeDesktopCommand(args.tool)) { await claudeDesktop.test(); return; }
282
283
  if (isAtomcodeCommand(args.tool)) { await atomcode.test(); return; }
283
284
  if (isHermesCommand(args.tool)) { await hermes.test(); return; }
package/src/config.js CHANGED
@@ -91,6 +91,7 @@ function applyConfig({ apiKey, baseUrl, model }) {
91
91
 
92
92
  module.exports = {
93
93
  SETTINGS_PATH,
94
+ readClaudeSettings,
94
95
  getCurrentConfig,
95
96
  applyConfig,
96
97
  };
@@ -143,4 +143,22 @@ async function testOpenAIResponses({ baseUrl, apiKey, model }) {
143
143
  }
144
144
  }
145
145
 
146
- module.exports = { testAnthropic, testOpenAI, testOpenAIResponses };
146
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
147
+
148
+ async function testWithSpinner({ label, testFn, args }) {
149
+ let frameIdx = 0;
150
+ const spinnerTimer = setInterval(() => {
151
+ const frame = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];
152
+ process.stdout.write('\x1b[1A\x1b[K');
153
+ process.stdout.write('\x1b[36m ' + frame + ' ' + label + ' 测试中...\x1b[0m\n');
154
+ frameIdx++;
155
+ }, 80);
156
+
157
+ const result = await testFn(args);
158
+ clearInterval(spinnerTimer);
159
+
160
+ process.stdout.write('\x1b[1A\x1b[K');
161
+ return result;
162
+ }
163
+
164
+ module.exports = { testAnthropic, testOpenAI, testOpenAIResponses, testWithSpinner };
@@ -11,7 +11,7 @@ const {
11
11
  } = require('../atomcode-config');
12
12
  const { printLogo } = require('../logo');
13
13
  const { clearScreen } = require('../screen');
14
- const { testOpenAI } = require('../test-connection');
14
+ const { testOpenAI, testWithSpinner } = require('../test-connection');
15
15
 
16
16
  let pendingConfig = { apiKey: null, model: null };
17
17
 
@@ -271,17 +271,18 @@ async function test() {
271
271
  console.log(chalk.gray(' 模型 : ') + config.model);
272
272
  console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
273
273
  console.log('');
274
+ console.log(chalk.gray(' ' + config.model + ' 等待测试'));
274
275
 
275
- const result = await testOpenAI({
276
- baseUrl: ATOMCODE_OPENTOKEN_BASE_URL,
277
- apiKey: config.apiKey,
278
- model: config.model,
276
+ const result = await testWithSpinner({
277
+ label: config.model,
278
+ testFn: testOpenAI,
279
+ args: { baseUrl: ATOMCODE_OPENTOKEN_BASE_URL, apiKey: config.apiKey, model: config.model },
279
280
  });
280
281
 
281
282
  if (result.ok) {
282
- console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
283
+ console.log(chalk.green(' ✓ ' + config.model + ' 连接成功 ' + result.latency_ms + 'ms'));
283
284
  } else {
284
- console.log(chalk.red(' ✗ 连接失败:' + result.error));
285
+ console.log(chalk.red(' ✗ ' + config.model + ' ' + result.error));
285
286
  }
286
287
  console.log('');
287
288
  }
@@ -4,10 +4,10 @@ const chalk = require('chalk');
4
4
  const inquirer = require('inquirer');
5
5
  const { API_NODES, DEFAULT_BASE_URL, CLAUDE_DEFAULT_MODEL } = require('../models');
6
6
  const { fetchModelsForBaseUrl } = require('../fetch-models');
7
- const { getCurrentConfig, applyConfig } = require('../config');
7
+ const { getCurrentConfig, applyConfig, readClaudeSettings } = require('../config');
8
8
  const { printLogo } = require('../logo');
9
9
  const { clearScreen } = require('../screen');
10
- const { testAnthropic } = require('../test-connection');
10
+ const { testAnthropic, testWithSpinner } = require('../test-connection');
11
11
 
12
12
  let pendingConfig = { apiKey: null, baseUrl: null, model: null };
13
13
 
@@ -303,7 +303,7 @@ function quickSet({ key, url, model }) {
303
303
  return true;
304
304
  }
305
305
 
306
- async function test() {
306
+ async function test({ model } = {}) {
307
307
  const config = getCurrentConfig();
308
308
 
309
309
  if (!config.apiKey) {
@@ -314,29 +314,109 @@ async function test() {
314
314
  console.log(chalk.red(' ✗ 请先配置 Base URL'));
315
315
  return;
316
316
  }
317
- if (!config.model) {
318
- console.log(chalk.red(' 请先配置模型'));
317
+
318
+ if (model) {
319
+ console.log('');
320
+ console.log(chalk.bold(chalk.cyan('正在测试 Claude Code 连接...')));
321
+ console.log(chalk.gray(' Base URL : ') + config.baseUrl);
322
+ console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
323
+ console.log('');
324
+ console.log(chalk.gray(' ' + model + ' 等待测试'));
325
+
326
+ const result = await testWithSpinner({
327
+ label: model,
328
+ testFn: testAnthropic,
329
+ args: { baseUrl: config.baseUrl, apiKey: config.apiKey, model },
330
+ });
331
+
332
+ if (result.ok) {
333
+ console.log(chalk.green(' ✓ ' + model + ' 连接成功 ' + result.latency_ms + 'ms'));
334
+ } else {
335
+ console.log(chalk.red(' ✗ ' + model + ' ' + result.error));
336
+ }
337
+ console.log('');
319
338
  return;
320
339
  }
321
340
 
341
+ const settings = readClaudeSettings();
342
+ const env = settings.env || {};
343
+
344
+ const modelEntries = [
345
+ { label: 'Haiku', id: env.ANTHROPIC_DEFAULT_HAIKU_MODEL || 'claude-haiku-4-5' },
346
+ { label: 'Sonnet', id: env.ANTHROPIC_DEFAULT_SONNET_MODEL || 'claude-sonnet-4-6' },
347
+ { label: 'Opus', id: env.ANTHROPIC_DEFAULT_OPUS_MODEL || 'claude-opus-4-7' },
348
+ { label: 'Fable', id: env.ANTHROPIC_DEFAULT_FABLE_MODEL || 'claude-fable-5' },
349
+ ];
350
+
351
+ if (config.model && !modelEntries.some((e) => e.id === config.model)) {
352
+ modelEntries.push({ label: '主模型', id: config.model });
353
+ }
354
+
355
+ const seen = new Set();
356
+ const unique = [];
357
+ for (const e of modelEntries) {
358
+ if (!seen.has(e.id)) {
359
+ seen.add(e.id);
360
+ unique.push(e);
361
+ }
362
+ }
363
+
364
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
365
+ const results = new Array(unique.length).fill(null);
366
+ let testingIdx = -1;
367
+ let frameIdx = 0;
368
+
369
+ function renderLines() {
370
+ const lines = unique.map((entry, i) => {
371
+ if (results[i]) {
372
+ return results[i].ok
373
+ ? chalk.green(' ✓ ' + entry.label + ' (' + entry.id + ') 连接成功 ' + results[i].latency_ms + 'ms')
374
+ : chalk.red(' ✗ ' + entry.label + ' (' + entry.id + ') ' + results[i].error);
375
+ }
376
+ if (i === testingIdx) {
377
+ const frame = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length];
378
+ return chalk.cyan(' ' + frame + ' ' + entry.label + ' (' + entry.id + ') 测试中...');
379
+ }
380
+ return chalk.gray(' ' + entry.label + ' (' + entry.id + ') 等待测试');
381
+ });
382
+ process.stdout.write('\x1b[' + unique.length + 'A\x1b[J');
383
+ process.stdout.write(lines.join('\n') + '\n');
384
+ }
385
+
322
386
  console.log('');
323
387
  console.log(chalk.bold(chalk.cyan('正在测试 Claude Code 连接...')));
324
388
  console.log(chalk.gray(' Base URL : ') + config.baseUrl);
325
- console.log(chalk.gray(' 模型 : ') + config.model);
326
389
  console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
327
390
  console.log('');
328
391
 
329
- const result = await testAnthropic({
330
- baseUrl: config.baseUrl,
331
- apiKey: config.apiKey,
332
- model: config.model,
333
- });
392
+ for (const entry of unique) {
393
+ console.log(chalk.gray(' ' + entry.label + ' (' + entry.id + ') 等待测试'));
394
+ }
334
395
 
335
- if (result.ok) {
336
- console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
337
- } else {
338
- console.log(chalk.red(' ✗ 连接失败:' + result.error));
396
+ for (let i = 0; i < unique.length; i++) {
397
+ testingIdx = i;
398
+ frameIdx = 0;
399
+
400
+ const spinnerTimer = setInterval(() => {
401
+ frameIdx++;
402
+ renderLines();
403
+ }, 80);
404
+
405
+ const result = await testAnthropic({
406
+ baseUrl: config.baseUrl,
407
+ apiKey: config.apiKey,
408
+ model: unique[i].id,
409
+ });
410
+
411
+ clearInterval(spinnerTimer);
412
+ results[i] = result;
413
+ testingIdx = -1;
414
+ renderLines();
339
415
  }
416
+
417
+ const okCount = results.filter((r) => r && r.ok).length;
418
+ console.log('');
419
+ console.log(chalk.gray(' ' + okCount + '/' + unique.length + ' 连接成功'));
340
420
  console.log('');
341
421
  }
342
422
 
@@ -6,7 +6,7 @@ const { API_NODES, DEFAULT_BASE_URL } = require('../models');
6
6
  const { getCurrentConfig, applyConfig } = require('../claude-desktop-config');
7
7
  const { printLogo } = require('../logo');
8
8
  const { clearScreen } = require('../screen');
9
- const { testAnthropic } = require('../test-connection');
9
+ const { testAnthropic, testWithSpinner } = require('../test-connection');
10
10
 
11
11
  let pendingConfig = { apiKey: null, baseUrl: null };
12
12
 
@@ -197,22 +197,25 @@ async function test() {
197
197
  return;
198
198
  }
199
199
 
200
+ const model = 'claude-sonnet-4-6';
201
+
200
202
  console.log('');
201
203
  console.log(chalk.bold(chalk.cyan('正在测试 Claude Desktop 连接...')));
202
204
  console.log(chalk.gray(' Base URL : ') + config.baseUrl);
203
205
  console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
204
206
  console.log('');
207
+ console.log(chalk.gray(' ' + model + ' 等待测试'));
205
208
 
206
- const result = await testAnthropic({
207
- baseUrl: config.baseUrl,
208
- apiKey: config.apiKey,
209
- model: 'claude-sonnet-4-6',
209
+ const result = await testWithSpinner({
210
+ label: model,
211
+ testFn: testAnthropic,
212
+ args: { baseUrl: config.baseUrl, apiKey: config.apiKey, model },
210
213
  });
211
214
 
212
215
  if (result.ok) {
213
- console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
216
+ console.log(chalk.green(' ✓ ' + model + ' 连接成功 ' + result.latency_ms + 'ms'));
214
217
  } else {
215
- console.log(chalk.red(' ✗ 连接失败:' + result.error));
218
+ console.log(chalk.red(' ✗ ' + model + ' ' + result.error));
216
219
  }
217
220
  console.log('');
218
221
  }
@@ -12,7 +12,7 @@ const {
12
12
  } = require('../codex-config');
13
13
  const { printLogo } = require('../logo');
14
14
  const { clearScreen } = require('../screen');
15
- const { testOpenAIResponses } = require('../test-connection');
15
+ const { testOpenAIResponses, testWithSpinner } = require('../test-connection');
16
16
 
17
17
  let pendingConfig = { apiKey: null, baseUrl: null, model: null, models: null };
18
18
 
@@ -349,17 +349,18 @@ async function test() {
349
349
  console.log(chalk.gray(' 模型 : ') + config.model);
350
350
  console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
351
351
  console.log('');
352
+ console.log(chalk.gray(' ' + config.model + ' 等待测试'));
352
353
 
353
- const result = await testOpenAIResponses({
354
- baseUrl: config.baseUrl,
355
- apiKey: config.apiKey,
356
- model: config.model,
354
+ const result = await testWithSpinner({
355
+ label: config.model,
356
+ testFn: testOpenAIResponses,
357
+ args: { baseUrl: config.baseUrl, apiKey: config.apiKey, model: config.model },
357
358
  });
358
359
 
359
360
  if (result.ok) {
360
- console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
361
+ console.log(chalk.green(' ✓ ' + config.model + ' 连接成功 ' + result.latency_ms + 'ms'));
361
362
  } else {
362
- console.log(chalk.red(' ✗ 连接失败:' + result.error));
363
+ console.log(chalk.red(' ✗ ' + config.model + ' ' + result.error));
363
364
  }
364
365
  console.log('');
365
366
  }
@@ -12,7 +12,7 @@ const {
12
12
  } = require('../hermes-config');
13
13
  const { printLogo } = require('../logo');
14
14
  const { clearScreen } = require('../screen');
15
- const { testOpenAI } = require('../test-connection');
15
+ const { testOpenAI, testWithSpinner } = require('../test-connection');
16
16
 
17
17
  let pendingConfig = { apiKey: null, model: null };
18
18
 
@@ -292,17 +292,18 @@ async function test() {
292
292
  console.log(chalk.gray(' 模型 : ') + config.model);
293
293
  console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
294
294
  console.log('');
295
+ console.log(chalk.gray(' ' + config.model + ' 等待测试'));
295
296
 
296
- const result = await testOpenAI({
297
- baseUrl: HERMES_OPENTOKEN_BASE_URL,
298
- apiKey: config.apiKey,
299
- model: config.model,
297
+ const result = await testWithSpinner({
298
+ label: config.model,
299
+ testFn: testOpenAI,
300
+ args: { baseUrl: HERMES_OPENTOKEN_BASE_URL, apiKey: config.apiKey, model: config.model },
300
301
  });
301
302
 
302
303
  if (result.ok) {
303
- console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
304
+ console.log(chalk.green(' ✓ ' + config.model + ' 连接成功 ' + result.latency_ms + 'ms'));
304
305
  } else {
305
- console.log(chalk.red(' ✗ 连接失败:' + result.error));
306
+ console.log(chalk.red(' ✗ ' + config.model + ' ' + result.error));
306
307
  }
307
308
  console.log('');
308
309
  }
@@ -11,7 +11,7 @@ const {
11
11
  } = require('../openclaw-config');
12
12
  const { printLogo } = require('../logo');
13
13
  const { clearScreen } = require('../screen');
14
- const { testOpenAI } = require('../test-connection');
14
+ const { testOpenAI, testWithSpinner } = require('../test-connection');
15
15
 
16
16
  let pendingConfig = { apiKey: null, model: null };
17
17
 
@@ -254,23 +254,26 @@ async function test() {
254
254
  return;
255
255
  }
256
256
 
257
+ const modelLabel = OPENCLAW_PROVIDER_KEY + '/' + config.model;
258
+
257
259
  console.log('');
258
260
  console.log(chalk.bold(chalk.cyan('正在测试 OpenClaw 连接...')));
259
261
  console.log(chalk.gray(' Base URL : ') + OPENCLAW_OPENTOKEN_BASE_URL);
260
- console.log(chalk.gray(' 模型 : ') + OPENCLAW_PROVIDER_KEY + '/' + config.model);
262
+ console.log(chalk.gray(' 模型 : ') + modelLabel);
261
263
  console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
262
264
  console.log('');
265
+ console.log(chalk.gray(' ' + modelLabel + ' 等待测试'));
263
266
 
264
- const result = await testOpenAI({
265
- baseUrl: OPENCLAW_OPENTOKEN_BASE_URL,
266
- apiKey: config.apiKey,
267
- model: config.model,
267
+ const result = await testWithSpinner({
268
+ label: modelLabel,
269
+ testFn: testOpenAI,
270
+ args: { baseUrl: OPENCLAW_OPENTOKEN_BASE_URL, apiKey: config.apiKey, model: config.model },
268
271
  });
269
272
 
270
273
  if (result.ok) {
271
- console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
274
+ console.log(chalk.green(' ✓ ' + modelLabel + ' 连接成功 ' + result.latency_ms + 'ms'));
272
275
  } else {
273
- console.log(chalk.red(' ✗ 连接失败:' + result.error));
276
+ console.log(chalk.red(' ✗ ' + modelLabel + ' ' + result.error));
274
277
  }
275
278
  console.log('');
276
279
  }
@@ -11,7 +11,7 @@ const {
11
11
  } = require('../opencode-config');
12
12
  const { printLogo } = require('../logo');
13
13
  const { clearScreen } = require('../screen');
14
- const { testOpenAI } = require('../test-connection');
14
+ const { testOpenAI, testWithSpinner } = require('../test-connection');
15
15
 
16
16
  let pendingConfig = { apiKey: null, model: null };
17
17
 
@@ -274,17 +274,18 @@ async function test() {
274
274
  console.log(chalk.gray(' 模型 : ') + config.model);
275
275
  console.log(chalk.gray(' API Key : ') + maskApiKey(config.apiKey));
276
276
  console.log('');
277
+ console.log(chalk.gray(' ' + config.model + ' 等待测试'));
277
278
 
278
- const result = await testOpenAI({
279
- baseUrl: OPENCODE_OPENTOKEN_BASE_URL,
280
- apiKey: config.apiKey,
281
- model: config.model,
279
+ const result = await testWithSpinner({
280
+ label: config.model,
281
+ testFn: testOpenAI,
282
+ args: { baseUrl: OPENCODE_OPENTOKEN_BASE_URL, apiKey: config.apiKey, model: config.model },
282
283
  });
283
284
 
284
285
  if (result.ok) {
285
- console.log(chalk.green(' ✓ 连接成功!延迟 ' + result.latency_ms + 'ms'));
286
+ console.log(chalk.green(' ✓ ' + config.model + ' 连接成功 ' + result.latency_ms + 'ms'));
286
287
  } else {
287
- console.log(chalk.red(' ✗ 连接失败:' + result.error));
288
+ console.log(chalk.red(' ✗ ' + config.model + ' ' + result.error));
288
289
  }
289
290
  console.log('');
290
291
  }