yingclaw 2.5.27 → 2.5.38
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 +96 -2
- package/bin/cli.js +118 -14
- package/index.js +1 -0
- package/lib/config.js +44 -11
- package/lib/desktop.js +235 -3
- package/lib/doctor.js +200 -2
- package/lib/gateway.js +313 -10
- package/lib/google.js +138 -0
- package/lib/install.js +1 -0
- package/lib/panel.js +18 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Claude Code × 国产大模型,一键接入。
|
|
4
4
|
|
|
5
|
-
支持 DeepSeek、Kimi、火山方舟 Coding Plan、阿里云百炼(Qwen)、MiniMax、智谱 GLM、小米 MiMo、B.AI、OpenCode Zen/Go,也支持自定义 Anthropic
|
|
5
|
+
支持 DeepSeek、Kimi、火山方舟 Coding Plan、阿里云百炼(Qwen)、MiniMax、智谱 GLM、小米 MiMo、B.AI、OpenCode Zen/Go,也支持自定义 Anthropic、OpenAI、Google/Gemini 兼容接口,无需梯子即可使用 Claude Code。
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
@@ -56,8 +56,34 @@ claw desktop
|
|
|
56
56
|
```
|
|
57
57
|
`claw desktop` 会把 Claude Desktop 指向 yingclaw 本机 Gateway,并只暴露 `claude-sonnet-4-6`、`claude-opus-4-7`、`claude-haiku-4-5` 这类 Claude Desktop 可接受的模型名。真实第三方模型仍按 `claw config` 保存的主模型和快速模型转发。
|
|
58
58
|
|
|
59
|
+
`claw desktop` 默认只接入 Claude 桌面应用的 Cowork / Gateway,不写入 Claude Code 共享设置,因此不会自动接入 Claude Code 终端。交互面板会询问是否同时接入 Claude 桌面 Code 模式,默认选 No;选择 Yes 时才会写入 Claude Code 共享设置。
|
|
60
|
+
|
|
61
|
+
如果你需要同时接入 Claude Desktop 的 Code 模式,再显式运行:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
claw desktop --with-code
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`--with-code` 会写入 `~/.claude/settings.json`,这个文件也会被 Claude Code CLI / VS Code Claude Code 读取。只想用桌面 Cowork 时不要加这个参数。
|
|
68
|
+
|
|
59
69
|
`claw desktop` 会设置 Gateway 登录自动运行;如果自动启动失败,可以手动运行 `claw gateway`。
|
|
60
70
|
|
|
71
|
+
出现问题时先运行:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
claw doctor
|
|
75
|
+
claw support -o yingclaw-support.json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Windows 桌面应用 / Gateway 问题建议运行:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
claw doctor --windows
|
|
82
|
+
claw support --windows -o yingclaw-support.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`support` 生成的是脱敏排障包,不包含 API Key / Gateway Key 明文。
|
|
86
|
+
|
|
61
87
|
## 支持的厂商
|
|
62
88
|
|
|
63
89
|
| 厂商 | 主模型 | 快速模型 |
|
|
@@ -75,6 +101,7 @@ claw desktop
|
|
|
75
101
|
| OpenRouter | deepseek/deepseek-r1-0528 | deepseek/deepseek-chat-v3-0324 |
|
|
76
102
|
| 自定义 Anthropic 兼容接口 | 自动获取或手动输入 | 手动选择 |
|
|
77
103
|
| 自定义 OpenAI 兼容接口 | 自动获取或手动输入 | 手动选择 |
|
|
104
|
+
| 自定义 Google/Gemini 兼容接口 | 自动获取或手动输入 | 手动选择 |
|
|
78
105
|
|
|
79
106
|
DeepSeek 的 `[1m]` 后缀是真实 API 模型 ID,表示 100 万 token 上下文窗口([官方说明](https://api-docs.deepseek.com/zh-cn/quick_start/agent_integrations/claude_code))。
|
|
80
107
|
|
|
@@ -89,10 +116,15 @@ claw code # 接入 Claude Code 终端
|
|
|
89
116
|
claw vscode # 接入 VS Code Claude Code 扩展
|
|
90
117
|
claw vscode-reset # 恢复 VS Code Claude Code 扩展默认配置
|
|
91
118
|
claw desktop # 接入 Claude 桌面应用
|
|
119
|
+
claw desktop --with-code # 同时接入 Claude Desktop Code 模式,会写 Claude Code 共享设置
|
|
92
120
|
claw gateway # 启动 Claude 桌面应用本机 Gateway
|
|
93
121
|
claw desktop --direct # 高级:直连写入厂商 Gateway URL
|
|
94
122
|
claw switch # 快速切换厂商或模型
|
|
95
123
|
claw status # 查看当前配置,验证 Key 是否有效
|
|
124
|
+
claw doctor # 自检终端、VS Code、桌面、Gateway 和 API
|
|
125
|
+
claw doctor --windows # 追加 Windows 桌面/Gateway 专项检查
|
|
126
|
+
claw support # 生成脱敏排障包
|
|
127
|
+
claw support --windows -o yingclaw-support.json
|
|
96
128
|
claw update # 检查并升级到最新版本
|
|
97
129
|
|
|
98
130
|
claw code-reset # 恢复 Claude Code 终端默认配置
|
|
@@ -132,9 +164,11 @@ CLAUDE_CODE_EFFORT_LEVEL
|
|
|
132
164
|
- macOS / Windows:写入 `Claude-3p/configLibrary/` 中的 yingclaw entry
|
|
133
165
|
- Claude Desktop 访问 `http://127.0.0.1:18080/yingclaw`
|
|
134
166
|
- Gateway 再转发到当前保存的 Anthropic 兼容接口;如当前为 Kimi 或 OpenAI 兼容接口,则自动转换为 `/v1/chat/completions`
|
|
167
|
+
- Google/Gemini 兼容接口会通过 Gateway 转换为 Gemini `generateContent` / `streamGenerateContent`
|
|
135
168
|
- macOS 使用 LaunchAgent 登录启动 Gateway,Windows 使用 Startup 目录脚本登录启动 Gateway
|
|
136
169
|
- Windows 会同时写入 Roaming / Local 两个 Claude-3p 位置,以兼容不同 Claude Desktop 安装方式
|
|
137
|
-
- Anthropic 兼容接口的终端接入仍直接使用 `ANTHROPIC_*` 环境变量;Kimi
|
|
170
|
+
- Anthropic 兼容接口的终端接入仍直接使用 `ANTHROPIC_*` 环境变量;Kimi、OpenAI 兼容接口和 Google/Gemini 兼容接口会通过本机 Gateway 转换协议
|
|
171
|
+
- 默认不会写入 `~/.claude/settings.json`,避免影响 Claude Code 终端或 VS Code 扩展;面板中选择同时接入 Code,或使用 `claw desktop --with-code` 时才写入
|
|
138
172
|
|
|
139
173
|
使用 `inferenceProvider=gateway`、`inferenceGatewayAuthScheme=bearer`,将 Gateway Base URL 指向 yingclaw 本机 Gateway。高级用户可用 `claw desktop --direct` 保留旧式直连写入,但新版 Claude Desktop 可能拒绝非 Claude 模型名。
|
|
140
174
|
|
|
@@ -149,6 +183,66 @@ CLAUDE_CODE_EFFORT_LEVEL
|
|
|
149
183
|
- 扩展与 CLI 共享 Claude Code 设置;如果 VS Code 已打开,需重载窗口或重启后生效
|
|
150
184
|
- 如需恢复默认,运行 `claw vscode-reset`,只移除 yingclaw 写入的 Claude Code env 和 VS Code Claude Code 扩展项,保留其它用户设置
|
|
151
185
|
|
|
186
|
+
## 排障
|
|
187
|
+
|
|
188
|
+
### Gateway 未运行或端口被占用
|
|
189
|
+
|
|
190
|
+
Claude Desktop 报 `Can't reach 127.0.0.1:18080` 时,先确认 Gateway:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
claw gateway
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
如果端口被占用,`claw gateway` 会提示占用进程和 PID。可以关闭旧进程,或换端口后重新接入:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
claw gateway --port 18081
|
|
200
|
+
claw desktop
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Windows 桌面应用不生效
|
|
204
|
+
|
|
205
|
+
Windows 只关闭窗口可能不会退出 Claude 进程,配置也可能被 Roaming / Local 两个目录读取差异影响。建议运行:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
claw doctor --windows
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
它会检查:
|
|
212
|
+
|
|
213
|
+
- Gateway 端口是否监听
|
|
214
|
+
- Gateway 登录启动脚本是否存在
|
|
215
|
+
- `%APPDATA%\Claude-3p` 和 `%LOCALAPPDATA%\Claude-3p` 配置是否一致
|
|
216
|
+
- 是否仍有 Claude 进程在后台运行
|
|
217
|
+
|
|
218
|
+
如果提示配置未生效,请从系统托盘退出 Claude,或在任务管理器结束所有 Claude 进程后重新打开。
|
|
219
|
+
|
|
220
|
+
### OpenAI / Kimi / Gemini 为什么要 Gateway
|
|
221
|
+
|
|
222
|
+
Claude Code 和 Claude Desktop 原生期望 Anthropic Messages 协议。OpenAI 兼容接口使用 `/v1/chat/completions`,Gemini 使用 `generateContent`。yingclaw 会在本机 Gateway 中做协议转换,所以这类接口需要保持 Gateway 运行。
|
|
223
|
+
|
|
224
|
+
### 上游错误说明
|
|
225
|
+
|
|
226
|
+
Gateway 会把常见上游错误转换为中文说明:
|
|
227
|
+
|
|
228
|
+
| 类型 | 含义 | 建议 |
|
|
229
|
+
|------|------|------|
|
|
230
|
+
| 401 / 403 | Key 无效、过期或无权限 | 运行 `claw config` 重新输入 Key |
|
|
231
|
+
| 404 | 模型不存在或账号不可见 | 运行 `claw switch` 重新选择模型 |
|
|
232
|
+
| 408 / 504 / timeout | 上游响应超时 | 检查网络/VPN,或换快速模型 |
|
|
233
|
+
| subscription / quota | 套餐、余额或权限不足 | 到厂商控制台确认套餐和模型权限 |
|
|
234
|
+
| 429 | 请求限流 | 稍后重试或更换 Key / 模型 |
|
|
235
|
+
|
|
236
|
+
### 生成排障包
|
|
237
|
+
|
|
238
|
+
需要反馈问题时运行:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
claw support --windows -o yingclaw-support.json
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
排障包会包含当前配置摘要、doctor 检查、Gateway 状态、Desktop Code 状态和 Windows 专项检查,并脱敏 API Key / Gateway Key。公开发送前仍建议快速看一眼 Base URL 是否包含私有域名。
|
|
245
|
+
|
|
152
246
|
## 卸载
|
|
153
247
|
|
|
154
248
|
```bash
|
package/bin/cli.js
CHANGED
|
@@ -39,17 +39,30 @@ const {
|
|
|
39
39
|
getNodeInstallGuide,
|
|
40
40
|
getInstallFailureHints,
|
|
41
41
|
} = require('../lib/install');
|
|
42
|
-
const {
|
|
42
|
+
const {
|
|
43
|
+
buildClaudeDesktopCodeEnv,
|
|
44
|
+
checkClaudeDesktopCodeSettingsEnv,
|
|
45
|
+
clearClaudeDesktopCodeSettings,
|
|
46
|
+
clearClaudeDesktopConfig,
|
|
47
|
+
isDesktopConfigured,
|
|
48
|
+
openClaudeDesktop,
|
|
49
|
+
shouldPromptClaudeDesktopCodeSettings,
|
|
50
|
+
shouldWriteClaudeDesktopCodeSettings,
|
|
51
|
+
writeClaudeDesktopCodeSettings,
|
|
52
|
+
writeClaudeDesktopConfig,
|
|
53
|
+
} = require('../lib/desktop');
|
|
43
54
|
const {
|
|
44
55
|
DEFAULT_DESKTOP_GATEWAY_PORT,
|
|
45
56
|
YINGCLAW_GATEWAY_PREFIX,
|
|
46
57
|
buildDesktopGatewayMappingRows,
|
|
47
58
|
checkDesktopGatewayStatus,
|
|
59
|
+
checkGatewayPortOwner,
|
|
48
60
|
createGatewayServer,
|
|
49
61
|
ensureDesktopGatewayConfig,
|
|
62
|
+
explainGatewayListenError,
|
|
50
63
|
isDesktopChatModel,
|
|
51
64
|
} = require('../lib/gateway');
|
|
52
|
-
const { runDoctorChecks, summarize, STATUS_OK, STATUS_FAIL, STATUS_WARN, STATUS_INFO } = require('../lib/doctor');
|
|
65
|
+
const { buildDiagnosticReport, buildSupportBundle, runDoctorChecks, summarize, writeDiagnosticReport, STATUS_OK, STATUS_FAIL, STATUS_WARN, STATUS_INFO } = require('../lib/doctor');
|
|
53
66
|
const { normalizeOpenAiBaseUrl } = require('../lib/openai');
|
|
54
67
|
|
|
55
68
|
const program = new Command();
|
|
@@ -141,7 +154,8 @@ async function offerDesktopSync(chalk, ora, config) {
|
|
|
141
154
|
if (!syncDesktop) return;
|
|
142
155
|
const spinner = ora('同步 Claude 桌面应用配置...').start();
|
|
143
156
|
try {
|
|
144
|
-
|
|
157
|
+
const desktopConfig = ensureDesktopGatewayConfig(config);
|
|
158
|
+
writeClaudeDesktopConfig(desktopConfig);
|
|
145
159
|
spinner.succeed(chalk.green('Claude 桌面应用配置已同步'));
|
|
146
160
|
} catch (e) {
|
|
147
161
|
spinner.fail(chalk.red(`桌面配置同步失败: ${e.message}`));
|
|
@@ -338,8 +352,13 @@ function isOpenAiCompatibleConfig(config) {
|
|
|
338
352
|
return getProviderProtocol(config) === 'openai';
|
|
339
353
|
}
|
|
340
354
|
|
|
355
|
+
function needsLocalGateway(config) {
|
|
356
|
+
const p = getProviderProtocol(config);
|
|
357
|
+
return p === 'openai' || p === 'google';
|
|
358
|
+
}
|
|
359
|
+
|
|
341
360
|
function ensureGatewayForOpenAiConfig(config) {
|
|
342
|
-
return
|
|
361
|
+
return needsLocalGateway(config) ? ensureDesktopGatewayConfig(config) : config;
|
|
343
362
|
}
|
|
344
363
|
|
|
345
364
|
function writeClaudeCodeEnv(config) {
|
|
@@ -352,15 +371,26 @@ function writeClaudeCodeEnv(config) {
|
|
|
352
371
|
);
|
|
353
372
|
}
|
|
354
373
|
|
|
374
|
+
function customBaseUrlPrompt(protocol) {
|
|
375
|
+
if (protocol === 'openai') return 'OpenAI Base URL';
|
|
376
|
+
if (protocol === 'google') return 'Google / Gemini Base URL';
|
|
377
|
+
return 'Anthropic Base URL';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function normalizeCustomBaseUrl(protocol, baseUrl) {
|
|
381
|
+
if (protocol === 'openai') return normalizeOpenAiBaseUrl(baseUrl);
|
|
382
|
+
return normalizeAnthropicBaseUrl(baseUrl);
|
|
383
|
+
}
|
|
384
|
+
|
|
355
385
|
async function configureCustomProvider({ chalk, ora, existingConfig, providerKey = 'custom' }) {
|
|
356
386
|
const provider = PROVIDERS[providerKey];
|
|
357
387
|
const protocol = provider.protocol || 'anthropic';
|
|
358
388
|
const sameProvider = existingConfig?.provider === providerKey;
|
|
359
389
|
const baseUrl = await input({
|
|
360
|
-
message: chalk.cyan(protocol
|
|
390
|
+
message: chalk.cyan(customBaseUrlPrompt(protocol)),
|
|
361
391
|
default: sameProvider ? existingConfig.baseUrl : undefined,
|
|
362
392
|
validate: (v) => v.trim().length > 0 && isValidUrl(v.trim()) ? true : '请输入有效 URL',
|
|
363
|
-
}).then(v => protocol
|
|
393
|
+
}).then(v => normalizeCustomBaseUrl(protocol, v.trim()));
|
|
364
394
|
|
|
365
395
|
let apiKey = sameProvider ? existingConfig.apiKey : '';
|
|
366
396
|
if (apiKey) {
|
|
@@ -449,6 +479,7 @@ async function showStatus() {
|
|
|
449
479
|
claudeInstalled: isClaudeInstalled(),
|
|
450
480
|
desktopGatewayStatus: await checkDesktopGatewayStatus(config),
|
|
451
481
|
desktopGatewayAutostartStatus: getGatewayAutostartStatus(),
|
|
482
|
+
desktopCodeStatus: checkClaudeDesktopCodeSettingsEnv(config),
|
|
452
483
|
env: process.env,
|
|
453
484
|
});
|
|
454
485
|
|
|
@@ -461,6 +492,8 @@ async function showStatus() {
|
|
|
461
492
|
? chalk.cyan(value)
|
|
462
493
|
: label === 'Desktop Gateway'
|
|
463
494
|
? value.includes('已运行') ? chalk.green(value) : chalk.yellow(value)
|
|
495
|
+
: label === 'Desktop Code'
|
|
496
|
+
? value.includes('已配置') ? chalk.green(value) : chalk.yellow(value)
|
|
464
497
|
: label === '当前终端' && value === '未生效'
|
|
465
498
|
? chalk.yellow(value)
|
|
466
499
|
: value;
|
|
@@ -737,7 +770,7 @@ program
|
|
|
737
770
|
console.log(chalk.dim(getStorageHint(file)));
|
|
738
771
|
const displayedConfig = effectiveConfig;
|
|
739
772
|
const needsGatewayHint = process.platform !== 'darwin' && process.platform !== 'win32'
|
|
740
|
-
&&
|
|
773
|
+
&& needsLocalGateway(effectiveConfig);
|
|
741
774
|
console.log(boxen(
|
|
742
775
|
chalk.bold('Claude Code 终端已接入\n\n') +
|
|
743
776
|
chalk.dim('Base URL ') + chalk.cyan(buildClaudeEnv(displayedConfig).ANTHROPIC_BASE_URL) + '\n' +
|
|
@@ -854,7 +887,7 @@ program
|
|
|
854
887
|
}
|
|
855
888
|
|
|
856
889
|
const vscodeNeedsGatewayHint = process.platform !== 'darwin' && process.platform !== 'win32'
|
|
857
|
-
&&
|
|
890
|
+
&& needsLocalGateway(effectiveConfig);
|
|
858
891
|
console.log(boxen(
|
|
859
892
|
chalk.bold('VS Code Claude Code 扩展已配置\n\n') +
|
|
860
893
|
chalk.dim('共享设置 ') + chalk.cyan(result.claudeSettings.file) + '\n' +
|
|
@@ -1045,7 +1078,8 @@ program
|
|
|
1045
1078
|
));
|
|
1046
1079
|
});
|
|
1047
1080
|
server.on('error', (error) => {
|
|
1048
|
-
|
|
1081
|
+
const owner = checkGatewayPortOwner(port);
|
|
1082
|
+
console.error(chalk.red(explainGatewayListenError(error, owner)));
|
|
1049
1083
|
process.exitCode = 1;
|
|
1050
1084
|
});
|
|
1051
1085
|
});
|
|
@@ -1054,6 +1088,7 @@ program
|
|
|
1054
1088
|
.command('desktop')
|
|
1055
1089
|
.description('接入 Claude 桌面应用使用当前模型')
|
|
1056
1090
|
.option('--direct', '高级:直接写入当前厂商 Gateway URL,不启用本机模型映射')
|
|
1091
|
+
.option('--with-code', '同时接入 Claude Desktop 的 Code 模式(会写入 Claude Code 共享设置)')
|
|
1057
1092
|
.action(async (options) => {
|
|
1058
1093
|
const chalk = (await import('chalk')).default;
|
|
1059
1094
|
const ora = (await import('ora')).default;
|
|
@@ -1098,16 +1133,27 @@ program
|
|
|
1098
1133
|
saveConfig({ ...desktopConfig, desktopModels: selectedModels });
|
|
1099
1134
|
desktopConfig = { ...desktopConfig, availableModels: selectedModels };
|
|
1100
1135
|
}
|
|
1136
|
+
|
|
1137
|
+
if (shouldPromptClaudeDesktopCodeSettings(options)) {
|
|
1138
|
+
options.withCode = await confirm({
|
|
1139
|
+
message: '是否同时接入 Claude 桌面 Code 模式?(会写入 Claude Code 共享设置)',
|
|
1140
|
+
default: false,
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1101
1143
|
}
|
|
1102
1144
|
|
|
1103
1145
|
const spinner = ora('写入 Claude 桌面应用配置...').start();
|
|
1104
1146
|
let result;
|
|
1147
|
+
let codeSettingsResult = null;
|
|
1105
1148
|
try {
|
|
1106
1149
|
result = writeClaudeDesktopConfig(desktopConfig, { direct: options.direct });
|
|
1107
1150
|
if (result.result === 'unsupported') {
|
|
1108
1151
|
spinner.warn(chalk.yellow('当前系统暂不支持自动配置 Claude 桌面应用'));
|
|
1109
1152
|
return;
|
|
1110
1153
|
}
|
|
1154
|
+
if (shouldWriteClaudeDesktopCodeSettings(options)) {
|
|
1155
|
+
codeSettingsResult = writeClaudeDesktopCodeSettings(desktopConfig);
|
|
1156
|
+
}
|
|
1111
1157
|
const extraFiles = Array.isArray(result.files) && result.files.length > 1
|
|
1112
1158
|
? chalk.dim(`(已同步 ${result.files.length} 个配置位置)`)
|
|
1113
1159
|
: '';
|
|
@@ -1137,6 +1183,9 @@ program
|
|
|
1137
1183
|
: chalk.dim('Gateway ') + chalk.cyan(gatewayUrl) + '\n' +
|
|
1138
1184
|
chalk.dim('主模型 ') + chalk.yellow(desktopConfig.model) + '\n' +
|
|
1139
1185
|
chalk.dim('快速模型 ') + chalk.yellow(desktopConfig.fastModel || desktopConfig.model) + '\n' +
|
|
1186
|
+
(codeSettingsResult
|
|
1187
|
+
? chalk.dim('Code 模式 ') + chalk.cyan(codeSettingsResult.file) + chalk.dim('(已写入 Claude Code 共享设置)') + '\n'
|
|
1188
|
+
: chalk.dim('Code 模式 ') + chalk.dim('未接入;下次运行 claw desktop 时选择 Yes,或使用 claw desktop --with-code') + '\n') +
|
|
1140
1189
|
chalk.dim('菜单映射 ') + buildDesktopGatewayMappingRows(desktopConfig)
|
|
1141
1190
|
.map((row) => `${chalk.white(row.desktopLabel)} ${chalk.dim('→')} ${chalk.yellow(row.upstreamModel)}`)
|
|
1142
1191
|
.join('\n' + chalk.dim(' ')) + '\n\n' +
|
|
@@ -1156,7 +1205,7 @@ program
|
|
|
1156
1205
|
if (shouldOpen) {
|
|
1157
1206
|
const openSpinner = ora('正在重启 Claude 桌面应用...').start();
|
|
1158
1207
|
try {
|
|
1159
|
-
await openClaudeDesktop();
|
|
1208
|
+
await openClaudeDesktop(shouldWriteClaudeDesktopCodeSettings(options) ? { injectEnv: buildClaudeDesktopCodeEnv(desktopConfig) } : {});
|
|
1160
1209
|
openSpinner.succeed(chalk.green('Claude 桌面应用已重新打开(旧实例已退出,新配置生效)'));
|
|
1161
1210
|
} catch (e) {
|
|
1162
1211
|
openSpinner.fail(chalk.red(`自动重启失败: ${e.message}`));
|
|
@@ -1201,10 +1250,14 @@ program
|
|
|
1201
1250
|
|
|
1202
1251
|
const spinner = ora('正在恢复 Claude 桌面应用默认配置...').start();
|
|
1203
1252
|
const result = clearClaudeDesktopConfig();
|
|
1253
|
+
const codeSettingsResult = clearClaudeDesktopCodeSettings();
|
|
1204
1254
|
|
|
1205
|
-
if (result.result === 'updated') {
|
|
1255
|
+
if (result.result === 'updated' || codeSettingsResult.result === 'updated') {
|
|
1206
1256
|
spinner.succeed(chalk.green('Claude 桌面应用已恢复默认'));
|
|
1207
|
-
const cleared =
|
|
1257
|
+
const cleared = [
|
|
1258
|
+
...desktopConfigClearedPaths(result),
|
|
1259
|
+
...(codeSettingsResult.result === 'updated' ? [codeSettingsResult.file] : []),
|
|
1260
|
+
];
|
|
1208
1261
|
console.log(boxen(
|
|
1209
1262
|
chalk.bold('已清除 Claude 桌面应用第三方推理配置:\n\n') +
|
|
1210
1263
|
cleared.map(f => chalk.cyan(' • ' + f)).join('\n'),
|
|
@@ -1291,7 +1344,9 @@ program
|
|
|
1291
1344
|
program
|
|
1292
1345
|
.command('doctor')
|
|
1293
1346
|
.description('诊断当前环境,列出所有问题和修复建议')
|
|
1294
|
-
.
|
|
1347
|
+
.option('--export [file]', '导出脱敏诊断报告 JSON')
|
|
1348
|
+
.option('--windows', '包含 Windows 专项诊断')
|
|
1349
|
+
.action(async (options) => {
|
|
1295
1350
|
const chalk = (await import('chalk')).default;
|
|
1296
1351
|
const ora = (await import('ora')).default;
|
|
1297
1352
|
const boxen = (await import('boxen')).default;
|
|
@@ -1299,7 +1354,7 @@ program
|
|
|
1299
1354
|
console.log(await getBanner());
|
|
1300
1355
|
|
|
1301
1356
|
const spinner = ora('运行诊断检查...').start();
|
|
1302
|
-
const checks = await runDoctorChecks();
|
|
1357
|
+
const checks = await runDoctorChecks({ windows: options.windows });
|
|
1303
1358
|
spinner.stop();
|
|
1304
1359
|
|
|
1305
1360
|
const icons = {
|
|
@@ -1332,6 +1387,53 @@ program
|
|
|
1332
1387
|
margin: { top: 1, bottom: 1 },
|
|
1333
1388
|
}
|
|
1334
1389
|
));
|
|
1390
|
+
|
|
1391
|
+
if (options.export !== undefined) {
|
|
1392
|
+
const config = loadConfig();
|
|
1393
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
1394
|
+
const file = typeof options.export === 'string'
|
|
1395
|
+
? options.export
|
|
1396
|
+
: `yingclaw-diagnostic-${stamp}.json`;
|
|
1397
|
+
const report = buildDiagnosticReport({
|
|
1398
|
+
packageVersion: pkg.version,
|
|
1399
|
+
config,
|
|
1400
|
+
checks,
|
|
1401
|
+
env: process.env,
|
|
1402
|
+
expectedEnv: config ? buildClaudeEnv(config) : {},
|
|
1403
|
+
desktopCodeStatus: config ? checkClaudeDesktopCodeSettingsEnv(config) : null,
|
|
1404
|
+
});
|
|
1405
|
+
writeDiagnosticReport(file, report);
|
|
1406
|
+
console.log(chalk.green(`诊断报告已导出:${file}`));
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
program
|
|
1411
|
+
.command('support')
|
|
1412
|
+
.description('生成脱敏排障包,便于反馈 Windows / Desktop / Gateway 问题')
|
|
1413
|
+
.option('-o, --output <file>', '输出文件', `yingclaw-support-${new Date().toISOString().replace(/[:.]/g, '-')}.json`)
|
|
1414
|
+
.option('--windows', '包含 Windows 专项诊断')
|
|
1415
|
+
.action(async (options) => {
|
|
1416
|
+
const chalk = (await import('chalk')).default;
|
|
1417
|
+
const ora = (await import('ora')).default;
|
|
1418
|
+
|
|
1419
|
+
console.log(await getBanner());
|
|
1420
|
+
|
|
1421
|
+
const config = loadConfig();
|
|
1422
|
+
const spinner = ora('生成脱敏排障包...').start();
|
|
1423
|
+
const checks = await runDoctorChecks({ windows: options.windows });
|
|
1424
|
+
const gatewayStatus = config ? await checkDesktopGatewayStatus(config, { timeoutMs: 600 }) : null;
|
|
1425
|
+
const bundle = buildSupportBundle({
|
|
1426
|
+
packageVersion: pkg.version,
|
|
1427
|
+
config,
|
|
1428
|
+
checks,
|
|
1429
|
+
env: process.env,
|
|
1430
|
+
expectedEnv: config ? buildClaudeEnv(config) : {},
|
|
1431
|
+
gatewayStatus,
|
|
1432
|
+
desktopCodeStatus: config ? checkClaudeDesktopCodeSettingsEnv(config) : null,
|
|
1433
|
+
});
|
|
1434
|
+
writeDiagnosticReport(options.output, bundle);
|
|
1435
|
+
spinner.succeed(chalk.green(`排障包已生成:${options.output}`));
|
|
1436
|
+
console.log(chalk.dim('已脱敏 API Key / Gateway Key;公开发送前仍建议快速看一眼 Base URL 是否含私有信息。'));
|
|
1335
1437
|
});
|
|
1336
1438
|
|
|
1337
1439
|
program
|
|
@@ -1430,6 +1532,7 @@ async function renderStatusBar(apiStatus, config) {
|
|
|
1430
1532
|
claudeInstalled,
|
|
1431
1533
|
desktopGatewayStatus: await checkDesktopGatewayStatus(config, { timeoutMs: 250 }),
|
|
1432
1534
|
desktopGatewayAutostartStatus: getGatewayAutostartStatus(),
|
|
1535
|
+
desktopCodeStatus: checkClaudeDesktopCodeSettingsEnv(config),
|
|
1433
1536
|
env: process.env,
|
|
1434
1537
|
});
|
|
1435
1538
|
const statusLines = buildMenuStatusLines(view, { apiStatus, claudeInstalled, platform: process.platform });
|
|
@@ -1440,6 +1543,7 @@ async function renderStatusBar(apiStatus, config) {
|
|
|
1440
1543
|
if (line.startsWith('快速模型跨系列')) return chalk.yellow(line);
|
|
1441
1544
|
if (line.startsWith('Desktop Gateway 未运行')) return chalk.yellow(line);
|
|
1442
1545
|
if (line.startsWith('Desktop Gateway 已运行')) return chalk.green(line);
|
|
1546
|
+
if (line.startsWith('Desktop Code 未配置')) return chalk.yellow(line);
|
|
1443
1547
|
if (line.startsWith('模型')) return line.replace(view.mainModel, chalk.yellow(view.mainModel)).replace(view.fastModel, chalk.yellow(view.fastModel));
|
|
1444
1548
|
return line;
|
|
1445
1549
|
}).join('\n ');
|
package/index.js
CHANGED
package/lib/config.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const { spawnSync } = require('child_process');
|
|
5
5
|
const { normalizeOpenAiBaseUrl, openAiChatCompletionsUrl } = require('./openai');
|
|
6
|
+
const { googleGenerateContentUrl, normalizeGoogleBaseUrl } = require('./google');
|
|
6
7
|
|
|
7
8
|
const CONFIG_FILE = path.join(os.homedir(), '.clawai.json');
|
|
8
9
|
const WINDOWS_ENV_LABEL = 'Windows 用户环境变量';
|
|
@@ -25,6 +26,7 @@ const VSCODE_CLAUDE_ENV_KEYS = [
|
|
|
25
26
|
];
|
|
26
27
|
const LEGACY_CLAUDE_ENV_KEYS = [
|
|
27
28
|
'ANTHROPIC_API_KEY',
|
|
29
|
+
'CLAUDE_CODE_SIMPLE',
|
|
28
30
|
];
|
|
29
31
|
const CLEAR_CLAUDE_ENV_KEYS = [...new Set([...CLAUDE_ENV_KEYS, ...VSCODE_CLAUDE_ENV_KEYS, ...LEGACY_CLAUDE_ENV_KEYS])];
|
|
30
32
|
|
|
@@ -181,10 +183,16 @@ const PROVIDERS = {
|
|
|
181
183
|
protocol: 'openai',
|
|
182
184
|
models: [],
|
|
183
185
|
},
|
|
186
|
+
custom_google: {
|
|
187
|
+
name: '自定义 Google 兼容接口',
|
|
188
|
+
custom: true,
|
|
189
|
+
protocol: 'google',
|
|
190
|
+
models: [],
|
|
191
|
+
},
|
|
184
192
|
};
|
|
185
193
|
|
|
186
194
|
const PREFERRED_MODEL_IDS = {
|
|
187
|
-
|
|
195
|
+
qwen: ['qwen3-max', 'qwen3-plus', 'qwen3.5-plus'],
|
|
188
196
|
minimax: ['MiniMax-M2.7', 'MiniMax-M2.7-Turbo', 'MiniMax-M2.5'],
|
|
189
197
|
glm: ['GLM-4.7', 'GLM-5.1', 'GLM-5-Turbo', 'GLM-4.5-Air'],
|
|
190
198
|
mimo: ['mimo-v2.5-pro', 'mimo-v2.5', 'mimo-v2-flash'],
|
|
@@ -250,6 +258,16 @@ function parseModelIdsResponse(providerKey, data) {
|
|
|
250
258
|
if (providerKey === 'bai') {
|
|
251
259
|
return normalizeModelIds(providerKey, ids.filter(id => !id.includes('/')));
|
|
252
260
|
}
|
|
261
|
+
if (providerKey === 'custom_google') {
|
|
262
|
+
const chatList = list.filter((m) =>
|
|
263
|
+
!Array.isArray(m.supportedGenerationMethods) || m.supportedGenerationMethods.includes('generateContent'),
|
|
264
|
+
);
|
|
265
|
+
const googleIds = chatList
|
|
266
|
+
.map((m) => (m.id || m.model || m.name || '').replace(/^models\//, ''))
|
|
267
|
+
.filter(Boolean)
|
|
268
|
+
.filter(isChatModelId);
|
|
269
|
+
return normalizeModelIds(providerKey, googleIds);
|
|
270
|
+
}
|
|
253
271
|
return normalizeModelIds(providerKey, ids);
|
|
254
272
|
}
|
|
255
273
|
|
|
@@ -288,6 +306,14 @@ function buildModelUrlCandidates(baseUrl) {
|
|
|
288
306
|
add(`${withoutAnthropic}/v1/models`);
|
|
289
307
|
}
|
|
290
308
|
|
|
309
|
+
// Google Gemini: /v1beta/models
|
|
310
|
+
const v1betaIdx = pathname.indexOf('/v1beta');
|
|
311
|
+
if (v1betaIdx !== -1) {
|
|
312
|
+
add(`${pathname.slice(0, v1betaIdx + '/v1beta'.length)}/models`);
|
|
313
|
+
} else {
|
|
314
|
+
add(`${pathname}/v1beta/models`);
|
|
315
|
+
}
|
|
316
|
+
|
|
291
317
|
add('/v1/models');
|
|
292
318
|
|
|
293
319
|
return [...new Set(candidates)];
|
|
@@ -391,8 +417,12 @@ function getProviderProtocol(config = {}) {
|
|
|
391
417
|
}
|
|
392
418
|
|
|
393
419
|
function buildProviderAuthHeaders(providerKey, apiKey) {
|
|
420
|
+
const protocol = getProviderProtocol({ provider: providerKey });
|
|
421
|
+
if (protocol === 'google') {
|
|
422
|
+
return { 'x-goog-api-key': apiKey };
|
|
423
|
+
}
|
|
394
424
|
const headers = { authorization: `Bearer ${apiKey}` };
|
|
395
|
-
if (
|
|
425
|
+
if (protocol !== 'openai') {
|
|
396
426
|
headers['api-key'] = apiKey; // MiMo 用这个 header
|
|
397
427
|
}
|
|
398
428
|
return headers;
|
|
@@ -430,7 +460,8 @@ function buildGatewayBaseUrlForEnv(config) {
|
|
|
430
460
|
function buildClaudeEnv(config) {
|
|
431
461
|
const { provider, baseUrl, apiKey, model, fastModel } = config;
|
|
432
462
|
const resolvedFastModel = fastModel || resolveFastModel(PROVIDERS[provider], model);
|
|
433
|
-
const
|
|
463
|
+
const protocol = getProviderProtocol(config);
|
|
464
|
+
const useLocalGateway = (protocol === 'openai' || protocol === 'google') && config.desktopGatewayKey;
|
|
434
465
|
return {
|
|
435
466
|
ANTHROPIC_BASE_URL: useLocalGateway ? buildGatewayBaseUrlForEnv(config) : baseUrl,
|
|
436
467
|
ANTHROPIC_AUTH_TOKEN: useLocalGateway ? config.desktopGatewayKey : apiKey,
|
|
@@ -478,9 +509,13 @@ async function validateKey(config, options = {}) {
|
|
|
478
509
|
const protocol = getProviderProtocol(config);
|
|
479
510
|
let url;
|
|
480
511
|
try {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
512
|
+
if (protocol === 'openai') {
|
|
513
|
+
url = openAiChatCompletionsUrl(config.baseUrl);
|
|
514
|
+
} else if (protocol === 'google') {
|
|
515
|
+
url = googleGenerateContentUrl(config.baseUrl, config.model, false);
|
|
516
|
+
} else {
|
|
517
|
+
url = `${normalizeAnthropicBaseUrl(config.baseUrl)}/v1/messages`;
|
|
518
|
+
}
|
|
484
519
|
new URL(url);
|
|
485
520
|
} catch {
|
|
486
521
|
return null;
|
|
@@ -497,11 +532,9 @@ async function validateKey(config, options = {}) {
|
|
|
497
532
|
...buildProviderAuthHeaders(config.provider, config.apiKey),
|
|
498
533
|
...(protocol === 'anthropic' ? { 'x-api-key': config.apiKey, 'anthropic-version': '2023-06-01' } : {}),
|
|
499
534
|
},
|
|
500
|
-
body: JSON.stringify(
|
|
501
|
-
|
|
502
|
-
max_tokens: 16,
|
|
503
|
-
messages: [{ role: 'user', content: 'hi' }],
|
|
504
|
-
}),
|
|
535
|
+
body: JSON.stringify(protocol === 'google'
|
|
536
|
+
? { contents: [{ role: 'user', parts: [{ text: 'hi' }] }], generationConfig: { maxOutputTokens: 16 } }
|
|
537
|
+
: { model: config.model, max_tokens: 16, messages: [{ role: 'user', content: 'hi' }] }),
|
|
505
538
|
});
|
|
506
539
|
return classifyValidationStatus(res.status);
|
|
507
540
|
} catch {
|