@aiyiran/myclaw 1.0.105 → 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.
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
@@ -365,6 +365,15 @@ function runRebind() {
365
365
  runRebind();
366
366
  }
367
367
 
368
+ // ============================================================================
369
+ // Fix 兜底修复 (独立模块)
370
+ // ============================================================================
371
+
372
+ function runFix() {
373
+ const fix = require('./fix');
374
+ fix.run();
375
+ }
376
+
368
377
  // ============================================================================
369
378
  // WSL2 安装 (独立模块)
370
379
  // ============================================================================
@@ -455,8 +464,15 @@ timeout /t 2 >nul
455
464
  fs.writeFileSync(batPath, batContent.replace(/\n/g, '\r\n'), 'utf8');
456
465
 
457
466
  // 用 PowerShell 创建带图标的桌面快捷方式 + 刷新桌面
458
- 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
+
459
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
+ }
460
476
  $ws = New-Object -ComObject WScript.Shell
461
477
  $sc = $ws.CreateShortcut('${lnkPath.replace(/\\/g, '\\\\')}')
462
478
  $sc.TargetPath = '${batPath.replace(/\\/g, '\\\\')}'
@@ -954,6 +970,7 @@ function showHelp() {
954
970
  console.log(' update 自动升级 MyClaw 到最新版本');
955
971
  console.log(' up 升级 + 刷新桌面快捷方式 (= update + bat)');
956
972
  console.log(' open 打开浏览器控制台(自动带 token)');
973
+ console.log(' fix 兜底修复(自动补装 WSL + Chrome,仅限 Windows)');
957
974
  console.log(' wsl2 WSL2 一键安装/修复 (仅限 Windows)');
958
975
  console.log(' bat 在桌面生成一键启动脚本 (仅限 Windows)');
959
976
  console.log(' list 查看注入资源管理列表(智能体/技能/配置)');
@@ -1011,6 +1028,8 @@ if (!command || command === 'help' || command === '--help' || command === '-h')
1011
1028
  runStart();
1012
1029
  } else if (command === 'open') {
1013
1030
  runOpen();
1031
+ } else if (command === 'fix') {
1032
+ runFix();
1014
1033
  } else if (command === 'wsl2') {
1015
1034
  runWsl2();
1016
1035
  } else if (command === 'launch') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.105",
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-02T12:35:21.624Z",
3
+ "_generated": "2026-04-02T13:18:25.628Z",
4
4
  "agents": [
5
5
  {
6
6
  "id": "danci",
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>