yingclaw 2.2.3 → 2.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.
- package/README.md +3 -1
- package/bin/cli.js +50 -37
- package/lib/config.js +37 -0
- package/lib/doctor.js +192 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Claude Code × 国产大模型,一键接入。
|
|
|
4
4
|
|
|
5
5
|
支持 DeepSeek、Kimi、阿里云百炼(Qwen)、MiniMax、智谱 GLM、小米 MiMo,也支持自定义 Anthropic 兼容接口,无需梯子即可使用 Claude Code。
|
|
6
6
|
|
|
7
|
+

|
|
8
|
+
|
|
7
9
|
## 安装
|
|
8
10
|
|
|
9
11
|
```bash
|
|
@@ -112,7 +114,7 @@ CLAUDE_CODE_EFFORT_LEVEL
|
|
|
112
114
|
**自定义接口**需支持 Anthropic `/v1/messages` 格式;工具会根据 Base URL 自动尝试获取模型列表,失败则手动输入。
|
|
113
115
|
|
|
114
116
|
## 卸载
|
|
115
|
-
|
|
117
|
+
|
|
116
118
|
```bash
|
|
117
119
|
npm uninstall -g yingclaw
|
|
118
120
|
claw reset # 可选:清除已写入的环境变量和桌面配置
|
package/bin/cli.js
CHANGED
|
@@ -11,10 +11,10 @@ const {
|
|
|
11
11
|
fetchModelsFromBaseUrl,
|
|
12
12
|
resetConfig,
|
|
13
13
|
validateConfig,
|
|
14
|
+
validateKey,
|
|
14
15
|
normalizeAnthropicBaseUrl,
|
|
15
16
|
resolveFastModel,
|
|
16
17
|
buildClaudeEnv,
|
|
17
|
-
classifyValidationStatus,
|
|
18
18
|
PROVIDERS,
|
|
19
19
|
CLAUDE_ENV_KEYS,
|
|
20
20
|
} = require('../lib/config');
|
|
@@ -23,6 +23,7 @@ const pkg = require('../package.json');
|
|
|
23
23
|
const { buildMenuStatusLines, buildStatusView } = require('../lib/panel');
|
|
24
24
|
const { buildClaudeInstallCommand } = require('../lib/install');
|
|
25
25
|
const { clearClaudeDesktopConfig, isDesktopConfigured, openClaudeDesktop, writeClaudeDesktopConfig } = require('../lib/desktop');
|
|
26
|
+
const { runDoctorChecks, summarize, STATUS_OK, STATUS_FAIL, STATUS_WARN, STATUS_INFO } = require('../lib/doctor');
|
|
26
27
|
|
|
27
28
|
const program = new Command();
|
|
28
29
|
|
|
@@ -38,42 +39,6 @@ async function getBanner() {
|
|
|
38
39
|
);
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
async function validateKey(config) {
|
|
42
|
-
let url;
|
|
43
|
-
try {
|
|
44
|
-
url = `${normalizeAnthropicBaseUrl(config.baseUrl)}/v1/messages`;
|
|
45
|
-
new URL(url);
|
|
46
|
-
} catch {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const controller = new AbortController();
|
|
51
|
-
const timeout = setTimeout(() => controller.abort(), 8000);
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const res = await fetch(url, {
|
|
55
|
-
method: 'POST',
|
|
56
|
-
signal: controller.signal,
|
|
57
|
-
headers: {
|
|
58
|
-
'content-type': 'application/json',
|
|
59
|
-
authorization: `Bearer ${config.apiKey}`,
|
|
60
|
-
'x-api-key': config.apiKey,
|
|
61
|
-
'anthropic-version': '2023-06-01',
|
|
62
|
-
},
|
|
63
|
-
body: JSON.stringify({
|
|
64
|
-
model: config.model,
|
|
65
|
-
max_tokens: 1,
|
|
66
|
-
messages: [{ role: 'user', content: 'hi' }],
|
|
67
|
-
}),
|
|
68
|
-
});
|
|
69
|
-
return classifyValidationStatus(res.status);
|
|
70
|
-
} catch {
|
|
71
|
-
return null;
|
|
72
|
-
} finally {
|
|
73
|
-
clearTimeout(timeout);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
42
|
function getConfigValidationMessage(config) {
|
|
78
43
|
const validation = validateConfig(config);
|
|
79
44
|
return validation.valid ? null : validation.message;
|
|
@@ -857,6 +822,52 @@ program
|
|
|
857
822
|
}
|
|
858
823
|
});
|
|
859
824
|
|
|
825
|
+
program
|
|
826
|
+
.command('doctor')
|
|
827
|
+
.description('诊断当前环境,列出所有问题和修复建议')
|
|
828
|
+
.action(async () => {
|
|
829
|
+
const chalk = (await import('chalk')).default;
|
|
830
|
+
const ora = (await import('ora')).default;
|
|
831
|
+
const boxen = (await import('boxen')).default;
|
|
832
|
+
|
|
833
|
+
console.log(await getBanner());
|
|
834
|
+
|
|
835
|
+
const spinner = ora('运行诊断检查...').start();
|
|
836
|
+
const checks = await runDoctorChecks();
|
|
837
|
+
spinner.stop();
|
|
838
|
+
|
|
839
|
+
const icons = {
|
|
840
|
+
[STATUS_OK]: chalk.green('✓'),
|
|
841
|
+
[STATUS_FAIL]: chalk.red('✗'),
|
|
842
|
+
[STATUS_WARN]: chalk.yellow('⚠'),
|
|
843
|
+
[STATUS_INFO]: chalk.cyan('ℹ'),
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
console.log();
|
|
847
|
+
for (const c of checks) {
|
|
848
|
+
console.log(` ${icons[c.status]} ${chalk.bold(c.name)} ${chalk.dim(c.message)}`);
|
|
849
|
+
if (c.fix) console.log(` ${chalk.dim('→')} ${chalk.cyan(c.fix)}`);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const counts = summarize(checks);
|
|
853
|
+
const summaryBits = [];
|
|
854
|
+
if (counts.fail) summaryBits.push(chalk.red(`${counts.fail} 个错误`));
|
|
855
|
+
if (counts.warn) summaryBits.push(chalk.yellow(`${counts.warn} 个警告`));
|
|
856
|
+
if (counts.ok) summaryBits.push(chalk.green(`${counts.ok} 个通过`));
|
|
857
|
+
|
|
858
|
+
const allOk = counts.fail === 0 && counts.warn === 0;
|
|
859
|
+
console.log(boxen(
|
|
860
|
+
(allOk ? chalk.bold.green('✓ 一切正常') : chalk.bold('诊断完成')) +
|
|
861
|
+
(summaryBits.length ? '\n\n' + summaryBits.join(' · ') : ''),
|
|
862
|
+
{
|
|
863
|
+
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
864
|
+
borderStyle: 'round',
|
|
865
|
+
borderColor: counts.fail ? 'red' : counts.warn ? 'yellow' : 'green',
|
|
866
|
+
margin: { top: 1, bottom: 1 },
|
|
867
|
+
}
|
|
868
|
+
));
|
|
869
|
+
});
|
|
870
|
+
|
|
860
871
|
program
|
|
861
872
|
.command('update')
|
|
862
873
|
.description('检查并更新 yingclaw 到最新版本')
|
|
@@ -997,6 +1008,7 @@ async function runAdvancedMenu(chalk, hasConfig) {
|
|
|
997
1008
|
const action = await select({ loop: false,
|
|
998
1009
|
message: chalk.cyan('高级选项'),
|
|
999
1010
|
choices: [
|
|
1011
|
+
{ name: '🩺 诊断(一键自检并给出修复建议)', value: 'doctor' },
|
|
1000
1012
|
{ name: '🔁 重新检测 API', value: 'recheck', disabled: !hasConfig && ADVANCED_DISABLED_HINT },
|
|
1001
1013
|
{ name: '↩️ 恢复 Claude Code 终端默认', value: 'code-reset' },
|
|
1002
1014
|
{ name: '↩️ 恢复 Claude 桌面默认', value: 'desktop-reset' },
|
|
@@ -1105,6 +1117,7 @@ async function runMenu() {
|
|
|
1105
1117
|
status: 'status',
|
|
1106
1118
|
reset: 'reset',
|
|
1107
1119
|
update: 'update',
|
|
1120
|
+
doctor: 'doctor',
|
|
1108
1121
|
};
|
|
1109
1122
|
|
|
1110
1123
|
// 执行子命令(用 spawn 隔离,避免 commander 对 program 的副作用)
|
package/lib/config.js
CHANGED
|
@@ -283,6 +283,42 @@ function classifyValidationStatus(statusCode) {
|
|
|
283
283
|
return null;
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
// 发一次最小的 /v1/messages 请求验证 Key(true=有效, false=无效, null=网络/服务异常)
|
|
287
|
+
async function validateKey(config, options = {}) {
|
|
288
|
+
let url;
|
|
289
|
+
try {
|
|
290
|
+
url = `${normalizeAnthropicBaseUrl(config.baseUrl)}/v1/messages`;
|
|
291
|
+
new URL(url);
|
|
292
|
+
} catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const controller = new AbortController();
|
|
297
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs || 8000);
|
|
298
|
+
try {
|
|
299
|
+
const res = await fetch(url, {
|
|
300
|
+
method: 'POST',
|
|
301
|
+
signal: controller.signal,
|
|
302
|
+
headers: {
|
|
303
|
+
'content-type': 'application/json',
|
|
304
|
+
authorization: `Bearer ${config.apiKey}`,
|
|
305
|
+
'x-api-key': config.apiKey,
|
|
306
|
+
'anthropic-version': '2023-06-01',
|
|
307
|
+
},
|
|
308
|
+
body: JSON.stringify({
|
|
309
|
+
model: config.model,
|
|
310
|
+
max_tokens: 1,
|
|
311
|
+
messages: [{ role: 'user', content: 'hi' }],
|
|
312
|
+
}),
|
|
313
|
+
});
|
|
314
|
+
return classifyValidationStatus(res.status);
|
|
315
|
+
} catch {
|
|
316
|
+
return null;
|
|
317
|
+
} finally {
|
|
318
|
+
clearTimeout(timeout);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
286
322
|
function shellQuote(value) {
|
|
287
323
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
288
324
|
}
|
|
@@ -405,6 +441,7 @@ module.exports = {
|
|
|
405
441
|
buildClaudeEnv,
|
|
406
442
|
buildEnvBlock,
|
|
407
443
|
classifyValidationStatus,
|
|
444
|
+
validateKey,
|
|
408
445
|
PROVIDERS,
|
|
409
446
|
CONFIG_FILE,
|
|
410
447
|
CLAUDE_ENV_KEYS,
|
package/lib/doctor.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { execSync, spawnSync } = require('child_process');
|
|
5
|
+
const {
|
|
6
|
+
loadConfig,
|
|
7
|
+
validateConfig,
|
|
8
|
+
validateKey,
|
|
9
|
+
buildClaudeEnv,
|
|
10
|
+
normalizeAnthropicBaseUrl,
|
|
11
|
+
PROVIDERS,
|
|
12
|
+
CLAUDE_ENV_KEYS,
|
|
13
|
+
} = require('./config');
|
|
14
|
+
const { isDesktopConfigured } = require('./desktop');
|
|
15
|
+
|
|
16
|
+
const STATUS_OK = 'ok';
|
|
17
|
+
const STATUS_FAIL = 'fail';
|
|
18
|
+
const STATUS_WARN = 'warn';
|
|
19
|
+
const STATUS_INFO = 'info';
|
|
20
|
+
|
|
21
|
+
async function runDoctorChecks(options = {}) {
|
|
22
|
+
const checks = [];
|
|
23
|
+
const platform = options.platform || process.platform;
|
|
24
|
+
const env = options.env || process.env;
|
|
25
|
+
|
|
26
|
+
// 1. Node 版本
|
|
27
|
+
const nodeVersion = process.versions.node;
|
|
28
|
+
const nodeMajor = parseInt(nodeVersion.split('.')[0], 10);
|
|
29
|
+
checks.push({
|
|
30
|
+
name: 'Node 版本',
|
|
31
|
+
status: nodeMajor >= 18 ? STATUS_OK : STATUS_FAIL,
|
|
32
|
+
message: `v${nodeVersion}${nodeMajor >= 18 ? '' : '(< 18)'}`,
|
|
33
|
+
fix: nodeMajor < 18 ? '从 https://nodejs.org 下载 LTS 版本(≥18)' : null,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 2. Claude Code
|
|
37
|
+
let claudeVersion = null;
|
|
38
|
+
try {
|
|
39
|
+
claudeVersion = execSync('claude --version', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
40
|
+
} catch {}
|
|
41
|
+
checks.push({
|
|
42
|
+
name: 'Claude Code',
|
|
43
|
+
status: claudeVersion ? STATUS_OK : STATUS_WARN,
|
|
44
|
+
message: claudeVersion || '未安装',
|
|
45
|
+
fix: claudeVersion ? null : '运行 claw install-claude',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 3. 配置文件
|
|
49
|
+
const config = loadConfig();
|
|
50
|
+
if (!config) {
|
|
51
|
+
checks.push({
|
|
52
|
+
name: '配置文件',
|
|
53
|
+
status: STATUS_FAIL,
|
|
54
|
+
message: '~/.clawai.json 不存在',
|
|
55
|
+
fix: '运行 claw config',
|
|
56
|
+
});
|
|
57
|
+
return checks;
|
|
58
|
+
}
|
|
59
|
+
const validation = validateConfig(config);
|
|
60
|
+
if (!validation.valid) {
|
|
61
|
+
checks.push({
|
|
62
|
+
name: '配置文件',
|
|
63
|
+
status: STATUS_FAIL,
|
|
64
|
+
message: `~/.clawai.json 无效:${validation.message}`,
|
|
65
|
+
fix: '运行 claw config 重新配置',
|
|
66
|
+
});
|
|
67
|
+
return checks;
|
|
68
|
+
}
|
|
69
|
+
const provider = PROVIDERS[config.provider];
|
|
70
|
+
checks.push({
|
|
71
|
+
name: '配置文件',
|
|
72
|
+
status: STATUS_OK,
|
|
73
|
+
message: `${provider?.name || config.provider} · ${config.model}`,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 4. shell rc / Windows 环境变量已写入
|
|
77
|
+
if (platform === 'win32') {
|
|
78
|
+
const result = checkWindowsEnvVars(options);
|
|
79
|
+
checks.push({
|
|
80
|
+
name: 'Windows 用户环境变量',
|
|
81
|
+
status: result.allWritten ? STATUS_OK : STATUS_WARN,
|
|
82
|
+
message: result.allWritten ? '已写入' : `${result.missing.length} 个变量未写入`,
|
|
83
|
+
fix: result.allWritten ? null : '运行 claw code',
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
const rcCheck = checkShellRcBlock(options);
|
|
87
|
+
checks.push({
|
|
88
|
+
name: 'shell 配置文件',
|
|
89
|
+
status: rcCheck.found ? STATUS_OK : STATUS_WARN,
|
|
90
|
+
message: rcCheck.found ? `${rcCheck.file} 已写入 yingclaw 块` : '未找到 yingclaw 配置块',
|
|
91
|
+
fix: rcCheck.found ? null : '运行 claw code',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 5. 当前终端环境变量是否生效
|
|
96
|
+
const expected = buildClaudeEnv(config);
|
|
97
|
+
const inactive = Object.entries(expected).filter(([k, v]) => env[k] !== v);
|
|
98
|
+
checks.push({
|
|
99
|
+
name: '当前终端环境变量',
|
|
100
|
+
status: inactive.length === 0 ? STATUS_OK : STATUS_WARN,
|
|
101
|
+
message: inactive.length === 0 ? '已生效' : `${inactive.length}/${Object.keys(expected).length} 未生效`,
|
|
102
|
+
fix: inactive.length === 0
|
|
103
|
+
? null
|
|
104
|
+
: (platform === 'win32' ? '重新打开 PowerShell / CMD' : '运行 source ~/.zshrc 或重开终端'),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// 6. API Key(顺便确认网络可达)
|
|
108
|
+
const valid = await validateKey(config, { timeoutMs: 6000 });
|
|
109
|
+
if (valid === true) {
|
|
110
|
+
checks.push({ name: 'API Key', status: STATUS_OK, message: '校验通过' });
|
|
111
|
+
} else if (valid === false) {
|
|
112
|
+
checks.push({
|
|
113
|
+
name: 'API Key',
|
|
114
|
+
status: STATUS_FAIL,
|
|
115
|
+
message: '无效或已过期(401/403)',
|
|
116
|
+
fix: '运行 claw config 重新输入 Key',
|
|
117
|
+
});
|
|
118
|
+
} else {
|
|
119
|
+
checks.push({
|
|
120
|
+
name: 'API Key',
|
|
121
|
+
status: STATUS_WARN,
|
|
122
|
+
message: '无法连接(网络 / 服务异常)',
|
|
123
|
+
fix: '检查网络 / VPN,或确认 Base URL 没拼错',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 7. Claude 桌面应用接入状态
|
|
128
|
+
const desktopConfigured = isDesktopConfigured();
|
|
129
|
+
checks.push({
|
|
130
|
+
name: 'Claude 桌面应用',
|
|
131
|
+
status: STATUS_INFO,
|
|
132
|
+
message: desktopConfigured ? '已通过 yingclaw 接入' : '未接入(如需运行 claw desktop)',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 8. DeepSeek 旧模型名提醒
|
|
136
|
+
if (config.provider === 'deepseek' && (
|
|
137
|
+
config.model === 'deepseek-v4-pro' ||
|
|
138
|
+
config.model === 'deepseek-v4-flash' ||
|
|
139
|
+
config.fastModel === 'deepseek-v4-flash'
|
|
140
|
+
)) {
|
|
141
|
+
checks.push({
|
|
142
|
+
name: '模型名',
|
|
143
|
+
status: STATUS_WARN,
|
|
144
|
+
message: '使用旧 DeepSeek 模型名',
|
|
145
|
+
fix: '运行 claw switch 升级到 [1m] 长上下文版本',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return checks;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function checkShellRcBlock(options = {}) {
|
|
153
|
+
const homeDir = options.homeDir || os.homedir();
|
|
154
|
+
const rcFiles = options.rcFiles || [
|
|
155
|
+
path.join(homeDir, '.zshrc'),
|
|
156
|
+
path.join(homeDir, '.bashrc'),
|
|
157
|
+
path.join(homeDir, '.bash_profile'),
|
|
158
|
+
];
|
|
159
|
+
for (const file of rcFiles) {
|
|
160
|
+
if (!fs.existsSync(file)) continue;
|
|
161
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
162
|
+
if (content.includes('# clawai-start')) return { found: true, file };
|
|
163
|
+
}
|
|
164
|
+
return { found: false };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function checkWindowsEnvVars(options = {}) {
|
|
168
|
+
const runner = options.runner || spawnSync;
|
|
169
|
+
const missing = [];
|
|
170
|
+
for (const key of CLAUDE_ENV_KEYS) {
|
|
171
|
+
const result = runner('reg', ['query', 'HKCU\\Environment', '/V', key], { stdio: 'pipe', encoding: 'utf8' });
|
|
172
|
+
if (result.status !== 0) missing.push(key);
|
|
173
|
+
}
|
|
174
|
+
return { allWritten: missing.length === 0, missing };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function summarize(checks) {
|
|
178
|
+
const counts = { ok: 0, fail: 0, warn: 0, info: 0 };
|
|
179
|
+
for (const c of checks) counts[c.status] = (counts[c.status] || 0) + 1;
|
|
180
|
+
return counts;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = {
|
|
184
|
+
runDoctorChecks,
|
|
185
|
+
checkShellRcBlock,
|
|
186
|
+
checkWindowsEnvVars,
|
|
187
|
+
summarize,
|
|
188
|
+
STATUS_OK,
|
|
189
|
+
STATUS_FAIL,
|
|
190
|
+
STATUS_WARN,
|
|
191
|
+
STATUS_INFO,
|
|
192
|
+
};
|