yingclaw 1.7.4 → 1.7.12
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 +56 -9
- package/bin/cli.js +343 -109
- package/index.js +1 -0
- package/lib/config.js +78 -8
- package/lib/desktop.js +152 -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
|
|
@@ -22,11 +22,43 @@ claw install-claude
|
|
|
22
22
|
```
|
|
23
23
|
根据提示选择网络环境(有梯子走官方,没梯子走淘宝镜像)。
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
**第二步:配置 API 连接**
|
|
26
|
+
```bash
|
|
27
|
+
claw config
|
|
28
|
+
```
|
|
29
|
+
选择厂商 → 输入 API Key → 选择模型。这个步骤只保存 API 连接,不会修改终端环境变量,也不会修改 Claude 桌面应用配置。
|
|
30
|
+
|
|
31
|
+
**第三步:选择接入目标**
|
|
32
|
+
|
|
33
|
+
接入 Claude Code 终端:
|
|
34
|
+
```bash
|
|
35
|
+
claw code
|
|
36
|
+
```
|
|
37
|
+
写入 Claude Code 所需的环境变量。配置完成后不会自动启动 Claude Code,需要时可在主菜单选择“启动 Claude Code”或直接运行 `claude`。
|
|
38
|
+
|
|
39
|
+
接入 Claude 桌面应用:
|
|
40
|
+
```bash
|
|
41
|
+
claw desktop
|
|
42
|
+
```
|
|
43
|
+
将当前模型配置写入 Claude Desktop 的第三方推理(Cowork on 3P)本地配置。macOS 会询问是否立即打开 Claude 桌面应用;Windows 需要手动打开。如果配置未生效,请完全退出 Claude Desktop 后重新打开。
|
|
44
|
+
|
|
45
|
+
恢复 Claude Code 终端默认配置:
|
|
46
|
+
```bash
|
|
47
|
+
claw code-reset
|
|
48
|
+
```
|
|
49
|
+
只清除 Claude Code 终端环境变量,不影响 API 连接和 Claude 桌面配置。
|
|
50
|
+
|
|
51
|
+
恢复 Claude 桌面应用默认配置:
|
|
52
|
+
```bash
|
|
53
|
+
claw desktop-reset
|
|
54
|
+
```
|
|
55
|
+
只清除 Claude Desktop 第三方推理配置,不影响终端里的 API Key 和模型配置。
|
|
56
|
+
|
|
57
|
+
兼容旧命令:
|
|
26
58
|
```bash
|
|
27
59
|
claw setup
|
|
28
60
|
```
|
|
29
|
-
|
|
61
|
+
等价于 `claw config` + `claw code`,用于一键配置 API 并接入 Claude Code 终端。
|
|
30
62
|
|
|
31
63
|
选择“自定义 Anthropic 兼容接口”时,需要输入:
|
|
32
64
|
|
|
@@ -35,9 +67,9 @@ claw setup
|
|
|
35
67
|
|
|
36
68
|
工具会根据 Base URL 自动尝试获取模型列表;如果获取失败,则手动输入主模型和快速模型。
|
|
37
69
|
|
|
38
|
-
注意:模型列表能获取不代表一定可用于 Claude Code
|
|
70
|
+
注意:模型列表能获取不代表一定可用于 Claude Code 或 Claude 桌面应用。自定义接口还必须支持 Anthropic `/v1/messages`,否则请求会被网关拒绝。Claude 桌面应用的 Gateway Base URL 还必须使用 HTTPS。
|
|
39
71
|
|
|
40
|
-
|
|
72
|
+
**以后直接用**
|
|
41
73
|
```bash
|
|
42
74
|
claude
|
|
43
75
|
```
|
|
@@ -47,6 +79,7 @@ claude
|
|
|
47
79
|
| 厂商 | 模型 |
|
|
48
80
|
|------|------|
|
|
49
81
|
| DeepSeek | V4 Flash、V4 Pro |
|
|
82
|
+
| Kimi / Moonshot | Kimi K2.5 |
|
|
50
83
|
| 阿里云百炼 | Qwen3 Max、Plus、Flash |
|
|
51
84
|
| MiniMax | M2.7、M2.7 Turbo、M2.5 |
|
|
52
85
|
| 智谱 GLM | GLM-4.7、GLM-5.1、GLM-5 Turbo、GLM-4.5 Air |
|
|
@@ -56,8 +89,15 @@ claude
|
|
|
56
89
|
## 其他命令
|
|
57
90
|
|
|
58
91
|
```bash
|
|
59
|
-
claw
|
|
60
|
-
claw
|
|
92
|
+
claw config # 配置 API 连接,不修改终端或桌面
|
|
93
|
+
claw code # 接入 Claude Code 终端
|
|
94
|
+
claw code-reset # 恢复 Claude Code 终端默认配置
|
|
95
|
+
claw switch # 快速切换模型(只更新 API 连接)
|
|
96
|
+
claw desktop # 接入 Claude 桌面应用第三方推理
|
|
97
|
+
claw desktop-reset # 恢复 Claude 桌面应用默认配置
|
|
98
|
+
claw setup # 兼容旧命令:config + code
|
|
99
|
+
claw status # 查看当前配置,验证 Key 是否有效
|
|
100
|
+
claw reset # 清除 API 连接、终端环境变量和桌面配置
|
|
61
101
|
```
|
|
62
102
|
|
|
63
103
|
## 卸载
|
|
@@ -68,7 +108,7 @@ npm uninstall -g yingclaw
|
|
|
68
108
|
|
|
69
109
|
## 原理
|
|
70
110
|
|
|
71
|
-
各厂商均原生支持 Anthropic API
|
|
111
|
+
各厂商均原生支持 Anthropic API 格式。`claw config` 只保存 API 连接;`claw code` 才会写入 Claude Code 所需的环境变量。macOS / Linux / WSL 写入 shell 配置文件,Windows 写入用户级环境变量,包括:
|
|
72
112
|
|
|
73
113
|
- `ANTHROPIC_BASE_URL`
|
|
74
114
|
- `ANTHROPIC_AUTH_TOKEN`
|
|
@@ -82,6 +122,13 @@ npm uninstall -g yingclaw
|
|
|
82
122
|
|
|
83
123
|
以 DeepSeek 为例,主模型默认使用 `deepseek-v4-pro[1m]`,Haiku/Subagent 快速模型使用 `deepseek-v4-flash`。如果在线模型列表获取失败,会回退到内置默认列表。
|
|
84
124
|
|
|
125
|
+
`claw desktop` 会额外写入 Claude Desktop 第三方推理配置:
|
|
126
|
+
|
|
127
|
+
- macOS:`~/Library/Application Support/Claude-3p/claude_desktop_config.json`
|
|
128
|
+
- Windows:`%APPDATA%\Claude-3p\claude_desktop_config.json`
|
|
129
|
+
|
|
130
|
+
写入的 `enterpriseConfig` 使用 `inferenceProvider=gateway`、`inferenceGatewayAuthScheme=bearer`,并把当前模型写入 `inferenceModels`。
|
|
131
|
+
|
|
85
132
|
## License
|
|
86
133
|
|
|
87
134
|
MIT
|
package/bin/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
loadConfig,
|
|
7
7
|
saveConfig,
|
|
8
8
|
writeEnvToZshrc,
|
|
9
|
+
clearClaudeCodeEnv,
|
|
9
10
|
fetchModels,
|
|
10
11
|
fetchModelsFromBaseUrl,
|
|
11
12
|
resetConfig,
|
|
@@ -20,6 +21,7 @@ const { execSync, spawn, spawnSync } = require('child_process');
|
|
|
20
21
|
const pkg = require('../package.json');
|
|
21
22
|
const { buildMenuStatusLines, buildStatusView } = require('../lib/panel');
|
|
22
23
|
const { buildClaudeInstallCommand } = require('../lib/install');
|
|
24
|
+
const { clearClaudeDesktopConfig, openClaudeDesktop, writeClaudeDesktopConfig } = require('../lib/desktop');
|
|
23
25
|
|
|
24
26
|
const program = new Command();
|
|
25
27
|
|
|
@@ -94,6 +96,28 @@ function isValidUrl(value) {
|
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
function getActivationHint(file) {
|
|
100
|
+
if (file === 'Windows 用户环境变量') {
|
|
101
|
+
return '重新打开 PowerShell / CMD 后生效';
|
|
102
|
+
}
|
|
103
|
+
return `运行 source ${file} 生效,或重新开一个终端`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getStorageHint(file) {
|
|
107
|
+
if (file === 'Windows 用户环境变量') {
|
|
108
|
+
return `⚠ API Key 以明文存储在 ${file} 和用户主目录下的 .clawai.json`;
|
|
109
|
+
}
|
|
110
|
+
return `⚠ API Key 以明文存储在 ${file} 和 ~/.clawai.json`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getDesktopOpenHint() {
|
|
114
|
+
return '请打开 Claude 桌面应用;如配置未生效,请完全退出后重新打开';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getSavedConfigHint() {
|
|
118
|
+
return '⚠ API Key 以明文存储在 ~/.clawai.json';
|
|
119
|
+
}
|
|
120
|
+
|
|
97
121
|
async function promptModelFromChoices({ chalk, choices, message, backLabel = '↩ 返回上一步', allowManual = true }) {
|
|
98
122
|
const selected = await select({ loop: false,
|
|
99
123
|
message: chalk.cyan(message),
|
|
@@ -182,13 +206,13 @@ async function showStatus() {
|
|
|
182
206
|
const config = loadConfig();
|
|
183
207
|
|
|
184
208
|
if (!config) {
|
|
185
|
-
console.log(chalk.red('\n未配置,请先运行: claw
|
|
209
|
+
console.log(chalk.red('\n未配置,请先运行: claw config\n'));
|
|
186
210
|
return;
|
|
187
211
|
}
|
|
188
212
|
const configProblem = getConfigValidationMessage(config);
|
|
189
213
|
if (configProblem) {
|
|
190
214
|
console.log(chalk.red(`\n配置无效:${configProblem}`));
|
|
191
|
-
console.log(chalk.dim('请运行 claw
|
|
215
|
+
console.log(chalk.dim('请运行 claw config 重新配置。\n'));
|
|
192
216
|
return;
|
|
193
217
|
}
|
|
194
218
|
|
|
@@ -238,6 +262,113 @@ async function showStatus() {
|
|
|
238
262
|
}));
|
|
239
263
|
}
|
|
240
264
|
|
|
265
|
+
async function runConfigFlow({ writeCodeEnv = false } = {}) {
|
|
266
|
+
const chalk = (await import('chalk')).default;
|
|
267
|
+
const ora = (await import('ora')).default;
|
|
268
|
+
const boxen = (await import('boxen')).default;
|
|
269
|
+
|
|
270
|
+
console.log(await getBanner());
|
|
271
|
+
|
|
272
|
+
const existing = loadConfig();
|
|
273
|
+
if (existing) {
|
|
274
|
+
const existingProvider = PROVIDERS[existing.provider];
|
|
275
|
+
console.log(boxen(
|
|
276
|
+
chalk.bold('当前 API 连接\n\n') +
|
|
277
|
+
chalk.dim('厂商 ') + chalk.white(existingProvider?.name || existing.provider) + '\n' +
|
|
278
|
+
chalk.dim('模型 ') + chalk.yellow(existing.model) + '\n' +
|
|
279
|
+
chalk.dim('Key ') + chalk.dim(existing.apiKey ? '已保存' : '缺失'),
|
|
280
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'yellow', margin: { top: 1, bottom: 1 } }
|
|
281
|
+
));
|
|
282
|
+
const overwrite = await confirm({ message: '覆盖现有 API 连接?', default: false });
|
|
283
|
+
if (!overwrite) return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let providerKey, provider, apiKey, model, customConfig;
|
|
287
|
+
let step = 'provider';
|
|
288
|
+
|
|
289
|
+
while (true) {
|
|
290
|
+
if (step === 'provider') {
|
|
291
|
+
providerKey = await select({ loop: false,
|
|
292
|
+
message: chalk.cyan('选择 AI 厂商'),
|
|
293
|
+
choices: [
|
|
294
|
+
...Object.entries(PROVIDERS).map(([value, p]) => ({ name: p.name, value })),
|
|
295
|
+
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
296
|
+
],
|
|
297
|
+
});
|
|
298
|
+
if (providerKey === '__BACK__') return;
|
|
299
|
+
provider = PROVIDERS[providerKey];
|
|
300
|
+
if (provider.custom) {
|
|
301
|
+
customConfig = await configureCustomProvider({ chalk, ora });
|
|
302
|
+
if (!customConfig) { step = 'provider'; continue; }
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
step = 'apikey';
|
|
306
|
+
} else if (step === 'apikey') {
|
|
307
|
+
const k = await input({
|
|
308
|
+
message: chalk.cyan(`${provider.name} API Key(输入 b 返回上一步)`),
|
|
309
|
+
transformer: (v) => v && v !== 'b' ? chalk.dim('•'.repeat(v.length)) : v,
|
|
310
|
+
validate: (v) => v.trim().length > 0 ? true : 'API Key 不能为空',
|
|
311
|
+
});
|
|
312
|
+
if (k.trim() === 'b') { step = 'provider'; continue; }
|
|
313
|
+
apiKey = k.trim();
|
|
314
|
+
step = 'model';
|
|
315
|
+
} else if (step === 'model') {
|
|
316
|
+
const fetchSpinner = ora('正在获取可用模型...').start();
|
|
317
|
+
const onlineModels = await fetchModels(providerKey, apiKey);
|
|
318
|
+
let modelChoices;
|
|
319
|
+
if (onlineModels && onlineModels.length > 0) {
|
|
320
|
+
fetchSpinner.succeed(chalk.green(`已获取 ${onlineModels.length} 个可用模型`));
|
|
321
|
+
modelChoices = onlineModels.map(id => ({ name: id, value: id }));
|
|
322
|
+
} else {
|
|
323
|
+
fetchSpinner.warn(chalk.yellow('无法获取在线列表,使用内置默认列表'));
|
|
324
|
+
modelChoices = provider.models;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const m = await select({ loop: false,
|
|
328
|
+
message: chalk.cyan('选择模型'),
|
|
329
|
+
choices: [
|
|
330
|
+
...modelChoices,
|
|
331
|
+
{ name: chalk.dim('↩ 返回上一步(重新输入 Key)'), value: '__BACK__' },
|
|
332
|
+
],
|
|
333
|
+
});
|
|
334
|
+
if (m === '__BACK__') { step = 'apikey'; continue; }
|
|
335
|
+
model = m;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const spinner = ora(writeCodeEnv ? '保存 API 连接并接入 Claude Code 终端...' : '保存 API 连接...').start();
|
|
341
|
+
let file;
|
|
342
|
+
let cfg;
|
|
343
|
+
try {
|
|
344
|
+
const fastModel = customConfig?.fastModel || resolveFastModel(provider, model);
|
|
345
|
+
cfg = customConfig || { provider: providerKey, model, fastModel, apiKey, baseUrl: provider.baseUrl };
|
|
346
|
+
saveConfig(cfg);
|
|
347
|
+
if (writeCodeEnv) {
|
|
348
|
+
({ file } = writeEnvToZshrc(cfg.baseUrl, cfg.apiKey, cfg.model, cfg.fastModel));
|
|
349
|
+
}
|
|
350
|
+
spinner.succeed(chalk.green(writeCodeEnv ? 'API 连接已保存,Claude Code 终端已接入' : 'API 连接已保存'));
|
|
351
|
+
} catch (e) {
|
|
352
|
+
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
console.log(chalk.dim(writeCodeEnv ? getStorageHint(file) : getSavedConfigHint()));
|
|
357
|
+
|
|
358
|
+
const nextStep = writeCodeEnv
|
|
359
|
+
? chalk.white('需要启动时,在主菜单选择“启动 Claude Code”,或直接输入 ') + chalk.cyan.bold('claude') + '\n' + chalk.dim(getActivationHint(file))
|
|
360
|
+
: chalk.white('下一步可选择“接入 Claude Code 终端”或“接入 Claude 桌面应用”。');
|
|
361
|
+
|
|
362
|
+
console.log(boxen(
|
|
363
|
+
chalk.bold(writeCodeEnv ? 'Claude Code 终端配置完成!\n\n' : 'API 连接配置完成!\n\n') +
|
|
364
|
+
chalk.dim('Base URL ') + chalk.cyan(cfg.baseUrl) + '\n' +
|
|
365
|
+
chalk.dim('API Key ') + chalk.cyan('已保存') + '\n' +
|
|
366
|
+
chalk.dim('模型 ') + chalk.yellow(cfg.model) + '\n\n' +
|
|
367
|
+
nextStep,
|
|
368
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
369
|
+
));
|
|
370
|
+
}
|
|
371
|
+
|
|
241
372
|
program
|
|
242
373
|
.name('claw')
|
|
243
374
|
.description('Claude Code × 国产大模型一键接入')
|
|
@@ -299,14 +430,25 @@ program
|
|
|
299
430
|
|
|
300
431
|
console.log(boxen(
|
|
301
432
|
chalk.bold('下一步\n\n') +
|
|
302
|
-
chalk.cyan(' claw
|
|
433
|
+
chalk.cyan(' claw config') + chalk.dim(' 配置 API 连接\n') +
|
|
434
|
+
chalk.cyan(' claw code') + chalk.dim(' 接入 Claude Code 终端'),
|
|
303
435
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'cyan', margin: { top: 1, bottom: 1 } }
|
|
304
436
|
));
|
|
305
437
|
});
|
|
306
438
|
|
|
439
|
+
program
|
|
440
|
+
.command('config')
|
|
441
|
+
.description('配置 API 连接(不修改终端或桌面)')
|
|
442
|
+
.action(() => runConfigFlow({ writeCodeEnv: false }));
|
|
443
|
+
|
|
307
444
|
program
|
|
308
445
|
.command('setup')
|
|
309
|
-
.description('配置 API
|
|
446
|
+
.description('配置 API 并接入 Claude Code 终端(兼容旧命令)')
|
|
447
|
+
.action(() => runConfigFlow({ writeCodeEnv: true }));
|
|
448
|
+
|
|
449
|
+
program
|
|
450
|
+
.command('code')
|
|
451
|
+
.description('接入 Claude Code 终端')
|
|
310
452
|
.action(async () => {
|
|
311
453
|
const chalk = (await import('chalk')).default;
|
|
312
454
|
const ora = (await import('ora')).default;
|
|
@@ -314,112 +456,81 @@ program
|
|
|
314
456
|
|
|
315
457
|
console.log(await getBanner());
|
|
316
458
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
console.log(boxen(
|
|
322
|
-
chalk.bold('当前配置\n\n') +
|
|
323
|
-
chalk.dim('厂商 ') + chalk.white(existingProvider?.name || existing.provider) + '\n' +
|
|
324
|
-
chalk.dim('模型 ') + chalk.yellow(existing.model) + '\n' +
|
|
325
|
-
chalk.dim('Key ') + chalk.dim(existing.apiKey ? '已保存' : '缺失'),
|
|
326
|
-
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'yellow', margin: { top: 1, bottom: 1 } }
|
|
327
|
-
));
|
|
328
|
-
const overwrite = await confirm({ message: '覆盖现有配置?', default: false });
|
|
329
|
-
if (!overwrite) return;
|
|
459
|
+
const config = loadConfig();
|
|
460
|
+
if (!config) {
|
|
461
|
+
console.log(chalk.red('\n未配置 API 连接,请先运行: claw config\n'));
|
|
462
|
+
return;
|
|
330
463
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (step === 'provider') {
|
|
337
|
-
providerKey = await select({ loop: false,
|
|
338
|
-
message: chalk.cyan('选择 AI 厂商'),
|
|
339
|
-
choices: [
|
|
340
|
-
...Object.entries(PROVIDERS).map(([value, p]) => ({ name: p.name, value })),
|
|
341
|
-
{ name: chalk.dim('↩ 返回主菜单'), value: '__BACK__' },
|
|
342
|
-
],
|
|
343
|
-
});
|
|
344
|
-
if (providerKey === '__BACK__') return;
|
|
345
|
-
provider = PROVIDERS[providerKey];
|
|
346
|
-
if (provider.custom) {
|
|
347
|
-
customConfig = await configureCustomProvider({ chalk, ora });
|
|
348
|
-
if (!customConfig) { step = 'provider'; continue; }
|
|
349
|
-
break;
|
|
350
|
-
}
|
|
351
|
-
step = 'apikey';
|
|
352
|
-
} else if (step === 'apikey') {
|
|
353
|
-
const k = await input({
|
|
354
|
-
message: chalk.cyan(`${provider.name} API Key(输入 b 返回上一步)`),
|
|
355
|
-
transformer: (v) => v && v !== 'b' ? chalk.dim('•'.repeat(v.length)) : v,
|
|
356
|
-
validate: (v) => v.trim().length > 0 ? true : 'API Key 不能为空',
|
|
357
|
-
});
|
|
358
|
-
if (k.trim() === 'b') { step = 'provider'; continue; }
|
|
359
|
-
apiKey = k.trim();
|
|
360
|
-
step = 'model';
|
|
361
|
-
} else if (step === 'model') {
|
|
362
|
-
const fetchSpinner = ora('正在获取可用模型...').start();
|
|
363
|
-
const onlineModels = await fetchModels(providerKey, apiKey);
|
|
364
|
-
let modelChoices;
|
|
365
|
-
if (onlineModels && onlineModels.length > 0) {
|
|
366
|
-
fetchSpinner.succeed(chalk.green(`已获取 ${onlineModels.length} 个可用模型`));
|
|
367
|
-
modelChoices = onlineModels.map(id => ({ name: id, value: id }));
|
|
368
|
-
} else {
|
|
369
|
-
fetchSpinner.warn(chalk.yellow('无法获取在线列表,使用内置默认列表'));
|
|
370
|
-
modelChoices = provider.models;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const m = await select({ loop: false,
|
|
374
|
-
message: chalk.cyan('选择模型'),
|
|
375
|
-
choices: [
|
|
376
|
-
...modelChoices,
|
|
377
|
-
{ name: chalk.dim('↩ 返回上一步(重新输入 Key)'), value: '__BACK__' },
|
|
378
|
-
],
|
|
379
|
-
});
|
|
380
|
-
if (m === '__BACK__') { step = 'apikey'; continue; }
|
|
381
|
-
model = m;
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
464
|
+
const configProblem = getConfigValidationMessage(config);
|
|
465
|
+
if (configProblem) {
|
|
466
|
+
console.log(chalk.red(`\n配置无效:${configProblem}`));
|
|
467
|
+
console.log(chalk.dim('请运行 claw config 重新配置。\n'));
|
|
468
|
+
return;
|
|
384
469
|
}
|
|
385
470
|
|
|
386
|
-
const spinner = ora('
|
|
387
|
-
let
|
|
471
|
+
const spinner = ora('写入 Claude Code 终端环境变量...').start();
|
|
472
|
+
let file;
|
|
388
473
|
try {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
saveConfig(cfg);
|
|
392
|
-
({ result, file } = writeEnvToZshrc(cfg.baseUrl, cfg.apiKey, cfg.model, cfg.fastModel));
|
|
393
|
-
spinner.succeed(chalk.green(result === 'updated' ? `环境变量已更新 → ${file}` : `环境变量已写入 → ${file}`));
|
|
474
|
+
({ file } = writeEnvToZshrc(config.baseUrl, config.apiKey, config.model, config.fastModel));
|
|
475
|
+
spinner.succeed(chalk.green(`Claude Code 终端已接入 → ${file}`));
|
|
394
476
|
} catch (e) {
|
|
395
477
|
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
396
478
|
return;
|
|
397
479
|
}
|
|
398
|
-
console.log(chalk.dim(`⚠ API Key 以明文存储在 ${file} 和 ~/.clawai.json`));
|
|
399
480
|
|
|
481
|
+
console.log(chalk.dim(getStorageHint(file)));
|
|
400
482
|
console.log(boxen(
|
|
401
|
-
chalk.bold('
|
|
402
|
-
chalk.dim('
|
|
403
|
-
chalk.dim('
|
|
404
|
-
chalk.white('
|
|
483
|
+
chalk.bold('Claude Code 终端已接入\n\n') +
|
|
484
|
+
chalk.dim('Base URL ') + chalk.cyan(config.baseUrl) + '\n' +
|
|
485
|
+
chalk.dim('模型 ') + chalk.yellow(config.model) + '\n\n' +
|
|
486
|
+
chalk.white('需要启动时,在主菜单选择“启动 Claude Code”,或直接输入 ') + chalk.cyan.bold('claude') + '\n' +
|
|
487
|
+
chalk.dim(getActivationHint(file)),
|
|
405
488
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
406
489
|
));
|
|
490
|
+
});
|
|
407
491
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
492
|
+
program
|
|
493
|
+
.command('code-reset')
|
|
494
|
+
.description('恢复 Claude Code 终端默认配置')
|
|
495
|
+
.action(async () => {
|
|
496
|
+
const chalk = (await import('chalk')).default;
|
|
497
|
+
const ora = (await import('ora')).default;
|
|
498
|
+
const boxen = (await import('boxen')).default;
|
|
499
|
+
|
|
500
|
+
console.log(await getBanner());
|
|
411
501
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}).on('error', () => {
|
|
416
|
-
console.log(chalk.yellow('\nClaude Code 未找到,请先运行: claw install-claude'));
|
|
502
|
+
const yes = await confirm({
|
|
503
|
+
message: chalk.red('确定要恢复 Claude Code 终端默认配置吗?API 连接和桌面配置不会被清除'),
|
|
504
|
+
default: false,
|
|
417
505
|
});
|
|
506
|
+
if (!yes) {
|
|
507
|
+
console.log(chalk.dim('已取消'));
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const spinner = ora('正在恢复 Claude Code 终端默认配置...').start();
|
|
512
|
+
const cleared = clearClaudeCodeEnv();
|
|
513
|
+
await new Promise(r => setTimeout(r, 300));
|
|
514
|
+
|
|
515
|
+
if (cleared.length === 0) {
|
|
516
|
+
spinner.warn(chalk.yellow('没有找到 Claude Code 终端环境变量,无需恢复'));
|
|
517
|
+
} else {
|
|
518
|
+
spinner.succeed(chalk.green('Claude Code 终端已恢复默认'));
|
|
519
|
+
const resetNote = cleared.includes('Windows 用户环境变量')
|
|
520
|
+
? '注:当前终端的环境变量还在内存中,重新打开 PowerShell / CMD 后才彻底清除'
|
|
521
|
+
: '注:当前终端的环境变量还在内存中,重开终端或 unset 才彻底清除';
|
|
522
|
+
console.log(boxen(
|
|
523
|
+
chalk.bold('已清除以下位置中的 Claude Code 终端环境变量:\n\n') +
|
|
524
|
+
cleared.map(f => chalk.cyan(' • ' + f)).join('\n') +
|
|
525
|
+
'\n\n' + chalk.dim(resetNote),
|
|
526
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
527
|
+
));
|
|
528
|
+
}
|
|
418
529
|
});
|
|
419
530
|
|
|
420
531
|
program
|
|
421
532
|
.command('switch')
|
|
422
|
-
.description('
|
|
533
|
+
.description('快速切换模型(只更新 API 连接)')
|
|
423
534
|
.action(async () => {
|
|
424
535
|
const chalk = (await import('chalk')).default;
|
|
425
536
|
const ora = (await import('ora')).default;
|
|
@@ -428,13 +539,13 @@ program
|
|
|
428
539
|
|
|
429
540
|
const config = loadConfig();
|
|
430
541
|
if (!config) {
|
|
431
|
-
console.log(chalk.red('\n未配置,请先运行: claw
|
|
542
|
+
console.log(chalk.red('\n未配置,请先运行: claw config\n'));
|
|
432
543
|
return;
|
|
433
544
|
}
|
|
434
545
|
const configProblem = getConfigValidationMessage(config);
|
|
435
546
|
if (configProblem) {
|
|
436
547
|
console.log(chalk.red(`\n配置无效:${configProblem}`));
|
|
437
|
-
console.log(chalk.dim('请运行 claw
|
|
548
|
+
console.log(chalk.dim('请运行 claw config 重新配置。\n'));
|
|
438
549
|
return;
|
|
439
550
|
}
|
|
440
551
|
|
|
@@ -454,10 +565,9 @@ program
|
|
|
454
565
|
|
|
455
566
|
const spinner = ora('切换中...').start();
|
|
456
567
|
saveConfig(customConfig);
|
|
457
|
-
const { file } = writeEnvToZshrc(customConfig.baseUrl, customConfig.apiKey, customConfig.model, customConfig.fastModel);
|
|
458
568
|
await new Promise(r => setTimeout(r, 300));
|
|
459
|
-
spinner.succeed(chalk.green(
|
|
460
|
-
console.log(chalk.dim(
|
|
569
|
+
spinner.succeed(chalk.green(`API 连接已切换至 ${customConfig.providerName} · ${customConfig.model}`));
|
|
570
|
+
console.log(chalk.dim('如需让外部 claude 命令使用新模型,请运行 claw code。'));
|
|
461
571
|
return;
|
|
462
572
|
}
|
|
463
573
|
|
|
@@ -500,10 +610,9 @@ program
|
|
|
500
610
|
const fastModel = resolveFastModel(provider, model);
|
|
501
611
|
const newConfig = { ...config, provider: providerKey, model, fastModel, baseUrl: provider.baseUrl, apiKey };
|
|
502
612
|
saveConfig(newConfig);
|
|
503
|
-
const { file } = writeEnvToZshrc(provider.baseUrl, apiKey, model, fastModel);
|
|
504
613
|
await new Promise(r => setTimeout(r, 300));
|
|
505
|
-
spinner.succeed(chalk.green(
|
|
506
|
-
console.log(chalk.dim(
|
|
614
|
+
spinner.succeed(chalk.green(`API 连接已切换至 ${provider.name} · ${model}`));
|
|
615
|
+
console.log(chalk.dim('如需让外部 claude 命令使用新模型,请运行 claw code。'));
|
|
507
616
|
});
|
|
508
617
|
|
|
509
618
|
program
|
|
@@ -511,9 +620,118 @@ program
|
|
|
511
620
|
.description('查看当前配置和 Key 有效性')
|
|
512
621
|
.action(showStatus);
|
|
513
622
|
|
|
623
|
+
program
|
|
624
|
+
.command('desktop')
|
|
625
|
+
.description('接入 Claude 桌面应用使用当前模型')
|
|
626
|
+
.action(async () => {
|
|
627
|
+
const chalk = (await import('chalk')).default;
|
|
628
|
+
const ora = (await import('ora')).default;
|
|
629
|
+
const boxen = (await import('boxen')).default;
|
|
630
|
+
|
|
631
|
+
console.log(await getBanner());
|
|
632
|
+
|
|
633
|
+
const config = loadConfig();
|
|
634
|
+
if (!config) {
|
|
635
|
+
console.log(chalk.red('\n未配置 API 连接,请先运行: claw config\n'));
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const configProblem = getConfigValidationMessage(config);
|
|
639
|
+
if (configProblem) {
|
|
640
|
+
console.log(chalk.red(`\n配置无效:${configProblem}`));
|
|
641
|
+
console.log(chalk.dim('请运行 claw config 重新配置。\n'));
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (process.platform !== 'darwin' && process.platform !== 'win32') {
|
|
646
|
+
console.log(chalk.yellow('\nClaude 桌面应用 3P 配置目前仅支持 macOS / Windows。\n'));
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const spinner = ora('写入 Claude 桌面应用配置...').start();
|
|
651
|
+
let result;
|
|
652
|
+
try {
|
|
653
|
+
result = writeClaudeDesktopConfig(config);
|
|
654
|
+
if (result.result === 'unsupported') {
|
|
655
|
+
spinner.warn(chalk.yellow('当前系统暂不支持自动配置 Claude 桌面应用'));
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
spinner.succeed(chalk.green(`Claude 桌面应用配置已写入 → ${result.file}`));
|
|
659
|
+
} catch (e) {
|
|
660
|
+
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
console.log(boxen(
|
|
665
|
+
chalk.bold('Claude 桌面应用已配置为 Gateway 模式\n\n') +
|
|
666
|
+
chalk.dim('Base URL ') + chalk.cyan(config.baseUrl) + '\n' +
|
|
667
|
+
chalk.dim('模型 ') + chalk.yellow(config.model) + '\n' +
|
|
668
|
+
chalk.dim('认证方式 ') + chalk.cyan('bearer') + '\n\n' +
|
|
669
|
+
chalk.yellow(getDesktopOpenHint()) + '\n' +
|
|
670
|
+
chalk.dim('要求:网关必须支持 Anthropic POST /v1/messages,且 Base URL 必须是 HTTPS。'),
|
|
671
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'cyan', margin: { top: 1, bottom: 1 } }
|
|
672
|
+
));
|
|
673
|
+
|
|
674
|
+
if (process.platform === 'darwin') {
|
|
675
|
+
const shouldOpen = await confirm({ message: '是否现在打开 Claude 桌面应用?', default: true });
|
|
676
|
+
if (shouldOpen) {
|
|
677
|
+
const openSpinner = ora('正在打开 Claude 桌面应用...').start();
|
|
678
|
+
try {
|
|
679
|
+
openClaudeDesktop();
|
|
680
|
+
openSpinner.succeed(chalk.green('Claude 桌面应用已打开'));
|
|
681
|
+
} catch (e) {
|
|
682
|
+
openSpinner.fail(chalk.red(`打开失败: ${e.message}`));
|
|
683
|
+
console.log(chalk.dim(getDesktopOpenHint()));
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
} else {
|
|
687
|
+
console.log(chalk.dim(getDesktopOpenHint()));
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
program
|
|
692
|
+
.command('desktop-reset')
|
|
693
|
+
.description('恢复 Claude 桌面应用默认配置')
|
|
694
|
+
.action(async () => {
|
|
695
|
+
const chalk = (await import('chalk')).default;
|
|
696
|
+
const ora = (await import('ora')).default;
|
|
697
|
+
const boxen = (await import('boxen')).default;
|
|
698
|
+
|
|
699
|
+
console.log(await getBanner());
|
|
700
|
+
|
|
701
|
+
if (process.platform !== 'darwin' && process.platform !== 'win32') {
|
|
702
|
+
console.log(chalk.yellow('\nClaude 桌面应用 3P 配置目前仅支持 macOS / Windows。\n'));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const yes = await confirm({
|
|
707
|
+
message: chalk.red('确定要恢复 Claude 桌面应用默认配置吗?终端配置不会被清除'),
|
|
708
|
+
default: false,
|
|
709
|
+
});
|
|
710
|
+
if (!yes) {
|
|
711
|
+
console.log(chalk.dim('已取消'));
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const spinner = ora('正在恢复 Claude 桌面应用默认配置...').start();
|
|
716
|
+
const result = clearClaudeDesktopConfig();
|
|
717
|
+
await new Promise(r => setTimeout(r, 300));
|
|
718
|
+
|
|
719
|
+
if (result.result === 'updated') {
|
|
720
|
+
spinner.succeed(chalk.green('Claude 桌面应用已恢复默认'));
|
|
721
|
+
console.log(boxen(
|
|
722
|
+
chalk.bold('已清除 Claude 桌面应用第三方推理配置:\n\n') +
|
|
723
|
+
chalk.cyan(' • ' + result.file) +
|
|
724
|
+
'\n\n' + chalk.dim('如 Claude Desktop 已打开,请完全退出后重新打开。'),
|
|
725
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
726
|
+
));
|
|
727
|
+
} else {
|
|
728
|
+
spinner.warn(chalk.yellow('没有找到 Claude 桌面应用第三方推理配置,无需恢复'));
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
|
|
514
732
|
program
|
|
515
733
|
.command('reset')
|
|
516
|
-
.description('
|
|
734
|
+
.description('清除 API 连接、终端环境变量和桌面配置')
|
|
517
735
|
.action(async () => {
|
|
518
736
|
const chalk = (await import('chalk')).default;
|
|
519
737
|
const ora = (await import('ora')).default;
|
|
@@ -532,16 +750,23 @@ program
|
|
|
532
750
|
|
|
533
751
|
const spinner = ora('清除中...').start();
|
|
534
752
|
const cleared = resetConfig();
|
|
753
|
+
const desktopCleared = clearClaudeDesktopConfig();
|
|
754
|
+
if (desktopCleared.result === 'updated') {
|
|
755
|
+
cleared.push(desktopCleared.file);
|
|
756
|
+
}
|
|
535
757
|
await new Promise(r => setTimeout(r, 300));
|
|
536
758
|
|
|
537
759
|
if (cleared.length === 0) {
|
|
538
760
|
spinner.warn(chalk.yellow('没有找到任何配置,无需清除'));
|
|
539
761
|
} else {
|
|
540
762
|
spinner.succeed(chalk.green('已恢复默认'));
|
|
763
|
+
const resetNote = cleared.includes('Windows 用户环境变量')
|
|
764
|
+
? '注:当前终端的环境变量还在内存中,重新打开 PowerShell / CMD 后才彻底清除'
|
|
765
|
+
: '注:当前终端的环境变量还在内存中,重开终端或 unset 才彻底清除';
|
|
541
766
|
console.log(boxen(
|
|
542
767
|
chalk.bold('已清除以下文件中的 clawai 配置:\n\n') +
|
|
543
768
|
cleared.map(f => chalk.cyan(' • ' + f)).join('\n') +
|
|
544
|
-
'\n\n' + chalk.dim(
|
|
769
|
+
'\n\n' + chalk.dim(resetNote),
|
|
545
770
|
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
546
771
|
));
|
|
547
772
|
}
|
|
@@ -562,7 +787,7 @@ async function renderStatusBar(apiStatus) {
|
|
|
562
787
|
claudeInstalled,
|
|
563
788
|
env: process.env,
|
|
564
789
|
});
|
|
565
|
-
const statusLines = buildMenuStatusLines(view, { apiStatus, claudeInstalled });
|
|
790
|
+
const statusLines = buildMenuStatusLines(view, { apiStatus, claudeInstalled, platform: process.platform });
|
|
566
791
|
cfgPart = statusLines.map((line, index) => {
|
|
567
792
|
if (index === 0) return line.replace('API 正常', chalk.green('API 正常')).replace('API Key 无效', chalk.red('API Key 无效')).replace('网络/服务异常', chalk.yellow('网络/服务异常'));
|
|
568
793
|
if (line.startsWith('环境变量未生效')) return chalk.yellow(line);
|
|
@@ -598,7 +823,7 @@ async function runMenu() {
|
|
|
598
823
|
|
|
599
824
|
if (configProblem) {
|
|
600
825
|
console.log(chalk.red(` ● 配置无效:${configProblem}`));
|
|
601
|
-
console.log(chalk.dim('
|
|
826
|
+
console.log(chalk.dim(' 请先选择“配置 API 连接”重新配置'));
|
|
602
827
|
} else {
|
|
603
828
|
console.log(await renderStatusBar(apiStatus));
|
|
604
829
|
}
|
|
@@ -607,13 +832,17 @@ async function runMenu() {
|
|
|
607
832
|
const action = await select({ loop: false,
|
|
608
833
|
message: chalk.cyan('选择操作'),
|
|
609
834
|
choices: [
|
|
610
|
-
{ name: '🤖 启动 Claude Code', value: 'launch', disabled: (!config || configProblem) && '
|
|
835
|
+
{ name: '🤖 启动 Claude Code', value: 'launch', disabled: (!config || configProblem) && '需先配置 API 连接' },
|
|
611
836
|
{ name: '📦 安装 Claude Code', value: 'install' },
|
|
612
|
-
{ name: config ? '
|
|
613
|
-
{ name: '
|
|
614
|
-
{ name: '
|
|
615
|
-
{ name: '
|
|
616
|
-
{ name: '
|
|
837
|
+
{ name: config ? '🔑 重新配置 API 连接' : '🔑 配置 API 连接', value: 'config' },
|
|
838
|
+
{ name: '💻 接入 Claude Code 终端', value: 'code', disabled: (!config || configProblem) && '需先配置 API 连接' },
|
|
839
|
+
{ name: '🔄 切换厂商或模型', value: 'switch', disabled: (!config || configProblem) && '需先配置 API 连接' },
|
|
840
|
+
{ name: '🖥 接入 Claude 桌面应用', value: 'desktop', disabled: (!config || configProblem) && '需先配置 API 连接' },
|
|
841
|
+
{ name: '↩️ 恢复 Claude Code 终端默认', value: 'code-reset' },
|
|
842
|
+
{ name: '↩️ 恢复 Claude 桌面默认', value: 'desktop-reset' },
|
|
843
|
+
{ name: '📊 查看当前配置', value: 'status', disabled: !config && '需先配置 API 连接' },
|
|
844
|
+
{ name: '🔁 重新检测 API', value: 'recheck', disabled: (!config || configProblem) && '需先配置 API 连接' },
|
|
845
|
+
{ name: '🗑 清除所有 yingclaw 配置', value: 'reset' },
|
|
617
846
|
{ name: '退出', value: 'exit' },
|
|
618
847
|
],
|
|
619
848
|
});
|
|
@@ -645,8 +874,13 @@ async function runMenu() {
|
|
|
645
874
|
|
|
646
875
|
const cmdMap = {
|
|
647
876
|
install: 'install-claude',
|
|
877
|
+
config: 'config',
|
|
648
878
|
setup: 'setup',
|
|
879
|
+
code: 'code',
|
|
880
|
+
'code-reset': 'code-reset',
|
|
649
881
|
switch: 'switch',
|
|
882
|
+
desktop: 'desktop',
|
|
883
|
+
'desktop-reset': 'desktop-reset',
|
|
650
884
|
status: 'status',
|
|
651
885
|
reset: 'reset',
|
|
652
886
|
};
|
package/index.js
CHANGED
package/lib/config.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
4
5
|
|
|
5
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
|
+
];
|
|
6
19
|
|
|
7
20
|
const PROVIDERS = {
|
|
8
21
|
deepseek: {
|
|
@@ -15,6 +28,15 @@ const PROVIDERS = {
|
|
|
15
28
|
{ name: 'DeepSeek V4 Flash(快速)', value: 'deepseek-v4-flash' },
|
|
16
29
|
],
|
|
17
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
|
+
},
|
|
18
40
|
qwen: {
|
|
19
41
|
name: '阿里云百炼 (Qwen)',
|
|
20
42
|
baseUrl: 'https://dashscope.aliyuncs.com/apps/anthropic',
|
|
@@ -222,6 +244,29 @@ function buildClaudeEnv({ provider, baseUrl, apiKey, model, fastModel }) {
|
|
|
222
244
|
};
|
|
223
245
|
}
|
|
224
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
|
+
|
|
225
270
|
function classifyValidationStatus(statusCode) {
|
|
226
271
|
if (statusCode >= 200 && statusCode < 300) return true;
|
|
227
272
|
if (statusCode === 401 || statusCode === 403) return false;
|
|
@@ -254,7 +299,15 @@ function buildEnvBlock(baseUrl, apiKey, model, fastModel) {
|
|
|
254
299
|
}
|
|
255
300
|
|
|
256
301
|
// 写入或更新 shell 配置文件中的环境变量块
|
|
257
|
-
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
|
+
|
|
258
311
|
const shell = process.env.SHELL || '';
|
|
259
312
|
let rcFile;
|
|
260
313
|
if (shell.includes('bash')) {
|
|
@@ -277,18 +330,18 @@ function writeEnvToZshrc(baseUrl, apiKey, model, fastModel) {
|
|
|
277
330
|
return { result: cleaned !== current ? 'updated' : 'added', file: rcFile };
|
|
278
331
|
}
|
|
279
332
|
|
|
280
|
-
|
|
281
|
-
function resetConfig() {
|
|
333
|
+
function clearClaudeCodeEnv(options = {}) {
|
|
282
334
|
const cleared = [];
|
|
335
|
+
const platform = options.platform || process.platform;
|
|
283
336
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
cleared
|
|
337
|
+
if (platform === 'win32') {
|
|
338
|
+
runWindowsEnvCommands(buildWindowsClearEnvCommands(), options.runner || spawnSync, { ignoreErrors: true });
|
|
339
|
+
cleared.push(WINDOWS_ENV_LABEL);
|
|
340
|
+
return cleared;
|
|
288
341
|
}
|
|
289
342
|
|
|
290
343
|
// 清理所有可能的 rc 文件中的 clawai 块
|
|
291
|
-
const rcFiles = [
|
|
344
|
+
const rcFiles = options.rcFiles || [
|
|
292
345
|
path.join(os.homedir(), '.zshrc'),
|
|
293
346
|
path.join(os.homedir(), '.bashrc'),
|
|
294
347
|
path.join(os.homedir(), '.bash_profile'),
|
|
@@ -308,10 +361,27 @@ function resetConfig() {
|
|
|
308
361
|
return cleared;
|
|
309
362
|
}
|
|
310
363
|
|
|
364
|
+
// 清除所有 clawai 配置(配置文件 + shell 环境变量块)
|
|
365
|
+
function resetConfig(options = {}) {
|
|
366
|
+
const cleared = [];
|
|
367
|
+
const configFile = options.configFile || CONFIG_FILE;
|
|
368
|
+
|
|
369
|
+
// 删配置文件
|
|
370
|
+
if (fs.existsSync(configFile)) {
|
|
371
|
+
fs.unlinkSync(configFile);
|
|
372
|
+
cleared.push(configFile);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return [...cleared, ...clearClaudeCodeEnv(options)];
|
|
376
|
+
}
|
|
377
|
+
|
|
311
378
|
module.exports = {
|
|
312
379
|
loadConfig,
|
|
313
380
|
saveConfig,
|
|
314
381
|
writeEnvToZshrc,
|
|
382
|
+
buildWindowsSetEnvCommands,
|
|
383
|
+
buildWindowsClearEnvCommands,
|
|
384
|
+
clearClaudeCodeEnv,
|
|
315
385
|
fetchModels,
|
|
316
386
|
resetConfig,
|
|
317
387
|
validateConfig,
|
package/lib/desktop.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
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 buildClaudeDesktopOpenCommands(platform = process.platform) {
|
|
123
|
+
if (platform !== 'darwin') return null;
|
|
124
|
+
return [
|
|
125
|
+
{ command: 'open', args: ['-a', 'Claude'] },
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function openClaudeDesktop(options = {}) {
|
|
130
|
+
const platform = options.platform || process.platform;
|
|
131
|
+
const commands = buildClaudeDesktopOpenCommands(platform);
|
|
132
|
+
if (!commands) return { result: 'unsupported' };
|
|
133
|
+
|
|
134
|
+
const runner = options.runner || spawnSync;
|
|
135
|
+
const [openCommand] = commands;
|
|
136
|
+
const result = runner(openCommand.command, openCommand.args, { stdio: 'ignore', windowsHide: true });
|
|
137
|
+
if (result.status !== 0) {
|
|
138
|
+
throw new Error('Claude 桌面应用打开失败');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { result: 'opened' };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
buildClaudeDesktopEnterpriseConfig,
|
|
146
|
+
buildClaudeDesktopOpenCommands,
|
|
147
|
+
clearClaudeDesktopConfig,
|
|
148
|
+
getClaudeDesktopConfigPath,
|
|
149
|
+
openClaudeDesktop,
|
|
150
|
+
writeClaudeDesktopConfig,
|
|
151
|
+
CLAUDE_DESKTOP_LABEL,
|
|
152
|
+
};
|
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.12",
|
|
4
|
+
"description": "Claude Code × 国产大模型一键接入:DeepSeek、Kimi、Qwen、MiniMax、GLM、MiMo",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"claw": "bin/cli.js"
|