@aiyiran/myclaw 1.0.24 → 1.0.26

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.
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ============================================================================
3
+ * MyClaw UI Inject — 浏览器端注入脚本
4
+ * ============================================================================
5
+ *
6
+ * 该脚本由 myclaw postinstall 自动注入到 OpenClaw Control UI 的 index.html 中。
7
+ * 每次 npm update @aiyiran/myclaw 时会自动更新。
8
+ *
9
+ * 功能:
10
+ * 1. 页面顶部 fixed 显示 myclaw 版本号
11
+ * 2. 语音输入按钮占位(后续接入)
12
+ * ============================================================================
13
+ */
14
+ (function () {
15
+ "use strict";
16
+
17
+ // ═══ 版本号(由 patch.js 在注入时替换) ═══
18
+ var MYCLAW_VERSION = "__MYCLAW_VERSION__";
19
+
20
+ // ═══ 1. 顶部版本号横条 ═══
21
+ function createVersionBar() {
22
+ if (document.querySelector("#myclaw-version-bar")) return;
23
+
24
+ var bar = document.createElement("div");
25
+ bar.id = "myclaw-version-bar";
26
+ bar.style.cssText = [
27
+ "position: fixed",
28
+ "top: 0",
29
+ "left: 0",
30
+ "right: 0",
31
+ "height: 28px",
32
+ "line-height: 28px",
33
+ "background: linear-gradient(90deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)",
34
+ "color: #e94560",
35
+ "font-size: 12px",
36
+ "font-family: monospace",
37
+ "text-align: center",
38
+ "z-index: 99999",
39
+ "box-shadow: 0 1px 4px rgba(0,0,0,0.3)",
40
+ "user-select: none",
41
+ "letter-spacing: 0.5px",
42
+ ].join(";");
43
+ bar.textContent = "🐾 MyClaw v" + MYCLAW_VERSION;
44
+
45
+ document.body.prepend(bar);
46
+
47
+ // 把页面整体往下推 28px,避免遮挡
48
+ document.body.style.paddingTop = "28px";
49
+
50
+ console.log("[myclaw-inject] ✅ 版本号横条已注入: v" + MYCLAW_VERSION);
51
+ }
52
+
53
+ // ═══ 2. 语音输入按钮占位(TODO: 后续接入) ═══
54
+ function initVoice() {
55
+ console.log("[myclaw-inject] 🎙️ 语音模块已加载(占位),等待后续接入...");
56
+ console.log("[myclaw-inject] 语音注入点就绪,后续在此处添加按钮和录音逻辑");
57
+ }
58
+
59
+ // ═══ 启动 ═══
60
+ function init() {
61
+ createVersionBar();
62
+ initVoice();
63
+ }
64
+
65
+ // 确保 DOM 就绪
66
+ if (document.readyState === "loading") {
67
+ document.addEventListener("DOMContentLoaded", init);
68
+ } else {
69
+ init();
70
+ }
71
+ })();
package/index.js CHANGED
@@ -284,6 +284,93 @@ pause
284
284
  }
285
285
  }
286
286
 
287
+ // ============================================================================
288
+ // Patch / Unpatch / Restart
289
+ // ============================================================================
290
+
291
+ function runPatch() {
292
+ const { patch, status: patchStatus } = require('./patch');
293
+ const bar = '----------------------------------------';
294
+
295
+ console.log('');
296
+ console.log('[' + colors.blue + 'MyClaw' + colors.nc + '] ' + colors.blue + 'UI 注入' + colors.nc);
297
+ console.log(bar);
298
+ console.log('');
299
+
300
+ const result = patch();
301
+
302
+ if (result.success) {
303
+ console.log('');
304
+ console.log(bar);
305
+ console.log('下一步: 运行 ' + colors.yellow + 'myclaw restart' + colors.nc + ' 重启 Gateway 使注入生效');
306
+ console.log('');
307
+ }
308
+ }
309
+
310
+ function runUnpatch() {
311
+ const { unpatch } = require('./patch');
312
+ const bar = '----------------------------------------';
313
+
314
+ console.log('');
315
+ console.log('[' + colors.blue + 'MyClaw' + colors.nc + '] ' + colors.blue + 'UI 回滚' + colors.nc);
316
+ console.log(bar);
317
+ console.log('');
318
+
319
+ unpatch();
320
+
321
+ console.log('');
322
+ console.log(bar);
323
+ console.log('下一步: 运行 ' + colors.yellow + 'myclaw restart' + colors.nc + ' 重启 Gateway');
324
+ console.log('');
325
+ }
326
+
327
+ function runRestart() {
328
+ const bar = '----------------------------------------';
329
+
330
+ console.log('');
331
+ console.log('[' + colors.blue + 'MyClaw' + colors.nc + '] ' + colors.blue + '重启 Gateway' + colors.nc);
332
+ console.log(bar);
333
+ console.log('');
334
+
335
+ try {
336
+ console.log('[停止] 正在停止 Gateway...');
337
+ try {
338
+ execSync('openclaw gateway stop', { stdio: 'pipe', timeout: 10000 });
339
+ console.log('[停止] ' + colors.green + 'Gateway 已停止' + colors.nc);
340
+ } catch {
341
+ console.log('[停止] Gateway 未在运行或已停止');
342
+ }
343
+
344
+ console.log('');
345
+ console.log('[启动] 正在启动 Gateway...');
346
+ // 使用 nohup + 后台启动,避免阻塞当前进程
347
+ execSync('nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &', {
348
+ stdio: 'ignore',
349
+ shell: true,
350
+ });
351
+
352
+ // 等待 2 秒让 Gateway 启动
353
+ execSync('sleep 2', { stdio: 'ignore' });
354
+
355
+ // 检查是否启动成功
356
+ try {
357
+ execSync('openclaw health', { stdio: 'pipe', timeout: 5000 });
358
+ console.log('[启动] ' + colors.green + 'Gateway 启动成功!' + colors.nc);
359
+ console.log('');
360
+ console.log('控制台: ' + colors.yellow + 'http://127.0.0.1:18789' + colors.nc);
361
+ } catch {
362
+ console.log('[启动] ' + colors.yellow + 'Gateway 正在启动中...' + colors.nc);
363
+ console.log('日志: ' + colors.yellow + 'tail -f /tmp/openclaw-gateway.log' + colors.nc);
364
+ }
365
+ } catch (err) {
366
+ console.error('[' + colors.red + '错误' + colors.nc + '] 重启失败: ' + err.message);
367
+ }
368
+
369
+ console.log('');
370
+ console.log(bar);
371
+ console.log('');
372
+ }
373
+
287
374
  // ============================================================================
288
375
  // 帮助信息
289
376
  // ============================================================================
@@ -302,14 +389,17 @@ function showHelp() {
302
389
  console.log(' new 创建新的 Agent(学生练习用)');
303
390
  console.log(' wsl2 WSL2 一键安装/修复 (仅限 Windows)');
304
391
  console.log(' bat 在桌面生成一键启动脚本 (仅限 Windows)');
392
+ console.log(' patch 注入 MyClaw UI 扩展到 WebChat');
393
+ console.log(' unpatch 回滚 UI 注入(恢复原版)');
394
+ console.log(' restart 重启 OpenClaw Gateway');
305
395
  console.log(' help 显示帮助信息');
306
396
  console.log('');
307
397
  console.log('示例:');
308
398
  console.log(' myclaw install # 安装 OpenClaw');
309
399
  console.log(' myclaw status # 查看状态');
310
400
  console.log(' myclaw new helper # 创建名为 helper 的 Agent');
311
- console.log(' myclaw wsl2 # Windows 下一键安装 WSL2');
312
- console.log(' myclaw bat # 生成桌面一键启动脚本');
401
+ console.log(' myclaw patch # 注入 UI 扩展');
402
+ console.log(' myclaw restart # 重启 Gateway');
313
403
  console.log('');
314
404
  console.log('跨平台: macOS / Linux / Windows (PowerShell/CMD)');
315
405
  console.log('');
@@ -333,6 +423,12 @@ if (!command || command === 'help' || command === '--help' || command === '-h')
333
423
  runWsl2();
334
424
  } else if (command === 'bat') {
335
425
  runBat();
426
+ } else if (command === 'patch') {
427
+ runPatch();
428
+ } else if (command === 'unpatch') {
429
+ runUnpatch();
430
+ } else if (command === 'restart') {
431
+ runRestart();
336
432
  } else {
337
433
  console.error('[' + colors.red + '错误' + colors.nc + '] 未知命令: ' + command);
338
434
  showHelp();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -8,7 +8,8 @@
8
8
  "mc": "index.js"
9
9
  },
10
10
  "scripts": {
11
- "test": "echo \"Error: no test specified\" && exit 1"
11
+ "test": "echo \"Error: no test specified\" && exit 1",
12
+ "postinstall": "node -e \"try{require('./patch').patch()}catch(e){console.log('[myclaw] patch skipped:',e.message)}\""
12
13
  },
13
14
  "keywords": [],
14
15
  "author": "",
package/patch.js ADDED
@@ -0,0 +1,225 @@
1
+ /**
2
+ * ============================================================================
3
+ * MyClaw Patch Engine
4
+ * ============================================================================
5
+ *
6
+ * 功能:
7
+ * 1. 探测 OpenClaw 安装路径
8
+ * 2. 备份原始 index.html
9
+ * 3. 复制 myclaw-inject.js 到 control-ui 目录
10
+ * 4. 在 index.html 的 </body> 前注入 <script> 标签
11
+ * 5. 幂等:重复执行不会重复注入
12
+ *
13
+ * 使用:
14
+ * const { patch, unpatch, status } = require('./patch');
15
+ * patch(); // 执行注入
16
+ * unpatch(); // 回滚
17
+ * status(); // 检查状态
18
+ * ============================================================================
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+
24
+ const INJECT_MARKER = '<!-- myclaw-inject -->';
25
+ const INJECT_FILENAME = 'myclaw-inject.js';
26
+ const BACKUP_SUFFIX = '.myclaw-backup';
27
+
28
+ /**
29
+ * 探测 OpenClaw 的 control-ui 目录
30
+ * 按优先级尝试多种路径
31
+ */
32
+ function findControlUiDir() {
33
+ const candidates = [];
34
+
35
+ // 1. 通过 require.resolve 查找
36
+ const globalPaths = [
37
+ '/usr/lib/node_modules',
38
+ '/usr/local/lib/node_modules',
39
+ ];
40
+
41
+ // 加上当前 npm prefix
42
+ try {
43
+ const { execSync } = require('child_process');
44
+ const prefix = execSync('npm config get prefix 2>/dev/null', { encoding: 'utf8' }).trim();
45
+ if (prefix) {
46
+ globalPaths.push(path.join(prefix, 'lib', 'node_modules'));
47
+ }
48
+ } catch {}
49
+
50
+ for (const globalPath of globalPaths) {
51
+ candidates.push(path.join(globalPath, 'openclaw', 'dist', 'control-ui'));
52
+ }
53
+
54
+ // 2. 通过 which openclaw 推断
55
+ try {
56
+ const { execSync } = require('child_process');
57
+ const bin = execSync('which openclaw 2>/dev/null', { encoding: 'utf8' }).trim();
58
+ if (bin) {
59
+ const real = fs.realpathSync(bin);
60
+ // openclaw.mjs → 包根目录
61
+ const pkgRoot = path.dirname(real);
62
+ candidates.push(path.join(pkgRoot, 'dist', 'control-ui'));
63
+ // 也可能 bin 在 dist/ 下
64
+ candidates.push(path.join(path.dirname(pkgRoot), 'dist', 'control-ui'));
65
+ }
66
+ } catch {}
67
+
68
+ // 3. 尝试当前目录(开发用)
69
+ candidates.push(path.join(process.cwd(), 'dist', 'control-ui'));
70
+
71
+ // 找到第一个有 index.html 的
72
+ for (const dir of candidates) {
73
+ try {
74
+ if (fs.existsSync(path.join(dir, 'index.html'))) {
75
+ return dir;
76
+ }
77
+ } catch {}
78
+ }
79
+
80
+ return null;
81
+ }
82
+
83
+ /**
84
+ * 获取 myclaw 版本号
85
+ */
86
+ function getMyclawVersion() {
87
+ try {
88
+ return require(path.join(__dirname, 'package.json')).version;
89
+ } catch {
90
+ return 'unknown';
91
+ }
92
+ }
93
+
94
+ /**
95
+ * 执行注入
96
+ */
97
+ function patch() {
98
+ const uiDir = findControlUiDir();
99
+ if (!uiDir) {
100
+ console.log('[myclaw-patch] ❌ 未找到 OpenClaw control-ui 目录,跳过注入');
101
+ console.log('[myclaw-patch] 提示: 确保 openclaw 已全局安装 (npm install -g openclaw)');
102
+ return { success: false, reason: 'control-ui-not-found' };
103
+ }
104
+
105
+ const indexPath = path.join(uiDir, 'index.html');
106
+ const backupPath = indexPath + BACKUP_SUFFIX;
107
+ const injectSrc = path.join(__dirname, 'assets', INJECT_FILENAME);
108
+ const injectDest = path.join(uiDir, INJECT_FILENAME);
109
+ const version = getMyclawVersion();
110
+
111
+ console.log('[myclaw-patch] 📍 找到 control-ui: ' + uiDir);
112
+ console.log('[myclaw-patch] 📦 MyClaw 版本: v' + version);
113
+
114
+ // 1. 备份原始 index.html(仅首次)
115
+ if (!fs.existsSync(backupPath)) {
116
+ try {
117
+ fs.copyFileSync(indexPath, backupPath);
118
+ console.log('[myclaw-patch] 💾 已备份: ' + backupPath);
119
+ } catch (err) {
120
+ console.error('[myclaw-patch] ❌ 备份失败: ' + err.message);
121
+ return { success: false, reason: 'backup-failed' };
122
+ }
123
+ }
124
+
125
+ // 2. 复制注入脚本(替换版本号占位符)
126
+ try {
127
+ let injectCode = fs.readFileSync(injectSrc, 'utf8');
128
+ injectCode = injectCode.replace(/__MYCLAW_VERSION__/g, version);
129
+ fs.writeFileSync(injectDest, injectCode, 'utf8');
130
+ console.log('[myclaw-patch] 📄 注入脚本已复制: ' + injectDest);
131
+ } catch (err) {
132
+ console.error('[myclaw-patch] ❌ 复制注入脚本失败: ' + err.message);
133
+ return { success: false, reason: 'copy-failed' };
134
+ }
135
+
136
+ // 3. Patch index.html(幂等)
137
+ try {
138
+ let html = fs.readFileSync(indexPath, 'utf8');
139
+
140
+ // 先移除旧的注入(如果有)
141
+ const markerRegex = new RegExp(
142
+ INJECT_MARKER + '\\s*<script[^>]*' + INJECT_FILENAME + '[^>]*><\\/script>\\s*',
143
+ 'g'
144
+ );
145
+ html = html.replace(markerRegex, '');
146
+
147
+ // 在 </body> 前注入
148
+ const injection = INJECT_MARKER + '\n<script src="./' + INJECT_FILENAME + '"></script>\n';
149
+ if (html.includes('</body>')) {
150
+ html = html.replace('</body>', injection + '</body>');
151
+ } else {
152
+ html += '\n' + injection;
153
+ }
154
+
155
+ fs.writeFileSync(indexPath, html, 'utf8');
156
+ console.log('[myclaw-patch] ✅ index.html 已注入');
157
+ } catch (err) {
158
+ console.error('[myclaw-patch] ❌ 注入 index.html 失败: ' + err.message);
159
+ return { success: false, reason: 'inject-failed' };
160
+ }
161
+
162
+ console.log('[myclaw-patch] 🎉 注入完成! 重启 Gateway 后生效');
163
+ return { success: true, uiDir: uiDir, version: version };
164
+ }
165
+
166
+ /**
167
+ * 回滚注入
168
+ */
169
+ function unpatch() {
170
+ const uiDir = findControlUiDir();
171
+ if (!uiDir) {
172
+ console.log('[myclaw-patch] ❌ 未找到 OpenClaw control-ui 目录');
173
+ return { success: false };
174
+ }
175
+
176
+ const indexPath = path.join(uiDir, 'index.html');
177
+ const backupPath = indexPath + BACKUP_SUFFIX;
178
+ const injectDest = path.join(uiDir, INJECT_FILENAME);
179
+
180
+ // 恢复备份
181
+ if (fs.existsSync(backupPath)) {
182
+ fs.copyFileSync(backupPath, indexPath);
183
+ fs.unlinkSync(backupPath);
184
+ console.log('[myclaw-patch] ✅ index.html 已恢复');
185
+ }
186
+
187
+ // 删除注入脚本
188
+ if (fs.existsSync(injectDest)) {
189
+ fs.unlinkSync(injectDest);
190
+ console.log('[myclaw-patch] ✅ 注入脚本已删除');
191
+ }
192
+
193
+ console.log('[myclaw-patch] 🔄 回滚完成! 重启 Gateway 后生效');
194
+ return { success: true };
195
+ }
196
+
197
+ /**
198
+ * 检查注入状态
199
+ */
200
+ function status() {
201
+ const uiDir = findControlUiDir();
202
+ if (!uiDir) {
203
+ return { installed: false, patched: false, message: 'OpenClaw control-ui 未找到' };
204
+ }
205
+
206
+ const indexPath = path.join(uiDir, 'index.html');
207
+ const backupPath = indexPath + BACKUP_SUFFIX;
208
+
209
+ try {
210
+ const html = fs.readFileSync(indexPath, 'utf8');
211
+ const patched = html.includes(INJECT_MARKER);
212
+ const hasBackup = fs.existsSync(backupPath);
213
+ return {
214
+ installed: true,
215
+ patched: patched,
216
+ hasBackup: hasBackup,
217
+ uiDir: uiDir,
218
+ message: patched ? '已注入' : '未注入',
219
+ };
220
+ } catch {
221
+ return { installed: true, patched: false, message: '无法读取 index.html' };
222
+ }
223
+ }
224
+
225
+ module.exports = { patch, unpatch, status, findControlUiDir };