Files
claude-pet/README.md
T
JianMiauandClaude Fable 5 48b10c43de 滑鼠移到寵物身上時跳躍三次,hover 判定改用矩形
摘要:
把 hover 跳躍加回來,並把觸發判定從逐像素 alpha 改成人物方框。

根本原因:
1. hover 跳躍在「逐項對照 Codex」那次被換成 proximity 版面(理由是 Codex 的
   respondToHover 全 app 沒有開啟),上一次移除浮出選單鈕時把整套版面拿掉,
   跳躍也跟著沒了,變成 hover 完全沒有反應。
2. 先前的 hover 判定用 ctx.getImageData 逐像素測 alpha。sprite 每一格的輪廓不同,
   游標停著不動時,人物身上同一點會隨著動畫在不透明與透明之間變化,
   造成 setHovered 在 true/false 間高速抖動(實測 2 ms 內來回多次),
   連帶 pet:ignore-mouse 也被瘋狂切換。

影響:
滑鼠移到寵物身上沒有任何反應;即使觸發也會因抖動而重複進出 hover 狀態。

修法:
- 還原 setHovered:進入時 play("jumping", STATE_REPEATS),對應 Codex 的
  `respondToHover && hovered ? "jumping" : state` 走 f() 播 3 次後沉澱;
  一次性動作或拖曳進行中不打斷,離開視窗時清除。
- 新增 overPetArea:hover 用 canvas 的矩形判定,與 Codex 的 pet-area-hover-changed
  同樣是用 rect 算的。點穿判定維持逐像素 alpha,透明處仍然點得到底下的視窗。
- README 的日常使用、行為對照表、與 Codex 的對照三處同步更新,
  對照表補上 jumping 與 hover 判定兩列。

驗證:
- 在 renderer 暫時加診斷 log 實測,確認進入人物區時
  `setHovered true overlay=null` 之後 overlay 變成 jumping,觸發路徑正確;
  同一份 log 也是抖動問題的證據來源。改成矩形判定後移除診斷。
- 以 ELECTRON_RUN_AS_NODE 讀打包後 app.asar:版本 2.0.0、hover 跳躍與矩形判定都在、
  點穿仍用 alpha、診斷已移除、無選單鈕殘留、右鍵選單仍在,九項全過。

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

413 lines
24 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)
- [新增 / 切換寵物](#新增--切換寵物)
- [設定檔](#設定檔)
- [架構與檔案](#架構與檔案)
- [與 Codex 的對照](#與-codex-的對照)
- [HTTP API](#http-api)
- [打包成 exe](#打包成-exe)
- [疑難排解](#疑難排解)
- [移除](#移除)
---
## 特色
- **完整實作 Codex V2 pet contract** — 8 欄 × 11 列、192×208 cell 的 spritesheet9 個標準動作列 + 16 個看向方向,**每一幀的時長都照 Codex 規格**,動起來跟在 Codex app 裡一模一樣。
- **行為邏輯逐項對照 Codex** — 狀態優先序、各狀態到期時間、動畫時序、hover 版面、初次問候、拖曳方向判定……都是從 Codex app 的 `app.asar` 讀出來的數值(見[與 Codex 的對照](#與-codex-的對照))。
- **16 方向追視(選配,預設關)** — 開啟後游標在哪她就看哪(000 = 正上方、順時針 22.5° 一格),追視時是靜態擺姿。Codex 的寵物**不看滑鼠**——它看的是 quick chat 的文字游標與 computer-use 的虛擬游標,所以預設關閉以求一致;喜歡的話在選單打開。
- **自動修正圖檔缺陷** — Codex 的 hatch 產生器有時會把整列 look 格子畫得比 idle 小(內附的小念第 10 列就小了 22%,游標一往下移人物就縮水)。以整列中位數偵測、用腳底當支點放大回正確身高;單格的姿勢差異不動。Codex 本身沒有做這件事。
- **與 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`(小念) |
## 快速開始
### 直接用打包好的(一般使用)
三種都不需要系統管理員權限,選一種即可(見[打包成 exe](#打包成-exe)):
| 方式 | 檔案 | 適合 |
|---|---|---|
| **免安裝資料夾**(推薦) | `Claude Pet-<版本>-win.zip` | 解壓到任何地方,點兩下裡面的 `Claude Pet.exe` 就跑。啟動快、寵物放旁邊的 `pets\`、適合開機自動啟動 |
| 安裝檔 | `Claude Pet-<版本>-setup.exe` | 裝到 `%LOCALAPPDATA%\Programs\Claude Pet`,有開始選單與桌面捷徑 |
| 免安裝單檔 | `Claude Pet-<版本>-portable.exe` | 只想帶一個檔案走。代價是**每次啟動都要重新解壓約 200 MB**,開得比較慢 |
開起來之後用系統匣選單的 **Claude Code hooks → 安裝** 接上 Claude Code,然後**重開一個新的 Claude Code session**。
### 從原始碼跑(開發)
```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;放開後位置寫入設定檔 |
| 右鍵 / 系統匣左鍵 | 開啟選單 |
| 滑鼠移到她身上 | 跳躍 3 次後回到原本狀態(Codex 的 hover 行為) |
| 滑鼠靠近 | 盯著游標看(選單開啟「跟著游標看」後才有) |
**選單功能**
- **切換寵物** — 列出所有找到的寵物;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 | 條件 | 動作 | 氣泡 |
|---|---|---|---|
| (程式啟動) | 該寵物第一次出現 | `waving` × 3 → 慢速 `idle` | 👋 嗨,我是〈寵物名〉(8 秒) |
| `SessionStart` | `source = startup` | `idle` | 👋 嗨!(專案) |
| `UserPromptSubmit` | | `running` | (關閉舊氣泡) |
| `PreToolUse` | 一般工具 | `running` | |
| `PreToolUse` | `AskUserQuestion` | `waiting` | ❓ 有問題想問你 |
| `PreToolUse` | `ExitPlanMode` | `waiting` | 📋 計畫等你確認 |
| `PostToolUse` | | `running` | |
| `PostToolUseFailure` | | `running`(不變) | 😵 〈工具〉失敗了 |
| `PermissionRequest` | | `waiting` | 🔐 需要你授權:〈工具〉 |
| `Notification` | `permission_prompt` | `waiting` | 🔐 需要你授權 |
| `Notification` | `idle_prompt` | (不變) | 💤 在等你回覆 |
| `Notification` | `elicitation_dialog` | `waiting` | ❓ 有問題想問你 |
| `Notification` | 其他 | (不變) | 💬 訊息內容 |
| `Stop` | `stop_hook_active = false` | `review`,直到該 session 下一個動作/結束/7 天 | ✅ 完成!(專案) |
| `StopFailure` | | `failed`,直到該 session 下一個動作/結束/1 小時 | ❌ 錯誤訊息 |
| `SubagentStart` / `SubagentStop` | | `running` | |
| `PreCompact` | | `running` | 🗜️ 整理記憶中… |
| `SessionEnd` | | 移除該 session | 最後一個結束時 👋 掰掰 |
| (無事件) | 游標移動(追視開啟時) | 16 方向 `look`,覆蓋所有狀態 | |
| (無事件) | 拖曳 | 單次位移 ≥ 4 px 才決定 `running-left` / `running-right` | |
| (無事件) | 游標進入人物方框 | `jumping` × 3 → 回原狀態 | |
規則:
- **待機時幾乎不動** — idle 每幀時長是合約值的 **6 倍**(一輪 6.6 秒,不是 1.1 秒),與 Codex 的 `codex-pet-assets` 一致。
- **非 idle 狀態播 3 次就沉澱** 成慢速 idle。動畫只在**狀態改變**時重新開始(Codex 的 effect 相依只有 state),工作中連續的工具呼叫不會讓她一直動。
- **一次性動作**(點擊揮手、初次問候)播完自動回到基礎狀態。
- **基礎狀態**依 session 彙整:`waiting(4) > failed(3) > review(2) > running(1) > idle(0)`,取最高者顯示——與 Codex 通知排序 `Ai()` 相同,注意 **review 高於 running**
- **到期**與 Codex 的 `Ei()` 相同:`failed` 1 小時、`waiting` 24 小時、`review` 7 天後退回 idle`running``idle` 不到期。另有一個 Codex 沒有的安全網:`running` 30 分鐘沒任何事件視為終端機已被關掉。
- **系統「減少動態效果」開啟時**只顯示每個狀態的第一格,與 Codex 相同。
- `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`)。
## 新增 / 切換寵物
掃描順序:**執行目錄的 `pets\`**portable 版是那顆 exe 旁邊的,其次才是內附的)→ `~/.codex/pets` → 設定檔 `petSources` 裡的額外路徑。同一個 id 以先掃到的為準。
1. **手動放入**(最直接)— 把寵物資料夾丟到 exe 旁邊的 `pets\<id>\`,選單「切換寵物 → 重新掃描」就會出現。選單的「開啟寵物資料夾」會直接開這一個。
2. **用 Codex hatch** — 在 Codex 裡 `/hatch`,完成後寵物在 `%USERPROFILE%\.codex\pets\<id>\`,本程式一律會掃這個資料夾。
3. **其他位置** — 在設定檔 `petSources` 加路徑。
切換後立即生效,不用重啟。
## 設定檔
位置:`%USERPROFILE%\.claude-pet\config.json`(第一次啟動後自動產生,選單「開啟設定檔」可直接開)。
| 欄位 | 預設 | 說明 |
|---|---|---|
| `activePetId` | `"xiao-nian"` | 目前使用的寵物 id |
| `petSources` | `[]` | **額外**的寵物資料夾。兩個隱含來源永遠會被掃到且不寫進這裡:**執行目錄的 `pets/`**(exe 旁邊,開發時是專案資料夾,排最前面)與 `~/.codex/pets`。不存在的路徑會在載入時自動剔除 |
| `scale` | `1` | 縮放:0.5 / 0.75 / 1 / 1.25 / 1.5。`1` 表示 126 × 137,與 Codex 桌面寵物同大小 |
| `position` | `null` | 視窗左上角座標;`null` 為主螢幕右下角。若座標落在所有螢幕之外會自動拉回 |
| `alwaysOnTop` | `true` | 永遠置頂 |
| `followCursor` | `false` | 追視游標(Codex 的寵物不看滑鼠,預設關) |
| `greetedPetIds` | `[]` | 已打過招呼的寵物 id;刪掉可讓該寵物下次啟動再揮手一次 |
| `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` | 開發/安裝版/portable 三種情境的路徑解析(`app.asar``app.asar.unpacked`、是否打包、portable 的真實 exe 位置) |
| `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 更新視窗位置,游標離開視窗也不會掉;方向由水平位移決定。
- **行為邏輯以 Codex 為準**,細節見[與 Codex 的對照](#與-codex-的對照)。
- **顯示尺寸與 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` 辨識自己的項目,重裝只替換自己的、不動別人的。
## 與 Codex 的對照
所有數值都是從 Codex app`WindowsApps\OpenAI.Codex_*\app\resources\app.asar`)的 `codex-pet-assets``avatar-overlay-native-frame` 與主程序讀出來的。
| 項目 | Codex | 本程式 |
|---|---|---|
| idle 速度 | 合約幀長 × 6(`_ = 6` | 相同 |
| 非 idle 狀態 | 播 3 次 → 慢速 idle(`[...n,...n,...n]` | 相同 |
| 動畫何時重播 | 只在狀態改變時(effect 相依 `[state, lookFrame, reducedMotion]` | 相同 |
| 跨 thread 優先序 | `waiting 0 > failed 1 > review 2 > running 3 > idle 4``Ai()` | 相同 |
| 狀態到期 | failed 1 h、waiting 24 h、review 7 d、running/idle 不到期(`Ei()` | 相同,另加 running 30 分鐘無事件的安全網 |
| `failed` | 持續狀態(turn 失敗/取消) | 相同(`StopFailure`);工具失敗不算 |
| `waiting` | 授權、`requestUserInput`、計畫待確認 | 相同;`idle_prompt` 不算 |
| `review` | turn 完成且未讀,直到使用者回到該 thread | 直到該 session 下一個動作 |
| `waving` | 只在 first-awake(每隻寵物一次,8 秒) | 相同(記在設定檔 `greetedPetIds` |
| `jumping` | 定義在 `respondToHover ? "jumping" : state`,但整個 app 沒人開啟那個 prop | **選擇打開**:游標進入人物方框就播 3 次 |
| hover 版面 | 40 px 內進入、56 px 外退出;人物縮 0.9、下移 14 px、上方 24 px 控制鈕、200 ms | 未採用——控制鈕的功能就是選單,右鍵已經有了 |
| hover 判定 | 用矩形(`pet-area-hover-changed` 由 rect 算) | 相同(逐像素會因每格輪廓不同而抖動) |
| 追視 | 看 quick chat 文字游標/computer-use 游標,死區 1 px,覆蓋所有狀態 | 沒有對應來源;改為**選配**看滑鼠,預設關 |
| 拖曳方向 | 單次取樣水平位移 ≥ 4 px | 相同 |
| 拖曳放開 | 彈簧回彈(spring 220 / damping 26 | 無(直接停在放開處) |
| 點擊 | 開 quick chat | 揮手(沒有對應功能) |
| reduced motion | 只顯示第一格 | 相同 |
| 預設位置 | 工作區右下各留 24 px | 相同 |
| 尺寸 | 寬 80224 px 可調 | 65%175%82221 px |
刻意不同的地方都是因為 Claude Code 沒有對應的功能(quick chat、computer-use 游標)或對應的訊號(thread 是否已讀)。
## 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 dist:portable # 只做免安裝單檔
npm run pack # 只產 win-unpacked 資料夾,最快,改程式時驗證用
```
產物在 `dist\`
| 檔案 | 大小 | 說明 |
|---|---|---|
| `Claude Pet-<版本>-win.zip` | ~140 MB | **免安裝資料夾版**。解壓後點兩下 `Claude Pet.exe`。啟動最快,寵物就放旁邊的 `pets\` |
| `Claude Pet-<版本>-setup.exe` | ~100 MB | NSIS 安裝檔。per-user、可改安裝路徑、建立桌面與開始選單捷徑 |
| `Claude Pet-<版本>-portable.exe` | ~100 MB | **免安裝單檔**,點兩下直接跑 |
| `win-unpacked\` | ~200 MB | 上面 zip 的未壓縮版,整個資料夾複製走也能用 |
第一次建置時 electron-builder 會從 GitHub 抓 NSIS 與相關工具到 `%LOCALAPPDATA%\electron-builder\Cache`,需要網路,之後走快取。
### portable 單檔版的代價
NSIS 的 portable 外殼**每次啟動**都會把整包解壓到 `%TEMP%\ClaudePet`**程式結束後又整個刪掉**(見 `app-builder-lib/templates/nsis/portable.nsi``RMDir /r $INSTDIR`)。所以:
- 每次開都要重新解壓約 200 MB,比資料夾版慢好幾秒。**要開機自動啟動的話建議用資料夾版**。
- 程式本體的位置是暫時的,因此 hook 腳本會另外複製一份到 `%USERPROFILE%\.claude-pet\hook\`,用那個穩定路徑去註冊。否則寵物沒開的時候 Claude Code 每次觸發 hook 都會找不到檔案而報錯。
- 使用者自己的寵物要放在**那顆 exe 旁邊**的 `pets\`(程式第一次跑會自己建),放進解壓目錄的會跟著被刪掉。同 id 時以 exe 旁邊那份為準。
- 程式被強制結束(工作管理員)時外殼來不及清理,`%TEMP%\ClaudePet` 會留下約 300 MB,可以手動刪。
### 打包後和原始碼跑不一樣的地方
都已經處理好,改程式時要留意:
| 項目 | 開發 | 安裝/資料夾版 | portable 單檔 |
|---|---|---|---|
| 內附寵物 | `<專案>\pets` | `<exe 旁>\pets` | `%TEMP%\ClaudePet\pets` |
| 使用者寵物 | 同上 | 同上 | `<exe 旁>\pets` |
| hook 腳本 | `<專案>\hook\` | `resources\app.asar.unpacked\hook\` | `%USERPROFILE%\.claude-pet\hook\` |
| 開機自動啟動 | `electron.exe` + 專案路徑 | 那顆 exe | `PORTABLE_EXECUTABLE_FILE` |
判斷邏輯都在 `lib/paths.js``isPackaged()``process.defaultApp``isPortable()``PORTABLE_EXECUTABLE_FILE``unpacked()``app.asar` 換成 `app.asar.unpacked`
- **`pets/``extraFiles` 放到 exe 旁邊**(不是 `extraResources`,那會進 `resources\`),使用者直接把寵物資料夾丟進去就會出現,不用碰 asar。
- **`hook/``asarUnpack`**,因為 Claude Code 是用 `node <路徑>` 執行它,而 node 讀不到 asar 裡的檔案。
- **hook 指令仍然需要 `node` 在 PATH 裡**。路徑含空白時會自動加引號。
換圖示就換掉 `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` 產生。