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 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
- : label === '当前终端' && value === '未生效'
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\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 next = { ...current, deploymentMode: '3p' };
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: modelSupports1m(upstreamModel),
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 route = buildDesktopGatewayRoutes(config).find((item) => item.id === routeId);
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
- return {
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yingclaw",
3
- "version": "2.5.2",
3
+ "version": "2.5.3",
4
4
  "description": "Claude Code × 国产大模型一键接入:DeepSeek、Kimi、Qwen、MiniMax、GLM、MiMo",
5
5
  "main": "index.js",
6
6
  "bin": {