yingclaw 1.6.0 → 1.6.2

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
@@ -48,6 +48,12 @@ claw switch # 快速切换模型(无需重新输入 Key)
48
48
  claw status # 查看当前配置,验证 Key 是否有效
49
49
  ```
50
50
 
51
+ ## 卸载
52
+
53
+ ```bash
54
+ npm uninstall -g yingclaw
55
+ ```
56
+
51
57
  ## 原理
52
58
 
53
59
  各厂商均原生支持 Anthropic API 格式。本工具会自动写入 Claude Code 所需的环境变量,包括:
package/bin/cli.js CHANGED
@@ -8,6 +8,7 @@ const {
8
8
  writeEnvToZshrc,
9
9
  fetchModels,
10
10
  resetConfig,
11
+ validateConfig,
11
12
  resolveFastModel,
12
13
  buildClaudeEnv,
13
14
  classifyValidationStatus,
@@ -16,7 +17,7 @@ const {
16
17
  const { execSync, spawn, spawnSync } = require('child_process');
17
18
  const https = require('https');
18
19
  const pkg = require('../package.json');
19
- const { buildStatusView } = require('../lib/panel');
20
+ const { buildMenuStatusLines, buildStatusView } = require('../lib/panel');
20
21
 
21
22
  const program = new Command();
22
23
 
@@ -66,6 +67,11 @@ async function validateKey(config) {
66
67
  });
67
68
  }
68
69
 
70
+ function getConfigValidationMessage(config) {
71
+ const validation = validateConfig(config);
72
+ return validation.valid ? null : validation.message;
73
+ }
74
+
69
75
  function isClaudeInstalled() {
70
76
  try {
71
77
  execSync('claude --version', { stdio: 'pipe' });
@@ -85,6 +91,12 @@ async function showStatus() {
85
91
  console.log(chalk.red('\n未配置,请先运行: claw setup\n'));
86
92
  return;
87
93
  }
94
+ const configProblem = getConfigValidationMessage(config);
95
+ if (configProblem) {
96
+ console.log(chalk.red(`\n配置无效:${configProblem}`));
97
+ console.log(chalk.dim('请运行 claw setup 重新配置。\n'));
98
+ return;
99
+ }
88
100
 
89
101
  const spinner = ora('验证 API Key...').start();
90
102
  const valid = await validateKey(config);
@@ -218,7 +230,7 @@ program
218
230
  chalk.bold('当前配置\n\n') +
219
231
  chalk.dim('厂商 ') + chalk.white(existingProvider?.name || existing.provider) + '\n' +
220
232
  chalk.dim('模型 ') + chalk.yellow(existing.model) + '\n' +
221
- chalk.dim('Key ') + chalk.dim(existing.apiKey.slice(0, 10) + '...'),
233
+ chalk.dim('Key ') + chalk.dim(existing.apiKey ? '已保存' : '缺失'),
222
234
  { padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'yellow', margin: { top: 1, bottom: 1 } }
223
235
  ));
224
236
  const overwrite = await confirm({ message: '覆盖现有配置?', default: false });
@@ -291,7 +303,7 @@ program
291
303
  console.log(boxen(
292
304
  chalk.bold('配置完成!\n\n') +
293
305
  chalk.dim('ANTHROPIC_BASE_URL ') + chalk.cyan(provider.baseUrl) + '\n' +
294
- chalk.dim('ANTHROPIC_API_KEY ') + chalk.cyan(apiKey.slice(0, 10) + '...') + '\n\n' +
306
+ chalk.dim('ANTHROPIC_API_KEY ') + chalk.cyan('已保存') + '\n\n' +
295
307
  chalk.white('下次直接输入 ') + chalk.cyan.bold('claude') + chalk.white(' 即可使用'),
296
308
  { padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
297
309
  ));
@@ -322,6 +334,12 @@ program
322
334
  console.log(chalk.red('\n未配置,请先运行: claw setup\n'));
323
335
  return;
324
336
  }
337
+ const configProblem = getConfigValidationMessage(config);
338
+ if (configProblem) {
339
+ console.log(chalk.red(`\n配置无效:${configProblem}`));
340
+ console.log(chalk.dim('请运行 claw setup 重新配置。\n'));
341
+ return;
342
+ }
325
343
 
326
344
  const providerKey = await select({ loop: false,
327
345
  message: chalk.cyan('选择 AI 厂商'),
@@ -435,23 +453,19 @@ async function renderStatusBar(apiStatus) {
435
453
  claudeInstalled,
436
454
  env: process.env,
437
455
  });
438
- let dot;
439
- if (apiStatus === true) dot = chalk.green('●');
440
- else if (apiStatus === false) dot = chalk.red('');
441
- else if (apiStatus === null) dot = chalk.yellow('●');
442
- else dot = chalk.dim('●'); // 未检测
443
- const apiTag = apiStatus === true ? chalk.green(' API ✓')
444
- : apiStatus === false ? chalk.red(' API ✘')
445
- : apiStatus === null ? chalk.yellow(' 网络异常')
446
- : '';
447
- const envTag = view.envActive ? chalk.green(' env ✓') : chalk.yellow(' env 未生效');
448
- const legacyTag = view.warnings.length > 0 ? chalk.yellow(' 旧模型') : '';
449
- cfgPart = `${dot} ${chalk.white(view.providerName)}${apiTag}${envTag}${legacyTag}\n ${chalk.dim('主 ')}${chalk.yellow(view.mainModel)}${chalk.dim(' 快 ')}${chalk.yellow(view.fastModel)}`;
456
+ const statusLines = buildMenuStatusLines(view, { apiStatus, claudeInstalled });
457
+ cfgPart = statusLines.map((line, index) => {
458
+ if (index === 0) return line.replace('API 正常', chalk.green('API 正常')).replace('API Key 无效', chalk.red('API Key 无效')).replace('网络/服务异常', chalk.yellow('网络/服务异常'));
459
+ if (line.startsWith('环境变量未生效')) return chalk.yellow(line);
460
+ if (line.startsWith('旧模型名')) return chalk.yellow(line);
461
+ if (line.startsWith('主模型')) return line.replace(view.mainModel, chalk.yellow(view.mainModel)).replace(view.fastModel, chalk.yellow(view.fastModel));
462
+ return line;
463
+ }).join('\n ');
450
464
  } else {
451
465
  cfgPart = chalk.red('●') + ' ' + chalk.dim('未配置');
452
466
  }
453
467
 
454
- return ` ${claudeIcon} ${claudeText} ${cfgPart}`;
468
+ return config ? ` ${cfgPart}` : ` ${claudeIcon} ${claudeText} ${cfgPart}`;
455
469
  }
456
470
 
457
471
  async function runMenu() {
@@ -464,7 +478,8 @@ async function runMenu() {
464
478
 
465
479
  const config = loadConfig();
466
480
  let apiStatus; // undefined = skipped, true/false/null = checked
467
- if (config) {
481
+ const configProblem = config ? getConfigValidationMessage(config) : null;
482
+ if (config && !configProblem) {
468
483
  const spinner = ora('正在检测 API 是否通畅...').start();
469
484
  apiStatus = await validateKey(config);
470
485
  if (apiStatus === true) spinner.succeed('API 连接正常');
@@ -472,18 +487,23 @@ async function runMenu() {
472
487
  else spinner.warn('网络异常,无法连接 API');
473
488
  }
474
489
 
475
- console.log(await renderStatusBar(apiStatus));
490
+ if (configProblem) {
491
+ console.log(chalk.red(` ● 配置无效:${configProblem}`));
492
+ console.log(chalk.dim(' 请先选择“首次配置 API Key 和模型”重新配置'));
493
+ } else {
494
+ console.log(await renderStatusBar(apiStatus));
495
+ }
476
496
  console.log();
477
497
 
478
498
  const action = await select({ loop: false,
479
499
  message: chalk.cyan('选择操作'),
480
500
  choices: [
481
- { name: '🤖 启动 Claude Code', value: 'launch', disabled: !config && '需先完成配置' },
501
+ { name: '🤖 启动 Claude Code', value: 'launch', disabled: (!config || configProblem) && '需先完成配置' },
482
502
  { name: '📦 安装 Claude Code', value: 'install' },
483
503
  { name: config ? '⚙️ 重新配置(输入新的 API Key)' : '⚙️ 首次配置 API Key 和模型', value: 'setup' },
484
- { name: '🔄 切换厂商或模型', value: 'switch', disabled: !config && '需先完成配置' },
504
+ { name: '🔄 切换厂商或模型', value: 'switch', disabled: (!config || configProblem) && '需先完成配置' },
485
505
  { name: '📊 查看当前配置', value: 'status', disabled: !config && '需先完成配置' },
486
- { name: '🔁 重新检测 API', value: 'recheck', disabled: !config && '需先完成配置' },
506
+ { name: '🔁 重新检测 API', value: 'recheck', disabled: (!config || configProblem) && '需先完成配置' },
487
507
  { name: '🗑 恢复默认(清除所有配置)', value: 'reset', disabled: !config && '没有可清除的配置' },
488
508
  { name: '退出', value: 'exit' },
489
509
  ],
@@ -498,7 +518,7 @@ async function runMenu() {
498
518
 
499
519
  if (action === 'launch') {
500
520
  const cfg = loadConfig();
501
- if (!cfg) continue;
521
+ if (!cfg || getConfigValidationMessage(cfg)) continue;
502
522
  // 启动 claude 后等它退出,然后回菜单
503
523
  await new Promise((resolve) => {
504
524
  const child = spawn('claude', [], {
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ ...require('./lib/config'),
3
+ ...require('./lib/panel'),
4
+ };
package/lib/config.js CHANGED
@@ -106,6 +106,21 @@ function loadConfig() {
106
106
  }
107
107
  }
108
108
 
109
+ function validateConfig(config) {
110
+ if (!config || typeof config !== 'object') {
111
+ return { valid: false, message: '未找到配置' };
112
+ }
113
+ for (const key of ['provider', 'baseUrl', 'apiKey', 'model']) {
114
+ if (typeof config[key] !== 'string' || config[key].trim().length === 0) {
115
+ return { valid: false, message: `配置缺少 ${key}` };
116
+ }
117
+ }
118
+ if (!PROVIDERS[config.provider]) {
119
+ return { valid: false, message: `未知厂商 ${config.provider}` };
120
+ }
121
+ return { valid: true };
122
+ }
123
+
109
124
  function saveConfig(config) {
110
125
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
111
126
  try {
@@ -113,6 +128,10 @@ function saveConfig(config) {
113
128
  } catch {}
114
129
  }
115
130
 
131
+ function providerKeyFromBaseUrl(baseUrl) {
132
+ return Object.entries(PROVIDERS).find(([, provider]) => provider.baseUrl === baseUrl)?.[0];
133
+ }
134
+
116
135
  function resolveFastModel(provider, model) {
117
136
  if (/flash|turbo|haiku|air|lite/i.test(model)) return model;
118
137
  return provider?.fastModel || model;
@@ -145,7 +164,8 @@ function shellQuote(value) {
145
164
 
146
165
  // 构造完整的 clawai 环境变量块
147
166
  function buildEnvBlock(baseUrl, apiKey, model, fastModel) {
148
- const env = buildClaudeEnv({ baseUrl, apiKey, model, fastModel });
167
+ const provider = providerKeyFromBaseUrl(baseUrl);
168
+ const env = buildClaudeEnv({ provider, baseUrl, apiKey, model, fastModel });
149
169
  return [
150
170
  '',
151
171
  '# clawai-start',
@@ -224,7 +244,9 @@ module.exports = {
224
244
  writeEnvToZshrc,
225
245
  fetchModels,
226
246
  resetConfig,
247
+ validateConfig,
227
248
  resolveFastModel,
249
+ providerKeyFromBaseUrl,
228
250
  buildClaudeEnv,
229
251
  buildEnvBlock,
230
252
  classifyValidationStatus,
package/lib/panel.js CHANGED
@@ -46,4 +46,25 @@ function buildStatusView(config, options = {}) {
46
46
  };
47
47
  }
48
48
 
49
- module.exports = { buildStatusView, apiStatusText, isEnvActive };
49
+ function buildMenuStatusLines(view, options = {}) {
50
+ const claudeText = options.claudeInstalled ? 'Claude 已安装' : 'Claude 未安装';
51
+ const lines = [
52
+ `${claudeText} · ${view.providerName} · ${apiStatusText(options.apiStatus)}`,
53
+ ];
54
+
55
+ if (view.envActive) {
56
+ lines.push('环境变量已生效');
57
+ } else {
58
+ lines.push('环境变量未生效:运行 source ~/.zshrc 或重开终端');
59
+ }
60
+
61
+ lines.push(`主模型 ${view.mainModel} · 快速模型 ${view.fastModel}`);
62
+
63
+ if (view.warnings.some((warning) => warning.includes('旧 DeepSeek 模型名'))) {
64
+ lines.push('旧模型名:选择下方“切换厂商或模型”更新');
65
+ }
66
+
67
+ return lines;
68
+ }
69
+
70
+ module.exports = { buildMenuStatusLines, buildStatusView, apiStatusText, isEnvActive };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yingclaw",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Claude Code × 国产大模型一键接入:DeepSeek、Qwen、MiniMax、GLM、MiMo",
5
5
  "main": "index.js",
6
6
  "bin": {