Files
claude-pet/README.md
T
JianMiauandClaude Fable 5 56fd8448ba 用 electron-builder 打包成 Windows exe
摘要:
加上 electron-builder 設定,`npm run dist` 產出 NSIS 安裝檔與免安裝版,並修正打包後才會出現的路徑問題。

根本原因:
原本三處路徑都假設程式是攤在資料夾裡跑的,打包成 asar 後會壞:
1. hook 腳本路徑會落在 app.asar 內,而 Claude Code 是用 `node <路徑>` 執行它,node 讀不到 asar 裡的檔案,hook 會直接失敗。
2. pets 資料夾同樣在 asar 內,使用者無法自己丟寵物進去,選單「開啟寵物資料夾」也開不起來。
3. 開機自動啟動寫入的是 `electron.exe + 專案路徑`,打包後沒有 node_modules,登錄值會指向不存在的檔案。

影響:
沒有可散佈的執行檔;直接打包的話 hook、換寵物、開機啟動三個功能都會壞掉。

修法:
- 新增 lib/paths.js:unpacked() 把 app.asar 換成 app.asar.unpacked,isPackaged() 以 process.defaultApp 判斷是否為打包版。
- build.asarUnpack 把 hook/ 與 pets/ 解到 asar 外,hooks-installer 與 main.js 的 PETS_DIR 都改走 unpacked()。
- autostart 打包後改用 process.execPath,並跳過開發版才需要的 electron.exe 存在檢查。
- NSIS 設為 per-user、可改安裝路徑、建立桌面與開始選單捷徑,解除安裝不刪 ~/.claude-pet。
- 新增 build/icon.ico(16–256,七種尺寸),取自小念 spritesheet 第 0 列第 6 欄的頭肩方形裁切。
- dist/ 加入 .gitignore;README 補上「打包成 exe」一節與安裝檔的使用方式。

驗證:
以 ELECTRON_RUN_AS_NODE 執行打包後的 exe(不開視窗)載入打包內的 lib,確認 isPackaged 為 true、hook 路徑指到 app.asar.unpacked 且檔案存在、autostart 指到 exe 本身、pets 掃得到 xiao-nian、renderer 讀得到 asar 內的 index.html;另確認 exe 版本資訊與七種尺寸圖示皆已嵌入。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 11:03:11 +08:00

341 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Claude Pet
**Codex Pets 相容的 Claude Code 桌面寵物。**
把 Codex `/hatch` 產出的寵物包原封不動拿來用,讓它站在桌面上,即時反映 Claude Code 正在做什麼:思考、等你授權、失敗、完成……還會盯著你的滑鼠游標看。
![狀態總覽](docs/states.png)
> 上圖由左至右:SessionStart 揮手、工作中、等待授權、完成時跳躍、檢視成果。氣泡會標出是哪個專案的 session。
---
## 目錄
- [特色](#特色)
- [需求](#需求)
- [快速開始](#快速開始)
- [日常使用](#日常使用)
- [行為對照表](#行為對照表)
- [寵物包格式(Codex V2](#寵物包格式codex-v2)
- [新增 / 切換寵物](#新增--切換寵物)
- [設定檔](#設定檔)
- [架構與檔案](#架構與檔案)
- [HTTP API](#http-api)
- [打包成 exe](#打包成-exe)
- [疑難排解](#疑難排解)
- [移除](#移除)
---
## 特色
- **完整實作 Codex V2 pet contract** — 8 欄 × 11 列、192×208 cell 的 spritesheet9 個標準動作列 + 16 個看向方向,**每一幀的時長都照 Codex 規格**,動起來跟在 Codex app 裡一模一樣。
- **16 方向追視** — 游標在哪,她就看哪(000 = 正上方、順時針 22.5° 一格)。游標靠太近或靜止 6 秒就回到 idle 呼吸動畫。
- **與 Codex 同尺寸** — 100% 就是 Codex 桌面寵物的大小(126 × 137),高度用 Codex 的 `ceil(寬 × 208/192)` 算,兩邊並排看起來一樣大。
- **真正的桌面寵物** — 透明、無邊框、永遠置頂;透明區域**點穿**,只有壓在人物像素上滑鼠才會被吃掉,不擋你操作底下的視窗。
- **不搶焦點** — 視窗設為 non-focusable,點她不會讓終端機失焦。
- **拖曳 / 點擊 / 右鍵** — 拖曳時依方向播 running-left / running-right 並記住位置;點一下揮手;右鍵或系統匣開選單。
- **對話氣泡** — 完成、需要授權、有問題要問你、工具失敗、整理記憶中……一眼看出 Claude Code 在等什麼。
- **多 session** — 同時開好幾個 Claude Code 時,以 `waiting > running > review > idle` 的優先序顯示最需要你注意的那個。
- **Codex 寵物自動出現** — 預設也掃描 `~/.codex/pets`Codex hatch 出來的寵物直接在選單裡切換。
- **Hook 零負擔** — hook 腳本約 100 ms 完成、寵物沒開也靜默 exit 0,不會拖慢或卡住 Claude Code。
## 需求
| 項目 | 說明 |
|---|---|
| 作業系統 | Windows 10 / 11(透明視窗、點穿、系統匣、開機啟動皆以 Windows 實作) |
| Node.js | 18 以上(開發時為 24.19 |
| Claude Code | 2.1 以上(使用 `SessionStart``PermissionRequest``PostToolUseFailure``StopFailure``PreCompact` 等事件) |
| 寵物包 | 一個 Codex V2 寵物包(`pet.json` + `spritesheet.webp`)。本專案內附 `pets/xiao-nian`(小念) |
## 快速開始
### 用安裝檔(一般使用)
執行 `dist\Claude Pet-<版本>-setup.exe`(見[打包成 exe](#打包成-exe)),裝在 `%LOCALAPPDATA%\Programs\Claude Pet`,不需要系統管理員權限。裝完從開始選單開啟,再用系統匣選單的 **Claude Code hooks → 安裝** 接上 Claude Code。
### 從原始碼跑(開發)
```powershell
cd E:\Project\Test\AI\Claude\claude-pet
npm install
npm run hooks:install # 把 13 個 hook 合併進 ~/.claude/settings.json(會先備份到 ~/.claude/backups
npm start # 啟動寵物
npm run autostart:install # (可選)開機自動啟動
```
> **注意**
> 1. 已開啟的 Claude Code session 不會重新讀 hooks**重開一個新 session** 才會看到她反應。
> 2. 若 `npm install` 後 `node_modules\electron\dist\` 是空的,請看 [疑難排解](#electron-裝完沒有-electronexe)。
## 日常使用
| 操作 | 效果 |
|---|---|
| 左鍵點一下 | 揮手(若有非固定的氣泡,先關閉氣泡) |
| 左鍵拖曳 | 搬家;往左播 running-left、往右播 running-right;放開後位置寫入設定檔 |
| 右鍵 / 系統匣左鍵 | 開啟選單 |
| 滑鼠靠近 | 盯著游標看(可在選單關閉) |
**選單功能**
- **切換寵物** — 列出所有找到的寵物;v1(9 列)寵物會標示「無追視」
- **大小** — 50% / 75% / 100% / 125% / 150%,以腳底為基準縮放(**100% = Codex 桌面寵物的大小**
- **跟著游標看**、**永遠置頂**、**開機自動啟動** — 勾選即生效
- **動作測試** — 不用真的跑 Claude Code 也能看每個動畫(idle / running / waiting / review / failed / jumping / waving
- **Claude Code hooks** — 安裝 / 移除 hooks
- **回到預設位置** — 跑到螢幕外面時用
- **開啟設定檔** / **開啟事件紀錄**
- **結束**
## 行為對照表
| Claude Code hook | 條件 | 動作 | 氣泡 |
|---|---|---|---|
| `SessionStart` | `source = startup` | `waving` 一次 → `idle` | 👋 嗨!(專案) |
| `UserPromptSubmit` | | `running` | (關閉舊氣泡) |
| `PreToolUse` | 一般工具 | `running` | |
| `PreToolUse` | `AskUserQuestion` | `waiting` | ❓ 有問題想問你 |
| `PreToolUse` | `ExitPlanMode` | `waiting` | 📋 計畫等你確認 |
| `PostToolUse` | | `running` | |
| `PostToolUseFailure` | | `failed` 一次 → `running` | 😵 〈工具〉失敗了 |
| `PermissionRequest` | | `waiting` | 🔐 需要你授權:〈工具〉 |
| `Notification` | `permission_prompt` | `waiting` | 🔐 需要你授權 |
| `Notification` | `idle_prompt` | `waiting` | 💤 在等你回覆 |
| `Notification` | `elicitation_dialog` | `waiting` | ❓ 有問題想問你 |
| `Notification` | 其他 | (不變) | 💬 訊息內容 |
| `Stop` | `stop_hook_active = false` | `jumping` 一次 → `review` 6 秒 → `idle` | ✅ 完成!(專案) |
| `StopFailure` | | `failed` 兩次 → `idle` | ❌ 錯誤訊息 |
| `SubagentStart` / `SubagentStop` | | `running` | |
| `PreCompact` | | `running` | 🗜️ 整理記憶中… |
| `SessionEnd` | | 移除該 session | 最後一個結束時 👋 掰掰 |
| (無事件) | 游標移動 | 16 方向 `look` | |
| (無事件) | 拖曳 | `running-left` / `running-right` | |
規則:
- **一次性動作**waving / jumping / failed)播完自動回到該 session 的基礎狀態。
- **基礎狀態**依 session 彙整:`waiting(3) > running(2) > review(1) > idle(0)`,取最高者顯示。
- 30 分鐘沒有任何事件的 session 會被視為結束而忽略(避免沒送 `SessionEnd` 的終端機卡住狀態)。
- `waiting` 的氣泡是固定的,狀態離開 `waiting` 時自動消失。
## 寵物包格式(Codex V2
與 Codex 完全相同,可直接互通。一個寵物是一個資料夾:
```
<pet-id>/
├── pet.json
└── spritesheet.webp (或 .png
```
`pet.json`
```json
{
"id": "xiao-nian",
"displayName": "小念",
"description": "一句話描述",
"spriteVersionNumber": 2,
"spritesheetPath": "spritesheet.webp"
}
```
Spritesheet**1536 × 2288**8 欄 × 11 列,每格 **192 × 208**,透明背景。
| 列 | 狀態 | 使用欄 | 每幀時長(ms) |
|---|---|---|---|
| 0 | idle | 05 | 280, 110, 110, 140, 140, 320 |
| 1 | running-right | 07 | 120 ×7, 220 |
| 2 | running-left | 07 | 120 ×7, 220 |
| 3 | waving | 03 | 140 ×3, 280 |
| 4 | jumping | 04 | 140 ×4, 280 |
| 5 | failed | 07 | 140 ×7, 240 |
| 6 | waiting | 05 | 150 ×5, 260 |
| 7 | running(工作中,不是跑步) | 05 | 120 ×5, 220 |
| 8 | review | 05 | 150 ×5, 280 |
| 9 | look A | 07 | 000, 022.5, 045, 067.5, 090, 112.5, 135, 157.5 度 |
| 10 | look B | 07 | 180, 202.5, 225, 247.5, 270, 292.5, 315, 337.5 度 |
- look 列的 **000 是正上方(12 點鐘)**,順時針。正面/中立不在 16 格裡,那是游標死區,退回 idle。
- 列 0 第 6 欄若有圖會被當作「中立正面」,拿來做系統匣圖示。
- 沒有 `spriteVersionNumber: 2` 的舊版(8×9、1536×1872)寵物也能用,只是沒有追視。
> 規格來源:Codex 的 `hatch-pet` skill`~/.codex/skills/hatch-pet/references/codex-pet-contract.md`、`animation-rows.md`)。
## 新增 / 切換寵物
1. **用 Codex hatch** — 在 Codex 裡 `/hatch`,完成後寵物會在 `%USERPROFILE%\.codex\pets\<id>\`,本程式預設掃描該資料夾,選單「切換寵物 → 重新掃描」就會出現。
2. **手動放入** — 把寵物資料夾複製到 `pets\<id>\`(選單「開啟寵物資料夾」可直接打開)。
3. **其他位置** — 在設定檔 `petSources` 加路徑。
切換後立即生效,不用重啟。
## 設定檔
位置:`%USERPROFILE%\.claude-pet\config.json`(第一次啟動後自動產生,選單「開啟設定檔」可直接開)。
| 欄位 | 預設 | 說明 |
|---|---|---|
| `activePetId` | `"xiao-nian"` | 目前使用的寵物 id |
| `petSources` | `[<app>/pets, ~/.codex/pets]` | 掃描寵物的資料夾清單;程式自己的 `pets/` 永遠會被加在最前面 |
| `scale` | `1` | 縮放:0.5 / 0.75 / 1 / 1.25 / 1.5。`1` 表示 126 × 137,與 Codex 桌面寵物同大小 |
| `position` | `null` | 視窗左上角座標;`null` 為主螢幕右下角。若座標落在所有螢幕之外會自動拉回 |
| `alwaysOnTop` | `true` | 永遠置頂 |
| `followCursor` | `true` | 追視游標 |
| `port` | `17333` | 接收 hook 事件的本機埠(改了之後 hook 端要設環境變數 `CLAUDE_PET_PORT` |
事件紀錄:`%USERPROFILE%\.claude-pet\events.log`(超過 2 MB 自動清空)。
## 架構與檔案
```
Claude Code ──hookstdin JSON)──▶ hook/claude-pet-hook.js ──POST /event──▶ 127.0.0.1:17333
│ Electron mainmain.js
│ 視窗 / 系統匣 / 游標輪詢 / 拖曳 / 設定
▼ IPC
renderer/renderer.js
狀態機 + 幀動畫 + 追視 + 氣泡 + alpha 點穿判定
```
| 檔案 | 職責 |
|---|---|
| `main.js` | Electron 主程序:透明置頂視窗、系統匣選單、HTTP 伺服器、游標輪詢(50 ms)、拖曳(60 fps)、設定讀寫、寵物掃描 |
| `preload.js` | 以 contextBridge 暴露白名單 IPC 頻道給 renderersandbox 模式) |
| `renderer/renderer.js` | Codex V2 幀時序播放器、session 狀態彙整、一次性動作、看向方向計算(含遲滯防抖)、氣泡、滑鼠 alpha 判定 |
| `renderer/index.html``style.css` | 版面與氣泡樣式(CSP 僅允許 self / data: |
| `hook/claude-pet-hook.js` | Claude Code hook 端:讀 stdin → 擷取欄位 → POST800 ms 逾時、永遠 exit 0 |
| `lib/hooks-installer.js` | 安全地合併 / 移除 `~/.claude/settings.json` 的 hooks(保留其他設定與其他 hooks,先備份) |
| `lib/autostart.js` | `HKCU\Software\Microsoft\Windows\CurrentVersion\Run\ClaudePet` 登錄值;開發時指向 `electron.exe + 專案路徑`,打包後直接指向那顆 exe |
| `lib/paths.js` | 開發/打包兩種情境的路徑解析(`app.asar``app.asar.unpacked`、是否為打包版) |
| `scripts/*.js` | 上述兩者的 CLI 包裝 |
| `pets/` | 內附寵物 |
| `docs/` | README 用圖 |
| `build/icon.ico` | exe 與安裝檔的圖示,取自小念 spritesheet 第 0 列第 6 欄(中立正面)的頭肩方形裁切 |
設計重點:
- **點穿**:視窗預設 `setIgnoreMouseEvents(true, { forward: true })`renderer 在 mousemove 時讀 canvas 該點 alpha(或是否在氣泡上)來即時切換,所以透明處永遠點得到底下的視窗。
- **拖曳**:由主程序用 `screen.getCursorScreenPoint()` 每 16 ms 更新視窗位置,游標離開視窗也不會掉;方向由水平位移決定。
- **顯示尺寸與 sprite 來源尺寸分開**:`CELL_W/CELL_H`192 × 208)只用來從 spritesheet 取格子,畫到畫面上的是 `PET_W/PET_H`126 × 137Codex 的 mascot 尺寸)。視窗寬度另有 `MIN_WIN_W = 240` 的下限,免得寵物變小後氣泡被截掉。
- **Spritesheet 以 data URL 傳給 renderer**,避免 `file://` 跨來源污染 canvas 導致 `getImageData` 失敗。
- **hook 合併規則**:以指令中是否含 `claude-pet-hook` 辨識自己的項目,重裝只替換自己的、不動別人的。
## HTTP API
只監聽 `127.0.0.1`
| 方法 | 路徑 | 說明 |
|---|---|---|
| `POST` | `/event` | 接收事件(JSON,≤ 64 KB),回 `204` |
| `GET` | `/state` | 目前狀態:`{"anim","base","sessions","pet","scale"}` |
| `GET` | `/health` | 回 `ok` |
`POST /event` 的 payloadhook 腳本送出的格式):
```json
{
"event": "Stop",
"sessionId": "uuid",
"cwd": "E:\\Project\\foo",
"toolName": null,
"notificationType": null,
"message": null,
"error": null,
"source": null,
"stopHookActive": false,
"ts": 1787278568523
}
```
手動觸發範例:
```powershell
# 模擬需要授權
curl -X POST http://127.0.0.1:17333/event -H "content-type: application/json" `
-d '{"event":"Notification","sessionId":"demo","cwd":"D:/demo","notificationType":"permission_prompt"}'
# 模擬完成
curl -X POST http://127.0.0.1:17333/event -H "content-type: application/json" `
-d '{"event":"Stop","sessionId":"demo","cwd":"D:/demo"}'
# 查狀態
curl http://127.0.0.1:17333/state
```
也可以送 `{"event":"__test","anim":"waiting"}``idle|running|waiting|review|failed|jumping|waving|clear`)直接指定動畫,與選單「動作測試」相同。
## 打包成 exe
```powershell
npm run dist # 安裝檔 + 免安裝版
npm run pack # 只產免安裝版,快很多,開發時驗證用
```
產物在 `dist\`
| 檔案 | 說明 |
|---|---|
| `Claude Pet-<版本>-setup.exe` | NSIS 安裝檔(約 100 MB)。per-user 安裝、不需要系統管理員權限,預設裝到 `%LOCALAPPDATA%\Programs\Claude Pet`,可自選路徑,會建立桌面與開始選單捷徑 |
| `win-unpacked\Claude Pet.exe` | 免安裝版,整個資料夾複製走就能直接執行 |
第一次建置時 electron-builder 會從 GitHub 抓 NSIS 與相關工具到 `%LOCALAPPDATA%\electron-builder\Cache`,需要網路,之後就會走快取。
**打包後和原始碼跑有三個地方不一樣**(都已經處理好,改程式時要留意):
- **`hook/``pets/` 不放進 asar**`build.asarUnpack`)。前者是因為 Claude Code 用 `node <路徑>` 執行 hook,而 node 讀不到 asar 裡的檔案;後者是因為使用者要能自己丟寵物進去、選單「開啟寵物資料夾」也要打得開。程式裡由 `lib/paths.js``unpacked()``app.asar` 換成 `app.asar.unpacked`
- **開機自動啟動**寫入的指令是那顆 exe 本身(`process.execPath`),不再是 `electron.exe + 專案路徑`。判斷方式是 `lib/paths.js``isPackaged()`
- **hook 指令仍然需要 `node` 在 PATH 裡**。安裝路徑含空白(`Claude Pet`)時會自動加引號。
換圖示就換掉 `build/icon.ico`(要含 256×256)。目前這顆是從 `pets/xiao-nian/spritesheet.webp` 第 0 列第 6 欄(中立正面)取頭肩方形裁切產生的。
## 疑難排解
### Electron 裝完沒有 `electron.exe`
`npm install` 成功但 `node_modules\electron\dist\` 是空的 → 你的 `~/.npmrc` 可能有 `allow-scripts=...`(npm 11 的供應鏈防護,只放行清單上套件的 postinstall)。兩種解法:
```powershell
node node_modules\electron\install.js # 手動跑一次下載
# 或把 electron 加進白名單
npm config set allow-scripts=@anthropic-ai/claude-code,electron --location=user
```
### 寵物對 Claude Code 沒反應
1. 確認 hooks 已安裝:`npm run hooks:install`,並**重開 Claude Code session**。
2. 確認寵物在跑:`curl http://127.0.0.1:17333/health` 應回 `ok`
3.`%USERPROFILE%\.claude-pet\events.log` 有沒有事件進來。有事件但沒動 → renderer 問題;沒事件 → hook 沒被呼叫。
4. hook 指令是 `node E:/.../claude-pet-hook.js`Claude Code 執行 hook 的環境要找得到 `node`PATH)。
### 啟動時跳出「無法監聽 127.0.0.1:17333」
已經有一隻在跑(本程式有單一實例鎖,通常不會發生),或埠被別的程式佔用。改設定檔 `port`,並在 Claude Code 的環境設 `CLAUDE_PET_PORT` 為同一個值。
### 她跑到螢幕外 / 換了螢幕配置看不到
選單「回到預設位置」,或刪掉設定檔的 `position`。啟動時若座標不在任何螢幕的工作區內會自動拉回。
### 專案搬家了
重新執行 `npm run hooks:install``npm run autostart:install`(兩者都以目前路徑重寫),寵物資料夾會自動跟著程式走。
## 移除
先在系統匣選單關掉 **開機自動啟動**、並用 **Claude Code hooks → 移除** 拆掉 hooks,然後結束程式。從原始碼跑的話也可以下指令:
```powershell
npm run hooks:uninstall # 從 ~/.claude/settings.json 移除所有 Claude Pet hook(先備份)
npm run autostart:uninstall # 移除開機啟動登錄值
```
裝過安裝檔的話,到「設定 → 應用程式 → 已安裝的應用程式」移除 **Claude Pet**(解除安裝不會動到 `%USERPROFILE%\.claude-pet\`)。最後刪掉專案資料夾與 `%USERPROFILE%\.claude-pet\` 即可。
---
## 致謝
- 寵物規格與動畫時序來自 OpenAI Codex 的 `hatch-pet` skillCodex V2 pet contract)。
- 內附寵物「小念」由 Codex `/hatch` 產生。