@aiyiran/myclaw 1.0.9 → 1.0.12
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/package.json +1 -1
- package/wsl2.js +148 -72
package/package.json
CHANGED
package/wsl2.js
CHANGED
|
@@ -5,46 +5,72 @@
|
|
|
5
5
|
* MyClaw WSL2 一键安装器
|
|
6
6
|
* ============================================================================
|
|
7
7
|
*
|
|
8
|
-
* 使用方式:
|
|
8
|
+
* 使用方式:
|
|
9
|
+
* myclaw wsl2 # 在线安装(从 CDN 下载)
|
|
10
|
+
* myclaw wsl2 --offline D:\myclaw_pkg # 离线安装(从指定目录读取文件)
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
* Phase 1
|
|
12
|
-
* Phase 2
|
|
12
|
+
* 离线模式所需文件(放到同一目录下):
|
|
13
|
+
* - wsl_full.msi → Phase 1 用(WSL 完整安装包)
|
|
14
|
+
* - openclaw-rootfs.tar → Phase 2 用(预制 Linux 环境)
|
|
13
15
|
*
|
|
14
|
-
*
|
|
16
|
+
* 自动检测安装状态,分两阶段完成:
|
|
17
|
+
* Phase 1: 启用底层 Windows 功能 + 安装 WSL → 需要重启
|
|
18
|
+
* Phase 2: 导入预制 Linux 环境 → 完成
|
|
15
19
|
* ============================================================================
|
|
16
20
|
*/
|
|
17
21
|
|
|
18
22
|
const { execSync } = require('child_process');
|
|
19
23
|
const os = require('os');
|
|
24
|
+
const path = require('path');
|
|
20
25
|
|
|
21
26
|
// ============================================================================
|
|
22
|
-
// 配置
|
|
27
|
+
// 配置
|
|
23
28
|
// ============================================================================
|
|
24
29
|
|
|
25
30
|
const WSL_CDN = {
|
|
26
|
-
//
|
|
31
|
+
// 新版 WSL 完整安装包(243MB)
|
|
27
32
|
wsl: 'https://cdn.yiranlaoshi.com/software/myclaw/wsl.2.7.1.0.x64.msi',
|
|
28
|
-
//
|
|
29
|
-
// TODO: 替换为你自己的 CDN
|
|
33
|
+
// 预制 rootfs tar 包(内含 Node.js + OpenClaw)
|
|
34
|
+
// TODO: 替换为你自己的 CDN 地址
|
|
30
35
|
rootfs: 'https://openclaw.ai/wsl/openclaw-rootfs.tar',
|
|
31
36
|
};
|
|
32
37
|
|
|
33
38
|
const isWindows = os.platform() === 'win32';
|
|
34
39
|
|
|
35
|
-
// 颜色(Windows 下禁用避免乱码)
|
|
36
40
|
const C = isWindows
|
|
37
41
|
? { r: '', g: '', y: '', b: '', nc: '' }
|
|
38
42
|
: { r: '\x1b[31m', g: '\x1b[32m', y: '\x1b[33m', b: '\x1b[34m', nc: '\x1b[0m' };
|
|
39
43
|
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// 解析 --offline 参数
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
function parseOfflineDir() {
|
|
49
|
+
const args = process.argv.slice(2);
|
|
50
|
+
const idx = args.indexOf('--offline');
|
|
51
|
+
if (idx === -1) return null;
|
|
52
|
+
const dir = args[idx + 1];
|
|
53
|
+
if (!dir) {
|
|
54
|
+
console.error('[' + C.r + '错误' + C.nc + '] --offline 后面需要指定目录路径');
|
|
55
|
+
console.log('');
|
|
56
|
+
console.log('用法: myclaw wsl2 --offline <目录路径>');
|
|
57
|
+
console.log('');
|
|
58
|
+
console.log('示例:');
|
|
59
|
+
console.log(' myclaw wsl2 --offline D:\\');
|
|
60
|
+
console.log(' myclaw wsl2 --offline E:\\myclaw_pkg');
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log('目录下应包含:');
|
|
63
|
+
console.log(' wsl_full.msi (Phase 1: WSL 安装包)');
|
|
64
|
+
console.log(' openclaw-rootfs.tar (Phase 2: Linux 环境)');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
return dir;
|
|
68
|
+
}
|
|
69
|
+
|
|
40
70
|
// ============================================================================
|
|
41
71
|
// 工具函数
|
|
42
72
|
// ============================================================================
|
|
43
73
|
|
|
44
|
-
/**
|
|
45
|
-
* 以管理员权限启动 PowerShell 脚本
|
|
46
|
-
* 利用 UTF-16LE Base64 编码避免引号转义地狱
|
|
47
|
-
*/
|
|
48
74
|
function launchElevatedPS(script) {
|
|
49
75
|
const b64 = Buffer.from(script, 'utf16le').toString('base64');
|
|
50
76
|
try {
|
|
@@ -64,51 +90,81 @@ function launchElevatedPS(script) {
|
|
|
64
90
|
// 状态检测
|
|
65
91
|
// ============================================================================
|
|
66
92
|
|
|
67
|
-
/**
|
|
68
|
-
* 自动检测当前 WSL2 安装状态
|
|
69
|
-
* @returns {'ready'|'needs-setup'|'needs-features'}
|
|
70
|
-
*/
|
|
71
93
|
function detectState() {
|
|
72
|
-
// 1. WSL 已完全可用(有发行版能跑命令)
|
|
73
94
|
try {
|
|
74
95
|
execSync('wsl echo ok', { stdio: 'pipe', timeout: 15000 });
|
|
75
96
|
return 'ready';
|
|
76
97
|
} catch {}
|
|
77
98
|
|
|
78
|
-
// 2. WSL 命令可用但没有发行版(功能已启用,重启后的状态)
|
|
79
99
|
try {
|
|
80
100
|
execSync('wsl --status', { stdio: 'pipe', timeout: 5000 });
|
|
81
101
|
return 'needs-setup';
|
|
82
102
|
} catch {}
|
|
83
103
|
|
|
84
|
-
// 3. WSL 功能未启用
|
|
85
104
|
return 'needs-features';
|
|
86
105
|
}
|
|
87
106
|
|
|
88
107
|
// ============================================================================
|
|
89
|
-
// Phase 1: 启用功能 + 安装 WSL
|
|
108
|
+
// Phase 1: 启用功能 + 安装 WSL
|
|
90
109
|
// ============================================================================
|
|
91
110
|
|
|
92
|
-
function runPhase1() {
|
|
111
|
+
function runPhase1(offlineDir) {
|
|
93
112
|
console.log('');
|
|
94
113
|
console.log('[当前进度] WSL 功能 ' + C.r + '[未启用]' + C.nc);
|
|
95
114
|
console.log(' WSL 组件 ' + C.r + '[未安装]' + C.nc);
|
|
96
115
|
console.log(' Linux ' + C.r + '[未安装]' + C.nc);
|
|
97
116
|
console.log('');
|
|
98
|
-
|
|
117
|
+
if (offlineDir) {
|
|
118
|
+
console.log('[离线模式] 从 ' + C.y + offlineDir + C.nc + ' 读取安装包');
|
|
119
|
+
}
|
|
120
|
+
console.log('阶段 1/2: 启用底层功能 + 安装 WSL');
|
|
99
121
|
console.log(' 完成后需要 ' + C.y + '重启电脑' + C.nc);
|
|
100
|
-
console.log(' 重启后再运行 ' + C.y + 'myclaw wsl2' + C.nc + ' 自动进入阶段 2');
|
|
101
122
|
console.log('');
|
|
102
123
|
console.log('[' + C.y + '注意' + C.nc + '] 请在 UAC 弹窗中点击【是】');
|
|
103
124
|
console.log('');
|
|
104
125
|
|
|
126
|
+
// 构造文件获取逻辑
|
|
127
|
+
let msiSource;
|
|
128
|
+
if (offlineDir) {
|
|
129
|
+
const localPath = path.join(offlineDir, 'wsl_full.msi').replace(/\\/g, '\\\\');
|
|
130
|
+
msiSource = `
|
|
131
|
+
$localMsi = '${localPath}'
|
|
132
|
+
if (Test-Path $localMsi) {
|
|
133
|
+
Write-Host " 从离线目录复制: $localMsi"
|
|
134
|
+
Copy-Item $localMsi $msi -Force
|
|
135
|
+
Write-Host ' [OK]'
|
|
136
|
+
} else {
|
|
137
|
+
Write-Host " [失败] 文件不存在: $localMsi"
|
|
138
|
+
Write-Host ''; Write-Host '按任意键关闭...'
|
|
139
|
+
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
|
140
|
+
exit 1
|
|
141
|
+
}`;
|
|
142
|
+
} else {
|
|
143
|
+
msiSource = `
|
|
144
|
+
if (-Not (Test-Path $msi)) {
|
|
145
|
+
Write-Host ' 正在从 CDN 下载 (约 243MB)...'
|
|
146
|
+
try {
|
|
147
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
148
|
+
Invoke-WebRequest -Uri '${WSL_CDN.wsl}' -OutFile $msi -UseBasicParsing
|
|
149
|
+
Write-Host ' 下载完成!'
|
|
150
|
+
} catch {
|
|
151
|
+
Write-Host ' [失败] 下载失败'
|
|
152
|
+
Write-Host ' 提示: 可用 myclaw wsl2 --offline <目录> 指定本地文件'
|
|
153
|
+
Write-Host ''; Write-Host '按任意键关闭...'
|
|
154
|
+
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
|
155
|
+
exit 1
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
Write-Host ' 使用上次缓存的文件'
|
|
159
|
+
}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
105
162
|
const ps = `
|
|
106
163
|
$ErrorActionPreference = 'Continue'
|
|
107
164
|
$Host.UI.RawUI.WindowTitle = 'MyClaw WSL2 Installer - Phase 1'
|
|
108
165
|
Write-Host ''
|
|
109
166
|
Write-Host '========================================'
|
|
110
167
|
Write-Host ' MyClaw WSL2 安装 - 阶段 1/2'
|
|
111
|
-
Write-Host ' 启用底层功能 + 安装 WSL 完整包'
|
|
112
168
|
Write-Host '========================================'
|
|
113
169
|
Write-Host ''
|
|
114
170
|
|
|
@@ -124,7 +180,7 @@ try {
|
|
|
124
180
|
}
|
|
125
181
|
Write-Host ''
|
|
126
182
|
|
|
127
|
-
Write-Host '[2/4]
|
|
183
|
+
Write-Host '[2/4] 启用虚拟机平台...'
|
|
128
184
|
try {
|
|
129
185
|
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart | Out-Null
|
|
130
186
|
Write-Host ' [OK]'
|
|
@@ -133,33 +189,31 @@ try {
|
|
|
133
189
|
}
|
|
134
190
|
Write-Host ''
|
|
135
191
|
|
|
136
|
-
Write-Host '[3/4]
|
|
192
|
+
Write-Host '[3/4] 获取 WSL 安装包...'
|
|
137
193
|
$msi = "$dir\\wsl_full.msi"
|
|
138
|
-
|
|
194
|
+
$wslInstalled = $false
|
|
195
|
+
try {
|
|
196
|
+
$ver = wsl --version 2>&1 | Out-String
|
|
197
|
+
if ($ver -match 'WSL') { $wslInstalled = $true }
|
|
198
|
+
} catch {}
|
|
199
|
+
|
|
200
|
+
if ($wslInstalled) {
|
|
201
|
+
Write-Host ' WSL 已安装,跳过'
|
|
202
|
+
Write-Host ' [OK]'
|
|
203
|
+
} else {
|
|
204
|
+
${msiSource}
|
|
205
|
+
|
|
206
|
+
Write-Host ''
|
|
207
|
+
Write-Host '[4/4] 安装 WSL...'
|
|
139
208
|
try {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Write-Host ' [OK] 下载完成'
|
|
209
|
+
Start-Process msiexec.exe -ArgumentList "/i \`"$msi\`" /quiet /norestart" -Wait -NoNewWindow
|
|
210
|
+
Write-Host ' [OK]'
|
|
143
211
|
} catch {
|
|
144
|
-
Write-Host ' [失败]
|
|
212
|
+
Write-Host ' [失败]'
|
|
145
213
|
Write-Host ''; Write-Host '按任意键关闭...'
|
|
146
214
|
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
|
147
215
|
exit 1
|
|
148
216
|
}
|
|
149
|
-
} else {
|
|
150
|
-
Write-Host ' 使用上次缓存的文件'
|
|
151
|
-
}
|
|
152
|
-
Write-Host ''
|
|
153
|
-
|
|
154
|
-
Write-Host '[4/4] 安装 WSL 完整包...'
|
|
155
|
-
try {
|
|
156
|
-
Start-Process msiexec.exe -ArgumentList "/i \`"$msi\`" /quiet /norestart" -Wait -NoNewWindow
|
|
157
|
-
Write-Host ' [OK] 安装成功!'
|
|
158
|
-
} catch {
|
|
159
|
-
Write-Host ' [失败] 安装出错'
|
|
160
|
-
Write-Host ''; Write-Host '按任意键关闭...'
|
|
161
|
-
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
|
162
|
-
exit 1
|
|
163
217
|
}
|
|
164
218
|
|
|
165
219
|
Write-Host ''
|
|
@@ -169,7 +223,6 @@ Write-Host ''
|
|
|
169
223
|
Write-Host ' >>> 请立即 [重启电脑] <<<'
|
|
170
224
|
Write-Host ''
|
|
171
225
|
Write-Host ' 重启后运行: myclaw wsl2'
|
|
172
|
-
Write-Host ' 将自动进入阶段 2 完成安装'
|
|
173
226
|
Write-Host '========================================'
|
|
174
227
|
Write-Host ''
|
|
175
228
|
Write-Host '按任意键关闭此窗口...'
|
|
@@ -188,16 +241,55 @@ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
|
|
188
241
|
// Phase 2: 导入预制 Linux 环境
|
|
189
242
|
// ============================================================================
|
|
190
243
|
|
|
191
|
-
function runPhase2() {
|
|
244
|
+
function runPhase2(offlineDir) {
|
|
192
245
|
console.log('');
|
|
193
246
|
console.log('[当前进度] WSL 功能 ' + C.g + '[OK]' + C.nc);
|
|
194
247
|
console.log(' WSL 组件 ' + C.g + '[OK]' + C.nc);
|
|
195
248
|
console.log(' Linux ' + C.y + '[待安装]' + C.nc);
|
|
196
249
|
console.log('');
|
|
197
|
-
|
|
250
|
+
if (offlineDir) {
|
|
251
|
+
console.log('[离线模式] 从 ' + C.y + offlineDir + C.nc + ' 读取安装包');
|
|
252
|
+
}
|
|
253
|
+
console.log('即将导入预制 Linux 环境...');
|
|
198
254
|
console.log('[' + C.y + '注意' + C.nc + '] 请在 UAC 弹窗中点击【是】');
|
|
199
255
|
console.log('');
|
|
200
256
|
|
|
257
|
+
// 构造 tar 获取逻辑
|
|
258
|
+
let tarSource;
|
|
259
|
+
if (offlineDir) {
|
|
260
|
+
const localPath = path.join(offlineDir, 'openclaw-rootfs.tar').replace(/\\/g, '\\\\');
|
|
261
|
+
tarSource = `
|
|
262
|
+
$localTar = '${localPath}'
|
|
263
|
+
if (Test-Path $localTar) {
|
|
264
|
+
Write-Host " 从离线目录复制: $localTar"
|
|
265
|
+
Copy-Item $localTar $tarPath -Force
|
|
266
|
+
Write-Host ' [OK]'
|
|
267
|
+
} else {
|
|
268
|
+
Write-Host " [失败] 文件不存在: $localTar"
|
|
269
|
+
Write-Host ''; Write-Host '按任意键关闭...'
|
|
270
|
+
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
|
271
|
+
exit 1
|
|
272
|
+
}`;
|
|
273
|
+
} else {
|
|
274
|
+
tarSource = `
|
|
275
|
+
if (-Not (Test-Path $tarPath)) {
|
|
276
|
+
Write-Host ' 正在从 CDN 下载...'
|
|
277
|
+
try {
|
|
278
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
279
|
+
Invoke-WebRequest -Uri '${WSL_CDN.rootfs}' -OutFile $tarPath -UseBasicParsing
|
|
280
|
+
Write-Host ' 下载完成!'
|
|
281
|
+
} catch {
|
|
282
|
+
Write-Host ' [失败] 下载失败'
|
|
283
|
+
Write-Host ' 提示: 可用 myclaw wsl2 --offline <目录> 指定本地文件'
|
|
284
|
+
Write-Host ''; Write-Host '按任意键关闭...'
|
|
285
|
+
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
|
286
|
+
exit 1
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
Write-Host ' 使用上次缓存的文件'
|
|
290
|
+
}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
201
293
|
const ps = `
|
|
202
294
|
$ErrorActionPreference = 'Continue'
|
|
203
295
|
$Host.UI.RawUI.WindowTitle = 'MyClaw WSL2 Installer - Phase 2'
|
|
@@ -216,27 +308,13 @@ try { wsl --set-default-version 2 2>$null } catch {}
|
|
|
216
308
|
Write-Host ' [OK]'
|
|
217
309
|
Write-Host ''
|
|
218
310
|
|
|
219
|
-
Write-Host '[2/3]
|
|
311
|
+
Write-Host '[2/3] 获取并导入预制 Linux 环境...'
|
|
220
312
|
$tarPath = "$dir\\openclaw-rootfs.tar"
|
|
221
313
|
$distroName = 'OpenClaw'
|
|
222
314
|
$installDir = "$dir\\OpenClaw"
|
|
223
315
|
$installed = $false
|
|
224
316
|
|
|
225
|
-
|
|
226
|
-
Write-Host ' 正在下载预制环境包...'
|
|
227
|
-
try {
|
|
228
|
-
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
229
|
-
Invoke-WebRequest -Uri '${WSL_CDN.rootfs}' -OutFile $tarPath -UseBasicParsing
|
|
230
|
-
Write-Host ' 下载完成!'
|
|
231
|
-
} catch {
|
|
232
|
-
Write-Host ' [失败] 下载失败,请检查网络后重试 myclaw wsl2'
|
|
233
|
-
Write-Host ''; Write-Host '按任意键关闭...'
|
|
234
|
-
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
|
235
|
-
exit 1
|
|
236
|
-
}
|
|
237
|
-
} else {
|
|
238
|
-
Write-Host ' 使用上次缓存的文件'
|
|
239
|
-
}
|
|
317
|
+
${tarSource}
|
|
240
318
|
|
|
241
319
|
Write-Host ' 正在导入 (可能需要几分钟)...'
|
|
242
320
|
try {
|
|
@@ -267,10 +345,8 @@ if ($installed) {
|
|
|
267
345
|
Write-Host ' [OK] WSL2 + OpenClaw Linux 安装完成!'
|
|
268
346
|
Write-Host ''
|
|
269
347
|
Write-Host ' 输入 wsl 即可进入 Linux 环境'
|
|
270
|
-
Write-Host ' OpenClaw 已预装就绪'
|
|
271
348
|
} else {
|
|
272
|
-
Write-Host ' [失败]
|
|
273
|
-
Write-Host ' 运行: myclaw wsl2'
|
|
349
|
+
Write-Host ' [失败] 请检查后重试: myclaw wsl2'
|
|
274
350
|
}
|
|
275
351
|
Write-Host '========================================'
|
|
276
352
|
Write-Host ''
|
|
@@ -282,7 +358,6 @@ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
|
|
|
282
358
|
console.log('[' + C.g + '已启动' + C.nc + '] 请查看新弹出的蓝色窗口');
|
|
283
359
|
console.log('');
|
|
284
360
|
console.log('安装完成后,输入 ' + C.y + 'wsl' + C.nc + ' 即可进入 Linux');
|
|
285
|
-
console.log('可随时运行 ' + C.y + 'myclaw wsl2' + C.nc + ' 检查状态');
|
|
286
361
|
console.log('');
|
|
287
362
|
}
|
|
288
363
|
}
|
|
@@ -297,6 +372,7 @@ function run() {
|
|
|
297
372
|
process.exit(0);
|
|
298
373
|
}
|
|
299
374
|
|
|
375
|
+
const offlineDir = parseOfflineDir();
|
|
300
376
|
const bar = '========================================';
|
|
301
377
|
const state = detectState();
|
|
302
378
|
|
|
@@ -321,9 +397,9 @@ function run() {
|
|
|
321
397
|
}
|
|
322
398
|
|
|
323
399
|
if (state === 'needs-setup') {
|
|
324
|
-
runPhase2();
|
|
400
|
+
runPhase2(offlineDir);
|
|
325
401
|
} else {
|
|
326
|
-
runPhase1();
|
|
402
|
+
runPhase1(offlineDir);
|
|
327
403
|
}
|
|
328
404
|
}
|
|
329
405
|
|