摘要:
把 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>
413 lines
24 KiB
Markdown
413 lines
24 KiB
Markdown
# Claude Pet
|
||
|
||
**Codex Pets 相容的 Claude Code 桌面寵物。**
|
||
把 Codex `/hatch` 產出的寵物包原封不動拿來用,讓它站在桌面上,即時反映 Claude Code 正在做什麼:思考、等你授權、失敗、完成……還會盯著你的滑鼠游標看。
|
||
|
||

|
||
|
||
> 上圖由左至右:SessionStart 揮手、工作中、等待授權、完成時跳躍、檢視成果。氣泡會標出是哪個專案的 session。
|
||
|
||
---
|
||
|
||
## 目錄
|
||
|
||
- [特色](#特色)
|
||
- [需求](#需求)
|
||
- [快速開始](#快速開始)
|
||
- [日常使用](#日常使用)
|
||
- [行為對照表](#行為對照表)
|
||
- [寵物包格式(Codex V2)](#寵物包格式codex-v2)
|
||
- [新增 / 切換寵物](#新增--切換寵物)
|
||
- [設定檔](#設定檔)
|
||
- [架構與檔案](#架構與檔案)
|
||
- [與 Codex 的對照](#與-codex-的對照)
|
||
- [HTTP API](#http-api)
|
||
- [打包成 exe](#打包成-exe)
|
||
- [疑難排解](#疑難排解)
|
||
- [移除](#移除)
|
||
|
||
---
|
||
|
||
## 特色
|
||
|
||
- **完整實作 Codex V2 pet contract** — 8 欄 × 11 列、192×208 cell 的 spritesheet,9 個標準動作列 + 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 | 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`)。
|
||
|
||
## 新增 / 切換寵物
|
||
|
||
掃描順序:**執行目錄的 `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 ──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` | 開發/安裝版/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 × 137,Codex 的 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 | 相同 |
|
||
| 尺寸 | 寬 80–224 px 可調 | 65%–175%(82–221 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` 的 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 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` skill(Codex V2 pet contract)。
|
||
- 內附寵物「小念」由 Codex `/hatch` 產生。
|