CatCode 桌面寵物如何得知 AI 智能體已完成任務
How a desktop pet finds out your AI agent has finished a task
CatCode 透過監看 `~/.catcode/events/` 事件資料夾接收任務完成訊號,讓 Windows 桌面寵物在 AI 智能體結束任務時作出反應。Claude Code 可透過托盤項目設定 Stop hook;Codex 和 Cursor 沒有專用整合,需自行接上完成命令或手動執行腳本。寵物只知道任務已結束,不會識別進行中的任務。
CatCode is a pixel pet that lives on the Windows desktop and reacts when a coding agent finishes a task. This is a walkthrough of how that detection works, taken from the app's code (an Electron app). It is simpler than you might expect, and support differs by agent, so I will be specific about what is and is not covered.
No log scraping, no process polling
CatCode does not tail agent logs, inspect processes or read any agent's internal files. I checked: there is no such code. The whole mechanism is a drop folder:
~/.catcode/events/
Anything that can write a small JSON file there can tell the pet a task is done:
{ "source": "Codex", "task": "update the UI", "message": "finished the check" }
That is the entire protocol: source, task and message. Missing values fall back to defaults.
The watcher
The main process consumes those files with fs.watch. Trimmed from main.js:
function consumeEvent(file) {
const full = path.join(EVENTS_DIR, file), data = loadJSON(full, null);
try { fs.unlinkSync(full); } catch {}
if (!data) return;
// ...append to a per-day log (only the last 3 days are kept)
celebrate(`Task complete\n${src} ${msg}`);
}
function watchEvents() {
for (const f of fs.readdirSync(EVENTS_DIR)) if (f.endsWith('.json')) consumeEvent(f);
fs.watch(EVENTS_DIR, (_e, file) => { if (file && file.endsWith('.json'))
setTimeout(() => { if (fs.existsSync(path.join(EVENTS_DIR, file))) consumeEvent(file); }, 150); });
}
Details that come straight from this code:
- Events that piled up while the app was closed are consumed on startup.
- There is a 150 ms delay before reading, so a half-written file is less likely to be read. It is a mitigation, not a guarantee: the writer uses a plain
writeFileSync, and a file that fails to parse is deleted anyway. - Each event file is deleted after it is handled. The app keeps a short in-memory list of recent tasks and a three-day per-agent log for a small "agent report" window.
Claude Code: a real hook
For Claude Code the trigger is its Stop hook. The app has a tray item that writes the hook into ~/.claude/settings.json for you. The resulting entry has this shape (the path to the script is yours to set):
{
"hooks": {
"Stop": [
{ "hooks": [
{ "type": "command",
"command": "node C:\\catcode\\tools\\emit-event.js \"Claude Code\" \"\" \"finished the task\"" }
] }
]
}
}
emit-event.js is about 30 lines: it creates the folder if needed and writes {source, task, message} as a timestamped .json file. It also accepts JSON on stdin and uses session_id as the task, if you call it without arguments.
Two honest caveats. First, Stop fires when Claude Code finishes responding, so "task finished" really means "the agent stopped and handed control back". Second, the hook command starts with node, so Node.js needs to be on your PATH.
Codex and Cursor: partial, and manual
This is the part to read carefully. The code has no Codex-specific or Cursor-specific integration. No config file for either is written, and nothing watches either tool. The names appear in the README, in the description, and in the tray's "test" button, which just plays a canned message.
What exists is the generic path: if your tool has a "run a command when finished" mechanism, call the same script with a different source:
node tools/emit-event.js "Codex" "update the UI" "finished the check"
If it has no such hook, you can run that line by hand. The pet reacts identically. So: Claude Code has a tray item that writes the hook for you (or you add it by hand, as above), Codex and Cursor work only if you wire a completion command yourself, and I can't tell you from this code whether either tool offers a suitable hook.
What the pet does about it
celebrate() sends the happy state to the renderer, shows a speech bubble for 6 seconds, then restores whatever state the pet was in before.
There is no sprite sheet. The pet is drawn procedurally: Sprite.makeRenderer(28) creates a 28x28 offscreen canvas, and primitives such as pdisc, ptri and pline plot pixels, which are then scaled up (110, 140 or 180 px depending on the size setting). A pose() function maps the state to parameters. For happy, the code makes the pet bounce, smile, stick out its tongue and wag its tail faster, while the renderer spawns sparks and hearts.
The full state list is: idle, working, waiting, sleeping, happy, play, run, pull. Note that working (the laptop pose) is set by the Pomodoro focus timer or by hand from the tray, not by agent activity. The pet never knows a task is in progress, only that one ended.
Skins are small JSON files rather than images:
{ "id": "ginger-cat", "species": "cat", "pattern": "tabby",
"palette": { "body": "#f2954e", "shade": "#c26a2c", "belly": "#fbe0c2",
"stripe": "#b8531f", "eye": "#4fae5a", "nose": "#d98c9a", "inner": "#f0a9b3" } }
Species, pattern (solid, tabby, patch), optional accessory and a palette drive the same drawing code, so a new colour scheme needs no art.
The rest, briefly
It also does the desktop-pet things: eyes and head follow the cursor, Pomodoro focus and break timers, water and stretch reminders, user reminders, and skin import. Memes and images from the internet are off by default and need to be enabled in the tray. The privacy note lists the services the app contacts directly: ipapi.co and open-meteo.com for weather, plus meme/image services only if you opt in.
Get it
CatCode is a Windows app, and the source is closed (proprietary EULA). It costs $5: CatCode on Gumroad.
The installer is not code-signed, so Windows SmartScreen will show an "unknown publisher" warning on first launch.
Disclosure: I am the author of CatCode.
來源:DEV Community · dev.to