@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 +126 -0
- package/create_agent.js +272 -0
- package/index.js +35 -57
- package/package.json +1 -1
- package/publish.sh +30 -0
- package/wsl2.js +392 -0
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
|
+
内部教学工具
|
package/create_agent.js
ADDED
|
@@ -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
|
-
|
|
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
|
|
181
|
+
const rawName = args[1];
|
|
193
182
|
|
|
194
|
-
if (!
|
|
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
|
|
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
|
|
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 =
|
|
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(' 名称:
|
|
246
|
-
console.log(' Session:
|
|
247
|
-
console.log(' 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 (
|
|
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 +
|
|
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('
|
|
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
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 };
|