yingclaw 2.5.18 → 2.5.20
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 +3 -3
- package/bin/cli.js +60 -14
- package/index.js +1 -0
- package/lib/autostart.js +31 -0
- package/lib/config.js +3 -0
- package/lib/desktop.js +79 -17
- package/lib/install.js +46 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,11 +47,10 @@ claw code
|
|
|
47
47
|
接入 Claude 桌面应用:
|
|
48
48
|
```bash
|
|
49
49
|
claw desktop
|
|
50
|
-
claw gateway
|
|
51
50
|
```
|
|
52
51
|
`claw desktop` 会把 Claude Desktop 指向 yingclaw 本机 Gateway,并只暴露 `claude-sonnet-4-6`、`claude-opus-4-7`、`claude-haiku-4-5` 这类 Claude Desktop 可接受的模型名。真实第三方模型仍按 `claw config` 保存的主模型和快速模型转发。
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
`claw desktop` 会设置 Gateway 登录自动运行;如果自动启动失败,可以手动运行 `claw gateway`。
|
|
55
54
|
|
|
56
55
|
## 支持的厂商
|
|
57
56
|
|
|
@@ -95,7 +94,7 @@ claw setup # 兼容旧命令:config + code
|
|
|
95
94
|
|------|---------|---------|
|
|
96
95
|
| macOS | ✅ 写入 `~/.zshrc` | ✅ 自动启动本机 Gateway 并重启 Claude Desktop |
|
|
97
96
|
| Linux / WSL | ✅ 写入 `~/.zshrc` / `~/.bashrc` | — |
|
|
98
|
-
| Windows | ✅ 写入用户级环境变量(需重开终端) | ✅
|
|
97
|
+
| Windows | ✅ 写入用户级环境变量(需重开终端) | ✅ 同步写入 `%APPDATA%\Claude-3p\` 和 `%LOCALAPPDATA%\Claude-3p\`,并写入登录启动脚本 |
|
|
99
98
|
|
|
100
99
|
## 原理
|
|
101
100
|
|
|
@@ -118,6 +117,7 @@ CLAUDE_CODE_EFFORT_LEVEL
|
|
|
118
117
|
- Claude Desktop 访问 `http://127.0.0.1:18080/yingclaw`
|
|
119
118
|
- Gateway 再转发到当前保存的 Anthropic 兼容接口
|
|
120
119
|
- macOS 使用 LaunchAgent 登录启动 Gateway,Windows 使用 Startup 目录脚本登录启动 Gateway
|
|
120
|
+
- Windows 会同时写入 Roaming / Local 两个 Claude-3p 位置,以兼容不同 Claude Desktop 安装方式
|
|
121
121
|
- 终端接入仍直接使用 `ANTHROPIC_*` 环境变量,不受桌面 Gateway 影响
|
|
122
122
|
|
|
123
123
|
使用 `inferenceProvider=gateway`、`inferenceGatewayAuthScheme=bearer`,将 Gateway Base URL 指向 yingclaw 本机 Gateway。高级用户可用 `claw desktop --direct` 保留旧式直连写入,但新版 Claude Desktop 可能拒绝非 Claude 模型名。
|
package/bin/cli.js
CHANGED
|
@@ -21,9 +21,16 @@ const {
|
|
|
21
21
|
} = require('../lib/config');
|
|
22
22
|
const { execSync, spawn, spawnSync } = require('child_process');
|
|
23
23
|
const pkg = require('../package.json');
|
|
24
|
-
const { getGatewayAutostartStatus, installGatewayAutostart } = require('../lib/autostart');
|
|
24
|
+
const { getGatewayAutostartStatus, installGatewayAutostart, removeGatewayAutostart } = require('../lib/autostart');
|
|
25
25
|
const { buildMenuStatusLines, buildStatusView } = require('../lib/panel');
|
|
26
|
-
const {
|
|
26
|
+
const {
|
|
27
|
+
buildClaudeInstallCommand,
|
|
28
|
+
buildYingclawUpgradeCommand,
|
|
29
|
+
buildWindowsYingclawUpgradeScript,
|
|
30
|
+
checkNodeEnv,
|
|
31
|
+
getNodeInstallGuide,
|
|
32
|
+
getInstallFailureHints,
|
|
33
|
+
} = require('../lib/install');
|
|
27
34
|
const { clearClaudeDesktopConfig, isDesktopConfigured, openClaudeDesktop, writeClaudeDesktopConfig } = require('../lib/desktop');
|
|
28
35
|
const {
|
|
29
36
|
DEFAULT_DESKTOP_GATEWAY_PORT,
|
|
@@ -102,6 +109,20 @@ function getSavedConfigHint() {
|
|
|
102
109
|
return '⚠ API Key 以明文存储在 ~/.clawai.json';
|
|
103
110
|
}
|
|
104
111
|
|
|
112
|
+
function clearCurrentProcessClaudeEnv() {
|
|
113
|
+
for (const key of CLEAR_CLAUDE_ENV_KEYS) {
|
|
114
|
+
delete process.env[key];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function desktopConfigClearedPaths(result) {
|
|
119
|
+
const path = require('path');
|
|
120
|
+
return [
|
|
121
|
+
...(Array.isArray(result.dataDirs) ? result.dataDirs.map(dir => path.join(dir, 'configLibrary/')) : []),
|
|
122
|
+
...(Array.isArray(result.files) ? result.files : [result.file]),
|
|
123
|
+
].filter(Boolean);
|
|
124
|
+
}
|
|
125
|
+
|
|
105
126
|
async function offerDesktopSync(chalk, ora, config) {
|
|
106
127
|
if (!isDesktopConfigured()) return;
|
|
107
128
|
const syncDesktop = await confirm({ message: 'Claude 桌面应用已配置,是否同步新模型?', default: true });
|
|
@@ -789,7 +810,10 @@ program
|
|
|
789
810
|
spinner.warn(chalk.yellow('当前系统暂不支持自动配置 Claude 桌面应用'));
|
|
790
811
|
return;
|
|
791
812
|
}
|
|
792
|
-
|
|
813
|
+
const extraFiles = Array.isArray(result.files) && result.files.length > 1
|
|
814
|
+
? chalk.dim(`(已同步 ${result.files.length} 个配置位置)`)
|
|
815
|
+
: '';
|
|
816
|
+
spinner.succeed(chalk.green(`Claude 桌面应用配置已写入 → ${result.file}`) + extraFiles);
|
|
793
817
|
} catch (e) {
|
|
794
818
|
spinner.fail(chalk.red(`写入失败: ${e.message}`));
|
|
795
819
|
return;
|
|
@@ -882,10 +906,7 @@ program
|
|
|
882
906
|
|
|
883
907
|
if (result.result === 'updated') {
|
|
884
908
|
spinner.succeed(chalk.green('Claude 桌面应用已恢复默认'));
|
|
885
|
-
const cleared =
|
|
886
|
-
result.dataDir ? require('path').join(result.dataDir, 'configLibrary/') : null,
|
|
887
|
-
result.file,
|
|
888
|
-
].filter(Boolean);
|
|
909
|
+
const cleared = desktopConfigClearedPaths(result);
|
|
889
910
|
console.log(boxen(
|
|
890
911
|
chalk.bold('已清除 Claude 桌面应用第三方推理配置:\n\n') +
|
|
891
912
|
cleared.map(f => chalk.cyan(' • ' + f)).join('\n'),
|
|
@@ -942,8 +963,11 @@ program
|
|
|
942
963
|
const cleared = resetConfig();
|
|
943
964
|
const desktopCleared = clearClaudeDesktopConfig();
|
|
944
965
|
if (desktopCleared.result === 'updated') {
|
|
945
|
-
|
|
946
|
-
|
|
966
|
+
cleared.push(...desktopConfigClearedPaths(desktopCleared));
|
|
967
|
+
}
|
|
968
|
+
const autostartCleared = removeGatewayAutostart();
|
|
969
|
+
if (autostartCleared.result === 'removed' && autostartCleared.file) {
|
|
970
|
+
cleared.push(autostartCleared.file);
|
|
947
971
|
}
|
|
948
972
|
|
|
949
973
|
if (cleared.length === 0) {
|
|
@@ -1048,12 +1072,31 @@ program
|
|
|
1048
1072
|
],
|
|
1049
1073
|
});
|
|
1050
1074
|
|
|
1051
|
-
const
|
|
1052
|
-
|
|
1053
|
-
|
|
1075
|
+
const upgradeCmd = buildYingclawUpgradeCommand(network);
|
|
1076
|
+
|
|
1077
|
+
if (process.platform === 'win32') {
|
|
1078
|
+
const fs = require('fs');
|
|
1079
|
+
const os = require('os');
|
|
1080
|
+
const path = require('path');
|
|
1081
|
+
const scriptFile = path.join(os.tmpdir(), `yingclaw-upgrade-${Date.now()}.cmd`);
|
|
1082
|
+
fs.writeFileSync(scriptFile, buildWindowsYingclawUpgradeScript(network), 'utf8');
|
|
1083
|
+
const child = spawn('cmd', ['/c', 'start', '', scriptFile], {
|
|
1084
|
+
detached: true,
|
|
1085
|
+
stdio: 'ignore',
|
|
1086
|
+
windowsHide: false,
|
|
1087
|
+
});
|
|
1088
|
+
if (child && typeof child.unref === 'function') child.unref();
|
|
1089
|
+
console.log(boxen(
|
|
1090
|
+
chalk.bold(`已打开独立更新窗口,准备升级到 v${latest}\n\n`) +
|
|
1091
|
+
chalk.dim('Windows 会锁定当前正在运行的包目录,所以更新会在当前 claw 退出后执行。\n') +
|
|
1092
|
+
chalk.dim('更新完成后重新运行 ') + chalk.cyan('claw'),
|
|
1093
|
+
{ padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderStyle: 'round', borderColor: 'green', margin: { top: 1, bottom: 1 } }
|
|
1094
|
+
));
|
|
1095
|
+
process.exit(0);
|
|
1096
|
+
}
|
|
1054
1097
|
|
|
1055
1098
|
console.log(chalk.dim('\n升级中...\n'));
|
|
1056
|
-
const result = spawnSync(upgradeCmd.command, upgradeCmd.args, { stdio: 'inherit'
|
|
1099
|
+
const result = spawnSync(upgradeCmd.command, upgradeCmd.args, { stdio: 'inherit' });
|
|
1057
1100
|
|
|
1058
1101
|
if (result.status === 0) {
|
|
1059
1102
|
console.log(boxen(
|
|
@@ -1272,10 +1315,13 @@ async function runMenu() {
|
|
|
1272
1315
|
});
|
|
1273
1316
|
|
|
1274
1317
|
// 改 config 的命令需要刷新缓存
|
|
1275
|
-
if (['config', 'switch', 'reset', 'code-reset'].includes(resolvedAction)) {
|
|
1318
|
+
if (['config', 'switch', 'reset', 'code-reset', 'desktop-reset'].includes(resolvedAction)) {
|
|
1276
1319
|
lastCheckResult = undefined;
|
|
1277
1320
|
lastCheckedHash = null;
|
|
1278
1321
|
}
|
|
1322
|
+
if (process.platform === 'win32' && ['reset', 'code-reset'].includes(resolvedAction)) {
|
|
1323
|
+
clearCurrentProcessClaudeEnv();
|
|
1324
|
+
}
|
|
1279
1325
|
// 安装 Claude 后刷新检测缓存
|
|
1280
1326
|
if (resolvedAction === 'install') {
|
|
1281
1327
|
invalidateClaudeInstalledCache();
|
package/index.js
CHANGED
package/lib/autostart.js
CHANGED
|
@@ -188,6 +188,36 @@ function installGatewayAutostart(options = {}) {
|
|
|
188
188
|
return { result: 'installed', file };
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
function removeGatewayAutostart(options = {}) {
|
|
192
|
+
const platform = options.platform || process.platform;
|
|
193
|
+
if (platform === 'win32') {
|
|
194
|
+
const file = options.file || getWindowsStartupScriptPath(options);
|
|
195
|
+
if (!fs.existsSync(file)) {
|
|
196
|
+
return { result: 'missing', file };
|
|
197
|
+
}
|
|
198
|
+
fs.unlinkSync(file);
|
|
199
|
+
return { result: 'removed', file };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (platform !== 'darwin') {
|
|
203
|
+
return { result: 'unsupported', file: null };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const homeDir = options.homeDir || os.homedir();
|
|
207
|
+
const file = options.file || getMacLaunchAgentPath({ homeDir });
|
|
208
|
+
const uid = options.uid || (typeof process.getuid === 'function' ? process.getuid() : null);
|
|
209
|
+
const runner = options.runner || spawnSync;
|
|
210
|
+
|
|
211
|
+
if (uid != null) {
|
|
212
|
+
runLaunchctl(runner, ['bootout', `gui/${uid}/${GATEWAY_LAUNCH_AGENT_LABEL}`], { optional: true });
|
|
213
|
+
}
|
|
214
|
+
if (!fs.existsSync(file)) {
|
|
215
|
+
return { result: 'missing', file };
|
|
216
|
+
}
|
|
217
|
+
fs.unlinkSync(file);
|
|
218
|
+
return { result: 'removed', file };
|
|
219
|
+
}
|
|
220
|
+
|
|
191
221
|
function getGatewayAutostartStatus(options = {}) {
|
|
192
222
|
const platform = options.platform || process.platform;
|
|
193
223
|
if (platform === 'win32') {
|
|
@@ -237,4 +267,5 @@ module.exports = {
|
|
|
237
267
|
getMacLaunchAgentPath,
|
|
238
268
|
getWindowsStartupScriptPath,
|
|
239
269
|
installGatewayAutostart,
|
|
270
|
+
removeGatewayAutostart,
|
|
240
271
|
};
|
package/lib/config.js
CHANGED
|
@@ -407,6 +407,9 @@ function clearClaudeCodeEnv(options = {}) {
|
|
|
407
407
|
|
|
408
408
|
if (platform === 'win32') {
|
|
409
409
|
runWindowsEnvCommands(buildWindowsClearEnvCommands(), options.runner || spawnSync, { ignoreErrors: true });
|
|
410
|
+
for (const key of CLEAR_CLAUDE_ENV_KEYS) {
|
|
411
|
+
delete process.env[key];
|
|
412
|
+
}
|
|
410
413
|
cleared.push(WINDOWS_ENV_LABEL);
|
|
411
414
|
return cleared;
|
|
412
415
|
}
|
package/lib/desktop.js
CHANGED
|
@@ -55,6 +55,25 @@ function getClaudeDesktopDataDir(options = {}) {
|
|
|
55
55
|
return null;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function getClaudeDesktopDataDirs(options = {}) {
|
|
59
|
+
if (options.dataDir) return [options.dataDir];
|
|
60
|
+
|
|
61
|
+
const platform = options.platform || process.platform;
|
|
62
|
+
const homeDir = options.homeDir || os.homedir();
|
|
63
|
+
|
|
64
|
+
if (platform === 'win32') {
|
|
65
|
+
const roamingAppData = options.appData || process.env.APPDATA || [homeDir, 'AppData', 'Roaming'].join('\\');
|
|
66
|
+
const localAppData = options.localAppData || process.env.LOCALAPPDATA || [homeDir, 'AppData', 'Local'].join('\\');
|
|
67
|
+
return [...new Set([
|
|
68
|
+
roamingAppData + '\\Claude-3p',
|
|
69
|
+
localAppData + '\\Claude-3p',
|
|
70
|
+
])];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const dataDir = getClaudeDesktopDataDir(options);
|
|
74
|
+
return dataDir ? [dataDir] : [];
|
|
75
|
+
}
|
|
76
|
+
|
|
58
77
|
function getClaudeDesktopConfigPath(options = {}) {
|
|
59
78
|
const platform = options.platform || process.platform;
|
|
60
79
|
const dataDir = options.dataDir || getClaudeDesktopDataDir(options);
|
|
@@ -172,7 +191,8 @@ function buildClaudeDesktopDirectEnterpriseConfig(config, options = {}) {
|
|
|
172
191
|
|
|
173
192
|
// 只清除 yingclaw 写入的那条 entry,保留用户其它 3P 配置
|
|
174
193
|
function clearClaudeDesktopConfigLibrary(options = {}) {
|
|
175
|
-
const
|
|
194
|
+
const dataDir = options.dataDir || getClaudeDesktopDataDir(options);
|
|
195
|
+
const dir = options.configLibraryDir || (dataDir ? path.join(dataDir, 'configLibrary') : null);
|
|
176
196
|
if (!dir || !fs.existsSync(dir)) {
|
|
177
197
|
return { result: 'missing', dir };
|
|
178
198
|
}
|
|
@@ -214,17 +234,11 @@ function clearClaudeDesktopConfigLibrary(options = {}) {
|
|
|
214
234
|
return { result: 'updated', dir };
|
|
215
235
|
}
|
|
216
236
|
|
|
217
|
-
|
|
218
|
-
function writeClaudeDesktopConfig(config, options = {}) {
|
|
237
|
+
function writeClaudeDesktopConfigToDir(config, options, dataDir) {
|
|
219
238
|
const effectiveConfig = options.direct
|
|
220
239
|
? config
|
|
221
240
|
: ensureDesktopGatewayConfig(config, options);
|
|
222
241
|
|
|
223
|
-
const dataDir = options.dataDir || getClaudeDesktopDataDir(options);
|
|
224
|
-
if (!dataDir) {
|
|
225
|
-
return { result: 'unsupported', file: null };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
242
|
const configLibraryDir = path.join(dataDir, 'configLibrary');
|
|
229
243
|
const existingMetaFile = path.join(configLibraryDir, '_meta.json');
|
|
230
244
|
const existingMeta = readJsonFile(existingMetaFile);
|
|
@@ -257,7 +271,7 @@ function writeClaudeDesktopConfig(config, options = {}) {
|
|
|
257
271
|
appliedId: uuid,
|
|
258
272
|
entries: [...otherEntries, { id: uuid, name: YINGCLAW_ENTRY_NAME }],
|
|
259
273
|
isManaged: typeof existingMeta.isManaged === 'boolean' ? existingMeta.isManaged : false,
|
|
260
|
-
platform: existingMeta.platform || process.platform,
|
|
274
|
+
platform: existingMeta.platform || options.platform || process.platform,
|
|
261
275
|
};
|
|
262
276
|
|
|
263
277
|
fs.mkdirSync(configLibraryDir, { recursive: true });
|
|
@@ -295,9 +309,32 @@ function writeClaudeDesktopConfig(config, options = {}) {
|
|
|
295
309
|
return { result: beforeEntry === entryBody ? 'unchanged' : 'updated', file, config: effectiveConfig };
|
|
296
310
|
}
|
|
297
311
|
|
|
298
|
-
|
|
312
|
+
// 写入 Claude-3p/configLibrary/ 下的 enterprise config 条目(主进程从此处读取)
|
|
313
|
+
function writeClaudeDesktopConfig(config, options = {}) {
|
|
314
|
+
const dataDirs = options.configFile
|
|
315
|
+
? [options.dataDir || path.dirname(options.configFile)]
|
|
316
|
+
: getClaudeDesktopDataDirs(options);
|
|
317
|
+
if (dataDirs.length === 0) {
|
|
318
|
+
return { result: 'unsupported', file: null, files: [] };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const results = dataDirs.map((dataDir, index) => {
|
|
322
|
+
const writeOptions = index === 0
|
|
323
|
+
? options
|
|
324
|
+
: { ...options, configFile: null, dataDir };
|
|
325
|
+
return writeClaudeDesktopConfigToDir(config, writeOptions, dataDir);
|
|
326
|
+
});
|
|
327
|
+
const primary = results[0];
|
|
328
|
+
return {
|
|
329
|
+
...primary,
|
|
330
|
+
result: results.some((item) => item.result === 'updated') ? 'updated' : primary.result,
|
|
331
|
+
files: results.map((item) => item.file).filter(Boolean),
|
|
332
|
+
dataDirs,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function clearClaudeDesktopConfigAtDir(options = {}, dataDir) {
|
|
299
337
|
// Derive dataDir only when configFile is not explicitly overridden (to avoid touching real system dirs in tests)
|
|
300
|
-
const dataDir = options.dataDir || (options.configFile ? null : getClaudeDesktopDataDir(options));
|
|
301
338
|
const file = options.configFile || (dataDir ? path.join(dataDir, 'claude_desktop_config.json') : null);
|
|
302
339
|
|
|
303
340
|
// Clear configLibrary regardless of whether the main config file exists
|
|
@@ -320,7 +357,7 @@ function clearClaudeDesktopConfig(options = {}) {
|
|
|
320
357
|
// Remove legacy enterpriseConfig gateway keys
|
|
321
358
|
if (next.enterpriseConfig && typeof next.enterpriseConfig === 'object') {
|
|
322
359
|
const enterpriseConfig = { ...next.enterpriseConfig };
|
|
323
|
-
for (const key of
|
|
360
|
+
for (const key of MAC_POLICY_KEYS) {
|
|
324
361
|
delete enterpriseConfig[key];
|
|
325
362
|
}
|
|
326
363
|
if (Object.keys(enterpriseConfig).length > 0) {
|
|
@@ -338,12 +375,36 @@ function clearClaudeDesktopConfig(options = {}) {
|
|
|
338
375
|
return { result: 'updated', file, dataDir };
|
|
339
376
|
}
|
|
340
377
|
|
|
378
|
+
function clearClaudeDesktopConfig(options = {}) {
|
|
379
|
+
const dataDirs = options.configFile
|
|
380
|
+
? [options.dataDir || path.dirname(options.configFile)]
|
|
381
|
+
: getClaudeDesktopDataDirs(options);
|
|
382
|
+
|
|
383
|
+
if (dataDirs.length === 0) {
|
|
384
|
+
return { result: 'missing', file: null, files: [], dataDir: null, dataDirs: [] };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const results = dataDirs.map((dataDir, index) => {
|
|
388
|
+
const clearOptions = index === 0
|
|
389
|
+
? options
|
|
390
|
+
: { ...options, configFile: null, dataDir };
|
|
391
|
+
return clearClaudeDesktopConfigAtDir(clearOptions, dataDir);
|
|
392
|
+
});
|
|
393
|
+
const primary = results[0];
|
|
394
|
+
return {
|
|
395
|
+
...primary,
|
|
396
|
+
result: results.some((item) => item.result === 'updated') ? 'updated' : primary.result,
|
|
397
|
+
files: results.map((item) => item.file).filter(Boolean),
|
|
398
|
+
dataDirs,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
341
402
|
function isDesktopConfigured(options = {}) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
403
|
+
return getClaudeDesktopDataDirs(options).some((dir) => {
|
|
404
|
+
const meta = readJsonFile(path.join(dir, 'configLibrary', '_meta.json'));
|
|
405
|
+
if (!Array.isArray(meta.entries)) return false;
|
|
406
|
+
return meta.entries.some((entry) => entry && entry.name === YINGCLAW_ENTRY_NAME);
|
|
407
|
+
});
|
|
347
408
|
}
|
|
348
409
|
|
|
349
410
|
function buildClaudeDesktopOpenCommands(platform = process.platform, options = {}) {
|
|
@@ -432,6 +493,7 @@ module.exports = {
|
|
|
432
493
|
getClaudeDesktopConfigLibraryDir,
|
|
433
494
|
getClaudeDesktopConfigPath,
|
|
434
495
|
getClaudeDesktopDataDir,
|
|
496
|
+
getClaudeDesktopDataDirs,
|
|
435
497
|
isDesktopConfigured,
|
|
436
498
|
openClaudeDesktop,
|
|
437
499
|
writeClaudeDesktopConfig,
|
package/lib/install.js
CHANGED
|
@@ -8,6 +8,44 @@ function buildClaudeInstallCommand(network) {
|
|
|
8
8
|
return { command: 'npm', args };
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
function buildYingclawUpgradeCommand(network) {
|
|
12
|
+
const args = ['install', '-g', 'yingclaw@latest'];
|
|
13
|
+
if (network === 'cn') {
|
|
14
|
+
args.push('--registry=https://registry.npmmirror.com');
|
|
15
|
+
}
|
|
16
|
+
return { command: 'npm', args };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function quoteWindowsBatchArg(value) {
|
|
20
|
+
return `"${String(value).replace(/"/g, '""')}"`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function buildWindowsYingclawUpgradeScript(network) {
|
|
24
|
+
const { command, args } = buildYingclawUpgradeCommand(network);
|
|
25
|
+
const commandLine = [command, ...args].map(quoteWindowsBatchArg).join(' ');
|
|
26
|
+
return [
|
|
27
|
+
'@echo off',
|
|
28
|
+
'chcp 65001 >nul',
|
|
29
|
+
'echo yingclaw Windows updater',
|
|
30
|
+
'echo.',
|
|
31
|
+
'echo 当前 claw 进程退出后开始升级,避免 Windows 文件锁 EBUSY...',
|
|
32
|
+
'timeout /t 2 /nobreak >nul',
|
|
33
|
+
commandLine,
|
|
34
|
+
'set "code=%ERRORLEVEL%"',
|
|
35
|
+
'echo.',
|
|
36
|
+
'if "%code%"=="0" (',
|
|
37
|
+
' echo yingclaw 更新完成,请重新运行 claw',
|
|
38
|
+
') else (',
|
|
39
|
+
' echo yingclaw 更新失败,错误码 %code%',
|
|
40
|
+
` echo 请手动运行: ${commandLine}`,
|
|
41
|
+
')',
|
|
42
|
+
'echo.',
|
|
43
|
+
'pause',
|
|
44
|
+
'del "%~f0" >nul 2>nul',
|
|
45
|
+
'',
|
|
46
|
+
].join('\r\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
11
49
|
function checkNodeEnv() {
|
|
12
50
|
const nodeVersion = process.versions.node;
|
|
13
51
|
const nodeMajor = parseInt(nodeVersion.split('.')[0], 10);
|
|
@@ -91,4 +129,11 @@ function getInstallFailureHints(result, chalk) {
|
|
|
91
129
|
];
|
|
92
130
|
}
|
|
93
131
|
|
|
94
|
-
module.exports = {
|
|
132
|
+
module.exports = {
|
|
133
|
+
buildClaudeInstallCommand,
|
|
134
|
+
buildYingclawUpgradeCommand,
|
|
135
|
+
buildWindowsYingclawUpgradeScript,
|
|
136
|
+
checkNodeEnv,
|
|
137
|
+
getNodeInstallGuide,
|
|
138
|
+
getInstallFailureHints,
|
|
139
|
+
};
|