yingclaw 1.5.2 → 1.6.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.
- package/README.md +13 -1
- package/bin/cli.js +137 -30
- package/index.js +4 -0
- package/lib/config.js +129 -18
- package/lib/panel.js +49 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -50,7 +50,19 @@ claw status # 查看当前配置,验证 Key 是否有效
|
|
|
50
50
|
|
|
51
51
|
## 原理
|
|
52
52
|
|
|
53
|
-
各厂商均原生支持 Anthropic API
|
|
53
|
+
各厂商均原生支持 Anthropic API 格式。本工具会自动写入 Claude Code 所需的环境变量,包括:
|
|
54
|
+
|
|
55
|
+
- `ANTHROPIC_BASE_URL`
|
|
56
|
+
- `ANTHROPIC_AUTH_TOKEN`
|
|
57
|
+
- `ANTHROPIC_API_KEY`
|
|
58
|
+
- `ANTHROPIC_MODEL`
|
|
59
|
+
- `ANTHROPIC_DEFAULT_OPUS_MODEL`
|
|
60
|
+
- `ANTHROPIC_DEFAULT_SONNET_MODEL`
|
|
61
|
+
- `ANTHROPIC_DEFAULT_HAIKU_MODEL`
|
|
62
|
+
- `CLAUDE_CODE_SUBAGENT_MODEL`
|
|
63
|
+
- `CLAUDE_CODE_EFFORT_LEVEL`
|
|
64
|
+
|
|
65
|
+
以 DeepSeek 为例,主模型默认使用 `deepseek-v4-pro[1m]`,Haiku/Subagent 快速模型使用 `deepseek-v4-flash`。如果在线模型列表获取失败,会回退到内置默认列表。
|
|
54
66
|
|
|
55
67
|
## License
|
|
56
68
|
|
package/bin/cli.js
CHANGED
|
@@ -2,10 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
const { Command } = require('commander');
|
|
4
4
|
const { select, input, confirm } = require('@inquirer/prompts');
|
|
5
|
-
const {
|
|
5
|
+
const {
|
|
6
|
+
loadConfig,
|
|
7
|
+
saveConfig,
|
|
8
|
+
writeEnvToZshrc,
|
|
9
|
+
fetchModels,
|
|
10
|
+
resetConfig,
|
|
11
|
+
validateConfig,
|
|
12
|
+
resolveFastModel,
|
|
13
|
+
buildClaudeEnv,
|
|
14
|
+
classifyValidationStatus,
|
|
15
|
+
PROVIDERS,
|
|
16
|
+
} = require('../lib/config');
|
|
6
17
|
const { execSync, spawn, spawnSync } = require('child_process');
|
|
7
18
|
const https = require('https');
|
|
8
19
|
const pkg = require('../package.json');
|
|
20
|
+
const { buildStatusView } = require('../lib/panel');
|
|
9
21
|
|
|
10
22
|
const program = new Command();
|
|
11
23
|
|
|
@@ -46,9 +58,7 @@ async function validateKey(config) {
|
|
|
46
58
|
'content-length': body.length,
|
|
47
59
|
},
|
|
48
60
|
}, (res) => {
|
|
49
|
-
|
|
50
|
-
if (res.statusCode === 401 || res.statusCode === 403) resolve(false);
|
|
51
|
-
else resolve(true);
|
|
61
|
+
resolve(classifyValidationStatus(res.statusCode));
|
|
52
62
|
});
|
|
53
63
|
req.on('error', () => resolve(null));
|
|
54
64
|
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
@@ -57,6 +67,20 @@ async function validateKey(config) {
|
|
|
57
67
|
});
|
|
58
68
|
}
|
|
59
69
|
|
|
70
|
+
function getConfigValidationMessage(config) {
|
|
71
|
+
const validation = validateConfig(config);
|
|
72
|
+
return validation.valid ? null : validation.message;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isClaudeInstalled() {
|
|
76
|
+
try {
|
|
77
|
+
execSync('claude --version', { stdio: 'pipe' });
|
|
78
|
+
return true;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
60
84
|
async function showStatus() {
|
|
61
85
|
const chalk = (await import('chalk')).default;
|
|
62
86
|
const boxen = (await import('boxen')).default;
|
|
@@ -67,8 +91,13 @@ async function showStatus() {
|
|
|
67
91
|
console.log(chalk.red('\n未配置,请先运行: claw setup\n'));
|
|
68
92
|
return;
|
|
69
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
|
+
}
|
|
70
100
|
|
|
71
|
-
const provider = PROVIDERS[config.provider];
|
|
72
101
|
const spinner = ora('验证 API Key...').start();
|
|
73
102
|
const valid = await validateKey(config);
|
|
74
103
|
|
|
@@ -76,12 +105,34 @@ async function showStatus() {
|
|
|
76
105
|
else if (valid === false) spinner.fail('API Key 无效或已过期');
|
|
77
106
|
else spinner.warn('网络异常,无法验证');
|
|
78
107
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
108
|
+
const view = buildStatusView(config, {
|
|
109
|
+
apiStatus: valid,
|
|
110
|
+
claudeInstalled: isClaudeInstalled(),
|
|
111
|
+
env: process.env,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const lines = view.lines.map(({ label, value }) => {
|
|
115
|
+
const coloredValue = label === '厂商'
|
|
116
|
+
? chalk.white.bold(value)
|
|
117
|
+
: label.includes('模型')
|
|
118
|
+
? chalk.yellow(value)
|
|
119
|
+
: label === 'Base URL'
|
|
120
|
+
? chalk.cyan(value)
|
|
121
|
+
: label === '当前终端' && value === '未生效'
|
|
122
|
+
? chalk.yellow(value)
|
|
123
|
+
: value;
|
|
124
|
+
return `${chalk.dim(label + ':')} ${coloredValue}`;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
for (const warning of view.warnings) {
|
|
128
|
+
if (warning.includes('建议运行')) {
|
|
129
|
+
const [summary, action] = warning.split(',建议运行 ');
|
|
130
|
+
lines.push(`${chalk.yellow('提示:')} ${chalk.yellow(summary)}`);
|
|
131
|
+
lines.push(`${chalk.yellow('建议:')} ${chalk.yellow(`运行 ${action}`)}`);
|
|
132
|
+
} else {
|
|
133
|
+
lines.push(`${chalk.yellow('提示:')} ${chalk.yellow(warning)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
85
136
|
|
|
86
137
|
console.log(boxen(lines.join('\n'), {
|
|
87
138
|
title: chalk.bold('当前配置'),
|
|
@@ -179,7 +230,7 @@ program
|
|
|
179
230
|
chalk.bold('当前配置\n\n') +
|
|
180
231
|
chalk.dim('厂商 ') + chalk.white(existingProvider?.name || existing.provider) + '\n' +
|
|
181
232
|
chalk.dim('模型 ') + chalk.yellow(existing.model) + '\n' +
|
|
182
|
-
chalk.dim('Key ') + chalk.dim(existing.apiKey
|
|
233
|
+
chalk.dim('Key ') + chalk.dim(existing.apiKey ? '已保存' : '缺失'),
|
|
183
234
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'yellow', margin: { top: 1, bottom: 1 } }
|
|
184
235
|
));
|
|
185
236
|
const overwrite = await confirm({ message: '覆盖现有配置?', default: false });
|
|
@@ -238,9 +289,10 @@ program
|
|
|
238
289
|
const spinner = ora('写入配置...').start();
|
|
239
290
|
let result, file;
|
|
240
291
|
try {
|
|
241
|
-
const
|
|
292
|
+
const fastModel = resolveFastModel(provider, model);
|
|
293
|
+
const cfg = { provider: providerKey, model, fastModel, apiKey, baseUrl: provider.baseUrl };
|
|
242
294
|
saveConfig(cfg);
|
|
243
|
-
({ result, file } = writeEnvToZshrc(provider.baseUrl, apiKey));
|
|
295
|
+
({ result, file } = writeEnvToZshrc(provider.baseUrl, apiKey, model, fastModel));
|
|
244
296
|
spinner.succeed(chalk.green(result === 'updated' ? `环境变量已更新 → ${file}` : `环境变量已写入 → ${file}`));
|
|
245
297
|
} catch (e) {
|
|
246
298
|
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
@@ -251,7 +303,7 @@ program
|
|
|
251
303
|
console.log(boxen(
|
|
252
304
|
chalk.bold('配置完成!\n\n') +
|
|
253
305
|
chalk.dim('ANTHROPIC_BASE_URL ') + chalk.cyan(provider.baseUrl) + '\n' +
|
|
254
|
-
chalk.dim('ANTHROPIC_API_KEY ') + chalk.cyan(
|
|
306
|
+
chalk.dim('ANTHROPIC_API_KEY ') + chalk.cyan('已保存') + '\n\n' +
|
|
255
307
|
chalk.white('下次直接输入 ') + chalk.cyan.bold('claude') + chalk.white(' 即可使用'),
|
|
256
308
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
257
309
|
));
|
|
@@ -262,7 +314,7 @@ program
|
|
|
262
314
|
|
|
263
315
|
spawn('claude', [], {
|
|
264
316
|
stdio: 'inherit',
|
|
265
|
-
env: { ...process.env,
|
|
317
|
+
env: { ...process.env, ...buildClaudeEnv({ provider: providerKey, baseUrl: provider.baseUrl, apiKey, model, fastModel: resolveFastModel(provider, model) }) },
|
|
266
318
|
}).on('error', () => {
|
|
267
319
|
console.log(chalk.yellow('\nClaude Code 未找到,请先运行: claw install-claude'));
|
|
268
320
|
});
|
|
@@ -282,6 +334,12 @@ program
|
|
|
282
334
|
console.log(chalk.red('\n未配置,请先运行: claw setup\n'));
|
|
283
335
|
return;
|
|
284
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
|
+
}
|
|
285
343
|
|
|
286
344
|
const providerKey = await select({ loop: false,
|
|
287
345
|
message: chalk.cyan('选择 AI 厂商'),
|
|
@@ -330,9 +388,10 @@ program
|
|
|
330
388
|
if (model === '__BACK__') return;
|
|
331
389
|
|
|
332
390
|
const spinner = ora('切换中...').start();
|
|
333
|
-
const
|
|
391
|
+
const fastModel = resolveFastModel(provider, model);
|
|
392
|
+
const newConfig = { ...config, provider: providerKey, model, fastModel, baseUrl: provider.baseUrl, apiKey };
|
|
334
393
|
saveConfig(newConfig);
|
|
335
|
-
const { file } = writeEnvToZshrc(provider.baseUrl, apiKey);
|
|
394
|
+
const { file } = writeEnvToZshrc(provider.baseUrl, apiKey, model, fastModel);
|
|
336
395
|
await new Promise(r => setTimeout(r, 300));
|
|
337
396
|
spinner.succeed(chalk.green(`已切换至 ${provider.name} · ${model}`));
|
|
338
397
|
console.log(chalk.dim(`运行 source ${file} 生效,或重新开一个终端`));
|
|
@@ -343,19 +402,57 @@ program
|
|
|
343
402
|
.description('查看当前配置和 Key 有效性')
|
|
344
403
|
.action(showStatus);
|
|
345
404
|
|
|
405
|
+
program
|
|
406
|
+
.command('reset')
|
|
407
|
+
.description('恢复默认(清除所有 clawai 配置)')
|
|
408
|
+
.action(async () => {
|
|
409
|
+
const chalk = (await import('chalk')).default;
|
|
410
|
+
const ora = (await import('ora')).default;
|
|
411
|
+
const boxen = (await import('boxen')).default;
|
|
412
|
+
|
|
413
|
+
console.log(await getBanner());
|
|
414
|
+
|
|
415
|
+
const yes = await confirm({
|
|
416
|
+
message: chalk.red('确定要清除所有 clawai 配置吗?此操作不可撤销'),
|
|
417
|
+
default: false,
|
|
418
|
+
});
|
|
419
|
+
if (!yes) {
|
|
420
|
+
console.log(chalk.dim('已取消'));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const spinner = ora('清除中...').start();
|
|
425
|
+
const cleared = resetConfig();
|
|
426
|
+
await new Promise(r => setTimeout(r, 300));
|
|
427
|
+
|
|
428
|
+
if (cleared.length === 0) {
|
|
429
|
+
spinner.warn(chalk.yellow('没有找到任何配置,无需清除'));
|
|
430
|
+
} else {
|
|
431
|
+
spinner.succeed(chalk.green('已恢复默认'));
|
|
432
|
+
console.log(boxen(
|
|
433
|
+
chalk.bold('已清除以下文件中的 clawai 配置:\n\n') +
|
|
434
|
+
cleared.map(f => chalk.cyan(' • ' + f)).join('\n') +
|
|
435
|
+
'\n\n' + chalk.dim('注:当前终端的环境变量还在内存中,重开终端或 unset 才彻底清除'),
|
|
436
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
437
|
+
));
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
|
|
346
441
|
async function renderStatusBar(apiStatus) {
|
|
347
442
|
const chalk = (await import('chalk')).default;
|
|
348
443
|
const config = loadConfig();
|
|
349
|
-
const claudeInstalled = (
|
|
350
|
-
try { execSync('claude --version', { stdio: 'pipe' }); return true; } catch { return false; }
|
|
351
|
-
})();
|
|
444
|
+
const claudeInstalled = isClaudeInstalled();
|
|
352
445
|
|
|
353
446
|
const claudeIcon = claudeInstalled ? chalk.green('●') : chalk.red('●');
|
|
354
447
|
const claudeText = chalk.dim('Claude');
|
|
355
448
|
|
|
356
449
|
let cfgPart;
|
|
357
450
|
if (config) {
|
|
358
|
-
const
|
|
451
|
+
const view = buildStatusView(config, {
|
|
452
|
+
apiStatus,
|
|
453
|
+
claudeInstalled,
|
|
454
|
+
env: process.env,
|
|
455
|
+
});
|
|
359
456
|
let dot;
|
|
360
457
|
if (apiStatus === true) dot = chalk.green('●');
|
|
361
458
|
else if (apiStatus === false) dot = chalk.red('●');
|
|
@@ -365,7 +462,9 @@ async function renderStatusBar(apiStatus) {
|
|
|
365
462
|
: apiStatus === false ? chalk.red(' API ✘')
|
|
366
463
|
: apiStatus === null ? chalk.yellow(' 网络异常')
|
|
367
464
|
: '';
|
|
368
|
-
|
|
465
|
+
const envTag = view.envActive ? chalk.green(' env ✓') : chalk.yellow(' env 未生效');
|
|
466
|
+
const legacyTag = view.warnings.length > 0 ? chalk.yellow(' 旧模型') : '';
|
|
467
|
+
cfgPart = `${dot} ${chalk.white(view.providerName)}${apiTag}${envTag}${legacyTag}\n ${chalk.dim('主 ')}${chalk.yellow(view.mainModel)}${chalk.dim(' 快 ')}${chalk.yellow(view.fastModel)}`;
|
|
369
468
|
} else {
|
|
370
469
|
cfgPart = chalk.red('●') + ' ' + chalk.dim('未配置');
|
|
371
470
|
}
|
|
@@ -383,7 +482,8 @@ async function runMenu() {
|
|
|
383
482
|
|
|
384
483
|
const config = loadConfig();
|
|
385
484
|
let apiStatus; // undefined = skipped, true/false/null = checked
|
|
386
|
-
|
|
485
|
+
const configProblem = config ? getConfigValidationMessage(config) : null;
|
|
486
|
+
if (config && !configProblem) {
|
|
387
487
|
const spinner = ora('正在检测 API 是否通畅...').start();
|
|
388
488
|
apiStatus = await validateKey(config);
|
|
389
489
|
if (apiStatus === true) spinner.succeed('API 连接正常');
|
|
@@ -391,18 +491,24 @@ async function runMenu() {
|
|
|
391
491
|
else spinner.warn('网络异常,无法连接 API');
|
|
392
492
|
}
|
|
393
493
|
|
|
394
|
-
|
|
494
|
+
if (configProblem) {
|
|
495
|
+
console.log(chalk.red(` ● 配置无效:${configProblem}`));
|
|
496
|
+
console.log(chalk.dim(' 请先选择“首次配置 API Key 和模型”重新配置'));
|
|
497
|
+
} else {
|
|
498
|
+
console.log(await renderStatusBar(apiStatus));
|
|
499
|
+
}
|
|
395
500
|
console.log();
|
|
396
501
|
|
|
397
502
|
const action = await select({ loop: false,
|
|
398
503
|
message: chalk.cyan('选择操作'),
|
|
399
504
|
choices: [
|
|
400
|
-
{ name: '🤖 启动 Claude Code', value: 'launch', disabled: !config && '需先完成配置' },
|
|
505
|
+
{ name: '🤖 启动 Claude Code', value: 'launch', disabled: (!config || configProblem) && '需先完成配置' },
|
|
401
506
|
{ name: '📦 安装 Claude Code', value: 'install' },
|
|
402
507
|
{ name: config ? '⚙️ 重新配置(输入新的 API Key)' : '⚙️ 首次配置 API Key 和模型', value: 'setup' },
|
|
403
|
-
{ name: '🔄
|
|
508
|
+
{ name: '🔄 切换厂商或模型', value: 'switch', disabled: (!config || configProblem) && '需先完成配置' },
|
|
404
509
|
{ name: '📊 查看当前配置', value: 'status', disabled: !config && '需先完成配置' },
|
|
405
|
-
{ name: '🔁 重新检测 API', value: 'recheck', disabled: !config && '需先完成配置' },
|
|
510
|
+
{ name: '🔁 重新检测 API', value: 'recheck', disabled: (!config || configProblem) && '需先完成配置' },
|
|
511
|
+
{ name: '🗑 恢复默认(清除所有配置)', value: 'reset', disabled: !config && '没有可清除的配置' },
|
|
406
512
|
{ name: '退出', value: 'exit' },
|
|
407
513
|
],
|
|
408
514
|
});
|
|
@@ -416,12 +522,12 @@ async function runMenu() {
|
|
|
416
522
|
|
|
417
523
|
if (action === 'launch') {
|
|
418
524
|
const cfg = loadConfig();
|
|
419
|
-
if (!cfg) continue;
|
|
525
|
+
if (!cfg || getConfigValidationMessage(cfg)) continue;
|
|
420
526
|
// 启动 claude 后等它退出,然后回菜单
|
|
421
527
|
await new Promise((resolve) => {
|
|
422
528
|
const child = spawn('claude', [], {
|
|
423
529
|
stdio: 'inherit',
|
|
424
|
-
env: { ...process.env,
|
|
530
|
+
env: { ...process.env, ...buildClaudeEnv(cfg) },
|
|
425
531
|
});
|
|
426
532
|
child.on('error', () => {
|
|
427
533
|
console.log(chalk.yellow('\nClaude Code 未找到,请先选择"安装 Claude Code"'));
|
|
@@ -437,6 +543,7 @@ async function runMenu() {
|
|
|
437
543
|
setup: 'setup',
|
|
438
544
|
switch: 'switch',
|
|
439
545
|
status: 'status',
|
|
546
|
+
reset: 'reset',
|
|
440
547
|
};
|
|
441
548
|
|
|
442
549
|
// 执行子命令
|
package/index.js
ADDED
package/lib/config.js
CHANGED
|
@@ -10,9 +10,10 @@ const PROVIDERS = {
|
|
|
10
10
|
name: 'DeepSeek',
|
|
11
11
|
baseUrl: 'https://api.deepseek.com/anthropic',
|
|
12
12
|
modelsUrl: 'https://api.deepseek.com/v1/models',
|
|
13
|
+
fastModel: 'deepseek-v4-flash',
|
|
13
14
|
models: [
|
|
14
15
|
{ name: 'DeepSeek V4 Flash(快速)', value: 'deepseek-v4-flash' },
|
|
15
|
-
{ name: 'DeepSeek V4 Pro(强力)', value: 'deepseek-v4-pro' },
|
|
16
|
+
{ name: 'DeepSeek V4 Pro(强力)', value: 'deepseek-v4-pro[1m]' },
|
|
16
17
|
],
|
|
17
18
|
},
|
|
18
19
|
qwen: {
|
|
@@ -105,14 +106,85 @@ function loadConfig() {
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
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
|
+
|
|
108
124
|
function saveConfig(config) {
|
|
109
125
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
126
|
+
try {
|
|
127
|
+
fs.chmodSync(CONFIG_FILE, 0o600);
|
|
128
|
+
} catch {}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function providerKeyFromBaseUrl(baseUrl) {
|
|
132
|
+
return Object.entries(PROVIDERS).find(([, provider]) => provider.baseUrl === baseUrl)?.[0];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveFastModel(provider, model) {
|
|
136
|
+
if (/flash|turbo|haiku|air|lite/i.test(model)) return model;
|
|
137
|
+
return provider?.fastModel || model;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function buildClaudeEnv({ provider, baseUrl, apiKey, model, fastModel }) {
|
|
141
|
+
const resolvedFastModel = fastModel || resolveFastModel(PROVIDERS[provider], model);
|
|
142
|
+
return {
|
|
143
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
144
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
145
|
+
ANTHROPIC_API_KEY: apiKey,
|
|
146
|
+
ANTHROPIC_MODEL: model,
|
|
147
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: model,
|
|
148
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: model,
|
|
149
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: resolvedFastModel,
|
|
150
|
+
CLAUDE_CODE_SUBAGENT_MODEL: resolvedFastModel,
|
|
151
|
+
CLAUDE_CODE_EFFORT_LEVEL: 'max',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function classifyValidationStatus(statusCode) {
|
|
156
|
+
if (statusCode >= 200 && statusCode < 300) return true;
|
|
157
|
+
if (statusCode === 401 || statusCode === 403) return false;
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function shellQuote(value) {
|
|
162
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 构造完整的 clawai 环境变量块
|
|
166
|
+
function buildEnvBlock(baseUrl, apiKey, model, fastModel) {
|
|
167
|
+
const provider = providerKeyFromBaseUrl(baseUrl);
|
|
168
|
+
const env = buildClaudeEnv({ provider, baseUrl, apiKey, model, fastModel });
|
|
169
|
+
return [
|
|
170
|
+
'',
|
|
171
|
+
'# clawai-start',
|
|
172
|
+
`export ANTHROPIC_BASE_URL=${shellQuote(env.ANTHROPIC_BASE_URL)}`,
|
|
173
|
+
`export ANTHROPIC_AUTH_TOKEN=${shellQuote(env.ANTHROPIC_AUTH_TOKEN)}`,
|
|
174
|
+
`export ANTHROPIC_API_KEY=${shellQuote(env.ANTHROPIC_API_KEY)}`,
|
|
175
|
+
`export ANTHROPIC_MODEL=${shellQuote(env.ANTHROPIC_MODEL)}`,
|
|
176
|
+
`export ANTHROPIC_DEFAULT_OPUS_MODEL=${shellQuote(env.ANTHROPIC_DEFAULT_OPUS_MODEL)}`,
|
|
177
|
+
`export ANTHROPIC_DEFAULT_SONNET_MODEL=${shellQuote(env.ANTHROPIC_DEFAULT_SONNET_MODEL)}`,
|
|
178
|
+
`export ANTHROPIC_DEFAULT_HAIKU_MODEL=${shellQuote(env.ANTHROPIC_DEFAULT_HAIKU_MODEL)}`,
|
|
179
|
+
`export CLAUDE_CODE_SUBAGENT_MODEL=${shellQuote(env.CLAUDE_CODE_SUBAGENT_MODEL)}`,
|
|
180
|
+
`export CLAUDE_CODE_EFFORT_LEVEL=${shellQuote(env.CLAUDE_CODE_EFFORT_LEVEL)}`,
|
|
181
|
+
'# clawai-end',
|
|
182
|
+
'',
|
|
183
|
+
].join('\n');
|
|
110
184
|
}
|
|
111
185
|
|
|
112
186
|
// 写入或更新 shell 配置文件中的环境变量块
|
|
113
|
-
function writeEnvToZshrc(baseUrl, apiKey) {
|
|
114
|
-
// 检测用户 shell,自动选配置文件
|
|
115
|
-
// macOS bash 默认读 ~/.bash_profile(登录 shell),Linux 读 ~/.bashrc
|
|
187
|
+
function writeEnvToZshrc(baseUrl, apiKey, model, fastModel) {
|
|
116
188
|
const shell = process.env.SHELL || '';
|
|
117
189
|
let rcFile;
|
|
118
190
|
if (shell.includes('bash')) {
|
|
@@ -123,22 +195,61 @@ function writeEnvToZshrc(baseUrl, apiKey) {
|
|
|
123
195
|
rcFile = path.join(os.homedir(), '.zshrc');
|
|
124
196
|
}
|
|
125
197
|
|
|
126
|
-
|
|
127
|
-
const safeKey = `"${apiKey.replace(/"/g, '\\"')}"`;
|
|
128
|
-
const block = `\n# clawai\nexport ANTHROPIC_BASE_URL=${baseUrl}\nexport ANTHROPIC_API_KEY=${safeKey}\n`;
|
|
198
|
+
const block = buildEnvBlock(baseUrl, apiKey, model, fastModel);
|
|
129
199
|
const current = fs.existsSync(rcFile) ? fs.readFileSync(rcFile, 'utf8') : '';
|
|
130
200
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
201
|
+
// 兼容旧版(# clawai 单行块)和新版(# clawai-start...# clawai-end 多行块)
|
|
202
|
+
const cleaned = current
|
|
203
|
+
.replace(/\n?# clawai-start[\s\S]*?# clawai-end\n?/g, '')
|
|
204
|
+
.replace(/\n?# clawai\nexport ANTHROPIC_BASE_URL=[^\n]*\nexport ANTHROPIC_API_KEY=[^\n]*\n?/g, '');
|
|
205
|
+
|
|
206
|
+
fs.writeFileSync(rcFile, cleaned + block);
|
|
207
|
+
return { result: cleaned !== current ? 'updated' : 'added', file: rcFile };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 清除所有 clawai 配置(配置文件 + shell 环境变量块)
|
|
211
|
+
function resetConfig() {
|
|
212
|
+
const cleared = [];
|
|
213
|
+
|
|
214
|
+
// 删配置文件
|
|
215
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
216
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
217
|
+
cleared.push(CONFIG_FILE);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 清理所有可能的 rc 文件中的 clawai 块
|
|
221
|
+
const rcFiles = [
|
|
222
|
+
path.join(os.homedir(), '.zshrc'),
|
|
223
|
+
path.join(os.homedir(), '.bashrc'),
|
|
224
|
+
path.join(os.homedir(), '.bash_profile'),
|
|
225
|
+
];
|
|
226
|
+
for (const f of rcFiles) {
|
|
227
|
+
if (!fs.existsSync(f)) continue;
|
|
228
|
+
const content = fs.readFileSync(f, 'utf8');
|
|
229
|
+
if (!content.includes('# clawai')) continue;
|
|
230
|
+
const cleaned = content
|
|
231
|
+
.replace(/\n?# clawai-start[\s\S]*?# clawai-end\n?/g, '')
|
|
232
|
+
.replace(/\n?# clawai\nexport ANTHROPIC_BASE_URL=[^\n]*\nexport ANTHROPIC_API_KEY=[^\n]*\n?/g, '');
|
|
233
|
+
if (cleaned === content) continue;
|
|
234
|
+
fs.writeFileSync(f, cleaned);
|
|
235
|
+
cleared.push(f);
|
|
141
236
|
}
|
|
237
|
+
|
|
238
|
+
return cleared;
|
|
142
239
|
}
|
|
143
240
|
|
|
144
|
-
module.exports = {
|
|
241
|
+
module.exports = {
|
|
242
|
+
loadConfig,
|
|
243
|
+
saveConfig,
|
|
244
|
+
writeEnvToZshrc,
|
|
245
|
+
fetchModels,
|
|
246
|
+
resetConfig,
|
|
247
|
+
validateConfig,
|
|
248
|
+
resolveFastModel,
|
|
249
|
+
providerKeyFromBaseUrl,
|
|
250
|
+
buildClaudeEnv,
|
|
251
|
+
buildEnvBlock,
|
|
252
|
+
classifyValidationStatus,
|
|
253
|
+
PROVIDERS,
|
|
254
|
+
CONFIG_FILE,
|
|
255
|
+
};
|
package/lib/panel.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const { buildClaudeEnv, PROVIDERS } = require('./config');
|
|
2
|
+
|
|
3
|
+
function apiStatusText(apiStatus) {
|
|
4
|
+
if (apiStatus === true) return 'API 正常';
|
|
5
|
+
if (apiStatus === false) return 'API Key 无效';
|
|
6
|
+
if (apiStatus === null) return '网络/服务异常';
|
|
7
|
+
return '未检测';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isEnvActive(config, env) {
|
|
11
|
+
const expected = buildClaudeEnv(config);
|
|
12
|
+
return Object.entries(expected).every(([key, value]) => env[key] === value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildStatusView(config, options = {}) {
|
|
16
|
+
const provider = PROVIDERS[config.provider];
|
|
17
|
+
const providerName = provider?.name || config.provider;
|
|
18
|
+
const claudeInstalled = options.claudeInstalled === true;
|
|
19
|
+
const env = options.env || {};
|
|
20
|
+
const expectedEnv = buildClaudeEnv(config);
|
|
21
|
+
const mainModel = expectedEnv.ANTHROPIC_MODEL;
|
|
22
|
+
const fastModel = expectedEnv.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
23
|
+
const envActive = isEnvActive(config, env);
|
|
24
|
+
const warnings = [];
|
|
25
|
+
|
|
26
|
+
if (config.provider === 'deepseek' && config.model === 'deepseek-v4-pro') {
|
|
27
|
+
warnings.push('检测到旧 DeepSeek 模型名,建议运行 claw switch 更新到 deepseek-v4-pro[1m]');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
providerName,
|
|
32
|
+
mainModel,
|
|
33
|
+
fastModel,
|
|
34
|
+
envActive,
|
|
35
|
+
warnings,
|
|
36
|
+
lines: [
|
|
37
|
+
{ label: '厂商', value: providerName },
|
|
38
|
+
{ label: '主模型', value: mainModel },
|
|
39
|
+
{ label: '快速模型', value: fastModel },
|
|
40
|
+
{ label: 'API Key', value: '已保存' },
|
|
41
|
+
{ label: 'API 状态', value: apiStatusText(options.apiStatus) },
|
|
42
|
+
{ label: 'Claude Code', value: claudeInstalled ? '已安装' : '未检测到' },
|
|
43
|
+
{ label: '当前终端', value: envActive ? '已生效' : '未生效' },
|
|
44
|
+
{ label: 'Base URL', value: config.baseUrl },
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { buildStatusView, apiStatusText, isEnvActive };
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yingclaw",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Claude Code × 国产大模型一键接入:DeepSeek、Qwen、MiniMax、GLM、MiMo",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"claw": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"start": "node bin/cli.js"
|
|
10
|
+
"start": "node bin/cli.js",
|
|
11
|
+
"test": "node --test"
|
|
11
12
|
},
|
|
12
13
|
"keywords": [
|
|
13
14
|
"claude",
|