@aiyiran/myclaw 1.0.5 → 1.0.7

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 ADDED
@@ -0,0 +1,126 @@
1
+ # MyClaw - 学生友好的 OpenClaw 工具
2
+
3
+ 一个简化 OpenClaw 日常操作的命令行工具,专为教学环境设计。
4
+
5
+ ## 跨平台要求 ⚠️
6
+
7
+ ```
8
+ ╔══════════════════════════════════════════════════════════════════════╗
9
+ ║ 跨平台兼容性说明 ║
10
+ ╠══════════════════════════════════════════════════════════════════════╣
11
+ ║ ║
12
+ ║ 支持系统: ║
13
+ ║ ✅ macOS - 完全支持,包括颜色和 Emoji ║
14
+ ║ ✅ Linux - 完全支持,包括颜色和 Emoji ║
15
+ ║ ✅ Windows 10/11 - 基本支持,推荐使用 Windows Terminal ║
16
+ ║ ⚠️ Windows 7/8 - 支持,但 Emoji 可能显示为方块 ║
17
+ ║ ║
18
+ ║ Windows 用户建议: ║
19
+ ║ • 安装 Windows Terminal (Microsoft Store 免费下载) ║
20
+ ║ • 或使用 PowerShell 7+ (https://aka.ms/powershell) ║
21
+ ║ ║
22
+ ║ 颜色说明: ║
23
+ ║ • macOS/Linux: 正常显示 ANSI 颜色 ║
24
+ ║ • Windows: 自动禁用颜色输出,避免乱码 ║
25
+ ║ ║
26
+ ╚══════════════════════════════════════════════════════════════════════╝
27
+ ```
28
+
29
+ ## 安装
30
+
31
+ ```bash
32
+ # 方式一: 直接运行
33
+ node myclaw/index.js <command>
34
+
35
+ # 方式二: 添加到 PATH (macOS/Linux)
36
+ ln -s /path/to/myclaw/index.js /usr/local/bin/myclaw
37
+ chmod +x /usr/local/bin/myclaw
38
+
39
+ # 方式三: Windows PowerShell
40
+ # 将 myclaw/index.js 路径添加到系统 PATH
41
+ ```
42
+
43
+ ## 使用方法
44
+
45
+ ### 查看状态
46
+ ```bash
47
+ myclaw status
48
+ ```
49
+ 简化版的 OpenClaw 状态面板,一眼看清 Gateway、Agents、Sessions 状态。
50
+
51
+ ### 创建新 Agent
52
+ ```bash
53
+ myclaw new <agent名称>
54
+ ```
55
+ 快速创建一个新的 OpenClaw Agent,自动完成:
56
+ - 创建 workspace 目录
57
+ - 配置 agent 文件
58
+ - 发送出生消息
59
+
60
+ 示例:
61
+ ```bash
62
+ myclaw new helper # 创建名为 helper 的 Agent
63
+ myclaw new testbot # 创建名为 testbot 的 Agent
64
+ ```
65
+
66
+ ### 安装 OpenClaw
67
+ ```bash
68
+ myclaw install
69
+ ```
70
+ 自动检测操作系统并执行对应的安装命令。
71
+
72
+ ### 帮助
73
+ ```bash
74
+ myclaw help
75
+ ```
76
+
77
+ ## 文件结构
78
+
79
+ ```
80
+ myclaw/
81
+ ├── index.js # 主入口,命令解析和输出
82
+ ├── create_agent.js # Agent 创建逻辑模块
83
+ └── README.md # 本文件
84
+ ```
85
+
86
+ ### 模块说明
87
+
88
+ | 文件 | 职责 |
89
+ |------|------|
90
+ | `index.js` | 主入口,解析命令、颜色输出、调用模块 |
91
+ | `create_agent.js` | Agent 创建的完整逻辑,可独立使用 |
92
+
93
+ ## 开发说明
94
+
95
+ ### 代码规范
96
+
97
+ 1. **跨平台兼容性** - 必须考虑 Windows 用户体验
98
+ - 使用 `os.platform()` 检测系统
99
+ - Windows 下禁用 ANSI 颜色输出
100
+ - 避免使用只在 Unix 系统有效的命令
101
+
102
+ 2. **错误处理**
103
+ - 所有 `execSync` 调用必须使用 `try-catch`
104
+ - 失败时提供清晰的错误信息和解决方案
105
+
106
+ 3. **模块化**
107
+ - 业务逻辑放入独立模块
108
+ - 主入口只做命令解析和输出
109
+
110
+ ### 测试清单
111
+
112
+ 发布前请在以下环境测试:
113
+ - [ ] macOS (Terminal)
114
+ - [ ] Linux (bash)
115
+ - [ ] Windows 10/11 (PowerShell)
116
+ - [ ] Windows 10/11 (CMD)
117
+ - [ ] Windows Terminal (如果可用)
118
+
119
+ ## 依赖
120
+
121
+ - Node.js 14+
122
+ - OpenClaw CLI (用于实际命令执行)
123
+
124
+ ## 许可证
125
+
126
+ 内部教学工具
@@ -0,0 +1,272 @@
1
+ /**
2
+ * ============================================================================
3
+ * MyClaw - Create Agent 模块
4
+ * ============================================================================
5
+ *
6
+ * 创建 OpenClaw Agent 的完整逻辑
7
+ *
8
+ * 功能:
9
+ * - 标准化 agent ID
10
+ * - 检查重复
11
+ * - 创建工作空间目录和默认文件
12
+ * - 更新 OpenClaw 配置文件
13
+ * - 发送出生消息
14
+ *
15
+ * ============================================================================
16
+ */
17
+
18
+ const { execSync } = require('child_process');
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const os = require('os');
22
+
23
+ // ============================================================================
24
+ // 路径工具
25
+ // ============================================================================
26
+
27
+ function normalizePath(p) {
28
+ return p.replace(/\\/g, '/');
29
+ }
30
+
31
+ function ensureDir(dirPath) {
32
+ if (!fs.existsSync(dirPath)) {
33
+ fs.mkdirSync(dirPath, { recursive: true });
34
+ }
35
+ }
36
+
37
+ function writeIfAbsent(filePath, content) {
38
+ if (!fs.existsSync(filePath)) {
39
+ fs.writeFileSync(filePath, content, 'utf8');
40
+ }
41
+ }
42
+
43
+ // ============================================================================
44
+ // 工具函数
45
+ // ============================================================================
46
+
47
+ function fail(msg, code = 1) {
48
+ console.error('[错误] ' + msg);
49
+ process.exit(code);
50
+ }
51
+
52
+ /**
53
+ * 标准化 agent ID
54
+ * - 转小写
55
+ * - 非法字符替换为连字符
56
+ * - 去除多余连字符
57
+ */
58
+ function normalizeAgentId(raw) {
59
+ let value = raw.trim().toLowerCase();
60
+ value = value.replace(/[^a-z0-9-]/g, '-');
61
+ value = value.replace(/-{2,}/g, '-').replace(/^-|-$/g, '');
62
+ return value;
63
+ }
64
+
65
+ /**
66
+ * 验证 agent ID
67
+ */
68
+ function validateAgentId(agentId) {
69
+ if (!agentId) {
70
+ fail('Agent 名称标准化后为空,请检查输入。');
71
+ }
72
+ if (agentId.length > 64) {
73
+ fail('Agent ID 太长,请保持在 64 个字符以内。');
74
+ }
75
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(agentId)) {
76
+ fail('Agent ID 只能包含小写字母、数字和连字符(不能以连字符开头或结尾)。');
77
+ }
78
+ }
79
+
80
+ /**
81
+ * 生成北京时间出生消息
82
+ */
83
+ function buildBirthMessage() {
84
+ const nowUtc = new Date();
85
+ const nowBj = new Date(nowUtc.getTime() + 8 * 60 * 60 * 1000);
86
+ const y = nowBj.getUTCFullYear();
87
+ const m = nowBj.getUTCMonth() + 1;
88
+ const d = nowBj.getUTCDate();
89
+ const h = String(nowBj.getUTCHours()).padStart(2, '0');
90
+ const min = String(nowBj.getUTCMinutes()).padStart(2, '0');
91
+ const ts = `${y}年${m}月${d}日${h}:${min}`;
92
+ return (
93
+ `你好,你的生日是北京时间${ts},请记录这个信息。\n` +
94
+ '欢迎你来到这个世界,我是你的造物主,我们是伙伴,我叫做孙依然,' +
95
+ '你可以叫我依然,请多多指教,共同成长。'
96
+ );
97
+ }
98
+
99
+ /**
100
+ * 默认工作空间文件内容
101
+ */
102
+ function defaultWorkspaceFiles(agentId) {
103
+ return {
104
+ 'AGENTS.md': (
105
+ `# AGENTS.md - ${agentId}\n\n` +
106
+ `This workspace belongs to the \`${agentId}\` agent.\n\n` +
107
+ '## Startup\n' +
108
+ '- Read `SOUL.md`\n' +
109
+ '- Read `USER.md`\n' +
110
+ '- Read recent memory if available\n\n' +
111
+ '## Rules\n' +
112
+ '- Be helpful, careful, and concise.\n' +
113
+ '- Prefer using the existing workspace and defaults.\n' +
114
+ '- Ask before destructive or external actions.\n'
115
+ ),
116
+ 'SOUL.md': (
117
+ `# SOUL.md\n\n` +
118
+ `You are \`${agentId}\`.\n\n` +
119
+ 'Be useful, calm, direct, and trustworthy.\n' +
120
+ 'Use the default OpenClaw behavior unless the user asks for something special.\n'
121
+ ),
122
+ 'USER.md': (
123
+ '# USER.md\n\n' +
124
+ '- Name: 孙依然\n' +
125
+ '- What to call them: 依然\n' +
126
+ '- Timezone: Asia/Shanghai\n\n' +
127
+ 'Build understanding gradually and respectfully.\n'
128
+ ),
129
+ 'IDENTITY.md': (
130
+ '# IDENTITY.md\n\n' +
131
+ `- Name: ${agentId}\n` +
132
+ '- Role: OpenClaw agent\n' +
133
+ '- Vibe: practical, reliable, clear\n'
134
+ ),
135
+ 'HEARTBEAT.md': (
136
+ '# HEARTBEAT.md\n\n' +
137
+ '# Keep empty unless periodic checks are needed.\n'
138
+ ),
139
+ 'BOOTSTRAP.md': (
140
+ `# BOOTSTRAP.md\n\n` +
141
+ `You are a newly created OpenClaw agent named \`${agentId}\`.\n\n` +
142
+ 'On first runs, learn your workspace files and begin helping.\n'
143
+ ),
144
+ };
145
+ }
146
+
147
+ // ============================================================================
148
+ // 主函数
149
+ // ============================================================================
150
+
151
+ /**
152
+ * 创建新的 OpenClaw Agent
153
+ *
154
+ * @param {string} rawName - 原始名称
155
+ * @returns {object} 创建结果
156
+ */
157
+ function createAgent(rawName) {
158
+ // 标准化 & 验证名称
159
+ const agentId = normalizeAgentId(rawName);
160
+ validateAgentId(agentId);
161
+
162
+ // 确定 OpenClaw 配置目录
163
+ const homeDir = os.homedir();
164
+ const openclawDir = path.join(homeDir, '.openclaw');
165
+ const configPath = path.join(openclawDir, 'openclaw.json');
166
+ const agentsDir = path.join(openclawDir, 'agents');
167
+ const workspaceDir = path.join(openclawDir, 'workspace-' + agentId);
168
+ const agentDir = path.join(agentsDir, agentId, 'agent');
169
+
170
+ // 检查配置是否存在
171
+ if (!fs.existsSync(configPath)) {
172
+ fail('配置文件不存在: ' + normalizePath(configPath) + '\n请先运行 openclaw onboard 完成初始化。');
173
+ }
174
+
175
+ // 加载配置
176
+ let configData;
177
+ try {
178
+ const raw = fs.readFileSync(configPath, 'utf8');
179
+ configData = JSON.parse(raw);
180
+ } catch (err) {
181
+ fail('配置文件读取失败: ' + err.message);
182
+ }
183
+
184
+ // 确保 agents 结构存在
185
+ if (!configData.agents) {
186
+ configData.agents = {};
187
+ }
188
+ if (!Array.isArray(configData.agents.list)) {
189
+ configData.agents.list = [];
190
+ }
191
+
192
+ // 检查重复 ID
193
+ const existingIds = new Set(
194
+ configData.agents.list
195
+ .filter(e => e && typeof e === 'object')
196
+ .map(e => e.id)
197
+ );
198
+ if (existingIds.has(agentId)) {
199
+ fail('Agent "' + agentId + '" 已存在,请使用其他名称。');
200
+ }
201
+
202
+ // 检查目录冲突
203
+ if (fs.existsSync(workspaceDir) || fs.existsSync(agentDir)) {
204
+ fail(
205
+ '工作空间或 Agent 目录已存在,请检查是否已有同名 Agent。\n' +
206
+ 'workspace=' + normalizePath(workspaceDir) + '\n' +
207
+ 'agentDir=' + normalizePath(agentDir)
208
+ );
209
+ }
210
+
211
+ // 创建目录结构
212
+ ensureDir(workspaceDir);
213
+ ensureDir(agentDir);
214
+ ensureDir(path.join(agentsDir, agentId, 'sessions'));
215
+
216
+ // 创建默认工作空间文件
217
+ const files = defaultWorkspaceFiles(agentId);
218
+ for (const [filename, content] of Object.entries(files)) {
219
+ writeIfAbsent(path.join(workspaceDir, filename), content);
220
+ }
221
+
222
+ // 备份配置
223
+ const backupPath = configPath + '.bak.agent-birth.' +
224
+ new Date().toISOString().replace(/[:.]/g, '-').slice(0, 26) + 'Z';
225
+ fs.copyFileSync(configPath, backupPath);
226
+
227
+ // 添加 Agent 条目
228
+ configData.agents.list.push({
229
+ id: agentId,
230
+ name: agentId,
231
+ workspace: workspaceDir,
232
+ agentDir: agentDir,
233
+ });
234
+
235
+ // 写入配置
236
+ try {
237
+ fs.writeFileSync(configPath, JSON.stringify(configData, null, 2) + '\n', 'utf8');
238
+ } catch (err) {
239
+ fail('配置文件写入失败: ' + err.message + '\n备份文件: ' + normalizePath(backupPath));
240
+ }
241
+
242
+ // 发送出生消息
243
+ const birthMessage = buildBirthMessage();
244
+ let firstMessageSent = false;
245
+ let firstMessageError = null;
246
+
247
+ try {
248
+ execSync(
249
+ 'openclaw agent --agent ' + agentId + ' --message ' + JSON.stringify(birthMessage) + ' --json 2>&1',
250
+ { encoding: 'utf8', timeout: 30000 }
251
+ );
252
+ firstMessageSent = true;
253
+ } catch (err) {
254
+ firstMessageSent = false;
255
+ firstMessageError = err.message;
256
+ }
257
+
258
+ // 返回结果
259
+ return {
260
+ ok: true,
261
+ agentId: agentId,
262
+ sessionKey: 'agent:' + agentId + ':main',
263
+ workspace: normalizePath(workspaceDir),
264
+ agentDir: normalizePath(agentDir),
265
+ configBackup: normalizePath(backupPath),
266
+ birthMessage: birthMessage,
267
+ firstMessageSent: firstMessageSent,
268
+ firstMessageError: firstMessageError,
269
+ };
270
+ }
271
+
272
+ module.exports = { createAgent, normalizeAgentId, validateAgentId };
package/index.js CHANGED
@@ -25,6 +25,7 @@
25
25
 
26
26
  const { execSync } = require('child_process');
27
27
  const os = require('os');
28
+ const { createAgent } = require('./create_agent');
28
29
 
29
30
  const args = process.argv.slice(2);
30
31
  const command = args[0];
@@ -46,22 +47,10 @@ const isWindows = detectPlatform() === 'windows';
46
47
  // 跨平台颜色输出
47
48
  // ============================================================================
48
49
 
49
- /**
50
- * 颜色输出函数 - 自动兼容 Windows
51
- * Windows 旧版本会 stripped ANSI 颜色代码,避免乱码
52
- */
53
50
  function makeColors() {
54
51
  if (isWindows) {
55
- // Windows: 禁用颜色,strip ANSI codes
56
- return {
57
- red: '',
58
- green: '',
59
- yellow: '',
60
- blue: '',
61
- nc: ''
62
- };
52
+ return { red: '', green: '', yellow: '', blue: '', nc: '' };
63
53
  }
64
- // macOS/Linux: 正常 ANSI 颜色
65
54
  return {
66
55
  red: '\x1b[31m',
67
56
  green: '\x1b[32m',
@@ -189,86 +178,71 @@ function runStatus() {
189
178
  // ============================================================================
190
179
 
191
180
  function runNew() {
192
- const agentName = args[1];
181
+ const rawName = args[1];
193
182
 
194
- if (!agentName) {
183
+ if (!rawName) {
195
184
  console.error('[' + colors.red + '错误' + colors.nc + '] 请提供 Agent 名称');
196
185
  console.log('');
197
186
  console.log('用法: myclaw new <agent名称>');
198
187
  console.log('');
199
188
  console.log('示例:');
200
189
  console.log(' myclaw new helper # 创建名为 helper 的 Agent');
201
- console.log(' myclaw new testbot # 创建名为 testbot 的 Agent');
202
- process.exit(1);
203
- }
204
-
205
- // 验证名称格式
206
- if (!/^[a-z0-9-]+$/.test(agentName)) {
207
- console.error('[' + colors.red + '错误' + colors.nc + '] Agent 名称只能包含小写字母、数字和连字符(-)');
208
- console.log('');
209
- console.log('示例:');
210
- console.log(' myclaw new helper');
211
- console.log(' myclaw new my-bot');
190
+ console.log(' myclaw new testbot # 创建名为 testbot 的 Agent');
212
191
  process.exit(1);
213
192
  }
214
193
 
215
194
  const bar = '----------------------------------------';
216
195
 
217
196
  console.log('');
218
- console.log('[' + colors.blue + 'MyClaw' + colors.nc + '] ' + colors.blue + '创建新 Agent: ' + agentName + colors.nc);
197
+ console.log('[' + colors.blue + 'MyClaw' + colors.nc + '] ' + colors.blue + '创建新 Agent' + colors.nc);
219
198
  console.log(bar);
220
199
  console.log('');
221
-
222
- // 检查是否已存在
223
- try {
224
- const agentsOutput = execSync('openclaw agents list 2>/dev/null', { encoding: 'utf8' });
225
- if (agentsOutput.includes(`agent:${agentName}:`)) {
226
- console.error('[' + colors.red + '错误' + colors.nc + '] Agent "' + agentName + '" 已存在');
227
- process.exit(1);
228
- }
229
- } catch (e) {
230
- // 继续创建
231
- }
232
-
233
- // 查找 yiran-agent-birth 脚本
234
- const birthScript = '/Users/yiran/.openclaw/workspace/skills/yiran-agent-birth/scripts/create_agent.py';
235
-
236
200
  console.log('[创建中] 正在创建 Agent...');
237
-
201
+ console.log('');
202
+
238
203
  try {
239
- const result = execSync(`python3 "${birthScript}" ${agentName}`, { encoding: 'utf8' });
240
- const jsonResult = JSON.parse(result);
204
+ const result = createAgent(rawName);
241
205
 
242
- console.log('');
243
206
  console.log('[' + colors.green + '成功' + colors.nc + '] Agent 创建成功!');
244
207
  console.log('');
245
- console.log(' 名称: ' + agentName);
246
- console.log(' Session: ' + jsonResult.sessionKey);
247
- console.log(' Workspace: ' + jsonResult.workspace);
208
+ console.log(' 名称: ' + result.agentId);
209
+ console.log(' Session: ' + result.sessionKey);
210
+ console.log(' Workspace: ' + result.workspace);
211
+ console.log(' 备份: ' + result.configBackup);
248
212
  console.log('');
249
213
 
250
- if (jsonResult.firstMessageSent) {
214
+ if (result.firstMessageSent) {
251
215
  console.log('[' + colors.green + 'OK' + colors.nc + '] 出生消息已发送');
252
216
  } else {
253
- console.log('[' + colors.yellow + '警告' + colors.nc + '] 出生消息发送失败,可手动重试');
217
+ console.log('[' + colors.yellow + '警告' + colors.nc + '] 出生消息发送失败');
218
+ if (result.firstMessageError) {
219
+ console.log(' 错误: ' + result.firstMessageError);
220
+ }
221
+ console.log(' 手动重试: ' + colors.yellow + 'openclaw agent --agent ' + result.agentId + ' --message "你好"' + colors.nc);
254
222
  }
255
223
 
256
224
  console.log('');
257
225
  console.log(bar);
258
- console.log('下一步: 运行 ' + colors.yellow + `openclaw agent --agent ${agentName} --message "你好"` + colors.nc + ' 与它对话');
226
+ console.log('下一步: 运行 ' + colors.yellow + 'openclaw agent --agent ' + result.agentId + ' --message "你好"' + colors.nc + ' 与它对话');
259
227
  console.log('');
260
228
 
261
229
  } catch (err) {
262
- console.error('[' + colors.red + '错误' + colors.nc + '] 创建失败:', err.message);
263
230
  console.log('');
264
- console.log('常见问题:');
265
- console.log(' - Agent 名称已存在');
266
- console.log(' - 权限不足');
267
- console.log(' - OpenClaw 未运行');
231
+ console.log('[' + colors.red + '失败' + colors.nc + '] Agent 创建失败');
232
+ console.log('');
268
233
  process.exit(1);
269
234
  }
270
235
  }
271
236
 
237
+ // ============================================================================
238
+ // WSL2 安装 (独立模块)
239
+ // ============================================================================
240
+
241
+ function runWsl2() {
242
+ const wsl2 = require('./wsl2');
243
+ wsl2.run();
244
+ }
245
+
272
246
  // ============================================================================
273
247
  // 帮助信息
274
248
  // ============================================================================
@@ -285,12 +259,14 @@ function showHelp() {
285
259
  console.log(' install 安装 OpenClaw 服务');
286
260
  console.log(' status 简化版状态查看(学生友好)');
287
261
  console.log(' new 创建新的 Agent(学生练习用)');
262
+ console.log(' wsl2 WSL2 一键安装/修复 (仅限 Windows)');
288
263
  console.log(' help 显示帮助信息');
289
264
  console.log('');
290
265
  console.log('示例:');
291
266
  console.log(' myclaw install # 安装 OpenClaw');
292
267
  console.log(' myclaw status # 查看状态');
293
268
  console.log(' myclaw new helper # 创建名为 helper 的 Agent');
269
+ console.log(' myclaw wsl2 # Windows 下一键安装 WSL2');
294
270
  console.log('');
295
271
  console.log('跨平台: macOS / Linux / Windows (PowerShell/CMD)');
296
272
  console.log('');
@@ -308,6 +284,8 @@ if (!command || command === 'help' || command === '--help' || command === '-h')
308
284
  runStatus();
309
285
  } else if (command === 'new') {
310
286
  runNew();
287
+ } else if (command === 'wsl2') {
288
+ runWsl2();
311
289
  } else {
312
290
  console.error('[' + colors.red + '错误' + colors.nc + '] 未知命令: ' + command);
313
291
  showHelp();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
package/publish.sh ADDED
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+ set -e # 遇到错误即停止
3
+
4
+ echo "📦 准备发布新版本..."
5
+
6
+ # 1. 添加所有变动
7
+ git add .
8
+
9
+ # 2. 检查是否有未提交的代码,如果有则先提交
10
+ if ! git diff-index --quiet HEAD; then
11
+ echo "📝 发现有修改的内容,正在自动提交..."
12
+ git commit -m "chore: auto update before publish"
13
+ else
14
+ echo "✅ 工作区很干净,不需要额外提交代码。"
15
+ fi
16
+
17
+ # 3. 自动更新最小版本号 (npm version patch 会自动再做一个改 package.json 的 commit 并且打上 Tag)
18
+ echo "📈 增加 Patch 版本号..."
19
+ npm version patch
20
+
21
+ # 4. 推送代码和 Tag 到 GitHub
22
+ echo "☁️ 推送代码和版本 Tag 到 GitHub..."
23
+ git push origin main
24
+ git push origin --tags
25
+
26
+ # 5. 发布到 npm
27
+ echo "🚀 发布到 npm 仓库..."
28
+ npm publish
29
+
30
+ echo "🎉 发布流执行完成!"
package/wsl2.js ADDED
@@ -0,0 +1,392 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ============================================================================
5
+ * MyClaw WSL2 一键安装器 (终极全自动版)
6
+ * ============================================================================
7
+ *
8
+ * 使用方式: myclaw wsl2
9
+ *
10
+ * 自动检测当前 WSL2 安装状态,分两个阶段完成安装:
11
+ * Phase 1: 启用底层 Windows 功能 + 预下载安装包 → 需要重启
12
+ * Phase 2: 安装内核更新 + Ubuntu → 完成
13
+ *
14
+ * 用户只需反复运行 myclaw wsl2,脚本会自动接续上次的进度。
15
+ * ============================================================================
16
+ */
17
+
18
+ const { execSync } = require('child_process');
19
+ const os = require('os');
20
+
21
+ // ============================================================================
22
+ // 配置
23
+ // ============================================================================
24
+
25
+ // 下载源(可替换为你自己的 CDN 地址加速国内下载)
26
+ const WSL_CDN = {
27
+ kernel: 'https://cdn.yiranlaoshi.com/software/myclaw/wsl_update_x64.msi',
28
+ // ★ 预制 rootfs tar 包(内含 Node.js + OpenClaw,用户开箱即用)
29
+ // TODO: 替换为你自己的 CDN 地址,制作方法见 README
30
+ rootfs: 'https://openclaw.ai/wsl/openclaw-rootfs.tar',
31
+ // 降级备选:微软官方 Ubuntu Appx 包
32
+ ubuntu: 'https://aka.ms/wslubuntu2204',
33
+ };
34
+
35
+ // ★ 是否启用降级方案(方法 B: wsl --install / 方法 C: Appx 离线包)
36
+ // 如果你的 CDN 稳定可靠,设为 false 只用方法 A(tar 导入)
37
+ const ENABLE_FALLBACK = false;
38
+
39
+ const isWindows = os.platform() === 'win32';
40
+
41
+ // 颜色(Windows 下禁用避免乱码)
42
+ const C = isWindows
43
+ ? { r: '', g: '', y: '', b: '', nc: '' }
44
+ : { r: '\x1b[31m', g: '\x1b[32m', y: '\x1b[33m', b: '\x1b[34m', nc: '\x1b[0m' };
45
+
46
+ // ============================================================================
47
+ // 工具函数
48
+ // ============================================================================
49
+
50
+ /**
51
+ * 以管理员权限启动 PowerShell 脚本
52
+ * 利用 UTF-16LE Base64 编码避免引号转义地狱
53
+ */
54
+ function launchElevatedPS(script) {
55
+ const b64 = Buffer.from(script, 'utf16le').toString('base64');
56
+ try {
57
+ execSync(
58
+ `powershell -Command "Start-Process powershell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -EncodedCommand ${b64}' -Verb RunAs"`,
59
+ { stdio: 'inherit' }
60
+ );
61
+ return true;
62
+ } catch (err) {
63
+ console.error('[' + C.r + '错误' + C.nc + '] 无法获取管理员权限: ' + err.message);
64
+ console.log('建议右键终端选择【以管理员身份运行】后重试。');
65
+ return false;
66
+ }
67
+ }
68
+
69
+ // ============================================================================
70
+ // 状态检测
71
+ // ============================================================================
72
+
73
+ /**
74
+ * 自动检测当前 WSL2 安装状态
75
+ * @returns {'ready'|'needs-setup'|'needs-features'}
76
+ */
77
+ function detectState() {
78
+ // 1. WSL 已完全可用(有发行版能跑命令)
79
+ try {
80
+ execSync('wsl echo ok', { stdio: 'pipe', timeout: 15000 });
81
+ return 'ready';
82
+ } catch {}
83
+
84
+ // 2. WSL 命令可用但没有发行版(功能已启用,重启后的状态)
85
+ try {
86
+ execSync('wsl --status', { stdio: 'pipe', timeout: 5000 });
87
+ return 'needs-setup';
88
+ } catch {}
89
+
90
+ // 3. WSL 功能未启用
91
+ return 'needs-features';
92
+ }
93
+
94
+ // ============================================================================
95
+ // Phase 1: 启用底层功能 + 预下载
96
+ // ============================================================================
97
+
98
+ function runPhase1() {
99
+ console.log('');
100
+ console.log('[当前进度] WSL 功能 ' + C.r + '[未启用]' + C.nc);
101
+ console.log(' 内核更新 ' + C.r + '[未安装]' + C.nc);
102
+ console.log(' Ubuntu ' + C.r + '[未安装]' + C.nc);
103
+ console.log('');
104
+ console.log('本次执行 阶段 1/2: 启用底层功能 + 预下载安装包');
105
+ console.log(' 完成后需要 ' + C.y + '重启电脑' + C.nc);
106
+ console.log(' 重启后再运行 ' + C.y + 'myclaw wsl2' + C.nc + ' 自动进入阶段 2');
107
+ console.log('');
108
+ console.log('[' + C.y + '注意' + C.nc + '] 请在 UAC 弹窗中点击【是】');
109
+ console.log('');
110
+
111
+ const ps = `
112
+ $ErrorActionPreference = 'Continue'
113
+ $Host.UI.RawUI.WindowTitle = 'MyClaw WSL2 Installer - Phase 1'
114
+ Write-Host ''
115
+ Write-Host '========================================'
116
+ Write-Host ' MyClaw WSL2 安装 - 阶段 1/2'
117
+ Write-Host ' 启用底层功能 + 预下载安装包'
118
+ Write-Host '========================================'
119
+ Write-Host ''
120
+
121
+ Write-Host '[1/4] 启用 Windows Subsystem for Linux...'
122
+ try {
123
+ dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart | Out-Null
124
+ Write-Host ' [OK]'
125
+ } catch {
126
+ Write-Host ' 跳过 (可能已启用)'
127
+ }
128
+ Write-Host ''
129
+
130
+ Write-Host '[2/4] 启用虚拟机平台 (VirtualMachinePlatform)...'
131
+ try {
132
+ dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart | Out-Null
133
+ Write-Host ' [OK]'
134
+ } catch {
135
+ Write-Host ' 跳过 (可能已启用)'
136
+ }
137
+ Write-Host ''
138
+
139
+ Write-Host '[3/4] 预下载 WSL2 内核更新包...'
140
+ $dir = "$env:LOCALAPPDATA\\myclaw"
141
+ $msi = "$dir\\wsl_update_x64.msi"
142
+ New-Item -ItemType Directory -Force -Path $dir | Out-Null
143
+ try {
144
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
145
+ Invoke-WebRequest -Uri '${WSL_CDN.kernel}' -OutFile $msi -UseBasicParsing
146
+ Write-Host ' [OK] 已缓存'
147
+ } catch {
148
+ Write-Host ' [跳过] 下载失败, 重启后将重试'
149
+ }
150
+ Write-Host ''
151
+
152
+ Write-Host '[4/4] 尝试提前安装内核更新...'
153
+ if (Test-Path $msi) {
154
+ try {
155
+ Start-Process msiexec.exe -ArgumentList "/i \`"$msi\`" /quiet /norestart" -Wait -NoNewWindow
156
+ Write-Host ' [OK]'
157
+ } catch {
158
+ Write-Host ' [跳过] 重启后将自动完成'
159
+ }
160
+ } else {
161
+ Write-Host ' [跳过] 文件未就绪'
162
+ }
163
+
164
+ Write-Host ''
165
+ Write-Host '========================================'
166
+ Write-Host ' 阶段 1/2 完成!'
167
+ Write-Host ''
168
+ Write-Host ' >>> 请立即 [重启电脑] <<<'
169
+ Write-Host ''
170
+ Write-Host ' 重启后运行: myclaw wsl2'
171
+ Write-Host ' 将自动进入阶段 2 完成安装'
172
+ Write-Host '========================================'
173
+ Write-Host ''
174
+ Write-Host '按任意键关闭此窗口...'
175
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
176
+ `;
177
+
178
+ if (launchElevatedPS(ps)) {
179
+ console.log('[' + C.g + '已启动' + C.nc + '] 请查看新弹出的蓝色窗口');
180
+ console.log('');
181
+ console.log('执行完请 ' + C.y + '重启电脑' + C.nc + ',然后运行 ' + C.y + 'myclaw wsl2' + C.nc + ' 续装');
182
+ console.log('');
183
+ }
184
+ }
185
+
186
+ // ============================================================================
187
+ // Phase 2: 安装内核 + Ubuntu
188
+ // ============================================================================
189
+
190
+ function runPhase2() {
191
+ console.log('');
192
+ console.log('[当前进度] WSL 功能已启用 ' + C.g + '[OK]' + C.nc);
193
+ console.log(' 内核更新 + Ubuntu ' + C.y + '[待安装]' + C.nc);
194
+ console.log('');
195
+ console.log('即将自动下载并安装(需要管理员权限)...');
196
+ console.log('[' + C.y + '注意' + C.nc + '] 请在 UAC 弹窗中点击【是】');
197
+ console.log('');
198
+
199
+ const ps = `
200
+ $ErrorActionPreference = 'Continue'
201
+ $Host.UI.RawUI.WindowTitle = 'MyClaw WSL2 Installer - Phase 2'
202
+ Write-Host ''
203
+ Write-Host '========================================'
204
+ Write-Host ' MyClaw WSL2 安装 - 阶段 2/2'
205
+ Write-Host ' 安装内核更新 + Ubuntu'
206
+ Write-Host '========================================'
207
+ Write-Host ''
208
+
209
+ Write-Host '[1/5] 设置 WSL2 为默认版本...'
210
+ try { wsl --set-default-version 2 2>$null } catch {}
211
+ Write-Host ' [OK]'
212
+ Write-Host ''
213
+
214
+ Write-Host '[2/5] 安装 WSL2 内核更新包...'
215
+ $dir = "$env:LOCALAPPDATA\\myclaw"
216
+ $msi = "$dir\\wsl_update_x64.msi"
217
+ New-Item -ItemType Directory -Force -Path $dir | Out-Null
218
+
219
+ if (-Not (Test-Path $msi)) {
220
+ Write-Host ' 正在从微软服务器下载...'
221
+ try {
222
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
223
+ Invoke-WebRequest -Uri '${WSL_CDN.kernel}' -OutFile $msi -UseBasicParsing
224
+ Write-Host ' 下载完成!'
225
+ } catch {
226
+ Write-Host ' [失败] 下载失败,请检查网络'
227
+ Write-Host ' 手动下载: https://aka.ms/wsl2kernel'
228
+ Write-Host ''; Write-Host '按任意键退出...'
229
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
230
+ exit 1
231
+ }
232
+ } else {
233
+ Write-Host ' 使用上次预下载的缓存文件'
234
+ }
235
+ try {
236
+ Start-Process msiexec.exe -ArgumentList "/i \`"$msi\`" /quiet /norestart" -Wait -NoNewWindow
237
+ Write-Host ' [OK] 内核更新安装成功!'
238
+ } catch {
239
+ Write-Host ' 可能已安装,继续...'
240
+ }
241
+ Write-Host ''
242
+
243
+ Write-Host '[3/5] 确认 WSL2 为默认版本...'
244
+ try { wsl --set-default-version 2 2>$null } catch {}
245
+ Write-Host ' [OK]'
246
+ Write-Host ''
247
+
248
+ Write-Host '[4/5] 安装 Linux 发行版...'
249
+ $installed = $false
250
+ $distroName = 'OpenClaw'
251
+ $installDir = "$dir\\OpenClaw"
252
+
253
+ # ---- 方法 A (最强): 预制 rootfs tar 包,wsl --import ----
254
+ Write-Host ' 方法 A: 导入 OpenClaw 预制 Linux 环境...'
255
+ $tarPath = "$dir\\openclaw-rootfs.tar"
256
+ if (-Not (Test-Path $tarPath)) {
257
+ Write-Host ' 正在下载预制环境包...'
258
+ try {
259
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
260
+ Invoke-WebRequest -Uri '` + WSL_CDN.rootfs + `' -OutFile $tarPath -UseBasicParsing
261
+ Write-Host ' 下载完成!'
262
+ } catch {
263
+ Write-Host ' 下载失败, 尝试备选方案...'
264
+ }
265
+ }
266
+ if (Test-Path $tarPath) {
267
+ try {
268
+ New-Item -ItemType Directory -Force -Path $installDir | Out-Null
269
+ wsl --import $distroName $installDir $tarPath 2>$null
270
+ if ($LASTEXITCODE -eq 0) {
271
+ $installed = $true
272
+ Write-Host ' [OK] 方法 A 成功! (预制环境已导入)'
273
+ }
274
+ } catch {}
275
+ }
276
+
277
+ # ---- 方法 B / C (降级备选,由 ENABLE_FALLBACK 控制) ----
278
+ ` + (ENABLE_FALLBACK ? `
279
+ if (-Not $installed) {
280
+ Write-Host ' 方法 B: wsl --install ...'
281
+ try {
282
+ $output = wsl --install -d Ubuntu --no-launch 2>&1
283
+ if ($LASTEXITCODE -eq 0) {
284
+ $installed = $true
285
+ Write-Host ' [OK] 方法 B 成功!'
286
+ }
287
+ } catch {}
288
+ }
289
+
290
+ if (-Not $installed) {
291
+ Write-Host ' 方法 C: 离线 Appx 安装...'
292
+ $appx = "$dir\\Ubuntu.appx"
293
+ if (-Not (Test-Path $appx)) {
294
+ Write-Host ' 正在下载 Ubuntu (约 500MB)...'
295
+ try {
296
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
297
+ Invoke-WebRequest -Uri '` + WSL_CDN.ubuntu + `' -OutFile $appx -UseBasicParsing
298
+ } catch {
299
+ Write-Host ' [失败] 下载失败'
300
+ }
301
+ }
302
+ if (Test-Path $appx) {
303
+ try {
304
+ Add-AppxPackage -Path $appx
305
+ $installed = $true
306
+ Write-Host ' [OK] 方法 C 成功!'
307
+ } catch {}
308
+ }
309
+ }
310
+ ` : `
311
+ if (-Not $installed) {
312
+ Write-Host ' [失败] 预制环境包安装失败'
313
+ Write-Host ' 请检查网络后重试: myclaw wsl2'
314
+ }
315
+ `) + `
316
+ Write-Host ''
317
+
318
+ Write-Host '[5/5] 验证安装结果...'
319
+ try {
320
+ $result = wsl -l -v 2>&1 | Out-String
321
+ Write-Host $result
322
+ } catch {
323
+ Write-Host ' 无法获取信息'
324
+ }
325
+
326
+ Write-Host ''
327
+ Write-Host '========================================'
328
+ if ($installed) {
329
+ Write-Host ' [OK] WSL2 + Linux 环境安装完成!'
330
+ Write-Host ''
331
+ Write-Host ' 输入 wsl 即可进入 Linux 环境'
332
+ Write-Host ' 如使用预制包, OpenClaw 已预装就绪'
333
+ } else {
334
+ Write-Host ' [注意] Linux 发行版可能需要手动安装'
335
+ Write-Host ' 请打开 Microsoft Store 搜索 Ubuntu'
336
+ }
337
+ Write-Host '========================================'
338
+ Write-Host ''
339
+ Write-Host '按任意键关闭此窗口...'
340
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
341
+ `;
342
+
343
+ if (launchElevatedPS(ps)) {
344
+ console.log('[' + C.g + '已启动' + C.nc + '] 请查看新弹出的蓝色窗口');
345
+ console.log('');
346
+ console.log('安装完成后,输入 ' + C.y + 'wsl' + C.nc + ' 即可进入 Linux');
347
+ console.log('可随时运行 ' + C.y + 'myclaw wsl2' + C.nc + ' 检查状态');
348
+ console.log('');
349
+ }
350
+ }
351
+
352
+ // ============================================================================
353
+ // 主入口
354
+ // ============================================================================
355
+
356
+ function run() {
357
+ if (!isWindows) {
358
+ console.error('[提示] 本命令仅用于 Windows 系统。Mac/Linux 不需要 WSL2。');
359
+ process.exit(0);
360
+ }
361
+
362
+ const bar = '========================================';
363
+ const state = detectState();
364
+
365
+ console.log('');
366
+ console.log('[' + C.b + 'MyClaw' + C.nc + '] WSL2 一键安装向导');
367
+ console.log(bar);
368
+
369
+ if (state === 'ready') {
370
+ console.log('');
371
+ console.log('[' + C.g + 'OK' + C.nc + '] WSL2 已完全安装就绪!');
372
+ console.log('');
373
+ try {
374
+ const info = execSync('wsl -l -v', { stdio: 'pipe' }).toString('utf16le');
375
+ console.log(info.trim());
376
+ } catch {
377
+ console.log('(运行 wsl -l -v 查看详情)');
378
+ }
379
+ console.log('');
380
+ console.log('输入 ' + C.y + 'wsl' + C.nc + ' 即可进入 Linux 环境。');
381
+ console.log('');
382
+ return;
383
+ }
384
+
385
+ if (state === 'needs-setup') {
386
+ runPhase2();
387
+ } else {
388
+ runPhase1();
389
+ }
390
+ }
391
+
392
+ module.exports = { run };