yingclaw 1.7.2 → 1.7.8
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 +22 -3
- package/bin/cli.js +130 -32
- package/index.js +1 -0
- package/lib/config.js +115 -38
- package/lib/desktop.js +161 -0
- package/lib/panel.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Claude Code × 国产大模型,一键接入。
|
|
4
4
|
|
|
5
|
-
支持 DeepSeek、阿里云百炼(Qwen)、MiniMax、智谱 GLM、小米 MiMo,也支持自定义 Anthropic 兼容接口,无需梯子即可使用 Claude Code。
|
|
5
|
+
支持 DeepSeek、Kimi、阿里云百炼(Qwen)、MiniMax、智谱 GLM、小米 MiMo,也支持自定义 Anthropic 兼容接口,无需梯子即可使用 Claude Code。
|
|
6
6
|
|
|
7
7
|
## 安装
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ npm install -g yingclaw
|
|
|
14
14
|
|
|
15
15
|
## 使用步骤
|
|
16
16
|
|
|
17
|
-
>
|
|
17
|
+
> 支持 macOS / Linux / Windows / WSL。macOS、Linux、WSL 会写入 zsh/bash 配置文件;Windows 会写入用户级环境变量,重新打开 PowerShell / CMD 后生效。
|
|
18
18
|
|
|
19
19
|
**第一步:安装 Claude Code**
|
|
20
20
|
```bash
|
|
@@ -28,6 +28,13 @@ claw setup
|
|
|
28
28
|
```
|
|
29
29
|
选择厂商 → 输入 API Key → 选择模型,自动写入环境变量,配置完成后自动启动 Claude。
|
|
30
30
|
|
|
31
|
+
**可选:配置 Claude 桌面应用**
|
|
32
|
+
```bash
|
|
33
|
+
claw desktop
|
|
34
|
+
```
|
|
35
|
+
将当前模型配置写入 Claude Desktop 的第三方推理(Cowork on 3P)本地配置。配置后需要完全退出并重新打开 Claude 桌面应用。
|
|
36
|
+
macOS 会询问是否自动重启 Claude 桌面应用;Windows 需要手动重新打开。
|
|
37
|
+
|
|
31
38
|
选择“自定义 Anthropic 兼容接口”时,需要输入:
|
|
32
39
|
|
|
33
40
|
- `ANTHROPIC_BASE_URL`
|
|
@@ -35,6 +42,8 @@ claw setup
|
|
|
35
42
|
|
|
36
43
|
工具会根据 Base URL 自动尝试获取模型列表;如果获取失败,则手动输入主模型和快速模型。
|
|
37
44
|
|
|
45
|
+
注意:模型列表能获取不代表一定可用于 Claude Code 或 Claude 桌面应用。自定义接口还必须支持 Anthropic `/v1/messages`,否则请求会被网关拒绝。Claude 桌面应用的 Gateway Base URL 还必须使用 HTTPS。
|
|
46
|
+
|
|
38
47
|
**第三步:以后直接用**
|
|
39
48
|
```bash
|
|
40
49
|
claude
|
|
@@ -45,6 +54,7 @@ claude
|
|
|
45
54
|
| 厂商 | 模型 |
|
|
46
55
|
|------|------|
|
|
47
56
|
| DeepSeek | V4 Flash、V4 Pro |
|
|
57
|
+
| Kimi / Moonshot | Kimi K2.5 |
|
|
48
58
|
| 阿里云百炼 | Qwen3 Max、Plus、Flash |
|
|
49
59
|
| MiniMax | M2.7、M2.7 Turbo、M2.5 |
|
|
50
60
|
| 智谱 GLM | GLM-4.7、GLM-5.1、GLM-5 Turbo、GLM-4.5 Air |
|
|
@@ -55,7 +65,9 @@ claude
|
|
|
55
65
|
|
|
56
66
|
```bash
|
|
57
67
|
claw switch # 快速切换模型(无需重新输入 Key)
|
|
68
|
+
claw desktop # 配置 Claude 桌面应用第三方推理
|
|
58
69
|
claw status # 查看当前配置,验证 Key 是否有效
|
|
70
|
+
claw reset # 清除配置和环境变量
|
|
59
71
|
```
|
|
60
72
|
|
|
61
73
|
## 卸载
|
|
@@ -66,7 +78,7 @@ npm uninstall -g yingclaw
|
|
|
66
78
|
|
|
67
79
|
## 原理
|
|
68
80
|
|
|
69
|
-
各厂商均原生支持 Anthropic API 格式。本工具会自动写入 Claude Code
|
|
81
|
+
各厂商均原生支持 Anthropic API 格式。本工具会自动写入 Claude Code 所需的环境变量。macOS / Linux / WSL 写入 shell 配置文件,Windows 写入用户级环境变量,包括:
|
|
70
82
|
|
|
71
83
|
- `ANTHROPIC_BASE_URL`
|
|
72
84
|
- `ANTHROPIC_AUTH_TOKEN`
|
|
@@ -80,6 +92,13 @@ npm uninstall -g yingclaw
|
|
|
80
92
|
|
|
81
93
|
以 DeepSeek 为例,主模型默认使用 `deepseek-v4-pro[1m]`,Haiku/Subagent 快速模型使用 `deepseek-v4-flash`。如果在线模型列表获取失败,会回退到内置默认列表。
|
|
82
94
|
|
|
95
|
+
`claw desktop` 会额外写入 Claude Desktop 第三方推理配置:
|
|
96
|
+
|
|
97
|
+
- macOS:`~/Library/Application Support/Claude-3p/claude_desktop_config.json`
|
|
98
|
+
- Windows:`%APPDATA%\Claude-3p\claude_desktop_config.json`
|
|
99
|
+
|
|
100
|
+
写入的 `enterpriseConfig` 使用 `inferenceProvider=gateway`、`inferenceGatewayAuthScheme=bearer`,并把当前模型写入 `inferenceModels`。
|
|
101
|
+
|
|
83
102
|
## License
|
|
84
103
|
|
|
85
104
|
MIT
|
package/bin/cli.js
CHANGED
|
@@ -10,16 +10,17 @@ const {
|
|
|
10
10
|
fetchModelsFromBaseUrl,
|
|
11
11
|
resetConfig,
|
|
12
12
|
validateConfig,
|
|
13
|
+
normalizeAnthropicBaseUrl,
|
|
13
14
|
resolveFastModel,
|
|
14
15
|
buildClaudeEnv,
|
|
15
16
|
classifyValidationStatus,
|
|
16
17
|
PROVIDERS,
|
|
17
18
|
} = require('../lib/config');
|
|
18
19
|
const { execSync, spawn, spawnSync } = require('child_process');
|
|
19
|
-
const https = require('https');
|
|
20
20
|
const pkg = require('../package.json');
|
|
21
21
|
const { buildMenuStatusLines, buildStatusView } = require('../lib/panel');
|
|
22
22
|
const { buildClaudeInstallCommand } = require('../lib/install');
|
|
23
|
+
const { clearClaudeDesktopConfig, restartClaudeDesktop, writeClaudeDesktopConfig } = require('../lib/desktop');
|
|
23
24
|
|
|
24
25
|
const program = new Command();
|
|
25
26
|
|
|
@@ -36,37 +37,39 @@ async function getBanner() {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
async function validateKey(config) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
hostname: url.hostname,
|
|
53
|
-
path: url.pathname,
|
|
40
|
+
let url;
|
|
41
|
+
try {
|
|
42
|
+
url = `${normalizeAnthropicBaseUrl(config.baseUrl)}/v1/messages`;
|
|
43
|
+
new URL(url);
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
const timeout = setTimeout(() => controller.abort(), 8000);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetch(url, {
|
|
54
53
|
method: 'POST',
|
|
55
|
-
|
|
54
|
+
signal: controller.signal,
|
|
56
55
|
headers: {
|
|
57
56
|
'content-type': 'application/json',
|
|
57
|
+
authorization: `Bearer ${config.apiKey}`,
|
|
58
58
|
'x-api-key': config.apiKey,
|
|
59
59
|
'anthropic-version': '2023-06-01',
|
|
60
|
-
'content-length': body.length,
|
|
61
60
|
},
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
body: JSON.stringify({
|
|
62
|
+
model: config.model,
|
|
63
|
+
max_tokens: 1,
|
|
64
|
+
messages: [{ role: 'user', content: 'hi' }],
|
|
65
|
+
}),
|
|
64
66
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
return classifyValidationStatus(res.status);
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
} finally {
|
|
71
|
+
clearTimeout(timeout);
|
|
72
|
+
}
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
function getConfigValidationMessage(config) {
|
|
@@ -92,6 +95,24 @@ function isValidUrl(value) {
|
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
|
|
98
|
+
function getActivationHint(file) {
|
|
99
|
+
if (file === 'Windows 用户环境变量') {
|
|
100
|
+
return '重新打开 PowerShell / CMD 后生效';
|
|
101
|
+
}
|
|
102
|
+
return `运行 source ${file} 生效,或重新开一个终端`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getStorageHint(file) {
|
|
106
|
+
if (file === 'Windows 用户环境变量') {
|
|
107
|
+
return `⚠ API Key 以明文存储在 ${file} 和用户主目录下的 .clawai.json`;
|
|
108
|
+
}
|
|
109
|
+
return `⚠ API Key 以明文存储在 ${file} 和 ~/.clawai.json`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getDesktopRestartHint() {
|
|
113
|
+
return '请完全退出 Claude 桌面应用后重新打开,第三方模型配置才会生效';
|
|
114
|
+
}
|
|
115
|
+
|
|
95
116
|
async function promptModelFromChoices({ chalk, choices, message, backLabel = '↩ 返回上一步', allowManual = true }) {
|
|
96
117
|
const selected = await select({ loop: false,
|
|
97
118
|
message: chalk.cyan(message),
|
|
@@ -123,7 +144,7 @@ async function configureCustomProvider({ chalk, ora, existingConfig }) {
|
|
|
123
144
|
message: chalk.cyan('Anthropic Base URL'),
|
|
124
145
|
default: existingConfig?.provider === 'custom' ? existingConfig.baseUrl : undefined,
|
|
125
146
|
validate: (v) => v.trim().length > 0 && isValidUrl(v.trim()) ? true : '请输入有效 URL',
|
|
126
|
-
}).then(v => v.trim()
|
|
147
|
+
}).then(v => normalizeAnthropicBaseUrl(v.trim()));
|
|
127
148
|
|
|
128
149
|
let apiKey = existingConfig?.provider === 'custom' ? existingConfig.apiKey : '';
|
|
129
150
|
if (apiKey) {
|
|
@@ -393,7 +414,7 @@ program
|
|
|
393
414
|
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
394
415
|
return;
|
|
395
416
|
}
|
|
396
|
-
console.log(chalk.dim(
|
|
417
|
+
console.log(chalk.dim(getStorageHint(file)));
|
|
397
418
|
|
|
398
419
|
console.log(boxen(
|
|
399
420
|
chalk.bold('配置完成!\n\n') +
|
|
@@ -455,7 +476,7 @@ program
|
|
|
455
476
|
const { file } = writeEnvToZshrc(customConfig.baseUrl, customConfig.apiKey, customConfig.model, customConfig.fastModel);
|
|
456
477
|
await new Promise(r => setTimeout(r, 300));
|
|
457
478
|
spinner.succeed(chalk.green(`已切换至 ${customConfig.providerName} · ${customConfig.model}`));
|
|
458
|
-
console.log(chalk.dim(
|
|
479
|
+
console.log(chalk.dim(getActivationHint(file)));
|
|
459
480
|
return;
|
|
460
481
|
}
|
|
461
482
|
|
|
@@ -501,7 +522,7 @@ program
|
|
|
501
522
|
const { file } = writeEnvToZshrc(provider.baseUrl, apiKey, model, fastModel);
|
|
502
523
|
await new Promise(r => setTimeout(r, 300));
|
|
503
524
|
spinner.succeed(chalk.green(`已切换至 ${provider.name} · ${model}`));
|
|
504
|
-
console.log(chalk.dim(
|
|
525
|
+
console.log(chalk.dim(getActivationHint(file)));
|
|
505
526
|
});
|
|
506
527
|
|
|
507
528
|
program
|
|
@@ -509,6 +530,74 @@ program
|
|
|
509
530
|
.description('查看当前配置和 Key 有效性')
|
|
510
531
|
.action(showStatus);
|
|
511
532
|
|
|
533
|
+
program
|
|
534
|
+
.command('desktop')
|
|
535
|
+
.description('配置 Claude 桌面应用使用当前模型')
|
|
536
|
+
.action(async () => {
|
|
537
|
+
const chalk = (await import('chalk')).default;
|
|
538
|
+
const ora = (await import('ora')).default;
|
|
539
|
+
const boxen = (await import('boxen')).default;
|
|
540
|
+
|
|
541
|
+
console.log(await getBanner());
|
|
542
|
+
|
|
543
|
+
const config = loadConfig();
|
|
544
|
+
if (!config) {
|
|
545
|
+
console.log(chalk.red('\n未配置,请先运行: claw setup\n'));
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const configProblem = getConfigValidationMessage(config);
|
|
549
|
+
if (configProblem) {
|
|
550
|
+
console.log(chalk.red(`\n配置无效:${configProblem}`));
|
|
551
|
+
console.log(chalk.dim('请运行 claw setup 重新配置。\n'));
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (process.platform !== 'darwin' && process.platform !== 'win32') {
|
|
556
|
+
console.log(chalk.yellow('\nClaude 桌面应用 3P 配置目前仅支持 macOS / Windows。\n'));
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const spinner = ora('写入 Claude 桌面应用配置...').start();
|
|
561
|
+
let result;
|
|
562
|
+
try {
|
|
563
|
+
result = writeClaudeDesktopConfig(config);
|
|
564
|
+
if (result.result === 'unsupported') {
|
|
565
|
+
spinner.warn(chalk.yellow('当前系统暂不支持自动配置 Claude 桌面应用'));
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
spinner.succeed(chalk.green(`Claude 桌面应用配置已写入 → ${result.file}`));
|
|
569
|
+
} catch (e) {
|
|
570
|
+
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
console.log(boxen(
|
|
575
|
+
chalk.bold('Claude 桌面应用已配置为 Gateway 模式\n\n') +
|
|
576
|
+
chalk.dim('Base URL ') + chalk.cyan(config.baseUrl) + '\n' +
|
|
577
|
+
chalk.dim('模型 ') + chalk.yellow(config.model) + '\n' +
|
|
578
|
+
chalk.dim('认证方式 ') + chalk.cyan('bearer') + '\n\n' +
|
|
579
|
+
chalk.yellow(getDesktopRestartHint()) + '\n' +
|
|
580
|
+
chalk.dim('要求:网关必须支持 Anthropic POST /v1/messages,且 Base URL 必须是 HTTPS。'),
|
|
581
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'cyan', margin: { top: 1, bottom: 1 } }
|
|
582
|
+
));
|
|
583
|
+
|
|
584
|
+
if (process.platform === 'darwin') {
|
|
585
|
+
const shouldRestart = await confirm({ message: '是否自动重启 Claude 桌面应用?', default: false });
|
|
586
|
+
if (shouldRestart) {
|
|
587
|
+
const restartSpinner = ora('正在重启 Claude 桌面应用...').start();
|
|
588
|
+
try {
|
|
589
|
+
await restartClaudeDesktop();
|
|
590
|
+
restartSpinner.succeed(chalk.green('Claude 桌面应用已重新打开'));
|
|
591
|
+
} catch (e) {
|
|
592
|
+
restartSpinner.fail(chalk.red(`重启失败: ${e.message}`));
|
|
593
|
+
console.log(chalk.dim(getDesktopRestartHint()));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
} else {
|
|
597
|
+
console.log(chalk.dim(getDesktopRestartHint()));
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
512
601
|
program
|
|
513
602
|
.command('reset')
|
|
514
603
|
.description('恢复默认(清除所有 clawai 配置)')
|
|
@@ -530,16 +619,23 @@ program
|
|
|
530
619
|
|
|
531
620
|
const spinner = ora('清除中...').start();
|
|
532
621
|
const cleared = resetConfig();
|
|
622
|
+
const desktopCleared = clearClaudeDesktopConfig();
|
|
623
|
+
if (desktopCleared.result === 'updated') {
|
|
624
|
+
cleared.push(desktopCleared.file);
|
|
625
|
+
}
|
|
533
626
|
await new Promise(r => setTimeout(r, 300));
|
|
534
627
|
|
|
535
628
|
if (cleared.length === 0) {
|
|
536
629
|
spinner.warn(chalk.yellow('没有找到任何配置,无需清除'));
|
|
537
630
|
} else {
|
|
538
631
|
spinner.succeed(chalk.green('已恢复默认'));
|
|
632
|
+
const resetNote = cleared.includes('Windows 用户环境变量')
|
|
633
|
+
? '注:当前终端的环境变量还在内存中,重新打开 PowerShell / CMD 后才彻底清除'
|
|
634
|
+
: '注:当前终端的环境变量还在内存中,重开终端或 unset 才彻底清除';
|
|
539
635
|
console.log(boxen(
|
|
540
636
|
chalk.bold('已清除以下文件中的 clawai 配置:\n\n') +
|
|
541
637
|
cleared.map(f => chalk.cyan(' • ' + f)).join('\n') +
|
|
542
|
-
'\n\n' + chalk.dim(
|
|
638
|
+
'\n\n' + chalk.dim(resetNote),
|
|
543
639
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
544
640
|
));
|
|
545
641
|
}
|
|
@@ -560,7 +656,7 @@ async function renderStatusBar(apiStatus) {
|
|
|
560
656
|
claudeInstalled,
|
|
561
657
|
env: process.env,
|
|
562
658
|
});
|
|
563
|
-
const statusLines = buildMenuStatusLines(view, { apiStatus, claudeInstalled });
|
|
659
|
+
const statusLines = buildMenuStatusLines(view, { apiStatus, claudeInstalled, platform: process.platform });
|
|
564
660
|
cfgPart = statusLines.map((line, index) => {
|
|
565
661
|
if (index === 0) return line.replace('API 正常', chalk.green('API 正常')).replace('API Key 无效', chalk.red('API Key 无效')).replace('网络/服务异常', chalk.yellow('网络/服务异常'));
|
|
566
662
|
if (line.startsWith('环境变量未生效')) return chalk.yellow(line);
|
|
@@ -609,9 +705,10 @@ async function runMenu() {
|
|
|
609
705
|
{ name: '📦 安装 Claude Code', value: 'install' },
|
|
610
706
|
{ name: config ? '⚙️ 重新配置(输入新的 API Key)' : '⚙️ 首次配置 API Key 和模型', value: 'setup' },
|
|
611
707
|
{ name: '🔄 切换厂商或模型', value: 'switch', disabled: (!config || configProblem) && '需先完成配置' },
|
|
708
|
+
{ name: '🖥 配置 Claude 桌面应用', value: 'desktop', disabled: (!config || configProblem) && '需先完成配置' },
|
|
612
709
|
{ name: '📊 查看当前配置', value: 'status', disabled: !config && '需先完成配置' },
|
|
613
710
|
{ name: '🔁 重新检测 API', value: 'recheck', disabled: (!config || configProblem) && '需先完成配置' },
|
|
614
|
-
{ name: '🗑 恢复默认(清除所有配置)', value: 'reset'
|
|
711
|
+
{ name: '🗑 恢复默认(清除所有配置)', value: 'reset' },
|
|
615
712
|
{ name: '退出', value: 'exit' },
|
|
616
713
|
],
|
|
617
714
|
});
|
|
@@ -645,6 +742,7 @@ async function runMenu() {
|
|
|
645
742
|
install: 'install-claude',
|
|
646
743
|
setup: 'setup',
|
|
647
744
|
switch: 'switch',
|
|
745
|
+
desktop: 'desktop',
|
|
648
746
|
status: 'status',
|
|
649
747
|
reset: 'reset',
|
|
650
748
|
};
|
package/index.js
CHANGED
package/lib/config.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
-
const
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
5
|
|
|
6
6
|
const CONFIG_FILE = path.join(os.homedir(), '.clawai.json');
|
|
7
|
+
const WINDOWS_ENV_LABEL = 'Windows 用户环境变量';
|
|
8
|
+
const CLAUDE_ENV_KEYS = [
|
|
9
|
+
'ANTHROPIC_BASE_URL',
|
|
10
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
11
|
+
'ANTHROPIC_API_KEY',
|
|
12
|
+
'ANTHROPIC_MODEL',
|
|
13
|
+
'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
14
|
+
'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
15
|
+
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
16
|
+
'CLAUDE_CODE_SUBAGENT_MODEL',
|
|
17
|
+
'CLAUDE_CODE_EFFORT_LEVEL',
|
|
18
|
+
];
|
|
7
19
|
|
|
8
20
|
const PROVIDERS = {
|
|
9
21
|
deepseek: {
|
|
@@ -16,6 +28,15 @@ const PROVIDERS = {
|
|
|
16
28
|
{ name: 'DeepSeek V4 Flash(快速)', value: 'deepseek-v4-flash' },
|
|
17
29
|
],
|
|
18
30
|
},
|
|
31
|
+
kimi: {
|
|
32
|
+
name: 'Kimi / Moonshot',
|
|
33
|
+
baseUrl: 'https://api.moonshot.ai/anthropic',
|
|
34
|
+
modelsUrl: 'https://api.moonshot.ai/v1/models',
|
|
35
|
+
fastModel: 'kimi-k2.5',
|
|
36
|
+
models: [
|
|
37
|
+
{ name: 'Kimi K2.5(代码)', value: 'kimi-k2.5' },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
19
40
|
qwen: {
|
|
20
41
|
name: '阿里云百炼 (Qwen)',
|
|
21
42
|
baseUrl: 'https://dashscope.aliyuncs.com/apps/anthropic',
|
|
@@ -80,9 +101,26 @@ function parseModelIdsResponse(providerKey, data) {
|
|
|
80
101
|
return normalizeModelIds(providerKey, ids);
|
|
81
102
|
}
|
|
82
103
|
|
|
104
|
+
function normalizeAnthropicBaseUrl(baseUrl) {
|
|
105
|
+
let url;
|
|
106
|
+
try { url = new URL(baseUrl); } catch { return baseUrl; }
|
|
107
|
+
|
|
108
|
+
const parts = url.pathname.split('/').filter(Boolean);
|
|
109
|
+
if (parts.at(-1) === 'messages' && parts.at(-2) === 'v1') {
|
|
110
|
+
parts.splice(-2, 2);
|
|
111
|
+
} else if (parts.at(-1) === 'v1') {
|
|
112
|
+
parts.pop();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
url.pathname = parts.length > 0 ? `/${parts.join('/')}` : '/';
|
|
116
|
+
url.search = '';
|
|
117
|
+
url.hash = '';
|
|
118
|
+
return url.toString().replace(/\/+$/, '');
|
|
119
|
+
}
|
|
120
|
+
|
|
83
121
|
function buildModelUrlCandidates(baseUrl) {
|
|
84
122
|
let url;
|
|
85
|
-
try { url = new URL(baseUrl); } catch { return []; }
|
|
123
|
+
try { url = new URL(normalizeAnthropicBaseUrl(baseUrl)); } catch { return []; }
|
|
86
124
|
|
|
87
125
|
const pathname = url.pathname.replace(/\/+$/, '');
|
|
88
126
|
const candidates = [];
|
|
@@ -118,41 +156,38 @@ async function fetchModelsFromBaseUrl(providerKey, apiKey, baseUrl, fetcher = fe
|
|
|
118
156
|
}
|
|
119
157
|
|
|
120
158
|
// 联网拉取厂商支持的模型列表,失败返回 null
|
|
121
|
-
function fetchModels(providerKey, apiKey, modelsUrlOverride) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
159
|
+
async function fetchModels(providerKey, apiKey, modelsUrlOverride) {
|
|
160
|
+
const provider = PROVIDERS[providerKey];
|
|
161
|
+
const modelsUrl = modelsUrlOverride || provider?.modelsUrl;
|
|
162
|
+
if (!modelsUrl) return null;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
new URL(modelsUrl);
|
|
166
|
+
} catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const controller = new AbortController();
|
|
171
|
+
const timeout = setTimeout(() => controller.abort(), 6000);
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const res = await fetch(modelsUrl, {
|
|
133
175
|
method: 'GET',
|
|
134
|
-
|
|
176
|
+
signal: controller.signal,
|
|
135
177
|
headers: {
|
|
136
|
-
|
|
178
|
+
authorization: `Bearer ${apiKey}`,
|
|
137
179
|
'api-key': apiKey, // MiMo 用这个 header
|
|
138
180
|
},
|
|
139
|
-
}, (res) => {
|
|
140
|
-
let data = '';
|
|
141
|
-
res.on('data', (c) => data += c);
|
|
142
|
-
res.on('end', () => {
|
|
143
|
-
try {
|
|
144
|
-
const ids = parseModelIdsResponse(providerKey, data);
|
|
145
|
-
if (ids.length === 0) return resolve(null);
|
|
146
|
-
resolve(ids);
|
|
147
|
-
} catch {
|
|
148
|
-
resolve(null);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
181
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
182
|
+
if (!res.ok) return null;
|
|
183
|
+
|
|
184
|
+
const ids = parseModelIdsResponse(providerKey, await res.text());
|
|
185
|
+
return ids.length > 0 ? ids : null;
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
} finally {
|
|
189
|
+
clearTimeout(timeout);
|
|
190
|
+
}
|
|
156
191
|
}
|
|
157
192
|
|
|
158
193
|
function loadConfig() {
|
|
@@ -209,6 +244,29 @@ function buildClaudeEnv({ provider, baseUrl, apiKey, model, fastModel }) {
|
|
|
209
244
|
};
|
|
210
245
|
}
|
|
211
246
|
|
|
247
|
+
function buildWindowsSetEnvCommands(env) {
|
|
248
|
+
return CLAUDE_ENV_KEYS.map((key) => ({
|
|
249
|
+
command: 'setx',
|
|
250
|
+
args: [key, env[key]],
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function buildWindowsClearEnvCommands() {
|
|
255
|
+
return CLAUDE_ENV_KEYS.map((key) => ({
|
|
256
|
+
command: 'reg',
|
|
257
|
+
args: ['delete', 'HKCU\\Environment', '/V', key, '/F'],
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function runWindowsEnvCommands(commands, runner = spawnSync, { ignoreErrors = false } = {}) {
|
|
262
|
+
for (const { command, args } of commands) {
|
|
263
|
+
const result = runner(command, args, { stdio: 'ignore', windowsHide: true });
|
|
264
|
+
if (!ignoreErrors && result.status !== 0) {
|
|
265
|
+
throw new Error(`${command} ${args[0]} 执行失败`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
212
270
|
function classifyValidationStatus(statusCode) {
|
|
213
271
|
if (statusCode >= 200 && statusCode < 300) return true;
|
|
214
272
|
if (statusCode === 401 || statusCode === 403) return false;
|
|
@@ -241,7 +299,15 @@ function buildEnvBlock(baseUrl, apiKey, model, fastModel) {
|
|
|
241
299
|
}
|
|
242
300
|
|
|
243
301
|
// 写入或更新 shell 配置文件中的环境变量块
|
|
244
|
-
function writeEnvToZshrc(baseUrl, apiKey, model, fastModel) {
|
|
302
|
+
function writeEnvToZshrc(baseUrl, apiKey, model, fastModel, options = {}) {
|
|
303
|
+
const platform = options.platform || process.platform;
|
|
304
|
+
if (platform === 'win32') {
|
|
305
|
+
const provider = providerKeyFromBaseUrl(baseUrl);
|
|
306
|
+
const env = buildClaudeEnv({ provider, baseUrl, apiKey, model, fastModel });
|
|
307
|
+
runWindowsEnvCommands(buildWindowsSetEnvCommands(env), options.runner || spawnSync);
|
|
308
|
+
return { result: 'updated', file: WINDOWS_ENV_LABEL };
|
|
309
|
+
}
|
|
310
|
+
|
|
245
311
|
const shell = process.env.SHELL || '';
|
|
246
312
|
let rcFile;
|
|
247
313
|
if (shell.includes('bash')) {
|
|
@@ -265,17 +331,25 @@ function writeEnvToZshrc(baseUrl, apiKey, model, fastModel) {
|
|
|
265
331
|
}
|
|
266
332
|
|
|
267
333
|
// 清除所有 clawai 配置(配置文件 + shell 环境变量块)
|
|
268
|
-
function resetConfig() {
|
|
334
|
+
function resetConfig(options = {}) {
|
|
269
335
|
const cleared = [];
|
|
336
|
+
const configFile = options.configFile || CONFIG_FILE;
|
|
337
|
+
const platform = options.platform || process.platform;
|
|
270
338
|
|
|
271
339
|
// 删配置文件
|
|
272
|
-
if (fs.existsSync(
|
|
273
|
-
fs.unlinkSync(
|
|
274
|
-
cleared.push(
|
|
340
|
+
if (fs.existsSync(configFile)) {
|
|
341
|
+
fs.unlinkSync(configFile);
|
|
342
|
+
cleared.push(configFile);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (platform === 'win32') {
|
|
346
|
+
runWindowsEnvCommands(buildWindowsClearEnvCommands(), options.runner || spawnSync, { ignoreErrors: true });
|
|
347
|
+
cleared.push(WINDOWS_ENV_LABEL);
|
|
348
|
+
return cleared;
|
|
275
349
|
}
|
|
276
350
|
|
|
277
351
|
// 清理所有可能的 rc 文件中的 clawai 块
|
|
278
|
-
const rcFiles = [
|
|
352
|
+
const rcFiles = options.rcFiles || [
|
|
279
353
|
path.join(os.homedir(), '.zshrc'),
|
|
280
354
|
path.join(os.homedir(), '.bashrc'),
|
|
281
355
|
path.join(os.homedir(), '.bash_profile'),
|
|
@@ -299,10 +373,13 @@ module.exports = {
|
|
|
299
373
|
loadConfig,
|
|
300
374
|
saveConfig,
|
|
301
375
|
writeEnvToZshrc,
|
|
376
|
+
buildWindowsSetEnvCommands,
|
|
377
|
+
buildWindowsClearEnvCommands,
|
|
302
378
|
fetchModels,
|
|
303
379
|
resetConfig,
|
|
304
380
|
validateConfig,
|
|
305
381
|
normalizeModelIds,
|
|
382
|
+
normalizeAnthropicBaseUrl,
|
|
306
383
|
parseModelIdsResponse,
|
|
307
384
|
buildModelUrlCandidates,
|
|
308
385
|
fetchModelsFromBaseUrl,
|
package/lib/desktop.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
const { normalizeAnthropicBaseUrl } = require('./config');
|
|
7
|
+
|
|
8
|
+
const CLAUDE_DESKTOP_LABEL = 'Claude 桌面应用配置';
|
|
9
|
+
const DESKTOP_GATEWAY_KEYS = [
|
|
10
|
+
'inferenceProvider',
|
|
11
|
+
'inferenceGatewayBaseUrl',
|
|
12
|
+
'inferenceGatewayApiKey',
|
|
13
|
+
'inferenceGatewayAuthScheme',
|
|
14
|
+
'inferenceModels',
|
|
15
|
+
'disableDeploymentModeChooser',
|
|
16
|
+
'deploymentOrganizationUuid',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
function getClaudeDesktopConfigPath(options = {}) {
|
|
20
|
+
const platform = options.platform || process.platform;
|
|
21
|
+
const homeDir = options.homeDir || os.homedir();
|
|
22
|
+
|
|
23
|
+
if (platform === 'darwin') {
|
|
24
|
+
return path.join(homeDir, 'Library', 'Application Support', 'Claude-3p', 'claude_desktop_config.json');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (platform === 'win32') {
|
|
28
|
+
const appData = options.appData || process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming');
|
|
29
|
+
return path.join(appData, 'Claude-3p', 'claude_desktop_config.json');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readJsonFile(file) {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
38
|
+
} catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function uniqueModels(model, fastModel) {
|
|
44
|
+
return [...new Set([model, fastModel].filter(Boolean))];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildClaudeDesktopEnterpriseConfig(config, options = {}) {
|
|
48
|
+
const models = uniqueModels(config.model, config.fastModel);
|
|
49
|
+
const baseUrl = normalizeAnthropicBaseUrl(config.baseUrl);
|
|
50
|
+
if (!baseUrl.startsWith('https://')) {
|
|
51
|
+
throw new Error('Claude 桌面应用要求 Gateway Base URL 使用 HTTPS');
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
inferenceProvider: 'gateway',
|
|
55
|
+
inferenceGatewayBaseUrl: baseUrl,
|
|
56
|
+
inferenceGatewayApiKey: config.apiKey,
|
|
57
|
+
inferenceGatewayAuthScheme: options.authScheme || 'bearer',
|
|
58
|
+
inferenceModels: JSON.stringify(models),
|
|
59
|
+
disableDeploymentModeChooser: 'true',
|
|
60
|
+
deploymentOrganizationUuid: options.uuid || crypto.randomUUID(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function writeClaudeDesktopConfig(config, options = {}) {
|
|
65
|
+
const file = options.configFile || getClaudeDesktopConfigPath(options);
|
|
66
|
+
if (!file) {
|
|
67
|
+
return { result: 'unsupported', file: null };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const current = readJsonFile(file);
|
|
71
|
+
const existingEnterpriseConfig = current.enterpriseConfig && typeof current.enterpriseConfig === 'object'
|
|
72
|
+
? current.enterpriseConfig
|
|
73
|
+
: {};
|
|
74
|
+
const deploymentOrganizationUuid = existingEnterpriseConfig.deploymentOrganizationUuid || options.uuid;
|
|
75
|
+
const enterpriseConfig = buildClaudeDesktopEnterpriseConfig(config, {
|
|
76
|
+
authScheme: options.authScheme,
|
|
77
|
+
uuid: deploymentOrganizationUuid,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const next = {
|
|
81
|
+
...current,
|
|
82
|
+
enterpriseConfig: {
|
|
83
|
+
...existingEnterpriseConfig,
|
|
84
|
+
...enterpriseConfig,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
89
|
+
const before = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
90
|
+
const body = JSON.stringify(next, null, 2) + '\n';
|
|
91
|
+
fs.writeFileSync(file, body);
|
|
92
|
+
return { result: before === body ? 'unchanged' : 'updated', file };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function clearClaudeDesktopConfig(options = {}) {
|
|
96
|
+
const file = options.configFile || getClaudeDesktopConfigPath(options);
|
|
97
|
+
if (!file || !fs.existsSync(file)) {
|
|
98
|
+
return { result: 'missing', file };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const current = readJsonFile(file);
|
|
102
|
+
if (!current.enterpriseConfig || typeof current.enterpriseConfig !== 'object') {
|
|
103
|
+
return { result: 'missing', file };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const enterpriseConfig = { ...current.enterpriseConfig };
|
|
107
|
+
for (const key of DESKTOP_GATEWAY_KEYS) {
|
|
108
|
+
delete enterpriseConfig[key];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const next = { ...current };
|
|
112
|
+
if (Object.keys(enterpriseConfig).length > 0) {
|
|
113
|
+
next.enterpriseConfig = enterpriseConfig;
|
|
114
|
+
} else {
|
|
115
|
+
delete next.enterpriseConfig;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fs.writeFileSync(file, JSON.stringify(next, null, 2) + '\n');
|
|
119
|
+
return { result: 'updated', file };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function buildClaudeDesktopRestartCommands(platform = process.platform) {
|
|
123
|
+
if (platform !== 'darwin') return null;
|
|
124
|
+
return [
|
|
125
|
+
{ command: 'osascript', args: ['-e', 'tell application "Claude" to quit'] },
|
|
126
|
+
{ command: 'open', args: ['-a', 'Claude'] },
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function delay(ms) {
|
|
131
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function restartClaudeDesktop(options = {}) {
|
|
135
|
+
const platform = options.platform || process.platform;
|
|
136
|
+
const commands = buildClaudeDesktopRestartCommands(platform);
|
|
137
|
+
if (!commands) return { result: 'unsupported' };
|
|
138
|
+
|
|
139
|
+
const runner = options.runner || spawnSync;
|
|
140
|
+
const delayMs = options.delayMs ?? 1500;
|
|
141
|
+
const [quitCommand, openCommand] = commands;
|
|
142
|
+
|
|
143
|
+
runner(quitCommand.command, quitCommand.args, { stdio: 'ignore', windowsHide: true });
|
|
144
|
+
await delay(delayMs);
|
|
145
|
+
const result = runner(openCommand.command, openCommand.args, { stdio: 'ignore', windowsHide: true });
|
|
146
|
+
if (result.status !== 0) {
|
|
147
|
+
throw new Error('Claude 桌面应用重新打开失败');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { result: 'restarted' };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
buildClaudeDesktopEnterpriseConfig,
|
|
155
|
+
buildClaudeDesktopRestartCommands,
|
|
156
|
+
clearClaudeDesktopConfig,
|
|
157
|
+
getClaudeDesktopConfigPath,
|
|
158
|
+
restartClaudeDesktop,
|
|
159
|
+
writeClaudeDesktopConfig,
|
|
160
|
+
CLAUDE_DESKTOP_LABEL,
|
|
161
|
+
};
|
package/lib/panel.js
CHANGED
|
@@ -54,6 +54,8 @@ function buildMenuStatusLines(view, options = {}) {
|
|
|
54
54
|
|
|
55
55
|
if (view.envActive) {
|
|
56
56
|
lines.push('环境变量已生效');
|
|
57
|
+
} else if (options.platform === 'win32') {
|
|
58
|
+
lines.push('环境变量未生效:重新打开 PowerShell / CMD');
|
|
57
59
|
} else {
|
|
58
60
|
lines.push('环境变量未生效:运行 source ~/.zshrc 或重开终端');
|
|
59
61
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yingclaw",
|
|
3
|
-
"version": "1.7.
|
|
4
|
-
"description": "Claude Code × 国产大模型一键接入:DeepSeek、Qwen、MiniMax、GLM、MiMo",
|
|
3
|
+
"version": "1.7.8",
|
|
4
|
+
"description": "Claude Code × 国产大模型一键接入:DeepSeek、Kimi、Qwen、MiniMax、GLM、MiMo",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"claw": "bin/cli.js"
|