yingclaw 2.5.2 → 2.5.3
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/bin/cli.js +13 -2
- package/lib/desktop.js +54 -1
- package/lib/gateway.js +81 -4
- package/lib/panel.js +21 -2
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -26,6 +26,8 @@ const { clearClaudeDesktopConfig, isDesktopConfigured, openClaudeDesktop, writeC
|
|
|
26
26
|
const {
|
|
27
27
|
DEFAULT_DESKTOP_GATEWAY_PORT,
|
|
28
28
|
YINGCLAW_GATEWAY_PREFIX,
|
|
29
|
+
buildDesktopGatewayMappingRows,
|
|
30
|
+
checkDesktopGatewayStatus,
|
|
29
31
|
createGatewayServer,
|
|
30
32
|
ensureDesktopGatewayConfig,
|
|
31
33
|
} = require('../lib/gateway');
|
|
@@ -238,6 +240,7 @@ async function showStatus() {
|
|
|
238
240
|
const view = buildStatusView(config, {
|
|
239
241
|
apiStatus: valid,
|
|
240
242
|
claudeInstalled: isClaudeInstalled(),
|
|
243
|
+
desktopGatewayStatus: await checkDesktopGatewayStatus(config),
|
|
241
244
|
env: process.env,
|
|
242
245
|
});
|
|
243
246
|
|
|
@@ -248,7 +251,9 @@ async function showStatus() {
|
|
|
248
251
|
? chalk.yellow(value)
|
|
249
252
|
: label === 'Base URL'
|
|
250
253
|
? chalk.cyan(value)
|
|
251
|
-
|
|
254
|
+
: label === 'Desktop Gateway'
|
|
255
|
+
? value.includes('已运行') ? chalk.green(value) : chalk.yellow(value)
|
|
256
|
+
: label === '当前终端' && value === '未生效'
|
|
252
257
|
? chalk.yellow(value)
|
|
253
258
|
: value;
|
|
254
259
|
return `${chalk.dim(label + ':')} ${coloredValue}`;
|
|
@@ -768,7 +773,10 @@ program
|
|
|
768
773
|
chalk.yellow('直连模式可能被新版 Claude Desktop 拒绝非 Claude 模型名。')
|
|
769
774
|
: chalk.dim('Gateway ') + chalk.cyan(gatewayUrl) + '\n' +
|
|
770
775
|
chalk.dim('主模型 ') + chalk.yellow(desktopConfig.model) + '\n' +
|
|
771
|
-
chalk.dim('快速模型 ') + chalk.yellow(desktopConfig.fastModel || desktopConfig.model) + '\n
|
|
776
|
+
chalk.dim('快速模型 ') + chalk.yellow(desktopConfig.fastModel || desktopConfig.model) + '\n' +
|
|
777
|
+
chalk.dim('菜单映射 ') + buildDesktopGatewayMappingRows(desktopConfig)
|
|
778
|
+
.map((row) => `${chalk.white(row.desktopLabel)} ${chalk.dim('→')} ${chalk.yellow(row.upstreamModel)}`)
|
|
779
|
+
.join('\n' + chalk.dim(' ')) + '\n\n' +
|
|
772
780
|
chalk.cyan('使用前请运行: claw gateway');
|
|
773
781
|
|
|
774
782
|
console.log(boxen(
|
|
@@ -1032,6 +1040,7 @@ async function renderStatusBar(apiStatus) {
|
|
|
1032
1040
|
const view = buildStatusView(config, {
|
|
1033
1041
|
apiStatus,
|
|
1034
1042
|
claudeInstalled,
|
|
1043
|
+
desktopGatewayStatus: await checkDesktopGatewayStatus(config, { timeoutMs: 250 }),
|
|
1035
1044
|
env: process.env,
|
|
1036
1045
|
});
|
|
1037
1046
|
const statusLines = buildMenuStatusLines(view, { apiStatus, claudeInstalled, platform: process.platform });
|
|
@@ -1039,6 +1048,8 @@ async function renderStatusBar(apiStatus) {
|
|
|
1039
1048
|
if (index === 0) return line.replace('API 正常', chalk.green('API 正常')).replace('API Key 无效', chalk.red('API Key 无效')).replace('网络/服务异常', chalk.yellow('网络/服务异常'));
|
|
1040
1049
|
if (line.startsWith('环境变量未生效')) return chalk.yellow(line);
|
|
1041
1050
|
if (line.startsWith('旧模型名')) return chalk.yellow(line);
|
|
1051
|
+
if (line.startsWith('Desktop Gateway 未运行')) return chalk.yellow(line);
|
|
1052
|
+
if (line.startsWith('Desktop Gateway 已运行')) return chalk.green(line);
|
|
1042
1053
|
if (line.startsWith('主模型')) return line.replace(view.mainModel, chalk.yellow(view.mainModel)).replace(view.fastModel, chalk.yellow(view.fastModel));
|
|
1043
1054
|
return line;
|
|
1044
1055
|
}).join('\n ');
|
package/lib/desktop.js
CHANGED
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
|
|
13
13
|
const CLAUDE_DESKTOP_LABEL = 'Claude 桌面应用配置';
|
|
14
14
|
const YINGCLAW_ENTRY_NAME = 'yingclaw';
|
|
15
|
+
const MAC_POLICY_BUNDLE = 'com.anthropic.claudefordesktop';
|
|
15
16
|
const DESKTOP_GATEWAY_KEYS = [
|
|
16
17
|
'inferenceProvider',
|
|
17
18
|
'inferenceGatewayBaseUrl',
|
|
@@ -22,6 +23,15 @@ const DESKTOP_GATEWAY_KEYS = [
|
|
|
22
23
|
'deploymentOrganizationUuid',
|
|
23
24
|
];
|
|
24
25
|
|
|
26
|
+
function buildRuntimeEnterpriseConfig(entry) {
|
|
27
|
+
return {
|
|
28
|
+
...entry,
|
|
29
|
+
inferenceGatewayHeaders: [],
|
|
30
|
+
isClaudeCodeForDesktopEnabled: true,
|
|
31
|
+
coworkEgressAllowedHosts: ['*'],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
// Claude Desktop 主进程从 Claude-3p/ 目录读取 deploymentMode 和 enterpriseConfig
|
|
26
36
|
function getClaudeDesktopDataDir(options = {}) {
|
|
27
37
|
const platform = options.platform || process.platform;
|
|
@@ -85,6 +95,29 @@ function serializeGatewayModels(config) {
|
|
|
85
95
|
});
|
|
86
96
|
}
|
|
87
97
|
|
|
98
|
+
function buildClaudeDesktopMacDefaultsCommands(entry) {
|
|
99
|
+
const runtime = buildRuntimeEnterpriseConfig(entry);
|
|
100
|
+
return [
|
|
101
|
+
{ command: 'defaults', args: ['write', MAC_POLICY_BUNDLE, 'inferenceProvider', '-string', runtime.inferenceProvider] },
|
|
102
|
+
{ command: 'defaults', args: ['write', MAC_POLICY_BUNDLE, 'inferenceGatewayBaseUrl', '-string', runtime.inferenceGatewayBaseUrl] },
|
|
103
|
+
{ command: 'defaults', args: ['write', MAC_POLICY_BUNDLE, 'inferenceGatewayApiKey', '-string', runtime.inferenceGatewayApiKey] },
|
|
104
|
+
{ command: 'defaults', args: ['write', MAC_POLICY_BUNDLE, 'inferenceGatewayAuthScheme', '-string', runtime.inferenceGatewayAuthScheme] },
|
|
105
|
+
{ command: 'defaults', args: ['write', MAC_POLICY_BUNDLE, 'inferenceGatewayHeaders', '-string', JSON.stringify(runtime.inferenceGatewayHeaders)] },
|
|
106
|
+
{ command: 'defaults', args: ['write', MAC_POLICY_BUNDLE, 'inferenceModels', '-string', JSON.stringify(runtime.inferenceModels)] },
|
|
107
|
+
{ command: 'defaults', args: ['write', MAC_POLICY_BUNDLE, 'isClaudeCodeForDesktopEnabled', '-int', '1'] },
|
|
108
|
+
{ command: 'defaults', args: ['write', MAC_POLICY_BUNDLE, 'coworkEgressAllowedHosts', '-string', JSON.stringify(runtime.coworkEgressAllowedHosts)] },
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function writeClaudeDesktopMacDefaults(entry) {
|
|
113
|
+
for (const { command, args } of buildClaudeDesktopMacDefaultsCommands(entry)) {
|
|
114
|
+
const result = spawnSync(command, args, { encoding: 'utf8' });
|
|
115
|
+
if (result.status !== 0) {
|
|
116
|
+
throw new Error(`macOS policy 写入失败: ${args[2] || command}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
88
121
|
function buildClaudeDesktopEnterpriseConfig(config, options = {}) {
|
|
89
122
|
const gatewayConfig = ensureDesktopGatewayConfig(config, options);
|
|
90
123
|
const models = serializeGatewayModels(gatewayConfig);
|
|
@@ -196,6 +229,7 @@ function writeClaudeDesktopConfig(config, options = {}) {
|
|
|
196
229
|
disableDeploymentModeChooser: true,
|
|
197
230
|
deploymentOrganizationUuid: uuid,
|
|
198
231
|
};
|
|
232
|
+
const runtimeEntry = buildRuntimeEnterpriseConfig(entry);
|
|
199
233
|
|
|
200
234
|
const otherEntries = existingEntries.filter((entry) => entry && entry.name !== YINGCLAW_ENTRY_NAME);
|
|
201
235
|
const meta = {
|
|
@@ -216,10 +250,28 @@ function writeClaudeDesktopConfig(config, options = {}) {
|
|
|
216
250
|
|
|
217
251
|
const file = options.configFile || path.join(dataDir, 'claude_desktop_config.json');
|
|
218
252
|
const current = readJsonFile(file);
|
|
219
|
-
const
|
|
253
|
+
const currentEnterprise = current.enterpriseConfig && typeof current.enterpriseConfig === 'object'
|
|
254
|
+
? current.enterpriseConfig
|
|
255
|
+
: {};
|
|
256
|
+
const next = {
|
|
257
|
+
...current,
|
|
258
|
+
deploymentMode: '3p',
|
|
259
|
+
enterpriseConfig: {
|
|
260
|
+
...currentEnterprise,
|
|
261
|
+
...runtimeEntry,
|
|
262
|
+
},
|
|
263
|
+
};
|
|
220
264
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
221
265
|
fs.writeFileSync(file, JSON.stringify(next, null, 2) + '\n');
|
|
222
266
|
|
|
267
|
+
const shouldWritePlatformPolicy = (options.platform || process.platform) === 'darwin'
|
|
268
|
+
&& !options.dataDir
|
|
269
|
+
&& !options.configFile
|
|
270
|
+
&& options.writePlatformPolicy !== false;
|
|
271
|
+
if (shouldWritePlatformPolicy) {
|
|
272
|
+
writeClaudeDesktopMacDefaults(entry);
|
|
273
|
+
}
|
|
274
|
+
|
|
223
275
|
return { result: beforeEntry === entryBody ? 'unchanged' : 'updated', file, config: effectiveConfig };
|
|
224
276
|
}
|
|
225
277
|
|
|
@@ -330,6 +382,7 @@ async function openClaudeDesktop(options = {}) {
|
|
|
330
382
|
module.exports = {
|
|
331
383
|
buildClaudeDesktopEnterpriseConfig,
|
|
332
384
|
buildClaudeDesktopDirectEnterpriseConfig,
|
|
385
|
+
buildClaudeDesktopMacDefaultsCommands,
|
|
333
386
|
buildClaudeDesktopOpenCommands,
|
|
334
387
|
clearClaudeDesktopConfig,
|
|
335
388
|
getClaudeDesktopConfigLibraryDir,
|
package/lib/gateway.js
CHANGED
|
@@ -6,9 +6,9 @@ const DEFAULT_DESKTOP_GATEWAY_PORT = 18080;
|
|
|
6
6
|
const YINGCLAW_GATEWAY_PREFIX = '/yingclaw';
|
|
7
7
|
const DESKTOP_ROUTE_SPECS = [
|
|
8
8
|
{ id: 'claude-sonnet-4-6', displayName: 'Sonnet', upstreamKey: 'model' },
|
|
9
|
-
{ id: 'claude-opus-4-7', displayName: 'Opus', upstreamKey: 'model' },
|
|
10
9
|
{ id: 'claude-haiku-4-5', displayName: 'Haiku', upstreamKey: 'fastModel' },
|
|
11
10
|
];
|
|
11
|
+
const ONE_M_CONTEXT_SUFFIX = ' [1M]';
|
|
12
12
|
|
|
13
13
|
function createGatewayKey() {
|
|
14
14
|
return `ygw-${crypto.randomUUID()}`;
|
|
@@ -27,24 +27,55 @@ function modelSupports1m(model) {
|
|
|
27
27
|
return /\[1m\]/i.test(String(model || ''));
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function stripOneMContextSuffix(model) {
|
|
31
|
+
const value = String(model || '').trim();
|
|
32
|
+
return value.replace(/\s*\[1m\]\s*$/i, '');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function desktopRouteId(routeId, supports1m) {
|
|
36
|
+
const base = stripOneMContextSuffix(routeId);
|
|
37
|
+
return supports1m ? `${base}${ONE_M_CONTEXT_SUFFIX}` : base;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function desktopRouteLabel(routeId) {
|
|
41
|
+
const raw = String(routeId || '').trim();
|
|
42
|
+
const is1m = /\[1m\]\s*$/i.test(raw);
|
|
43
|
+
const base = stripOneMContextSuffix(raw).replace(/^anthropic\//i, '').replace(/^claude-/i, '');
|
|
44
|
+
const parts = base.split('-').filter(Boolean);
|
|
45
|
+
const family = parts.shift() || base;
|
|
46
|
+
const version = parts.join('.');
|
|
47
|
+
const label = `${family.charAt(0).toUpperCase()}${family.slice(1)}${version ? ` ${version}` : ''}`;
|
|
48
|
+
return is1m ? `${label} 1M` : label;
|
|
49
|
+
}
|
|
50
|
+
|
|
30
51
|
function buildDesktopGatewayRoutes(config) {
|
|
31
52
|
const fastModel = config.fastModel || config.model;
|
|
32
53
|
const source = { model: config.model, fastModel };
|
|
33
54
|
return DESKTOP_ROUTE_SPECS
|
|
34
55
|
.map((spec) => {
|
|
35
56
|
const upstreamModel = source[spec.upstreamKey];
|
|
57
|
+
const supports1m = modelSupports1m(upstreamModel);
|
|
36
58
|
return upstreamModel ? {
|
|
37
|
-
id: spec.id,
|
|
59
|
+
id: desktopRouteId(spec.id, supports1m),
|
|
38
60
|
displayName: spec.displayName,
|
|
39
61
|
upstreamModel,
|
|
40
|
-
supports1m
|
|
62
|
+
supports1m,
|
|
41
63
|
} : null;
|
|
42
64
|
})
|
|
43
65
|
.filter(Boolean);
|
|
44
66
|
}
|
|
45
67
|
|
|
68
|
+
function buildDesktopGatewayMappingRows(config) {
|
|
69
|
+
return buildDesktopGatewayRoutes(config).map((route) => ({
|
|
70
|
+
routeId: route.id,
|
|
71
|
+
desktopLabel: desktopRouteLabel(route.id),
|
|
72
|
+
upstreamModel: route.upstreamModel,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
46
76
|
function mapDesktopRouteToUpstream(config, routeId) {
|
|
47
|
-
const
|
|
77
|
+
const requested = stripOneMContextSuffix(routeId);
|
|
78
|
+
const route = buildDesktopGatewayRoutes(config).find((item) => stripOneMContextSuffix(item.id) === requested);
|
|
48
79
|
if (!route) {
|
|
49
80
|
throw new Error(`未配置的 Claude Desktop 模型路由: ${routeId}`);
|
|
50
81
|
}
|
|
@@ -98,6 +129,45 @@ function providerMessagesUrl(config) {
|
|
|
98
129
|
return `${normalizeAnthropicBaseUrl(config.baseUrl)}/v1/messages`;
|
|
99
130
|
}
|
|
100
131
|
|
|
132
|
+
function buildDesktopGatewayUrl(config = {}) {
|
|
133
|
+
const port = Number.parseInt(config.desktopGatewayPort || DEFAULT_DESKTOP_GATEWAY_PORT, 10);
|
|
134
|
+
return {
|
|
135
|
+
port,
|
|
136
|
+
url: `http://127.0.0.1:${port}${YINGCLAW_GATEWAY_PREFIX}`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function checkDesktopGatewayStatus(config = {}, options = {}) {
|
|
141
|
+
const { port, url } = buildDesktopGatewayUrl(config);
|
|
142
|
+
const fetchImpl = options.fetch || fetch;
|
|
143
|
+
const configured = !!config.desktopGatewayKey;
|
|
144
|
+
|
|
145
|
+
if (!configured) {
|
|
146
|
+
return { configured: false, running: false, port, url, error: null };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetchImpl(`${url}/health`, {
|
|
151
|
+
signal: AbortSignal.timeout(options.timeoutMs || 800),
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
configured: true,
|
|
155
|
+
running: response.ok,
|
|
156
|
+
port,
|
|
157
|
+
url,
|
|
158
|
+
error: response.ok ? null : `HTTP ${response.status}`,
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return {
|
|
162
|
+
configured: true,
|
|
163
|
+
running: false,
|
|
164
|
+
port,
|
|
165
|
+
url,
|
|
166
|
+
error: error.message,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
101
171
|
async function proxyMessages(req, res, config) {
|
|
102
172
|
const raw = await readRequestBody(req);
|
|
103
173
|
let body;
|
|
@@ -188,11 +258,18 @@ module.exports = {
|
|
|
188
258
|
DEFAULT_DESKTOP_GATEWAY_PORT,
|
|
189
259
|
YINGCLAW_GATEWAY_PREFIX,
|
|
190
260
|
DESKTOP_ROUTE_SPECS,
|
|
261
|
+
ONE_M_CONTEXT_SUFFIX,
|
|
191
262
|
createGatewayKey,
|
|
192
263
|
ensureDesktopGatewayConfig,
|
|
193
264
|
modelSupports1m,
|
|
265
|
+
stripOneMContextSuffix,
|
|
266
|
+
desktopRouteId,
|
|
267
|
+
desktopRouteLabel,
|
|
194
268
|
buildDesktopGatewayRoutes,
|
|
269
|
+
buildDesktopGatewayMappingRows,
|
|
195
270
|
mapDesktopRouteToUpstream,
|
|
196
271
|
buildGatewayModelsResponse,
|
|
272
|
+
buildDesktopGatewayUrl,
|
|
273
|
+
checkDesktopGatewayStatus,
|
|
197
274
|
createGatewayServer,
|
|
198
275
|
};
|
package/lib/panel.js
CHANGED
|
@@ -12,6 +12,13 @@ function isEnvActive(config, env) {
|
|
|
12
12
|
return Object.entries(expected).every(([key, value]) => env[key] === value);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function desktopGatewayStatusText(status) {
|
|
16
|
+
if (!status) return null;
|
|
17
|
+
if (!status.configured) return '未配置';
|
|
18
|
+
if (status.running) return `已运行 · 127.0.0.1:${status.port}`;
|
|
19
|
+
return '未运行:执行 claw gateway';
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
function buildStatusView(config, options = {}) {
|
|
16
23
|
const provider = PROVIDERS[config.provider];
|
|
17
24
|
const providerName = config.providerName || provider?.name || config.provider;
|
|
@@ -21,6 +28,8 @@ function buildStatusView(config, options = {}) {
|
|
|
21
28
|
const mainModel = expectedEnv.ANTHROPIC_MODEL;
|
|
22
29
|
const fastModel = expectedEnv.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
23
30
|
const envActive = isEnvActive(config, env);
|
|
31
|
+
const desktopGatewayStatus = options.desktopGatewayStatus;
|
|
32
|
+
const desktopGatewayText = desktopGatewayStatusText(desktopGatewayStatus);
|
|
24
33
|
const warnings = [];
|
|
25
34
|
|
|
26
35
|
if (config.provider === 'deepseek' && (
|
|
@@ -31,7 +40,7 @@ function buildStatusView(config, options = {}) {
|
|
|
31
40
|
warnings.push('检测到旧 DeepSeek 模型名,建议运行 claw switch 更新到 [1m] 长上下文版本');
|
|
32
41
|
}
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
const view = {
|
|
35
44
|
providerName,
|
|
36
45
|
mainModel,
|
|
37
46
|
fastModel,
|
|
@@ -45,9 +54,14 @@ function buildStatusView(config, options = {}) {
|
|
|
45
54
|
{ label: 'API 状态', value: apiStatusText(options.apiStatus) },
|
|
46
55
|
{ label: 'Claude Code', value: claudeInstalled ? '已安装' : '未检测到' },
|
|
47
56
|
{ label: '当前终端', value: envActive ? '已生效' : '未生效' },
|
|
57
|
+
...(desktopGatewayText ? [{ label: 'Desktop Gateway', value: desktopGatewayText }] : []),
|
|
48
58
|
{ label: 'Base URL', value: config.baseUrl },
|
|
49
59
|
],
|
|
50
60
|
};
|
|
61
|
+
if (desktopGatewayStatus) {
|
|
62
|
+
view.desktopGatewayStatus = desktopGatewayStatus;
|
|
63
|
+
}
|
|
64
|
+
return view;
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
function buildMenuStatusLines(view, options = {}) {
|
|
@@ -66,6 +80,11 @@ function buildMenuStatusLines(view, options = {}) {
|
|
|
66
80
|
|
|
67
81
|
lines.push(`主模型 ${view.mainModel} · 快速模型 ${view.fastModel}`);
|
|
68
82
|
|
|
83
|
+
const desktopGatewayText = desktopGatewayStatusText(view.desktopGatewayStatus || options.desktopGatewayStatus);
|
|
84
|
+
if (desktopGatewayText && desktopGatewayText !== '未配置') {
|
|
85
|
+
lines.push(`Desktop Gateway ${desktopGatewayText}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
if (view.warnings.some((warning) => warning.includes('旧 DeepSeek 模型名'))) {
|
|
70
89
|
lines.push('旧模型名:选择下方"切换厂商或模型"更新到 [1m]');
|
|
71
90
|
}
|
|
@@ -73,4 +92,4 @@ function buildMenuStatusLines(view, options = {}) {
|
|
|
73
92
|
return lines;
|
|
74
93
|
}
|
|
75
94
|
|
|
76
|
-
module.exports = { buildMenuStatusLines, buildStatusView, apiStatusText, isEnvActive };
|
|
95
|
+
module.exports = { buildMenuStatusLines, buildStatusView, apiStatusText, desktopGatewayStatusText, isEnvActive };
|