yingclaw 2.0.1 → 2.0.6

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 CHANGED
@@ -124,10 +124,10 @@ npm uninstall -g yingclaw
124
124
 
125
125
  `claw desktop` 会额外写入 Claude Desktop 第三方推理配置:
126
126
 
127
- - macOS:`~/Library/Application Support/Claude-3p/configLibrary/`
128
- - Windows:`%LOCALAPPDATA%\Claude-3p\configLibrary`
127
+ - macOS:`~/Library/Application Support/Claude-3p/claude_desktop_config.json`
128
+ - Windows:`%APPDATA%\Claude-3p\claude_desktop_config.json`
129
129
 
130
- 写入的 `enterpriseConfig` 使用 `inferenceProvider=gateway`、`inferenceGatewayAuthScheme=bearer`,并把可用于 Claude 桌面的模型路由写入 `inferenceModels`。DeepSeek 的终端模型仍使用 `deepseek-v4-pro[1m]` / `deepseek-v4-flash`;Claude 桌面应用会使用 DeepSeek 支持的 Anthropic 路由名:`claude-sonnet-4-5`、`claude-opus-4-5`、`claude-haiku-4-5`。
130
+ 写入的 `enterpriseConfig` 使用 `inferenceProvider=gateway`、`inferenceGatewayAuthScheme=bearer`,并按旧版方式把当前厂商模型写入 `inferenceModels`。如果本机存在新版 `configLibrary` 本地配置,`claw desktop` 会同步移除,避免 Claude 优先读取旧的错误配置。DeepSeek Claude Code 终端模型仍使用 `deepseek-v4-pro[1m]`;Claude 桌面应用会写入 `deepseek-v4-pro` `deepseek-v4-flash`。
131
131
 
132
132
  ## License
133
133
 
package/lib/desktop.js CHANGED
@@ -6,7 +6,15 @@ const { spawnSync } = require('child_process');
6
6
  const { normalizeAnthropicBaseUrl } = require('./config');
7
7
 
8
8
  const CLAUDE_DESKTOP_LABEL = 'Claude 桌面应用配置';
9
- const CONFIG_NAME = 'yingclaw';
9
+ const DESKTOP_GATEWAY_KEYS = [
10
+ 'inferenceProvider',
11
+ 'inferenceGatewayBaseUrl',
12
+ 'inferenceGatewayApiKey',
13
+ 'inferenceGatewayAuthScheme',
14
+ 'inferenceModels',
15
+ 'disableDeploymentModeChooser',
16
+ 'deploymentOrganizationUuid',
17
+ ];
10
18
 
11
19
  // Claude 3P 数据目录(与 1P 的 Claude/ 目录区分)
12
20
  function getClaudeDesktopDataDir(options = {}) {
@@ -18,23 +26,21 @@ function getClaudeDesktopDataDir(options = {}) {
18
26
  }
19
27
 
20
28
  if (platform === 'win32') {
21
- const localAppData = options.localAppData || process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local');
22
- return path.join(localAppData, 'Claude-3p');
29
+ const appData = options.appData || process.env.APPDATA || options.localAppData || path.join(homeDir, 'AppData', 'Roaming');
30
+ return path.join(appData, 'Claude-3p');
23
31
  }
24
32
 
25
33
  return null;
26
34
  }
27
35
 
28
- function getClaudeDesktopConfigLibrary(options = {}) {
36
+ function getClaudeDesktopConfigPath(options = {}) {
29
37
  const dataDir = options.dataDir || getClaudeDesktopDataDir(options);
30
- if (!dataDir) return null;
31
- return path.join(dataDir, 'configLibrary');
38
+ return dataDir ? path.join(dataDir, 'claude_desktop_config.json') : null;
32
39
  }
33
40
 
34
- // 兼容旧测试名(指向 _meta.json 入口)
35
- function getClaudeDesktopConfigPath(options = {}) {
36
- const lib = getClaudeDesktopConfigLibrary(options);
37
- return lib ? path.join(lib, '_meta.json') : null;
41
+ function getClaudeDesktopConfigLibraryDir(options = {}) {
42
+ const dataDir = options.dataDir || getClaudeDesktopDataDir(options);
43
+ return dataDir ? path.join(dataDir, 'configLibrary') : null;
38
44
  }
39
45
 
40
46
  function readJsonFile(file) {
@@ -45,24 +51,19 @@ function readJsonFile(file) {
45
51
  }
46
52
  }
47
53
 
48
- function isDeepSeekConfig(config) {
49
- const baseUrl = normalizeAnthropicBaseUrl(config.baseUrl);
50
- return config.provider === 'deepseek' || baseUrl === 'https://api.deepseek.com/anthropic';
54
+ function normalizeLegacyDesktopModelId(model) {
55
+ return model.replace(/\[\w+\]$/, '');
51
56
  }
52
57
 
53
- function getDesktopGatewayModels(config) {
54
- if (isDeepSeekConfig(config)) {
55
- return ['claude-sonnet-4-5', 'claude-opus-4-5', 'claude-haiku-4-5'];
56
- }
58
+ function toDesktopModelId(model) {
59
+ return model.startsWith('claude-') ? model : `claude-${model}`;
60
+ }
57
61
 
62
+ function collectModels(config) {
58
63
  const list = Array.isArray(config.availableModels) && config.availableModels.length > 0
59
64
  ? [config.model, config.fastModel, ...config.availableModels]
60
65
  : [config.model, config.fastModel];
61
- return list.filter(Boolean);
62
- }
63
-
64
- function collectModels(config) {
65
- return [...new Set(getDesktopGatewayModels(config))];
66
+ return [...new Set(list.filter(Boolean).map(normalizeLegacyDesktopModelId).map(toDesktopModelId))];
66
67
  }
67
68
 
68
69
  // 按官方 schema:所有值必须是字符串(包括布尔、数组都序列化)
@@ -83,92 +84,104 @@ function buildClaudeDesktopEnterpriseConfig(config, options = {}) {
83
84
  };
84
85
  }
85
86
 
86
- // 写入官方 3P 配置目录:configLibrary/_meta.json + configLibrary/<id>.json
87
- function writeClaudeDesktopConfig(config, options = {}) {
88
- const lib = options.configLibrary || getClaudeDesktopConfigLibrary(options);
89
- if (!lib) {
90
- return { result: 'unsupported', file: null };
87
+ function clearClaudeDesktopConfigLibrary(options = {}) {
88
+ const dir = options.configLibraryDir || getClaudeDesktopConfigLibraryDir(options);
89
+ if (!dir || !fs.existsSync(dir)) {
90
+ return { result: 'missing', dir };
91
+ }
92
+
93
+ const metaFile = path.join(dir, '_meta.json');
94
+ const meta = readJsonFile(metaFile);
95
+ const ids = new Set();
96
+ if (typeof meta.appliedId === 'string' && meta.appliedId) {
97
+ ids.add(meta.appliedId);
98
+ }
99
+ if (Array.isArray(meta.entries)) {
100
+ for (const entry of meta.entries) {
101
+ if (entry && typeof entry.id === 'string' && entry.id) {
102
+ ids.add(entry.id);
103
+ }
104
+ }
91
105
  }
92
106
 
93
- fs.mkdirSync(lib, { recursive: true });
107
+ for (const id of ids) {
108
+ const file = path.join(dir, `${id}.json`);
109
+ if (fs.existsSync(file)) {
110
+ fs.unlinkSync(file);
111
+ }
112
+ }
113
+ if (fs.existsSync(metaFile)) {
114
+ fs.unlinkSync(metaFile);
115
+ }
94
116
 
95
- // 读已有 _meta.json,复用 entry id(保持稳定 UUID)
96
- const metaFile = path.join(lib, '_meta.json');
97
- const existingMeta = readJsonFile(metaFile);
98
- const existingEntry = Array.isArray(existingMeta.entries)
99
- ? existingMeta.entries.find(e => e.name === CONFIG_NAME)
100
- : null;
117
+ return { result: 'updated', dir };
118
+ }
101
119
 
102
- const entryId = existingEntry?.id || crypto.randomUUID();
120
+ // 旧版写法:写入 Claude-3p/claude_desktop_config.json,保留用户其他偏好。
121
+ function writeClaudeDesktopConfig(config, options = {}) {
122
+ const file = options.configFile || getClaudeDesktopConfigPath(options);
123
+ if (!file) {
124
+ return { result: 'unsupported', file: null };
125
+ }
103
126
 
104
- // 读已有 <id>.json,复用 deploymentOrganizationUuid
105
- const configFile = path.join(lib, `${entryId}.json`);
106
- const existingConfig = readJsonFile(configFile);
107
- const deploymentUuid = existingConfig.deploymentOrganizationUuid || options.uuid;
127
+ const current = readJsonFile(file);
128
+ const existingEnterpriseConfig = current.enterpriseConfig && typeof current.enterpriseConfig === 'object'
129
+ ? current.enterpriseConfig
130
+ : {};
131
+ const deploymentUuid = existingEnterpriseConfig.deploymentOrganizationUuid || options.uuid;
108
132
 
109
133
  const enterpriseConfig = buildClaudeDesktopEnterpriseConfig(config, {
110
134
  authScheme: options.authScheme,
111
135
  uuid: deploymentUuid,
112
136
  });
113
137
 
114
- // <id>.json
115
- fs.writeFileSync(configFile, JSON.stringify(enterpriseConfig, null, 2) + '\n');
116
-
117
- // 更新 _meta.json:appliedId + entries(复用其他 entry,仅替换 yingclaw 这一条)
118
- const otherEntries = Array.isArray(existingMeta.entries)
119
- ? existingMeta.entries.filter(e => e.name !== CONFIG_NAME)
120
- : [];
121
- const meta = {
122
- appliedId: entryId,
123
- entries: [
124
- ...otherEntries,
125
- {
126
- id: entryId,
127
- name: CONFIG_NAME,
128
- provider: 'gateway',
129
- note: enterpriseConfig.inferenceGatewayBaseUrl,
130
- },
131
- ],
138
+ const next = {
139
+ ...current,
140
+ deploymentMode: '3p',
141
+ enterpriseConfig: {
142
+ ...existingEnterpriseConfig,
143
+ ...enterpriseConfig,
144
+ },
132
145
  };
133
- fs.writeFileSync(metaFile, JSON.stringify(meta, null, 2) + '\n');
134
146
 
135
- return { result: 'updated', file: lib, entryId };
147
+ fs.mkdirSync(path.dirname(file), { recursive: true });
148
+ const before = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
149
+ const body = JSON.stringify(next, null, 2) + '\n';
150
+ fs.writeFileSync(file, body);
151
+ clearClaudeDesktopConfigLibrary({ ...options, dataDir: path.dirname(file) });
152
+ return { result: before === body ? 'unchanged' : 'updated', file };
136
153
  }
137
154
 
138
155
  function clearClaudeDesktopConfig(options = {}) {
139
- const lib = options.configLibrary || getClaudeDesktopConfigLibrary(options);
140
- if (!lib || !fs.existsSync(lib)) {
141
- return { result: 'missing', file: lib };
156
+ const file = options.configFile || getClaudeDesktopConfigPath(options);
157
+ if (!file || !fs.existsSync(file)) {
158
+ return { result: 'missing', file };
142
159
  }
143
160
 
144
- const metaFile = path.join(lib, '_meta.json');
145
- const meta = readJsonFile(metaFile);
146
- const entries = Array.isArray(meta.entries) ? meta.entries : [];
147
- const yingclawEntry = entries.find(e => e.name === CONFIG_NAME);
148
-
149
- if (!yingclawEntry) {
150
- return { result: 'missing', file: lib };
161
+ const current = readJsonFile(file);
162
+ if (!current.enterpriseConfig || typeof current.enterpriseConfig !== 'object') {
163
+ return { result: 'missing', file };
151
164
  }
152
165
 
153
- // 删除 <id>.json
154
- const configFile = path.join(lib, `${yingclawEntry.id}.json`);
155
- if (fs.existsSync(configFile)) {
156
- fs.unlinkSync(configFile);
166
+ const enterpriseConfig = { ...current.enterpriseConfig };
167
+ for (const key of DESKTOP_GATEWAY_KEYS) {
168
+ delete enterpriseConfig[key];
157
169
  }
158
170
 
159
- // 更新 _meta.json
160
- const remaining = entries.filter(e => e.name !== CONFIG_NAME);
161
- if (remaining.length === 0) {
162
- fs.unlinkSync(metaFile);
171
+ const next = { ...current };
172
+ if (Object.keys(enterpriseConfig).length > 0) {
173
+ next.enterpriseConfig = enterpriseConfig;
163
174
  } else {
164
- const newMeta = {
165
- appliedId: meta.appliedId === yingclawEntry.id ? (remaining[0]?.id || '') : meta.appliedId,
166
- entries: remaining,
167
- };
168
- fs.writeFileSync(metaFile, JSON.stringify(newMeta, null, 2) + '\n');
175
+ delete next.enterpriseConfig;
176
+ }
177
+ if (Object.keys(next).length === 1 && next.deploymentMode === '3p') {
178
+ delete next.deploymentMode;
169
179
  }
170
180
 
171
- return { result: 'updated', file: lib };
181
+ fs.writeFileSync(file, JSON.stringify(next, null, 2) + '\n');
182
+ clearClaudeDesktopConfigLibrary({ ...options, dataDir: path.dirname(file) });
183
+
184
+ return { result: 'updated', file };
172
185
  }
173
186
 
174
187
  function buildClaudeDesktopOpenCommands(platform = process.platform) {
@@ -194,10 +207,11 @@ async function openClaudeDesktop(options = {}) {
194
207
 
195
208
  const runner = options.runner || spawnSync;
196
209
  const isMocked = options.runner !== undefined;
210
+ const timeoutMs = options.timeoutMs || 5000;
197
211
 
198
212
  const trace = [];
199
213
  for (const { command, args, optional, waitAfter } of commands) {
200
- const result = runner(command, args, { stdio: 'pipe', encoding: 'utf8', windowsHide: true });
214
+ const result = runner(command, args, { stdio: 'pipe', encoding: 'utf8', windowsHide: true, timeout: timeoutMs });
201
215
  const stderr = (result.stderr || '').toString().trim();
202
216
  trace.push({ command, args, status: result.status, stderr });
203
217
 
@@ -220,7 +234,6 @@ module.exports = {
220
234
  clearClaudeDesktopConfig,
221
235
  getClaudeDesktopConfigPath,
222
236
  getClaudeDesktopDataDir,
223
- getClaudeDesktopConfigLibrary,
224
237
  openClaudeDesktop,
225
238
  writeClaudeDesktopConfig,
226
239
  CLAUDE_DESKTOP_LABEL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yingclaw",
3
- "version": "2.0.1",
3
+ "version": "2.0.6",
4
4
  "description": "Claude Code × 国产大模型一键接入:DeepSeek、Kimi、Qwen、MiniMax、GLM、MiMo",
5
5
  "main": "index.js",
6
6
  "bin": {