yingclaw 2.0.0 → 2.0.4

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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/lib/desktop.js +100 -74
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -40,7 +40,7 @@ claw code
40
40
  ```bash
41
41
  claw desktop
42
42
  ```
43
- 将当前模型配置写入 Claude Desktop 的第三方推理(Cowork on 3P)本地配置。macOS 会询问是否立即打开 Claude 桌面应用;Windows 需要手动打开。如果配置未生效,请完全退出 Claude Desktop 后重新打开。
43
+ 将当前模型配置写入 Claude Desktop 的第三方推理(Cowork on 3P)本地配置。macOS 会自动重启并打开 Claude 桌面应用;Windows 需要手动重新打开。如果配置未生效,请完全退出 Claude Desktop 后重新打开。
44
44
 
45
45
  恢复 Claude Code 终端默认配置:
46
46
  ```bash
@@ -127,7 +127,7 @@ npm uninstall -g yingclaw
127
127
  - macOS:`~/Library/Application Support/Claude-3p/claude_desktop_config.json`
128
128
  - Windows:`%APPDATA%\Claude-3p\claude_desktop_config.json`
129
129
 
130
- 写入的 `enterpriseConfig` 使用 `inferenceProvider=gateway`、`inferenceGatewayAuthScheme=bearer`,并把当前模型写入 `inferenceModels`。
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,11 +51,19 @@ function readJsonFile(file) {
45
51
  }
46
52
  }
47
53
 
54
+ function normalizeLegacyDesktopModelId(model) {
55
+ return model.replace(/\[\w+\]$/, '');
56
+ }
57
+
58
+ function toDesktopModelId(model) {
59
+ return model.startsWith('claude-') ? model : `claude-${model}`;
60
+ }
61
+
48
62
  function collectModels(config) {
49
63
  const list = Array.isArray(config.availableModels) && config.availableModels.length > 0
50
64
  ? [config.model, config.fastModel, ...config.availableModels]
51
65
  : [config.model, config.fastModel];
52
- return [...new Set(list.filter(Boolean))];
66
+ return [...new Set(list.filter(Boolean).map(normalizeLegacyDesktopModelId).map(toDesktopModelId))];
53
67
  }
54
68
 
55
69
  // 按官方 schema:所有值必须是字符串(包括布尔、数组都序列化)
@@ -70,92 +84,104 @@ function buildClaudeDesktopEnterpriseConfig(config, options = {}) {
70
84
  };
71
85
  }
72
86
 
73
- // 写入官方 3P 配置目录:configLibrary/_meta.json + configLibrary/<id>.json
74
- function writeClaudeDesktopConfig(config, options = {}) {
75
- const lib = options.configLibrary || getClaudeDesktopConfigLibrary(options);
76
- if (!lib) {
77
- 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 };
78
91
  }
79
92
 
80
- fs.mkdirSync(lib, { recursive: true });
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
+ }
105
+ }
81
106
 
82
- // 读已有 _meta.json,复用 entry id(保持稳定 UUID)
83
- const metaFile = path.join(lib, '_meta.json');
84
- const existingMeta = readJsonFile(metaFile);
85
- const existingEntry = Array.isArray(existingMeta.entries)
86
- ? existingMeta.entries.find(e => e.name === CONFIG_NAME)
87
- : null;
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
+ }
88
116
 
89
- const entryId = existingEntry?.id || crypto.randomUUID();
117
+ return { result: 'updated', dir };
118
+ }
90
119
 
91
- // 读已有 <id>.json,复用 deploymentOrganizationUuid
92
- const configFile = path.join(lib, `${entryId}.json`);
93
- const existingConfig = readJsonFile(configFile);
94
- const deploymentUuid = existingConfig.deploymentOrganizationUuid || options.uuid;
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
+ }
126
+
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;
95
132
 
96
133
  const enterpriseConfig = buildClaudeDesktopEnterpriseConfig(config, {
97
134
  authScheme: options.authScheme,
98
135
  uuid: deploymentUuid,
99
136
  });
100
137
 
101
- // <id>.json
102
- fs.writeFileSync(configFile, JSON.stringify(enterpriseConfig, null, 2) + '\n');
103
-
104
- // 更新 _meta.json:appliedId + entries(复用其他 entry,仅替换 yingclaw 这一条)
105
- const otherEntries = Array.isArray(existingMeta.entries)
106
- ? existingMeta.entries.filter(e => e.name !== CONFIG_NAME)
107
- : [];
108
- const meta = {
109
- appliedId: entryId,
110
- entries: [
111
- ...otherEntries,
112
- {
113
- id: entryId,
114
- name: CONFIG_NAME,
115
- provider: 'gateway',
116
- note: enterpriseConfig.inferenceGatewayBaseUrl,
117
- },
118
- ],
138
+ const next = {
139
+ ...current,
140
+ deploymentMode: '3p',
141
+ enterpriseConfig: {
142
+ ...existingEnterpriseConfig,
143
+ ...enterpriseConfig,
144
+ },
119
145
  };
120
- fs.writeFileSync(metaFile, JSON.stringify(meta, null, 2) + '\n');
121
146
 
122
- 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 };
123
153
  }
124
154
 
125
155
  function clearClaudeDesktopConfig(options = {}) {
126
- const lib = options.configLibrary || getClaudeDesktopConfigLibrary(options);
127
- if (!lib || !fs.existsSync(lib)) {
128
- return { result: 'missing', file: lib };
156
+ const file = options.configFile || getClaudeDesktopConfigPath(options);
157
+ if (!file || !fs.existsSync(file)) {
158
+ return { result: 'missing', file };
129
159
  }
130
160
 
131
- const metaFile = path.join(lib, '_meta.json');
132
- const meta = readJsonFile(metaFile);
133
- const entries = Array.isArray(meta.entries) ? meta.entries : [];
134
- const yingclawEntry = entries.find(e => e.name === CONFIG_NAME);
135
-
136
- if (!yingclawEntry) {
137
- return { result: 'missing', file: lib };
161
+ const current = readJsonFile(file);
162
+ if (!current.enterpriseConfig || typeof current.enterpriseConfig !== 'object') {
163
+ return { result: 'missing', file };
138
164
  }
139
165
 
140
- // 删除 <id>.json
141
- const configFile = path.join(lib, `${yingclawEntry.id}.json`);
142
- if (fs.existsSync(configFile)) {
143
- fs.unlinkSync(configFile);
166
+ const enterpriseConfig = { ...current.enterpriseConfig };
167
+ for (const key of DESKTOP_GATEWAY_KEYS) {
168
+ delete enterpriseConfig[key];
144
169
  }
145
170
 
146
- // 更新 _meta.json
147
- const remaining = entries.filter(e => e.name !== CONFIG_NAME);
148
- if (remaining.length === 0) {
149
- fs.unlinkSync(metaFile);
171
+ const next = { ...current };
172
+ if (Object.keys(enterpriseConfig).length > 0) {
173
+ next.enterpriseConfig = enterpriseConfig;
150
174
  } else {
151
- const newMeta = {
152
- appliedId: meta.appliedId === yingclawEntry.id ? (remaining[0]?.id || '') : meta.appliedId,
153
- entries: remaining,
154
- };
155
- 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;
156
179
  }
157
180
 
158
- 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 };
159
185
  }
160
186
 
161
187
  function buildClaudeDesktopOpenCommands(platform = process.platform) {
@@ -181,10 +207,11 @@ async function openClaudeDesktop(options = {}) {
181
207
 
182
208
  const runner = options.runner || spawnSync;
183
209
  const isMocked = options.runner !== undefined;
210
+ const timeoutMs = options.timeoutMs || 5000;
184
211
 
185
212
  const trace = [];
186
213
  for (const { command, args, optional, waitAfter } of commands) {
187
- 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 });
188
215
  const stderr = (result.stderr || '').toString().trim();
189
216
  trace.push({ command, args, status: result.status, stderr });
190
217
 
@@ -207,7 +234,6 @@ module.exports = {
207
234
  clearClaudeDesktopConfig,
208
235
  getClaudeDesktopConfigPath,
209
236
  getClaudeDesktopDataDir,
210
- getClaudeDesktopConfigLibrary,
211
237
  openClaudeDesktop,
212
238
  writeClaudeDesktopConfig,
213
239
  CLAUDE_DESKTOP_LABEL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yingclaw",
3
- "version": "2.0.0",
3
+ "version": "2.0.4",
4
4
  "description": "Claude Code × 国产大模型一键接入:DeepSeek、Kimi、Qwen、MiniMax、GLM、MiMo",
5
5
  "main": "index.js",
6
6
  "bin": {