@aiyiran/myclaw 1.0.11 → 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 +137 -73
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.11",
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,7 +189,8 @@ try {
133
189
  }
134
190
  Write-Host ''
135
191
 
136
- Write-Host '[3/4] 检查并安装 WSL 完整包...'
192
+ Write-Host '[3/4] 获取 WSL 安装包...'
193
+ $msi = "$dir\\wsl_full.msi"
137
194
  $wslInstalled = $false
138
195
  try {
139
196
  $ver = wsl --version 2>&1 | Out-String
@@ -141,33 +198,18 @@ try {
141
198
  } catch {}
142
199
 
143
200
  if ($wslInstalled) {
144
- Write-Host ' WSL 组件已安装,跳过下载 (节省 243MB)'
201
+ Write-Host ' WSL 已安装,跳过'
145
202
  Write-Host ' [OK]'
146
203
  } else {
147
- Write-Host ' WSL 未安装,正在下载完整安装包 (约 243MB)...'
148
- $msi = "$dir\\wsl_full.msi"
149
- if (-Not (Test-Path $msi)) {
150
- try {
151
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
152
- Invoke-WebRequest -Uri '${WSL_CDN.wsl}' -OutFile $msi -UseBasicParsing
153
- Write-Host ' 下载完成!'
154
- } catch {
155
- Write-Host ' [失败] 下载失败,请检查网络后重试 myclaw wsl2'
156
- Write-Host ''; Write-Host '按任意键关闭...'
157
- $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
158
- exit 1
159
- }
160
- } else {
161
- Write-Host ' 使用上次缓存的文件'
162
- }
204
+ ${msiSource}
163
205
 
164
206
  Write-Host ''
165
- Write-Host '[4/4] 安装 WSL 完整包...'
207
+ Write-Host '[4/4] 安装 WSL...'
166
208
  try {
167
209
  Start-Process msiexec.exe -ArgumentList "/i \`"$msi\`" /quiet /norestart" -Wait -NoNewWindow
168
- Write-Host ' [OK] 安装成功!'
210
+ Write-Host ' [OK]'
169
211
  } catch {
170
- Write-Host ' [失败] 安装出错'
212
+ Write-Host ' [失败]'
171
213
  Write-Host ''; Write-Host '按任意键关闭...'
172
214
  $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
173
215
  exit 1
@@ -181,7 +223,6 @@ Write-Host ''
181
223
  Write-Host ' >>> 请立即 [重启电脑] <<<'
182
224
  Write-Host ''
183
225
  Write-Host ' 重启后运行: myclaw wsl2'
184
- Write-Host ' 将自动进入阶段 2 完成安装'
185
226
  Write-Host '========================================'
186
227
  Write-Host ''
187
228
  Write-Host '按任意键关闭此窗口...'
@@ -200,16 +241,55 @@ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
200
241
  // Phase 2: 导入预制 Linux 环境
201
242
  // ============================================================================
202
243
 
203
- function runPhase2() {
244
+ function runPhase2(offlineDir) {
204
245
  console.log('');
205
246
  console.log('[当前进度] WSL 功能 ' + C.g + '[OK]' + C.nc);
206
247
  console.log(' WSL 组件 ' + C.g + '[OK]' + C.nc);
207
248
  console.log(' Linux ' + C.y + '[待安装]' + C.nc);
208
249
  console.log('');
209
- console.log('即将导入预制 Linux 环境(需要管理员权限)...');
250
+ if (offlineDir) {
251
+ console.log('[离线模式] 从 ' + C.y + offlineDir + C.nc + ' 读取安装包');
252
+ }
253
+ console.log('即将导入预制 Linux 环境...');
210
254
  console.log('[' + C.y + '注意' + C.nc + '] 请在 UAC 弹窗中点击【是】');
211
255
  console.log('');
212
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
+
213
293
  const ps = `
214
294
  $ErrorActionPreference = 'Continue'
215
295
  $Host.UI.RawUI.WindowTitle = 'MyClaw WSL2 Installer - Phase 2'
@@ -228,27 +308,13 @@ try { wsl --set-default-version 2 2>$null } catch {}
228
308
  Write-Host ' [OK]'
229
309
  Write-Host ''
230
310
 
231
- Write-Host '[2/3] 下载并导入预制 Linux 环境...'
311
+ Write-Host '[2/3] 获取并导入预制 Linux 环境...'
232
312
  $tarPath = "$dir\\openclaw-rootfs.tar"
233
313
  $distroName = 'OpenClaw'
234
314
  $installDir = "$dir\\OpenClaw"
235
315
  $installed = $false
236
316
 
237
- if (-Not (Test-Path $tarPath)) {
238
- Write-Host ' 正在下载预制环境包...'
239
- try {
240
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
241
- Invoke-WebRequest -Uri '${WSL_CDN.rootfs}' -OutFile $tarPath -UseBasicParsing
242
- Write-Host ' 下载完成!'
243
- } catch {
244
- Write-Host ' [失败] 下载失败,请检查网络后重试 myclaw wsl2'
245
- Write-Host ''; Write-Host '按任意键关闭...'
246
- $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
247
- exit 1
248
- }
249
- } else {
250
- Write-Host ' 使用上次缓存的文件'
251
- }
317
+ ${tarSource}
252
318
 
253
319
  Write-Host ' 正在导入 (可能需要几分钟)...'
254
320
  try {
@@ -279,10 +345,8 @@ if ($installed) {
279
345
  Write-Host ' [OK] WSL2 + OpenClaw Linux 安装完成!'
280
346
  Write-Host ''
281
347
  Write-Host ' 输入 wsl 即可进入 Linux 环境'
282
- Write-Host ' OpenClaw 已预装就绪'
283
348
  } else {
284
- Write-Host ' [失败] 导入未成功,请检查网络后重试'
285
- Write-Host ' 运行: myclaw wsl2'
349
+ Write-Host ' [失败] 请检查后重试: myclaw wsl2'
286
350
  }
287
351
  Write-Host '========================================'
288
352
  Write-Host ''
@@ -294,7 +358,6 @@ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
294
358
  console.log('[' + C.g + '已启动' + C.nc + '] 请查看新弹出的蓝色窗口');
295
359
  console.log('');
296
360
  console.log('安装完成后,输入 ' + C.y + 'wsl' + C.nc + ' 即可进入 Linux');
297
- console.log('可随时运行 ' + C.y + 'myclaw wsl2' + C.nc + ' 检查状态');
298
361
  console.log('');
299
362
  }
300
363
  }
@@ -309,6 +372,7 @@ function run() {
309
372
  process.exit(0);
310
373
  }
311
374
 
375
+ const offlineDir = parseOfflineDir();
312
376
  const bar = '========================================';
313
377
  const state = detectState();
314
378
 
@@ -333,9 +397,9 @@ function run() {
333
397
  }
334
398
 
335
399
  if (state === 'needs-setup') {
336
- runPhase2();
400
+ runPhase2(offlineDir);
337
401
  } else {
338
- runPhase1();
402
+ runPhase1(offlineDir);
339
403
  }
340
404
  }
341
405