@aiyiran/myclaw 1.0.104 → 1.0.112

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.
@@ -9,11 +9,10 @@
9
9
  * - 光标处插入文字
10
10
  * - 持续录入,手动停止
11
11
  * - 讯飞 60 秒断开后自动重连
12
- * 3. 拦截 chat-tts-btn 按钮,点击后使用讯飞 TTS 播放消息
13
12
  *
14
13
  * 依赖:
15
14
  * - voice-input.js(讯飞 VoiceInput SDK)
16
- * - voice-output.js(讯飞 VoiceOutput SDK)
15
+ * - myclaw-tts.js(TTS 按钮绑定,由 myclaw-tts.js 提供)
17
16
  * ============================================================================
18
17
  */
19
18
  (function () {
@@ -23,13 +22,11 @@
23
22
 
24
23
  // ═══ 状态 ═══
25
24
  var voice = null; // VoiceInput 实例
26
- var tts = null; // VoiceOutput 实例
27
25
  var recording = false; // 用户层面的录音状态(独立于 SDK 的 status)
28
26
  var pendingText = ""; // 当前这轮识别的文字(实时更新)
29
27
  var committedText = ""; // 已经提交到 textarea 的文字(上一轮累积)
30
28
  var cursorOffset = 0; // 录音开始时光标在 textarea 中的位置
31
29
  var injected = false;
32
- var ttsBtnHooked = false; // TTS 按钮是否已绑定
33
30
 
34
31
  // ═══ 1. 右下角版本标签(点击测试麦克风) ═══
35
32
  function createVersionBar() {
@@ -401,92 +398,6 @@
401
398
  console.log("[myclaw-inject] ✅ 发送按钮拦截器已挂载");
402
399
  }
403
400
 
404
- // ═══ 7. TTS 按钮绑定(讯飞语音合成) ═══
405
-
406
- function initTts() {
407
- if (typeof window.VoiceOutput === "undefined") {
408
- console.error("[myclaw-tts] VoiceOutput SDK 未加载");
409
- return;
410
- }
411
- if (tts) return;
412
-
413
- tts = new window.VoiceOutput({
414
- APPID: 'de6ec59e',
415
- APISecret: 'ZTA3ZDU3YjAyNDUyZGVkNjQwMGM5MWNi',
416
- APIKey: '37104a0463a5460d7571869324b667d5',
417
- vcn: 'x4_yezi', // 小露 - 普通话女声
418
- onAudio: function (audioBuffer) {
419
- console.log('[myclaw-tts] audio chunk:', audioBuffer.byteLength, 'bytes');
420
- },
421
- onStatusChange: function (oldStatus, newStatus) {
422
- console.log('[myclaw-tts] status:', oldStatus, '->', newStatus);
423
- },
424
- onError: function (err) {
425
- console.error('[myclaw-tts] error:', err);
426
- }
427
- });
428
-
429
- console.log("[myclaw-tts] ✅ 讯飞 TTS SDK 已初始化");
430
- }
431
-
432
- /**
433
- * 从 .chat-tts-btn 按钮获取对应的消息文字
434
- * DOM 结构:
435
- * .chat-group-messages
436
- * .chat-bubble
437
- * .chat-text > p (消息文字)
438
- * .chat-group-footer
439
- * .chat-tts-btn
440
- */
441
- function extractMessageText(ttsBtn) {
442
- // 向上找到 .chat-group-messages
443
- var group = ttsBtn.closest('.chat-group-messages');
444
- if (!group) return '';
445
-
446
- // 在 group 内找 .chat-bubble
447
- var bubble = group.querySelector('.chat-bubble');
448
- if (!bubble) return '';
449
-
450
- // 在气泡内找 .chat-text 的文字
451
- var chatText = bubble.querySelector('.chat-text');
452
- if (!chatText) return '';
453
-
454
- // 获取文字,移除多余空白
455
- var text = chatText.textContent || '';
456
- return text.replace(/\s+/g, ' ').trim();
457
- }
458
-
459
- function hookTtsButton() {
460
- if (ttsBtnHooked) return;
461
- ttsBtnHooked = true;
462
-
463
- // 使用捕获阶段拦截点击
464
- document.addEventListener('click', function (e) {
465
- var btn = e.target.closest('.chat-tts-btn');
466
- if (!btn) return;
467
-
468
- e.preventDefault();
469
- e.stopPropagation();
470
-
471
- var text = extractMessageText(btn);
472
- if (!text) {
473
- console.warn('[myclaw-tts] 未找到消息文字');
474
- return;
475
- }
476
-
477
- console.log('[myclaw-tts] 播放:', text.substring(0, 50) + (text.length > 50 ? '...' : ''));
478
-
479
- if (!tts) {
480
- initTts();
481
- }
482
- if (tts) {
483
- tts.speak(text);
484
- }
485
- }, true);
486
-
487
- console.log("[myclaw-inject] ✅ TTS 按钮拦截器已挂载");
488
- }
489
-
490
401
  function fallbackCopy(text) {
491
402
  var ta = document.createElement("textarea");
492
403
  ta.value = text;
@@ -506,9 +417,6 @@
506
417
  // 初始化 VoiceInput SDK
507
418
  initVoice();
508
419
 
509
- // 初始化 TTS SDK
510
- initTts();
511
-
512
420
  // 持续监听 DOM 变化,确保按钮始终在
513
421
  new MutationObserver(function () {
514
422
  if (!document.querySelector("#myclaw-voice-btn")) {
@@ -524,9 +432,7 @@
524
432
 
525
433
  // 挂载发送拦截
526
434
  hookSendButton();
527
-
528
- // 挂载 TTS 按钮拦截
529
- hookTtsButton();
435
+ // TTS 按钮绑定由 myclaw-tts.js 独立处理
530
436
  }
531
437
 
532
438
  if (document.readyState === "loading") {
@@ -0,0 +1,270 @@
1
+ /**
2
+ * ============================================================================
3
+ * MyClaw TTS — 讯飞语音合成
4
+ * ============================================================================
5
+ *
6
+ * 功能:拦截 chat-tts-btn 按钮,点击后使用讯飞 TTS 播放消息
7
+ *
8
+ * 状态:
9
+ * idle → 默认状态,显示小嘴巴 emoji
10
+ * generating → 合成中,显示加载动画
11
+ * playing → 播放中,显示喇叭 emoji,点击可停止
12
+ *
13
+ * 依赖:voice-output.js(讯飞 VoiceOutput SDK,需先于本脚本加载)
14
+ * ============================================================================
15
+ */
16
+ (function () {
17
+ 'use strict';
18
+
19
+ // ═══ 状态 ═══
20
+ var tts = null; // VoiceOutput 实例
21
+ var ttsBtnHooked = false;
22
+ var currentBtn = null; // 当前正在播放的按钮
23
+ var playerState = 'idle'; // idle | generating | playing
24
+
25
+ // ═══ 讯飞配置 ═══
26
+ var IFLYTEK_CONFIG = {
27
+ APPID: 'de6ec59e',
28
+ APISecret: 'ZTA3ZDU3YjAyNDUyZGVkNjQwMGM5MWNi',
29
+ APIKey: '37104a0463a5460d7571869324b667d5',
30
+ vcn: 'x4_yezi', // 默认音色:小露(普通话女声)
31
+ };
32
+
33
+ // ═══ 公开配置接口 ═══
34
+ window.MyClawTts = {
35
+ setVcn: function (vcn) {
36
+ IFLYTEK_CONFIG.vcn = vcn;
37
+ if (tts) tts.vcn = vcn;
38
+ },
39
+ getVcn: function () {
40
+ return IFLYTEK_CONFIG.vcn;
41
+ },
42
+ speak: function (text) {
43
+ if (!tts) initTts();
44
+ if (tts) tts.speak(text);
45
+ },
46
+ };
47
+
48
+ // ═══ 按钮图标 ═══
49
+ var ICONS = {
50
+ idle: '👁', // 👁 小嘴巴
51
+ generating: '🔄', // 🔄 加载中
52
+ playing: '🔊', // 🔊 喇叭
53
+ };
54
+
55
+ // ═══ 状态更新 ═══
56
+ function setPlayerState(btn, state) {
57
+ if (!btn) return;
58
+ playerState = state;
59
+
60
+ // 恢复原生 SVG
61
+ var svg = btn.querySelector('svg');
62
+ if (svg) {
63
+ svg.style.display = state === 'idle' ? '' : 'none';
64
+ }
65
+
66
+ // 移除所有状态 class
67
+ btn.classList.remove('myclaw-tts--generating', 'myclaw-tts--playing');
68
+
69
+ if (state === 'generating') {
70
+ btn.classList.add('myclaw-tts--generating');
71
+ btn.title = '生成中...';
72
+ } else if (state === 'playing') {
73
+ btn.classList.add('myclaw-tts--playing');
74
+ btn.title = '点击停止';
75
+ } else {
76
+ btn.title = '朗读';
77
+ }
78
+ }
79
+
80
+ function updateBtnIcon(btn, state) {
81
+ if (!btn) return;
82
+
83
+ // 移除旧的 emoji 容器
84
+ var oldEmoji = btn.querySelector('.myclaw-tts-icon');
85
+ if (oldEmoji) oldEmoji.remove();
86
+
87
+ // 创建 emoji 容器
88
+ var emoji = document.createElement('span');
89
+ emoji.className = 'myclaw-tts-icon';
90
+ emoji.innerHTML = ICONS[state];
91
+ emoji.style.cssText = 'display:inline-block;width:18px;height:18px;line-height:18px;text-align:center;';
92
+
93
+ // 插入到按钮最前面
94
+ btn.insertBefore(emoji, btn.firstChild);
95
+ }
96
+
97
+ // ═══ 初始化 TTS ═══
98
+ function initTts() {
99
+ if (typeof window.VoiceOutput === 'undefined') {
100
+ console.error('[myclaw-tts] VoiceOutput SDK 未加载');
101
+ return;
102
+ }
103
+ if (tts) return;
104
+
105
+ tts = new window.VoiceOutput({
106
+ APPID: IFLYTEK_CONFIG.APPID,
107
+ APISecret: IFLYTEK_CONFIG.APISecret,
108
+ APIKey: IFLYTEK_CONFIG.APIKey,
109
+ vcn: IFLYTEK_CONFIG.vcn,
110
+ onAudio: function (audioBuffer) {
111
+ // console.log('[myclaw-tts] audio chunk:', audioBuffer.byteLength, 'bytes');
112
+ },
113
+ onStatusChange: function (oldStatus, newStatus) {
114
+ console.log('[myclaw-tts] status:', oldStatus, '->', newStatus);
115
+
116
+ if (!currentBtn) return;
117
+
118
+ if (newStatus === 'connecting' || newStatus === 'synthesizing') {
119
+ updateBtnIcon(currentBtn, 'generating');
120
+ setPlayerState(currentBtn, 'generating');
121
+ } else if (newStatus === 'idle') {
122
+ if (playerState === 'generating') {
123
+ // 合成完成但未开始播放(可能是空文字或其他错误)
124
+ setPlayerState(currentBtn, 'idle');
125
+ }
126
+ // playing 状态会在 audio.onended 时处理
127
+ }
128
+ },
129
+ onError: function (err) {
130
+ console.error('[myclaw-tts] error:', err);
131
+ if (currentBtn) {
132
+ setPlayerState(currentBtn, 'idle');
133
+ updateBtnIcon(currentBtn, 'idle');
134
+ }
135
+ currentBtn = null;
136
+ playerState = 'idle';
137
+ },
138
+ });
139
+
140
+ console.log('[myclaw-tts] ✅ 讯飞 TTS SDK 已初始化');
141
+ }
142
+
143
+ // ═══ 提取消息文字 ═══
144
+ function extractMessageText(ttsBtn) {
145
+ var group = ttsBtn.closest('.chat-group-messages');
146
+ if (!group) return '';
147
+
148
+ var bubble = group.querySelector('.chat-bubble');
149
+ if (!bubble) return '';
150
+
151
+ var chatText = bubble.querySelector('.chat-text');
152
+ if (!chatText) return '';
153
+
154
+ var text = chatText.textContent || '';
155
+ return text.replace(/\s+/g, ' ').trim();
156
+ }
157
+
158
+ // ═══ 注入样式 ═══
159
+ function injectStyles() {
160
+ if (document.querySelector('#myclaw-tts-styles')) return;
161
+ var style = document.createElement('style');
162
+ style.id = 'myclaw-tts-styles';
163
+ style.textContent = [
164
+ /* 生成中:旋转动画 */
165
+ '.myclaw-tts--generating {',
166
+ ' animation: myclaw-tts-spin 1s linear infinite;',
167
+ ' opacity: 0.7;',
168
+ '}',
169
+ '@keyframes myclaw-tts-spin {',
170
+ ' from { transform: rotate(0deg); }',
171
+ ' to { transform: rotate(360deg); }',
172
+ '}',
173
+ /* 播放中:脉冲动画 */
174
+ '.myclaw-tts--playing {',
175
+ ' animation: myclaw-tts-pulse 0.8s ease-in-out infinite;',
176
+ '}',
177
+ '@keyframes myclaw-tts-pulse {',
178
+ ' 0%, 100% { opacity: 1; }',
179
+ ' 50% { opacity: 0.5; }',
180
+ '}',
181
+ ].join('\n');
182
+ document.head.appendChild(style);
183
+ }
184
+
185
+ // ═══ 挂载按钮拦截 ═══
186
+ function hookTtsButton() {
187
+ if (ttsBtnHooked) return;
188
+ ttsBtnHooked = true;
189
+
190
+ document.addEventListener('click', function (e) {
191
+ var btn = e.target.closest('.chat-tts-btn');
192
+ if (!btn) return;
193
+
194
+ e.preventDefault();
195
+ e.stopPropagation();
196
+
197
+ // 如果正在播放,点击停止
198
+ if (playerState === 'playing' && currentBtn === btn) {
199
+ console.log('[myclaw-tts] 停止播放');
200
+ if (tts) tts.stop();
201
+ setPlayerState(btn, 'idle');
202
+ updateBtnIcon(btn, 'idle');
203
+ currentBtn = null;
204
+ playerState = 'idle';
205
+ return;
206
+ }
207
+
208
+ // 如果当前有其他按钮在播放,先停掉
209
+ if (playerState === 'playing' && currentBtn && currentBtn !== btn) {
210
+ if (tts) tts.stop();
211
+ setPlayerState(currentBtn, 'idle');
212
+ updateBtnIcon(currentBtn, 'idle');
213
+ }
214
+
215
+ var text = extractMessageText(btn);
216
+ if (!text) {
217
+ console.warn('[myclaw-tts] 未找到消息文字');
218
+ return;
219
+ }
220
+
221
+ console.log('[myclaw-tts] 播放:', text.substring(0, 50) + (text.length > 50 ? '...' : ''));
222
+
223
+ // 设置当前按钮和状态
224
+ currentBtn = btn;
225
+ setPlayerState(btn, 'generating');
226
+ updateBtnIcon(btn, 'generating');
227
+
228
+ // 拦截 onAudioEnd 来设置 playing 状态
229
+ if (!tts) initTts();
230
+ if (tts) {
231
+ tts.onAudioEnd = function () {
232
+ console.log('[myclaw-tts] 播放结束');
233
+ if (currentBtn) {
234
+ setPlayerState(currentBtn, 'idle');
235
+ updateBtnIcon(currentBtn, 'idle');
236
+ currentBtn = null;
237
+ }
238
+ playerState = 'idle';
239
+ };
240
+ tts.vcn = IFLYTEK_CONFIG.vcn;
241
+ tts.speak(text);
242
+
243
+ // 状态会在 onStatusChange 里变成 playing
244
+ // 手动设置一次,因为 Web Audio API 可能比 status 更快
245
+ setTimeout(function () {
246
+ if (playerState === 'generating') {
247
+ setPlayerState(btn, 'playing');
248
+ updateBtnIcon(btn, 'playing');
249
+ playerState = 'playing';
250
+ }
251
+ }, 100);
252
+ }
253
+ }, true);
254
+
255
+ console.log('[myclaw-tts] ✅ TTS 按钮拦截器已挂载');
256
+ }
257
+
258
+ // ═══ 启动 ═══
259
+ function init() {
260
+ injectStyles();
261
+ initTts();
262
+ hookTtsButton();
263
+ }
264
+
265
+ if (document.readyState === 'loading') {
266
+ document.addEventListener('DOMContentLoaded', init);
267
+ } else {
268
+ init();
269
+ }
270
+ })();
package/create_agent.js CHANGED
@@ -154,7 +154,8 @@ function defaultWorkspaceFiles(agentId) {
154
154
  * @param {string} rawName - 原始名称
155
155
  * @returns {object} 创建结果
156
156
  */
157
- function createAgent(rawName) {
157
+ function createAgent(rawName, opts) {
158
+ opts = opts || {};
158
159
  // 标准化 & 验证名称
159
160
  const agentId = normalizeAgentId(rawName);
160
161
  validateAgentId(agentId);
@@ -239,8 +240,8 @@ function createAgent(rawName) {
239
240
  fail('配置文件写入失败: ' + err.message + '\n备份文件: ' + normalizePath(backupPath));
240
241
  }
241
242
 
242
- // 发送出生消息
243
- const birthMessage = buildBirthMessage();
243
+ // 发送首条消息:优先用自定义内容,否则走默认出生文案
244
+ const birthMessage = opts.firstMessage || buildBirthMessage();
244
245
  let firstMessageSent = false;
245
246
  let firstMessageError = null;
246
247
 
package/fix.js ADDED
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ============================================================================
5
+ * MyClaw Fix - 兜底修复命令
6
+ * ============================================================================
7
+ *
8
+ * 使用: myclaw fix
9
+ *
10
+ * 运行后自动执行两件事:
11
+ * 1. 强制下载并安装 WSL MSI(无论是否已装,确保补齐)
12
+ * 2. 检查 Chrome 是否已安装,未装则下载并静默安装
13
+ *
14
+ * 设计原则:
15
+ * - 仅限 Windows 使用
16
+ * - 管理员权限运行(MSI 安装需要)
17
+ * - WSL 强制安装(兜底,避免漏装)
18
+ * - Chrome 按需安装(检测到才跳过)
19
+ * - 全自动:学生不需要做任何操作
20
+ * ============================================================================
21
+ */
22
+
23
+ const { execSync } = require('child_process');
24
+ const os = require('os');
25
+ const path = require('path');
26
+
27
+ // ============================================================================
28
+ // 配置
29
+ // ============================================================================
30
+
31
+ const CDN = {
32
+ wsl: 'https://cdn.yiranlaoshi.com/software/myclaw/wsl.2.7.1.0.x64.msi',
33
+ chrome: 'https://cdn.yiranlaoshi.com/software/myclaw/ChromeStandaloneSetup64.exe',
34
+ };
35
+
36
+ const isWindows = os.platform() === 'win32';
37
+
38
+ const C = isWindows
39
+ ? { r: '', g: '', y: '', b: '', nc: '' }
40
+ : { r: '\x1b[31m', g: '\x1b[32m', y: '\x1b[33m', b: '\x1b[34m', nc: '\x1b[0m' };
41
+
42
+ // ============================================================================
43
+ // 工具:启动 PowerShell 管理员窗口执行脚本
44
+ // ============================================================================
45
+
46
+ function launchElevatedPS(script) {
47
+ const fs = require('fs');
48
+
49
+ const tmpDir = process.env.LOCALAPPDATA
50
+ ? path.join(process.env.LOCALAPPDATA, 'myclaw')
51
+ : path.join(os.tmpdir(), 'myclaw');
52
+ try { fs.mkdirSync(tmpDir, { recursive: true }); } catch {}
53
+ const scriptPath = path.join(tmpDir, 'fix_installer.ps1');
54
+
55
+ // UTF-8 with BOM — PowerShell 需要 BOM 才能正确读取中文
56
+ const BOM = '\uFEFF';
57
+ fs.writeFileSync(scriptPath, BOM + script, 'utf8');
58
+
59
+ const escaped = scriptPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
60
+ const cmd = `powershell -Command "Start-Process powershell -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-NoExit','-File','${escaped}') -Verb RunAs"`;
61
+
62
+ try {
63
+ execSync(cmd, { stdio: 'inherit' });
64
+ return true;
65
+ } catch (err) {
66
+ console.error('[' + C.r + '错误' + C.nc + '] 无法获取管理员权限: ' + err.message);
67
+ console.log('建议右键终端选择【以管理员身份运行】后重试。');
68
+ return false;
69
+ }
70
+ }
71
+
72
+ // ============================================================================
73
+ // 检测 Chrome
74
+ // ============================================================================
75
+
76
+ function isChromeInstalled() {
77
+ const locations = [
78
+ path.join(process.env.PROGRAMFILES || 'C:\\Program Files', 'Google', 'Chrome', 'Application', 'chrome.exe'),
79
+ path.join(process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)', 'Google', 'Chrome', 'Application', 'chrome.exe'),
80
+ path.join(os.homedir(), 'AppData', 'Local', 'Google', 'Chrome', 'Application', 'chrome.exe'),
81
+ ];
82
+
83
+ const fs = require('fs');
84
+ return locations.some(loc => {
85
+ try { fs.accessSync(loc); return true; } catch { return false; }
86
+ });
87
+ }
88
+
89
+ // ============================================================================
90
+ // 主入口
91
+ // ============================================================================
92
+
93
+ function run() {
94
+ if (!isWindows) {
95
+ console.error('[' + C.y + '提示' + C.nc + '] 本命令仅用于 Windows 系统。');
96
+ process.exit(0);
97
+ }
98
+
99
+ const BAR = '========================================';
100
+
101
+ console.log('');
102
+ console.log('[' + C.b + 'MyClaw' + C.nc + '] ' + C.b + '兜底修复' + C.nc);
103
+ console.log(BAR);
104
+ console.log('');
105
+
106
+ // Chrome 检测(WSL 不检测,强制安装)
107
+ const chromeOK = isChromeInstalled();
108
+
109
+ console.log('[检测结果]');
110
+ console.log(' WSL: ' + C.y + '[强制安装]' + C.nc);
111
+ console.log(' Chrome: ' + (chromeOK ? C.g + '[已安装] 跳过' + C.nc : C.r + '[未安装] 将安装' + C.nc));
112
+ console.log('');
113
+
114
+ const needChrome = !chromeOK;
115
+ const totalSteps = 2 + (needChrome ? 1 : 0);
116
+
117
+ // Node 端也设置一下(当前终端环境)
118
+ try { execSync('npm config set registry https://registry.npmmirror.com', { stdio: 'pipe' }); } catch {}
119
+
120
+ let psScript = `
121
+ $ErrorActionPreference = 'Continue'
122
+ $Host.UI.RawUI.WindowTitle = 'MyClaw Fix'
123
+ try {
124
+ Write-Host ''
125
+ Write-Host '========================================'
126
+ Write-Host ' MyClaw 兜底修复'
127
+ Write-Host '========================================'
128
+ Write-Host ''
129
+
130
+ $dir = "$env:LOCALAPPDATA\\\\myclaw"
131
+ New-Item -ItemType Directory -Force -Path $dir | Out-Null
132
+
133
+ # ================================================
134
+ # [1/${totalSteps}] 配置 npm 中国镜像
135
+ # ================================================
136
+ Write-Host '[1/${totalSteps}] 配置 npm 中国镜像...'
137
+ try {
138
+ npm config set registry https://registry.npmmirror.com 2>$null
139
+ Write-Host ' [OK] registry -> https://registry.npmmirror.com'
140
+ } catch {
141
+ Write-Host ' [跳过] npm 未安装或配置失败'
142
+ }
143
+ Write-Host ''
144
+
145
+ # ================================================
146
+ # [2/${totalSteps}] 强制安装 WSL
147
+ # ================================================
148
+ Write-Host '[2/${totalSteps}] 强制安装 WSL...'
149
+ Write-Host ''
150
+
151
+ Write-Host ' [a] 启用 Windows Subsystem for Linux...'
152
+ try {
153
+ dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart 2>$null | Out-Null
154
+ Write-Host ' [OK]'
155
+ } catch {
156
+ Write-Host ' 跳过 (可能已启用)'
157
+ }
158
+
159
+ Write-Host ' [b] 启用虚拟机平台...'
160
+ try {
161
+ dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart 2>$null | Out-Null
162
+ Write-Host ' [OK]'
163
+ } catch {
164
+ Write-Host ' 跳过 (可能已启用)'
165
+ }
166
+
167
+ $msi = "$dir\\\\wsl_fix.msi"
168
+ Write-Host ' [c] 下载 WSL 安装包...'
169
+ try {
170
+ Start-BitsTransfer -Source '${CDN.wsl}' -Destination $msi -Description '下载 WSL' -DisplayName 'MyClaw Fix'
171
+ Write-Host ' 下载完成'
172
+ } catch {
173
+ Write-Host ' BitsTransfer 失败,尝试 Invoke-WebRequest...'
174
+ try {
175
+ Invoke-WebRequest -Uri '${CDN.wsl}' -OutFile $msi -UseBasicParsing
176
+ Write-Host ' 下载完成'
177
+ } catch {
178
+ Write-Host ' [失败] 下载失败,请检查网络'
179
+ }
180
+ }
181
+
182
+ if (Test-Path $msi) {
183
+ Write-Host ' [d] 静默安装 WSL...'
184
+ Start-Process msiexec.exe -ArgumentList "/i \`\\"$msi\`\\" /quiet /norestart" -Wait -NoNewWindow
185
+ Write-Host ' [OK]'
186
+ }
187
+ Write-Host ''
188
+ `;
189
+
190
+ // === Chrome(按需)===
191
+ if (needChrome) {
192
+ psScript += `
193
+ # ================================================
194
+ # [3/${totalSteps}] 安装 Chrome
195
+ # ================================================
196
+ Write-Host '[3/${totalSteps}] 安装 Chrome...'
197
+ Write-Host ''
198
+
199
+ $chromeExists = $false
200
+ $chromePaths = @(
201
+ "$env:ProgramFiles\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe",
202
+ "\${env:ProgramFiles(x86)}\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe",
203
+ "$env:LOCALAPPDATA\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe"
204
+ )
205
+ foreach ($cp in $chromePaths) {
206
+ if (Test-Path $cp) { $chromeExists = $true; break }
207
+ }
208
+
209
+ if ($chromeExists) {
210
+ Write-Host ' Chrome 已安装,跳过'
211
+ } else {
212
+ $exe = "$dir\\\\ChromeSetup.exe"
213
+ Write-Host ' [a] 下载 Chrome 安装包...'
214
+ try {
215
+ Start-BitsTransfer -Source '${CDN.chrome}' -Destination $exe -Description '下载 Chrome' -DisplayName 'MyClaw Fix'
216
+ Write-Host ' 下载完成'
217
+ } catch {
218
+ Write-Host ' BitsTransfer 失败,尝试 Invoke-WebRequest...'
219
+ try {
220
+ Invoke-WebRequest -Uri '${CDN.chrome}' -OutFile $exe -UseBasicParsing
221
+ Write-Host ' 下载完成'
222
+ } catch {
223
+ Write-Host ' [失败] 下载失败,请检查网络'
224
+ }
225
+ }
226
+
227
+ if (Test-Path $exe) {
228
+ Write-Host ' [b] 安装 Chrome (静默)...'
229
+ Start-Process $exe -ArgumentList '/silent /install' -Wait -NoNewWindow
230
+ Write-Host ' [OK]'
231
+ }
232
+ }
233
+ Write-Host ''
234
+ `;
235
+ }
236
+
237
+ // === 结尾 ===
238
+ psScript += `
239
+ Write-Host '========================================'
240
+ Write-Host ' 修复完成!'
241
+ Write-Host '========================================'
242
+ Write-Host ''
243
+ Write-Host ' [重要] WSL 安装后可能需要 重启电脑 才能生效'
244
+ Write-Host ' 重启后运行: myclaw wsl2'
245
+ Write-Host ''
246
+
247
+ } catch {
248
+ Write-Host ''
249
+ Write-Host '========================================'
250
+ Write-Host " [异常] 发生错误: $_"
251
+ Write-Host ' 请截图此窗口内容后联系老师'
252
+ Write-Host '========================================'
253
+ } finally {
254
+ Write-Host ''
255
+ Write-Host '按任意键关闭此窗口...'
256
+ $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
257
+ }
258
+ `;
259
+
260
+ // 显示即将执行的操作
261
+ console.log('[即将执行]');
262
+ console.log(' • 强制下载并安装 WSL (' + CDN.wsl + ')');
263
+ if (needChrome) console.log(' • 下载并安装 Chrome (' + CDN.chrome + ')');
264
+ console.log('');
265
+ console.log('[' + C.y + '注意' + C.nc + '] 请在 UAC 弹窗中点击【是】');
266
+ console.log('');
267
+
268
+ if (launchElevatedPS(psScript)) {
269
+ console.log('[' + C.g + '已启动' + C.nc + '] 请查看新弹出的蓝色窗口');
270
+ console.log('');
271
+ console.log('WSL 安装完成后可能需要 ' + C.y + '重启电脑' + C.nc);
272
+ console.log('重启后运行 ' + C.y + 'myclaw wsl2' + C.nc + ' 继续安装');
273
+ console.log('');
274
+ }
275
+ }
276
+
277
+ module.exports = { run };
package/index.js CHANGED
@@ -181,8 +181,175 @@ function runStatus() {
181
181
  // ============================================================================
182
182
 
183
183
  function runNew() {
184
- const { runNewAgent } = require('./wizards/index');
185
- runNewAgent();
184
+ const rawName = args[1];
185
+
186
+ // 无参数 → 向导模式
187
+ if (!rawName) {
188
+ const { runNewAgent } = require('./wizards/index');
189
+ runNewAgent();
190
+ return;
191
+ }
192
+
193
+ // 有参数 → 一键创建模式(带分步日志)
194
+ // myclaw new haha → 默认出生文案
195
+ // myclaw new haha 你好见到你很高兴 → 自定义首条消息
196
+ const firstMessage = args.slice(2).join(' ').trim() || null;
197
+
198
+ const bar = '────────────────────────────────────────';
199
+ const G = colors.green;
200
+ const Y = colors.yellow;
201
+ const R = colors.red;
202
+ const B = colors.blue;
203
+ const NC = colors.nc;
204
+
205
+ console.log('');
206
+ console.log('[' + B + 'MyClaw' + NC + '] ' + B + '创建 Agent: ' + rawName + NC);
207
+ console.log(bar);
208
+ console.log('');
209
+
210
+ const { normalizeAgentId, validateAgentId } = require('./create_agent');
211
+ const fs = require('fs');
212
+ const path = require('path');
213
+ const os = require('os');
214
+ const { execSync } = require('child_process');
215
+
216
+ const homeDir = os.homedir();
217
+ const openclawDir = path.join(homeDir, '.openclaw');
218
+ const configPath = path.join(openclawDir, 'openclaw.json');
219
+
220
+ // ── Step 1: 名称标准化 ──
221
+ console.log(B + '[1/6]' + NC + ' 标准化名称...');
222
+ console.log(' ' + G + '▶ 发起:' + NC + ' normalizeAgentId("' + rawName + '")');
223
+ let agentId;
224
+ try {
225
+ agentId = normalizeAgentId(rawName);
226
+ validateAgentId(agentId);
227
+ console.log(' ' + G + '◀ 返回:' + NC + ' agentId = "' + agentId + '" ✅');
228
+ } catch (err) {
229
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ ' + err.message);
230
+ process.exit(1);
231
+ }
232
+ console.log('');
233
+
234
+ // ── Step 2: 环境检查 ──
235
+ console.log(B + '[2/6]' + NC + ' 环境检查(查重 + 目录冲突)...');
236
+ console.log(' ' + G + '▶ 发起:' + NC + ' 读取 ' + configPath);
237
+ let configData;
238
+ try {
239
+ configData = JSON.parse(fs.readFileSync(configPath, 'utf8'));
240
+ if (!configData.agents) configData.agents = {};
241
+ if (!Array.isArray(configData.agents.list)) configData.agents.list = [];
242
+
243
+ const exists = configData.agents.list.find(a => a && a.id === agentId);
244
+ if (exists) {
245
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ Agent "' + agentId + '" 已存在');
246
+ process.exit(1);
247
+ }
248
+ const workspaceDir = path.join(openclawDir, 'workspace-' + agentId);
249
+ const agentDir = path.join(openclawDir, 'agents', agentId, 'agent');
250
+ if (fs.existsSync(workspaceDir) || fs.existsSync(agentDir)) {
251
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ 目录已存在');
252
+ process.exit(1);
253
+ }
254
+ console.log(' ' + G + '◀ 返回:' + NC + ' 名称无冲突,目录可用 ✅');
255
+ } catch (err) {
256
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ ' + err.message);
257
+ process.exit(1);
258
+ }
259
+ console.log('');
260
+
261
+ // ── Step 3: 创建工作空间 ──
262
+ console.log(B + '[3/6]' + NC + ' 创建工作空间(SOUL.md / AGENTS.md / USER.md ...)...');
263
+ const workspaceDir = path.join(openclawDir, 'workspace-' + agentId);
264
+ const agentDir = path.join(openclawDir, 'agents', agentId, 'agent');
265
+ const sessionsDir = path.join(openclawDir, 'agents', agentId, 'sessions');
266
+ console.log(' ' + G + '▶ 发起:' + NC + ' mkdir + writeFile → ' + workspaceDir);
267
+ try {
268
+ fs.mkdirSync(workspaceDir, { recursive: true });
269
+ fs.mkdirSync(agentDir, { recursive: true });
270
+ fs.mkdirSync(sessionsDir, { recursive: true });
271
+ // 写入默认文件
272
+ const defaultFiles = {
273
+ 'AGENTS.md': '# AGENTS.md - ' + agentId + '\n\nThis workspace belongs to the `' + agentId + '` agent.\n\n## Startup\n- Read `SOUL.md`\n- Read `USER.md`\n\n## Rules\n- Be helpful, careful, and concise.\n',
274
+ 'SOUL.md': '# SOUL.md\n\nYou are `' + agentId + '`.\n\nBe useful, calm, direct, and trustworthy.\n',
275
+ 'USER.md': '# USER.md\n\n- Name: 孙依然\n- What to call them: 依然\n- Timezone: Asia/Shanghai\n',
276
+ 'IDENTITY.md': '# IDENTITY.md\n\n- Name: ' + agentId + '\n- Role: OpenClaw agent\n',
277
+ 'BOOTSTRAP.md': '# BOOTSTRAP.md\n\nYou are a newly created agent named `' + agentId + '`.\nOn first runs, learn your workspace files and begin helping.\n',
278
+ };
279
+ for (const [fn, content] of Object.entries(defaultFiles)) {
280
+ const fp = path.join(workspaceDir, fn);
281
+ if (!fs.existsSync(fp)) fs.writeFileSync(fp, content, 'utf8');
282
+ }
283
+ console.log(' ' + G + '◀ 返回:' + NC + ' 已创建 ' + Object.keys(defaultFiles).length + ' 个基础文件 ✅');
284
+ } catch (err) {
285
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ ' + err.message);
286
+ process.exit(1);
287
+ }
288
+ console.log('');
289
+
290
+ // ── Step 4: 注册到配置 ──
291
+ console.log(B + '[4/6]' + NC + ' 注册到 openclaw.json(agents.list)...');
292
+ const backupPath = configPath + '.bak.agent-birth.' + new Date().toISOString().replace(/[:.]/g, '-').slice(0, 26) + 'Z';
293
+ console.log(' ' + G + '▶ 发起:' + NC + ' 备份 → ' + path.basename(backupPath));
294
+ try {
295
+ fs.copyFileSync(configPath, backupPath);
296
+ configData.agents.list.push({
297
+ id: agentId,
298
+ name: agentId,
299
+ workspace: workspaceDir.replace(/\\/g, '/'),
300
+ agentDir: agentDir.replace(/\\/g, '/'),
301
+ });
302
+ fs.writeFileSync(configPath, JSON.stringify(configData, null, 2) + '\n', 'utf8');
303
+ console.log(' ' + G + '◀ 返回:' + NC + ' 配置已写入,备份已保存 ✅');
304
+ } catch (err) {
305
+ console.log(' ' + R + '◀ 返回:' + NC + ' ❌ ' + err.message);
306
+ process.exit(1);
307
+ }
308
+ console.log('');
309
+
310
+ // ── Step 5: 发送首条消息 ──
311
+ console.log(B + '[5/6]' + NC + ' 发送首条消息(唤醒 Agent)...');
312
+ const nowUtc = new Date();
313
+ const nowBj = new Date(nowUtc.getTime() + 8 * 60 * 60 * 1000);
314
+ const ts = nowBj.getUTCFullYear() + '年' + (nowBj.getUTCMonth() + 1) + '月' + nowBj.getUTCDate() + '日' + String(nowBj.getUTCHours()).padStart(2, '0') + ':' + String(nowBj.getUTCMinutes()).padStart(2, '0');
315
+ const birthMessage = firstMessage || ('你好,你的生日是北京时间' + ts + ',请记录这个信息。\n欢迎你来到这个世界,我是你的造物主,我们是伙伴,我叫做孙依然,你可以叫我依然,请多多指教,共同成长。');
316
+
317
+ const cmd = 'openclaw agent --agent ' + agentId + ' --message ' + JSON.stringify(birthMessage) + ' --json';
318
+ console.log(' ' + G + '▶ 发起:' + NC + ' $ ' + G + cmd.substring(0, 80) + (cmd.length > 80 ? '...' : '') + NC);
319
+ console.log(' ' + Y + ' ⏳ 等待大模型响应(可能需要 5~15 秒)...' + NC);
320
+
321
+ let firstMessageSent = false;
322
+ try {
323
+ execSync(cmd + ' 2>&1', { encoding: 'utf8', timeout: 45000 });
324
+ firstMessageSent = true;
325
+ console.log(' ' + G + '◀ 返回:' + NC + ' 唤醒成功,Session 已建立 ✅');
326
+ } catch (err) {
327
+ console.log(' ' + Y + '◀ 返回:' + NC + ' ⚠️ 唤醒超时或失败(非致命)');
328
+ console.log(' ' + '手动重试: ' + Y + 'openclaw agent --agent ' + agentId + ' --message "你好"' + NC);
329
+ }
330
+ console.log('');
331
+
332
+ // ── Step 6: 验证 ──
333
+ console.log(B + '[6/6]' + NC + ' 验证创建结果...');
334
+ console.log(' ' + G + '▶ 发起:' + NC + ' 检查 agents.list 是否包含 "' + agentId + '"');
335
+ try {
336
+ const verify = JSON.parse(fs.readFileSync(configPath, 'utf8'));
337
+ const found = verify.agents && verify.agents.list && verify.agents.list.find(a => a.id === agentId);
338
+ if (found) {
339
+ console.log(' ' + G + '◀ 返回:' + NC + ' Agent "' + agentId + '" 已成功登记 ✅');
340
+ } else {
341
+ console.log(' ' + R + '◀ 返回:' + NC + ' ⚠️ 未在配置中找到,请检查');
342
+ }
343
+ } catch (err) {
344
+ console.log(' ' + Y + '◀ 返回:' + NC + ' ⚠️ 验证读取失败(非致命)');
345
+ }
346
+
347
+ console.log('');
348
+ console.log(bar);
349
+ console.log(G + '🎉 Agent "' + agentId + '" 创建完成!' + NC);
350
+ console.log('');
351
+ console.log('下一步: ' + Y + 'openclaw agent --agent ' + agentId + ' --message "你好"' + NC);
352
+ console.log('');
186
353
  }
187
354
 
188
355
  // ============================================================================
@@ -198,6 +365,15 @@ function runRebind() {
198
365
  runRebind();
199
366
  }
200
367
 
368
+ // ============================================================================
369
+ // Fix 兜底修复 (独立模块)
370
+ // ============================================================================
371
+
372
+ function runFix() {
373
+ const fix = require('./fix');
374
+ fix.run();
375
+ }
376
+
201
377
  // ============================================================================
202
378
  // WSL2 安装 (独立模块)
203
379
  // ============================================================================
@@ -288,8 +464,15 @@ timeout /t 2 >nul
288
464
  fs.writeFileSync(batPath, batContent.replace(/\n/g, '\r\n'), 'utf8');
289
465
 
290
466
  // 用 PowerShell 创建带图标的桌面快捷方式 + 刷新桌面
291
- const iconPath = path.join(__dirname, 'assets', 'openclaw.ico');
467
+ // PowerShell 下载图标并创建带图标的桌面快捷方式 + 刷新桌面
468
+ const iconPath = path.join(myClawDir, 'openclaw.ico');
469
+ const iconUrl = 'https://cdn.yiranlaoshi.com/software/myclaw/openclaw.ico';
470
+
292
471
  const psScript = `
472
+ $ErrorActionPreference = 'SilentlyContinue'
473
+ if (-not (Test-Path '${iconPath.replace(/\\/g, '\\\\')}')) {
474
+ Invoke-WebRequest -Uri '${iconUrl}' -OutFile '${iconPath.replace(/\\/g, '\\\\')}' -UseBasicParsing
475
+ }
293
476
  $ws = New-Object -ComObject WScript.Shell
294
477
  $sc = $ws.CreateShortcut('${lnkPath.replace(/\\/g, '\\\\')}')
295
478
  $sc.TargetPath = '${batPath.replace(/\\/g, '\\\\')}'
@@ -776,7 +959,7 @@ function showHelp() {
776
959
  console.log('');
777
960
  console.log('向导 (交互式):');
778
961
  console.log(' prepare 智能初始化(环境检测 + 安装 + Patch)');
779
- console.log(' new 创建新 Agent(分步理解生命周期)');
962
+ console.log(' new 创建新 Agent(无参数=向导,有参数=一键)');
780
963
  console.log(' weixin 微信接入向导(步骤引导 + 教学说明)');
781
964
  console.log(' rebind 微信换绑向导(清空、扫码、换Agent)');
782
965
  console.log('');
@@ -787,6 +970,7 @@ function showHelp() {
787
970
  console.log(' update 自动升级 MyClaw 到最新版本');
788
971
  console.log(' up 升级 + 刷新桌面快捷方式 (= update + bat)');
789
972
  console.log(' open 打开浏览器控制台(自动带 token)');
973
+ console.log(' fix 兜底修复(自动补装 WSL + Chrome,仅限 Windows)');
790
974
  console.log(' wsl2 WSL2 一键安装/修复 (仅限 Windows)');
791
975
  console.log(' bat 在桌面生成一键启动脚本 (仅限 Windows)');
792
976
  console.log(' list 查看注入资源管理列表(智能体/技能/配置)');
@@ -844,6 +1028,8 @@ if (!command || command === 'help' || command === '--help' || command === '-h')
844
1028
  runStart();
845
1029
  } else if (command === 'open') {
846
1030
  runOpen();
1031
+ } else if (command === 'fix') {
1032
+ runFix();
847
1033
  } else if (command === 'wsl2') {
848
1034
  runWsl2();
849
1035
  } else if (command === 'launch') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.104",
3
+ "version": "1.0.112",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "_doc": "MyClaw 注入清单 (auto-generated)。strategy: auto | on | off",
3
- "_generated": "2026-04-01T18:15:05.980Z",
3
+ "_generated": "2026-04-02T13:18:25.628Z",
4
4
  "agents": [
5
5
  {
6
6
  "id": "danci",
package/patch.js CHANGED
@@ -25,6 +25,7 @@ const INJECT_MARKER = '<!-- myclaw-inject -->';
25
25
  const INJECT_FILENAME = 'myclaw-inject.js';
26
26
  const VOICE_SDK_FILENAME = 'voice-input.js';
27
27
  const VOICE_OUTPUT_SDK_FILENAME = 'voice-output.js';
28
+ const TTS_INJECT_FILENAME = 'myclaw-tts.js';
28
29
  const BACKUP_SUFFIX = '.myclaw-backup';
29
30
 
30
31
  /**
@@ -112,6 +113,8 @@ function patch() {
112
113
  const voiceSdkDest = path.join(uiDir, VOICE_SDK_FILENAME);
113
114
  const voiceOutputSdkSrc = path.join(__dirname, 'voice-output', VOICE_OUTPUT_SDK_FILENAME);
114
115
  const voiceOutputSdkDest = path.join(uiDir, VOICE_OUTPUT_SDK_FILENAME);
116
+ const ttsInjectSrc = path.join(__dirname, 'assets', TTS_INJECT_FILENAME);
117
+ const ttsInjectDest = path.join(uiDir, TTS_INJECT_FILENAME);
115
118
  const version = getMyclawVersion();
116
119
 
117
120
  console.log('[myclaw-patch] 📍 找到 control-ui: ' + uiDir);
@@ -156,7 +159,15 @@ function patch() {
156
159
  console.error('[myclaw-patch] ⚠️ TTS SDK 复制失败 (非致命): ' + err.message);
157
160
  }
158
161
 
159
- // 4. Patch index.html(幂等)
162
+ // 5. 复制 TTS 注入脚本
163
+ try {
164
+ fs.copyFileSync(ttsInjectSrc, ttsInjectDest);
165
+ console.log('[myclaw-patch] 📄 TTS 注入脚本已复制: ' + ttsInjectDest);
166
+ } catch (err) {
167
+ console.error('[myclaw-patch] ⚠️ TTS 注入脚本复制失败 (非致命): ' + err.message);
168
+ }
169
+
170
+ // 6. Patch index.html(幂等)
160
171
  try {
161
172
  let html = fs.readFileSync(indexPath, 'utf8');
162
173
 
@@ -172,6 +183,7 @@ function patch() {
172
183
  INJECT_MARKER,
173
184
  '<script src="./' + VOICE_OUTPUT_SDK_FILENAME + '"></script>',
174
185
  '<script src="./' + VOICE_SDK_FILENAME + '"></script>',
186
+ '<script src="./' + TTS_INJECT_FILENAME + '"></script>',
175
187
  '<script src="./' + INJECT_FILENAME + '"></script>',
176
188
  '',
177
189
  ].join('\n');
@@ -249,6 +261,7 @@ function unpatch() {
249
261
  const injectDest = path.join(uiDir, INJECT_FILENAME);
250
262
  const voiceSdkDest = path.join(uiDir, VOICE_SDK_FILENAME);
251
263
  const voiceOutputSdkDest = path.join(uiDir, VOICE_OUTPUT_SDK_FILENAME);
264
+ const ttsInjectDest = path.join(uiDir, TTS_INJECT_FILENAME);
252
265
 
253
266
  // 恢复备份
254
267
  if (fs.existsSync(backupPath)) {
@@ -275,6 +288,12 @@ function unpatch() {
275
288
  console.log('[myclaw-patch] ✅ TTS SDK 已删除');
276
289
  }
277
290
 
291
+ // 删除 TTS 注入脚本
292
+ if (fs.existsSync(ttsInjectDest)) {
293
+ fs.unlinkSync(ttsInjectDest);
294
+ console.log('[myclaw-patch] ✅ TTS 注入脚本已删除');
295
+ }
296
+
278
297
  // 恢复 gateway-cli-*.js 的 Permissions-Policy
279
298
  try {
280
299
  const distParent = path.resolve(uiDir, '..');
@@ -158,6 +158,8 @@ switch (action) {
158
158
  const nowBj = new Date(nowUtc.getTime() + 8 * 60 * 60 * 1000);
159
159
  const ts = `${nowBj.getUTCFullYear()}年${nowBj.getUTCMonth() + 1}月${nowBj.getUTCDate()}日${String(nowBj.getUTCHours()).padStart(2, '0')}:${String(nowBj.getUTCMinutes()).padStart(2, '0')}`;
160
160
 
161
+ const birthMessage = `你好,你的生日是北京时间${ts},请记录这个信息。\n欢迎你来到这个世界,我是你的造物主,我们是伙伴,我叫做孙依然,你可以叫我依然,请多多指教,共同成长。`;
162
+
161
163
  const cmd = `openclaw agent --agent ${agentId} --message ${JSON.stringify(birthMessage)} --json 2>&1`;
162
164
  console.log('\n\x1b[32m$ openclaw agent --agent ' + agentId + ' --message "你好,你的生日..."\x1b[0m\n');
163
165
 
Binary file
@@ -1,309 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8">
5
- <title>OpenClaw - 科技龙虾图标生成器</title>
6
- <style>
7
- body {
8
- background-color: #0f172a;
9
- color: #e2e8f0;
10
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
11
- display: flex;
12
- flex-direction: column;
13
- align-items: center;
14
- justify-content: center;
15
- height: 100vh;
16
- margin: 0;
17
- }
18
- .container {
19
- text-align: center;
20
- background: #1e293b;
21
- padding: 2rem;
22
- border-radius: 12px;
23
- box-shadow: 0 10px 25px rgba(0,0,0,0.5);
24
- border: 1px solid #334155;
25
- position: relative;
26
- overflow: hidden;
27
- }
28
- .container::before {
29
- content: "";
30
- position: absolute;
31
- top: -50%; left: -50%;
32
- width: 200%; height: 200%;
33
- background: conic-gradient(transparent, transparent, transparent, #ef4444);
34
- animation: rotate 4s linear infinite;
35
- z-index: 0;
36
- opacity: 0.15;
37
- }
38
- @keyframes rotate {
39
- 100% { transform: rotate(360deg); }
40
- }
41
- .content {
42
- position: relative;
43
- z-index: 1;
44
- }
45
- h1 {
46
- color: #ef4444;
47
- text-transform: uppercase;
48
- letter-spacing: 2px;
49
- font-size: 1.5rem;
50
- margin-bottom: 20px;
51
- text-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
52
- }
53
- canvas {
54
- image-rendering: pixelated; /* 保持像素边缘锐利 */
55
- border: 2px solid #450a0a;
56
- background-color: transparent;
57
- margin-bottom: 20px;
58
- border-radius: 8px;
59
- box-shadow: 0 0 20px rgba(239, 68, 68, 0.2);
60
- width: 256px; /* 放大展示,但实际画布是 32x32 */
61
- height: 256px;
62
- }
63
- button {
64
- background: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%);
65
- color: #ffffff;
66
- border: none;
67
- padding: 10px 24px;
68
- font-size: 16px;
69
- font-weight: bold;
70
- border-radius: 6px;
71
- cursor: pointer;
72
- transition: all 0.3s ease;
73
- box-shadow: 0 0 15px rgba(239, 68, 68, 0.4);
74
- }
75
- button:hover {
76
- transform: translateY(-2px);
77
- box-shadow: 0 0 25px rgba(239, 68, 68, 0.7);
78
- }
79
- .desc {
80
- margin-top: 15px;
81
- font-size: 0.85rem;
82
- color: #94a3b8;
83
- }
84
- </style>
85
- </head>
86
- <body>
87
-
88
- <div class="container">
89
- <div class="content">
90
- <h1>Hero Lobster / 英雄赤虾</h1>
91
- <!-- 画布分辨率提升,渲染战斗姿态和旋转 -->
92
- <canvas id="iconCanvas" width="256" height="256"></canvas>
93
- <br>
94
- <div style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
95
- <button onclick="downloadIcon()">⬇️ 下载为 PNG 格式</button>
96
- <button onclick="downloadICO()" style="background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%); box-shadow: 0 0 15px rgba(6, 182, 212, 0.4);">⬇️ 下载为 ICO 格式</button>
97
- </div>
98
- <div class="desc">
99
- PNG 为原始图片,ICO 包含 16/32/48/256 四种尺寸,可直接用于桌面图标。
100
- </div>
101
- </div>
102
- </div>
103
-
104
- <script>
105
- // 调色板
106
- const colors = {
107
- '0': null, // 透明背景
108
- 'C': '#fbbf24', // 科技金 (外骨骼描边)
109
- 'B': '#b91c1c', // 暗装甲红 (战甲主体)
110
- 'R': '#ef4444', // 亮装甲红 (巨钳/关键装甲)
111
- 'W': '#00f0ff', // 赛博青 (能量眼/核心高光)
112
- 'D': '#450a0a' // 深红阴影
113
- };
114
-
115
- // 16x16 像素阵列 (手工打造的机甲龙虾)
116
- const pixels16 = [
117
- "00R00000000R00",
118
- "0RBR00C00RBR0",
119
- "0RRRC0D0CRR00",
120
- "00C00BDB00C00",
121
- "00C00RDR00C00",
122
- "000CBBBBBC000",
123
- "0CBC0BBB0CBC0",
124
- "CBBB0CBC0BBBC",
125
- "0CCBBBBBBBCC0",
126
- "00C0BBBBB0C00",
127
- "000C0BBB0C000",
128
- "0000CBBB0C000",
129
- "000CBBBBBC000",
130
- "00CBB0C0BB0C0",
131
- "0CBB0C0C0BBC0",
132
- "CC00CC0CC00CC"
133
- ];
134
-
135
- // 修正对称性和细节,使其完美 16x16
136
- const refined16 = [
137
- "00RR000000RR00",
138
- "00RBR0000RBR00",
139
- "00RRRC00CRRR00",
140
- "0000CBBBBC0000",
141
- "0000CRWBRC0000",
142
- "000CBBBBBBC000",
143
- "00CBCDBBDCBC00",
144
- "0CBBBCBBCBBBC0",
145
- "00CCBBBBBBCC00",
146
- "000CBBBBBBC000",
147
- "0000CBBBBC0000",
148
- "0000CBBBBC0000",
149
- "000CBBBBBBC000",
150
- "00CBBCCBBCCBB0",
151
- "0CBB0C00C0BBC0",
152
- "0CC00000000CC0"
153
- ];
154
-
155
- // 我们重新设计了 32x32 矩阵:保留巨镰,极大化扩充躯干宽度,并增加了一个霸气的全幅扇形尾翼,彻底填满 32x32 空间。
156
- const design32 = [
157
- "000000000R000000000000R000000000",
158
- "00000000RRC0000000000CRR00000000",
159
- "0000000RRRC0000000000CRRR0000000",
160
- "000000RRRRC0000000000CRRRR000000",
161
- "00000RRBRRC0000000000CRRBRR00000",
162
- "0000RBBBRRC0000000000CRRBBBR0000",
163
- "000RBBBRRRC0000000000CRRRBBBR000",
164
- "00RBBBBRRRC0CC0000CC0CRRRBBBBR00",
165
- "0RBBBBRRRRCC0D0000D0CCRRRRBBBBR0",
166
- "RBBBBRRRRRCC0WWDDWW0CCRRRRRBBBBR",
167
- "RBBBBRRRRRCC0BCCCCB0CCRRRRRBBBBR",
168
- "RBBBBRRRRRC0CBBBBBBC0CRRRRRBBBBR",
169
- "RBBBBRRRRRC0CBBBBBBC0CRRRRRBBBBR",
170
- "RBBBBRRRRRC0CBB00BBC0CRRRRRBBBBR",
171
- "RBBBBRRRRRC0CBB00BBC0CRRRRRBBBBR",
172
- "RRBBBRRRRRC0CBBBBBBC0CRRRRRBBBRR",
173
- "RRRBBBRRRRC0CBBBBBBC0CRRRRBBBRRR",
174
- "RRRRBBRRRRC0CBBBBBBC0CRRRRBRRRRR",
175
- "RRRRRRRRRRC0CCBBBBCC0CRRRRRRRRRR",
176
- "RRRRRRRRRRC00CBBBBC00CRRRRRRRRRR",
177
- "RRRRRRRRRRC00CBBBBC00CRRRRRRRRRR",
178
- "RRRRRRRRRRCCCCCBBCCCCCRRRRRRRRRR",
179
- "RRRRRRRRRCBBBBBBBBBBBBCRRRRRRRRR",
180
- "RRRRRRRRCBBBBC0BB0CBBBBCRRRRRRRR",
181
- "CCCCRRRCBBBBC00CC00CBBBBCRRRCCCC",
182
- "BBBBBBBCCBBBC0000000CBBBCCBBBBBB",
183
- "BBBBBBBBBBBBC00000000BBBBBBBBBBB",
184
- "BBBBBBBBBBBBC00000000BBBBBBBBBBB",
185
- "BBBBBBBBBBBBC00000000BBBBBBBBBBB",
186
- "BBBBBBBBBBBBC00000000BBBBBBBBBBB",
187
- "BBBBBBBBBBBBC00000000BBBBBBBBBBB",
188
- "CCCCCCCCCCCCC00000000CCCCCCCCCCC"
189
- ];
190
-
191
- const canvas = document.getElementById('iconCanvas');
192
- const ctx = canvas.getContext('2d');
193
-
194
- // 绘制函数
195
- function drawLobster() {
196
- // 背景渐变:从黑到蓝
197
- const gradient = ctx.createLinearGradient(0, 0, 0, 256);
198
- gradient.addColorStop(0, '#000000');
199
- gradient.addColorStop(1, '#000088');
200
- ctx.fillStyle = gradient;
201
- ctx.fillRect(0, 0, 256, 256);
202
-
203
- ctx.save();
204
- // 平移到画布中心
205
- ctx.translate(128, 128);
206
- // 旋转135度,使得原本朝上(-Y)的头部,指向左下角(-X,+Y方向)
207
- ctx.rotate(-135 * Math.PI / 180);
208
-
209
- // 设定每个“虚拟像素块”的缩放尺寸
210
- const size = 5.5;
211
- const offset = -(32 * size) / 2;
212
-
213
- for (let y = 0; y < 32; y++) {
214
- for (let x = 0; x < 32; x++) {
215
- const char = design32[y][x];
216
- if (char && colors[char]) {
217
- ctx.fillStyle = colors[char];
218
- // 宽高加上0.5像素防止抗锯齿在不同浏览器上产生像素缝隙
219
- ctx.fillRect(offset + x * size, offset + y * size, size + 0.5, size + 0.5);
220
- }
221
- }
222
- }
223
- ctx.restore();
224
- }
225
-
226
- // 初始化绘制
227
- drawLobster();
228
-
229
- // 导出 PNG
230
- function downloadIcon() {
231
- const dataURL = canvas.toDataURL('image/png');
232
- const link = document.createElement('a');
233
- link.download = 'openclaw-tech-lobster.png';
234
- link.href = dataURL;
235
- link.click();
236
- }
237
-
238
- // ====== ICO 生成逻辑 ======
239
- // 将 Canvas 缩放到指定尺寸并返回 PNG 的 Uint8Array
240
- function canvasToPngBytes(srcCanvas, targetSize) {
241
- const tmp = document.createElement('canvas');
242
- tmp.width = targetSize;
243
- tmp.height = targetSize;
244
- const tctx = tmp.getContext('2d');
245
- // 对小尺寸禁用平滑,保持像素锐利
246
- tctx.imageSmoothingEnabled = (targetSize > 48);
247
- tctx.drawImage(srcCanvas, 0, 0, targetSize, targetSize);
248
- const dataURL = tmp.toDataURL('image/png');
249
- const base64 = dataURL.split(',')[1];
250
- const binary = atob(base64);
251
- const bytes = new Uint8Array(binary.length);
252
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
253
- return bytes;
254
- }
255
-
256
- function buildICO(pngArrays, sizes) {
257
- // ICO 文件结构:
258
- // ICONDIR (6 bytes) + N x ICONDIRENTRY (16 bytes each) + N x PNG data
259
- const numImages = pngArrays.length;
260
- const headerSize = 6 + numImages * 16;
261
- let totalSize = headerSize;
262
- for (const png of pngArrays) totalSize += png.length;
263
-
264
- const buffer = new ArrayBuffer(totalSize);
265
- const view = new DataView(buffer);
266
-
267
- // ICONDIR header
268
- view.setUint16(0, 0, true); // reserved
269
- view.setUint16(2, 1, true); // type: 1 = ICO
270
- view.setUint16(4, numImages, true); // image count
271
-
272
- let dataOffset = headerSize;
273
- for (let i = 0; i < numImages; i++) {
274
- const entryOffset = 6 + i * 16;
275
- const s = sizes[i];
276
- const pngData = pngArrays[i];
277
-
278
- view.setUint8(entryOffset + 0, s >= 256 ? 0 : s); // width (0 = 256)
279
- view.setUint8(entryOffset + 1, s >= 256 ? 0 : s); // height
280
- view.setUint8(entryOffset + 2, 0); // color palette count
281
- view.setUint8(entryOffset + 3, 0); // reserved
282
- view.setUint16(entryOffset + 4, 1, true); // color planes
283
- view.setUint16(entryOffset + 6, 32, true); // bits per pixel
284
- view.setUint32(entryOffset + 8, pngData.length, true); // data size
285
- view.setUint32(entryOffset + 12, dataOffset, true); // data offset
286
-
287
- // 写入 PNG 数据
288
- const dst = new Uint8Array(buffer, dataOffset, pngData.length);
289
- dst.set(pngData);
290
- dataOffset += pngData.length;
291
- }
292
-
293
- return new Blob([buffer], { type: 'image/x-icon' });
294
- }
295
-
296
- function downloadICO() {
297
- const sizes = [16, 32, 48, 256];
298
- const pngArrays = sizes.map(s => canvasToPngBytes(canvas, s));
299
- const blob = buildICO(pngArrays, sizes);
300
- const url = URL.createObjectURL(blob);
301
- const link = document.createElement('a');
302
- link.download = 'openclaw-tech-lobster.ico';
303
- link.href = url;
304
- link.click();
305
- URL.revokeObjectURL(url);
306
- }
307
- </script>
308
- </body>
309
- </html>