@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/wsl2.js +148 -72
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.9",
3
+ "version": "1.0.12",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
package/wsl2.js CHANGED
@@ -5,46 +5,72 @@
5
5
  * MyClaw WSL2 一键安装器
6
6
  * ============================================================================
7
7
  *
8
- * 使用方式: myclaw wsl2
8
+ * 使用方式:
9
+ * myclaw wsl2 # 在线安装(从 CDN 下载)
10
+ * myclaw wsl2 --offline D:\myclaw_pkg # 离线安装(从指定目录读取文件)
9
11
  *
10
- * 自动检测当前 WSL2 安装状态,分两个阶段完成安装:
11
- * Phase 1: 启用底层 Windows 功能 + 安装 WSL 完整包 → 需要重启
12
- * Phase 2: 导入预制 Linux 环境(tar 包) → 完成
12
+ * 离线模式所需文件(放到同一目录下):
13
+ * - wsl_full.msi → Phase 1 用(WSL 完整安装包)
14
+ * - openclaw-rootfs.tar → Phase 2 用(预制 Linux 环境)
13
15
  *
14
- * 用户只需反复运行 myclaw wsl2,脚本会自动接续上次的进度。
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
- // 新版 WSL 完整安装包(243MB,含内核,替代老的 wsl_update_x64.msi 和 wsl --update
31
+ // 新版 WSL 完整安装包(243MB)
27
32
  wsl: 'https://cdn.yiranlaoshi.com/software/myclaw/wsl.2.7.1.0.x64.msi',
28
- // 预制 rootfs tar 包(内含 Node.js + OpenClaw,用户开箱即用)
29
- // TODO: 替换为你自己的 CDN 地址,制作方法见 README
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
- console.log('本次执行 阶段 1/2: 启用底层功能 + 安装 WSL 完整包');
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] 启用虚拟机平台 (VirtualMachinePlatform)...'
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] 下载 WSL 完整安装包 (约 243MB)...'
192
+ Write-Host '[3/4] 获取 WSL 安装包...'
137
193
  $msi = "$dir\\wsl_full.msi"
138
- if (-Not (Test-Path $msi)) {
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
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
141
- Invoke-WebRequest -Uri '${WSL_CDN.wsl}' -OutFile $msi -UseBasicParsing
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 ' [失败] 下载失败,请检查网络后重试 myclaw wsl2'
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
- console.log('即将导入预制 Linux 环境(需要管理员权限)...');
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] 下载并导入预制 Linux 环境...'
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
- if (-Not (Test-Path $tarPath)) {
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