# 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 的 spritesheet,9 個標準動作列 + 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.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 | 0–5 | 280, 110, 110, 140, 140, 320 | | 1 | running-right | 0–7 | 120 ×7, 220 | | 2 | running-left | 0–7 | 120 ×7, 220 | | 3 | waving | 0–3 | 140 ×3, 280 | | 4 | jumping | 0–4 | 140 ×4, 280 | | 5 | failed | 0–7 | 140 ×7, 240 | | 6 | waiting | 0–5 | 150 ×5, 260 | | 7 | running(工作中,不是跑步) | 0–5 | 120 ×5, 220 | | 8 | review | 0–5 | 150 ×5, 280 | | 9 | look A | 0–7 | 000, 022.5, 045, 067.5, 090, 112.5, 135, 157.5 度 | | 10 | look B | 0–7 | 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\\`,本程式預設掃描該資料夾,選單「切換寵物 → 重新掃描」就會出現。 2. **手動放入** — 把寵物資料夾複製到 `pets\\`(選單「開啟寵物資料夾」可直接打開)。 3. **其他位置** — 在設定檔 `petSources` 加路徑。 切換後立即生效,不用重啟。 ## 設定檔 位置:`%USERPROFILE%\.claude-pet\config.json`(第一次啟動後自動產生,選單「開啟設定檔」可直接開)。 | 欄位 | 預設 | 說明 | |---|---|---| | `activePetId` | `"xiao-nian"` | 目前使用的寵物 id | | `petSources` | `[/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 ──hook(stdin JSON)──▶ hook/claude-pet-hook.js ──POST /event──▶ 127.0.0.1:17333 │ Electron main(main.js) │ 視窗 / 系統匣 / 游標輪詢 / 拖曳 / 設定 ▼ IPC renderer/renderer.js 狀態機 + 幀動畫 + 追視 + 氣泡 + alpha 點穿判定 ``` | 檔案 | 職責 | |---|---| | `main.js` | Electron 主程序:透明置頂視窗、系統匣選單、HTTP 伺服器、游標輪詢(50 ms)、拖曳(60 fps)、設定讀寫、寵物掃描 | | `preload.js` | 以 contextBridge 暴露白名單 IPC 頻道給 renderer(sandbox 模式) | | `renderer/renderer.js` | Codex V2 幀時序播放器、session 狀態彙整、一次性動作、看向方向計算(含遲滯防抖)、氣泡、滑鼠 alpha 判定 | | `renderer/index.html`、`style.css` | 版面與氣泡樣式(CSP 僅允許 self / data:) | | `hook/claude-pet-hook.js` | Claude Code hook 端:讀 stdin → 擷取欄位 → POST;800 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 × 137,Codex 的 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` 的 payload(hook 腳本送出的格式): ```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` skill(Codex V2 pet contract)。 - 內附寵物「小念」由 Codex `/hatch` 產生。