How a desktop pet finds out your AI agent has finished a task A developer built CatCode, an Electron-based pixel desktop pet for Windows that reacts when a coding agent finishes a task, using a simple drop-folder protocol at ~/.catcode/events/ where any tool can write a small JSON file with source, task and message fields. The app consumes those files via fs.watch and triggers a celebration animation, with a tray item that writes a Claude Code Stop hook into ~/.claude/settings.json; the developer notes there is no Codex- or Cursor-specific integration, so those tools work only if a completion command is wired manually. The pet is drawn procedurally on a 28x28 offscreen canvas rather than from a sprite sheet. 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. 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 main process consumes those files with fs.watch . Trimmed from main.js : 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: writeFileSync , and a file that fails to parse is deleted anyway. 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. 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. 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. 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. CatCode is a Windows app, and the source is closed proprietary EULA . It costs $5: CatCode on Gumroad https://chvdbox.gumroad.com/l/tlwwyz?utm source=devto&utm medium=article&utm campaign=catcode . 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.